Contents of /trunk/mkinitrd-magellan/busybox/printutils/lpr.c
Parent Directory | Revision Log
Revision 984 -
(show annotations)
(download)
Sun May 30 11:32:42 2010 UTC (14 years, 4 months ago) by niro
File MIME type: text/plain
File size: 7351 byte(s)
Sun May 30 11:32:42 2010 UTC (14 years, 4 months ago) by niro
File MIME type: text/plain
File size: 7351 byte(s)
-updated to busybox-1.16.1 and enabled blkid/uuid support in default config
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 tarball for details. |
11 | * |
12 | * See RFC 1179 for protocol description. |
13 | */ |
14 | #include "libbb.h" |
15 | |
16 | /* |
17 | * LPD returns binary 0 on success. |
18 | * Otherwise it returns error message. |
19 | */ |
20 | static void get_response_or_say_and_die(int fd, const char *errmsg) |
21 | { |
22 | ssize_t sz; |
23 | char buf[128]; |
24 | |
25 | buf[0] = ' '; |
26 | sz = safe_read(fd, buf, 1); |
27 | if ('\0' != buf[0]) { |
28 | // request has failed |
29 | // try to make sure last char is '\n', but do not add |
30 | // superfluous one |
31 | sz = full_read(fd, buf + 1, 126); |
32 | bb_error_msg("error while %s%s", errmsg, |
33 | (sz > 0 ? ". Server said:" : "")); |
34 | if (sz > 0) { |
35 | // sz = (bytes in buf) - 1 |
36 | if (buf[sz] != '\n') |
37 | buf[++sz] = '\n'; |
38 | safe_write(STDERR_FILENO, buf, sz + 1); |
39 | } |
40 | xfunc_die(); |
41 | } |
42 | } |
43 | |
44 | int lpqr_main(int argc, char *argv[]) MAIN_EXTERNALLY_VISIBLE; |
45 | int lpqr_main(int argc UNUSED_PARAM, char *argv[]) |
46 | { |
47 | enum { |
48 | OPT_P = 1 << 0, // -P queue[@host[:port]]. If no -P is given use $PRINTER, then "lp@localhost:515" |
49 | OPT_U = 1 << 1, // -U username |
50 | |
51 | LPR_V = 1 << 2, // -V: be verbose |
52 | LPR_h = 1 << 3, // -h: want banner printed |
53 | LPR_C = 1 << 4, // -C class: job "class" (? supposedly printed on banner) |
54 | LPR_J = 1 << 5, // -J title: the job title for the banner page |
55 | LPR_m = 1 << 6, // -m: send mail back to user |
56 | |
57 | LPQ_SHORT_FMT = 1 << 2, // -s: short listing format |
58 | LPQ_DELETE = 1 << 3, // -d: delete job(s) |
59 | LPQ_FORCE = 1 << 4, // -f: force waiting job(s) to be printed |
60 | }; |
61 | char tempfile[sizeof("/tmp/lprXXXXXX")]; |
62 | const char *job_title; |
63 | const char *printer_class = ""; // printer class, max 32 char |
64 | const char *queue; // name of printer queue |
65 | const char *server = "localhost"; // server[:port] of printer queue |
66 | char *hostname; |
67 | // N.B. IMHO getenv("USER") can be way easily spoofed! |
68 | const char *user = xuid2uname(getuid()); |
69 | unsigned job; |
70 | unsigned opts; |
71 | int fd; |
72 | |
73 | // parse options |
74 | // TODO: set opt_complementary: s,d,f are mutually exclusive |
75 | opts = getopt32(argv, |
76 | (/*lp*/'r' == applet_name[2]) ? "P:U:VhC:J:m" : "P:U:sdf" |
77 | , &queue, &user |
78 | , &printer_class, &job_title |
79 | ); |
80 | argv += optind; |
81 | |
82 | // if queue is not specified -> use $PRINTER |
83 | if (!(opts & OPT_P)) |
84 | queue = getenv("PRINTER"); |
85 | // if queue is still not specified -> |
86 | if (!queue) { |
87 | // ... queue defaults to "lp" |
88 | // server defaults to "localhost" |
89 | queue = "lp"; |
90 | // if queue is specified -> |
91 | } else { |
92 | // queue name is to the left of '@' |
93 | char *s = strchr(queue, '@'); |
94 | if (s) { |
95 | // server name is to the right of '@' |
96 | *s = '\0'; |
97 | server = s + 1; |
98 | } |
99 | } |
100 | |
101 | // do connect |
102 | fd = create_and_connect_stream_or_die(server, 515); |
103 | |
104 | // |
105 | // LPQ ------------------------ |
106 | // |
107 | if (/*lp*/'q' == applet_name[2]) { |
108 | char cmd; |
109 | // force printing of every job still in queue |
110 | if (opts & LPQ_FORCE) { |
111 | cmd = 1; |
112 | goto command; |
113 | // delete job(s) |
114 | } else if (opts & LPQ_DELETE) { |
115 | fdprintf(fd, "\x5" "%s %s", queue, user); |
116 | while (*argv) { |
117 | fdprintf(fd, " %s", *argv++); |
118 | } |
119 | bb_putchar('\n'); |
120 | // dump current jobs status |
121 | // N.B. periodical polling should be achieved |
122 | // via "watch -n delay lpq" |
123 | // They say it's the UNIX-way :) |
124 | } else { |
125 | cmd = (opts & LPQ_SHORT_FMT) ? 3 : 4; |
126 | command: |
127 | fdprintf(fd, "%c" "%s\n", cmd, queue); |
128 | bb_copyfd_eof(fd, STDOUT_FILENO); |
129 | } |
130 | |
131 | return EXIT_SUCCESS; |
132 | } |
133 | |
134 | // |
135 | // LPR ------------------------ |
136 | // |
137 | if (opts & LPR_V) |
138 | bb_error_msg("connected to server"); |
139 | |
140 | job = getpid() % 1000; |
141 | hostname = safe_gethostname(); |
142 | |
143 | // no files given on command line? -> use stdin |
144 | if (!*argv) |
145 | *--argv = (char *)"-"; |
146 | |
147 | fdprintf(fd, "\x2" "%s\n", queue); |
148 | get_response_or_say_and_die(fd, "setting queue"); |
149 | |
150 | // process files |
151 | do { |
152 | unsigned cflen; |
153 | int dfd; |
154 | struct stat st; |
155 | char *c; |
156 | char *remote_filename; |
157 | char *controlfile; |
158 | |
159 | // if data file is stdin, we need to dump it first |
160 | if (LONE_DASH(*argv)) { |
161 | strcpy(tempfile, "/tmp/lprXXXXXX"); |
162 | dfd = mkstemp(tempfile); |
163 | if (dfd < 0) |
164 | bb_perror_msg_and_die("mkstemp"); |
165 | bb_copyfd_eof(STDIN_FILENO, dfd); |
166 | xlseek(dfd, 0, SEEK_SET); |
167 | *argv = (char*)bb_msg_standard_input; |
168 | } else { |
169 | dfd = xopen(*argv, O_RDONLY); |
170 | } |
171 | |
172 | /* "The name ... should start with ASCII "cfA", |
173 | * followed by a three digit job number, followed |
174 | * by the host name which has constructed the file." |
175 | * We supply 'c' or 'd' as needed for control/data file. */ |
176 | remote_filename = xasprintf("fA%03u%s", job, hostname); |
177 | |
178 | // create control file |
179 | // TODO: all lines but 2 last are constants! How we can use this fact? |
180 | controlfile = xasprintf( |
181 | "H" "%.32s\n" "P" "%.32s\n" /* H HOST, P USER */ |
182 | "C" "%.32s\n" /* C CLASS - printed on banner page (if L cmd is also given) */ |
183 | "J" "%.99s\n" /* J JOBNAME */ |
184 | /* "class name for banner page and job name |
185 | * for banner page commands must precede L command" */ |
186 | "L" "%.32s\n" /* L USER - print banner page, with given user's name */ |
187 | "M" "%.32s\n" /* M WHOM_TO_MAIL */ |
188 | "l" "d%.31s\n" /* l DATA_FILE_NAME ("dfAxxx") */ |
189 | , hostname, user |
190 | , printer_class /* can be "" */ |
191 | , ((opts & LPR_J) ? job_title : *argv) |
192 | , (opts & LPR_h) ? user : "" |
193 | , (opts & LPR_m) ? user : "" |
194 | , remote_filename |
195 | ); |
196 | // delete possible "\nX\n" patterns |
197 | c = controlfile; |
198 | cflen = (unsigned)strlen(controlfile); |
199 | while ((c = strchr(c, '\n')) != NULL) { |
200 | if (c[1] && c[2] == '\n') { |
201 | /* can't use strcpy, results are undefined */ |
202 | memmove(c, c+2, cflen - (c-controlfile) - 1); |
203 | cflen -= 2; |
204 | } else { |
205 | c++; |
206 | } |
207 | } |
208 | |
209 | // send control file |
210 | if (opts & LPR_V) |
211 | bb_error_msg("sending control file"); |
212 | /* "Acknowledgement processing must occur as usual |
213 | * after the command is sent." */ |
214 | fdprintf(fd, "\x2" "%u c%s\n", cflen, remote_filename); |
215 | get_response_or_say_and_die(fd, "sending control file"); |
216 | /* "Once all of the contents have |
217 | * been delivered, an octet of zero bits is sent as |
218 | * an indication that the file being sent is complete. |
219 | * A second level of acknowledgement processing |
220 | * must occur at this point." */ |
221 | full_write(fd, controlfile, cflen + 1); /* writes NUL byte too */ |
222 | get_response_or_say_and_die(fd, "sending control file"); |
223 | |
224 | // send data file, with name "dfaXXX" |
225 | if (opts & LPR_V) |
226 | bb_error_msg("sending data file"); |
227 | st.st_size = 0; /* paranoia: fstat may theoretically fail */ |
228 | fstat(dfd, &st); |
229 | fdprintf(fd, "\x3" "%"OFF_FMT"u d%s\n", st.st_size, remote_filename); |
230 | get_response_or_say_and_die(fd, "sending data file"); |
231 | if (bb_copyfd_size(dfd, fd, st.st_size) != st.st_size) { |
232 | // We're screwed. We sent less bytes than we advertised. |
233 | bb_error_msg_and_die("local file changed size?!"); |
234 | } |
235 | write(fd, "", 1); // send ACK |
236 | get_response_or_say_and_die(fd, "sending data file"); |
237 | |
238 | // delete temporary file if we dumped stdin |
239 | if (*argv == (char*)bb_msg_standard_input) |
240 | unlink(tempfile); |
241 | |
242 | // cleanup |
243 | close(fd); |
244 | free(remote_filename); |
245 | free(controlfile); |
246 | |
247 | // say job accepted |
248 | if (opts & LPR_V) |
249 | bb_error_msg("job accepted"); |
250 | |
251 | // next, please! |
252 | job = (job + 1) % 1000; |
253 | } while (*++argv); |
254 | |
255 | return EXIT_SUCCESS; |
256 | } |