source: MondoRescue/branches/3.3/mindi-busybox/printutils/lpr.c@ 3791

Last change on this file since 3791 was 3621, checked in by Bruno Cornec, 10 years ago

New 3?3 banch for incorporation of latest busybox 1.25. Changing minor version to handle potential incompatibilities.

  • Property svn:eol-style set to native
File size: 8.4 KB
Line 
1/* vi: set sw=4 ts=4: */
2/*
3 * bare bones version of lpr & lpq: BSD printing utilities
4 *
5 * Copyright (C) 2008 by Vladimir Dronnikov <dronnikov@gmail.com>
6 *
7 * Original idea and code:
8 * Walter Harms <WHarms@bfs.de>
9 *
10 * Licensed under GPLv2, see file LICENSE in this source tree.
11 *
12 * See RFC 1179 for protocol description.
13 */
14//config:config LPR
15//config: bool "lpr"
16//config: default y
17//config: help
18//config: lpr sends files (or standard input) to a print spooling daemon.
19//config:
20//config:config LPQ
21//config: bool "lpq"
22//config: default y
23//config: help
24//config: lpq is a print spool queue examination and manipulation program.
25
26//applet:IF_LPQ(APPLET_ODDNAME(lpq, lpqr, BB_DIR_USR_BIN, BB_SUID_DROP, lpq))
27//applet:IF_LPR(APPLET_ODDNAME(lpr, lpqr, BB_DIR_USR_BIN, BB_SUID_DROP, lpr))
28
29//kbuild:lib-$(CONFIG_LPR) += lpr.o
30//kbuild:lib-$(CONFIG_LPQ) += lpr.o
31
32//usage:#define lpr_trivial_usage
33//usage: "-P queue[@host[:port]] -U USERNAME -J TITLE -Vmh [FILE]..."
34/* -C CLASS exists too, not shown.
35 * CLASS is supposed to be printed on banner page, if one is requested */
36//usage:#define lpr_full_usage "\n\n"
37//usage: " -P lp service to connect to (else uses $PRINTER)"
38//usage: "\n -m Send mail on completion"
39//usage: "\n -h Print banner page too"
40//usage: "\n -V Verbose"
41//usage:
42//usage:#define lpq_trivial_usage
43//usage: "[-P queue[@host[:port]]] [-U USERNAME] [-d JOBID]... [-fs]"
44//usage:#define lpq_full_usage "\n\n"
45//usage: " -P lp service to connect to (else uses $PRINTER)"
46//usage: "\n -d Delete jobs"
47//usage: "\n -f Force any waiting job to be printed"
48//usage: "\n -s Short display"
49
50#include "libbb.h"
51
52/*
53 * LPD returns binary 0 on success.
54 * Otherwise it returns error message.
55 */
56static void get_response_or_say_and_die(int fd, const char *errmsg)
57{
58 ssize_t sz;
59 char buf[128];
60
61 buf[0] = ' ';
62 sz = safe_read(fd, buf, 1);
63 if ('\0' != buf[0]) {
64 // request has failed
65 // try to make sure last char is '\n', but do not add
66 // superfluous one
67 sz = full_read(fd, buf + 1, 126);
68 bb_error_msg("error while %s%s", errmsg,
69 (sz > 0 ? ". Server said:" : ""));
70 if (sz > 0) {
71 // sz = (bytes in buf) - 1
72 if (buf[sz] != '\n')
73 buf[++sz] = '\n';
74 safe_write(STDERR_FILENO, buf, sz + 1);
75 }
76 xfunc_die();
77 }
78}
79
80int lpqr_main(int argc, char *argv[]) MAIN_EXTERNALLY_VISIBLE;
81int lpqr_main(int argc UNUSED_PARAM, char *argv[])
82{
83 enum {
84 OPT_P = 1 << 0, // -P queue[@host[:port]]. If no -P is given use $PRINTER, then "lp@localhost:515"
85 OPT_U = 1 << 1, // -U username
86
87 LPR_V = 1 << 2, // -V: be verbose
88 LPR_h = 1 << 3, // -h: want banner printed
89 LPR_C = 1 << 4, // -C class: job "class" (? supposedly printed on banner)
90 LPR_J = 1 << 5, // -J title: the job title for the banner page
91 LPR_m = 1 << 6, // -m: send mail back to user
92
93 LPQ_SHORT_FMT = 1 << 2, // -s: short listing format
94 LPQ_DELETE = 1 << 3, // -d: delete job(s)
95 LPQ_FORCE = 1 << 4, // -f: force waiting job(s) to be printed
96 };
97 char tempfile[sizeof("/tmp/lprXXXXXX")];
98 const char *job_title;
99 const char *printer_class = ""; // printer class, max 32 char
100 const char *queue; // name of printer queue
101 const char *server = "localhost"; // server[:port] of printer queue
102 char *hostname;
103 // N.B. IMHO getenv("USER") can be way easily spoofed!
104 const char *user = xuid2uname(getuid());
105 unsigned job;
106 unsigned opts;
107 int fd;
108
109 queue = getenv("PRINTER");
110 if (!queue)
111 queue = "lp";
112
113 // parse options
114 // TODO: set opt_complementary: s,d,f are mutually exclusive
115 opts = getopt32(argv,
116 (/*lp*/'r' == applet_name[2]) ? "P:U:VhC:J:m" : "P:U:sdf"
117 , &queue, &user
118 , &printer_class, &job_title
119 );
120 argv += optind;
121
122 {
123 // queue name is to the left of '@'
124 char *s = strchr(queue, '@');
125 if (s) {
126 // server name is to the right of '@'
127 *s = '\0';
128 server = s + 1;
129 }
130 }
131
132 // do connect
133 fd = create_and_connect_stream_or_die(server, 515);
134
135 //
136 // LPQ ------------------------
137 //
138 if (/*lp*/'q' == applet_name[2]) {
139 char cmd;
140 // force printing of every job still in queue
141 if (opts & LPQ_FORCE) {
142 cmd = 1;
143 goto command;
144 // delete job(s)
145 } else if (opts & LPQ_DELETE) {
146 fdprintf(fd, "\x5" "%s %s", queue, user);
147 while (*argv) {
148 fdprintf(fd, " %s", *argv++);
149 }
150 bb_putchar('\n');
151 // dump current jobs status
152 // N.B. periodical polling should be achieved
153 // via "watch -n delay lpq"
154 // They say it's the UNIX-way :)
155 } else {
156 cmd = (opts & LPQ_SHORT_FMT) ? 3 : 4;
157 command:
158 fdprintf(fd, "%c" "%s\n", cmd, queue);
159 bb_copyfd_eof(fd, STDOUT_FILENO);
160 }
161
162 return EXIT_SUCCESS;
163 }
164
165 //
166 // LPR ------------------------
167 //
168 if (opts & LPR_V)
169 bb_error_msg("connected to server");
170
171 job = getpid() % 1000;
172 hostname = safe_gethostname();
173
174 // no files given on command line? -> use stdin
175 if (!*argv)
176 *--argv = (char *)"-";
177
178 fdprintf(fd, "\x2" "%s\n", queue);
179 get_response_or_say_and_die(fd, "setting queue");
180
181 // process files
182 do {
183 unsigned cflen;
184 int dfd;
185 struct stat st;
186 char *c;
187 char *remote_filename;
188 char *controlfile;
189
190 // if data file is stdin, we need to dump it first
191 if (LONE_DASH(*argv)) {
192 strcpy(tempfile, "/tmp/lprXXXXXX");
193 dfd = xmkstemp(tempfile);
194 bb_copyfd_eof(STDIN_FILENO, dfd);
195 xlseek(dfd, 0, SEEK_SET);
196 *argv = (char*)bb_msg_standard_input;
197 } else {
198 dfd = xopen(*argv, O_RDONLY);
199 }
200
201 st.st_size = 0; /* paranoia: fstat may theoretically fail */
202 fstat(dfd, &st);
203
204 /* Apparently, some servers are buggy and won't accept 0-sized jobs.
205 * Standard lpr works around it by refusing to send such jobs:
206 */
207 if (st.st_size == 0) {
208 bb_error_msg("nothing to print");
209 continue;
210 }
211
212 /* "The name ... should start with ASCII "cfA",
213 * followed by a three digit job number, followed
214 * by the host name which has constructed the file."
215 * We supply 'c' or 'd' as needed for control/data file. */
216 remote_filename = xasprintf("fA%03u%s", job, hostname);
217
218 // create control file
219 // TODO: all lines but 2 last are constants! How we can use this fact?
220 controlfile = xasprintf(
221 "H" "%.32s\n" "P" "%.32s\n" /* H HOST, P USER */
222 "C" "%.32s\n" /* C CLASS - printed on banner page (if L cmd is also given) */
223 "J" "%.99s\n" /* J JOBNAME */
224 /* "class name for banner page and job name
225 * for banner page commands must precede L command" */
226 "L" "%.32s\n" /* L USER - print banner page, with given user's name */
227 "M" "%.32s\n" /* M WHOM_TO_MAIL */
228 "l" "d%.31s\n" /* l DATA_FILE_NAME ("dfAxxx") */
229 , hostname, user
230 , printer_class /* can be "" */
231 , ((opts & LPR_J) ? job_title : *argv)
232 , (opts & LPR_h) ? user : ""
233 , (opts & LPR_m) ? user : ""
234 , remote_filename
235 );
236 // delete possible "\nX\n" (that is, one-char) patterns
237 c = controlfile;
238 while ((c = strchr(c, '\n')) != NULL) {
239 if (c[1] && c[2] == '\n') {
240 overlapping_strcpy(c, c+2);
241 } else {
242 c++;
243 }
244 }
245
246 // send control file
247 if (opts & LPR_V)
248 bb_error_msg("sending control file");
249 /* "Acknowledgement processing must occur as usual
250 * after the command is sent." */
251 cflen = (unsigned)strlen(controlfile);
252 fdprintf(fd, "\x2" "%u c%s\n", cflen, remote_filename);
253 get_response_or_say_and_die(fd, "sending control file");
254 /* "Once all of the contents have
255 * been delivered, an octet of zero bits is sent as
256 * an indication that the file being sent is complete.
257 * A second level of acknowledgement processing
258 * must occur at this point." */
259 full_write(fd, controlfile, cflen + 1); /* writes NUL byte too */
260 get_response_or_say_and_die(fd, "sending control file");
261
262 // send data file, with name "dfaXXX"
263 if (opts & LPR_V)
264 bb_error_msg("sending data file");
265 fdprintf(fd, "\x3" "%"OFF_FMT"u d%s\n", st.st_size, remote_filename);
266 get_response_or_say_and_die(fd, "sending data file");
267 if (bb_copyfd_size(dfd, fd, st.st_size) != st.st_size) {
268 // We're screwed. We sent less bytes than we advertised.
269 bb_error_msg_and_die("local file changed size?!");
270 }
271 write(fd, "", 1); // send ACK
272 get_response_or_say_and_die(fd, "sending data file");
273
274 // delete temporary file if we dumped stdin
275 if (*argv == (char*)bb_msg_standard_input)
276 unlink(tempfile);
277
278 // cleanup
279 close(fd);
280 free(remote_filename);
281 free(controlfile);
282
283 // say job accepted
284 if (opts & LPR_V)
285 bb_error_msg("job accepted");
286
287 // next, please!
288 job = (job + 1) % 1000;
289 } while (*++argv);
290
291 return EXIT_SUCCESS;
292}
Note: See TracBrowser for help on using the repository browser.