diff options
author | Denis Vlasenko <vda.linux@googlemail.com> | 2008-02-24 18:44:20 +0000 |
---|---|---|
committer | Denis Vlasenko <vda.linux@googlemail.com> | 2008-02-24 18:44:20 +0000 |
commit | 4f82bdb050024b3475f5371a0612b1fb0fc4c09f (patch) | |
tree | ba0f2f986546c7798553f76c305f052b2b190048 /printutils/lpr.c | |
parent | 52feee9b1fc996acb9c2857596bc5c2440644525 (diff) | |
download | busybox-w32-4f82bdb050024b3475f5371a0612b1fb0fc4c09f.tar.gz busybox-w32-4f82bdb050024b3475f5371a0612b1fb0fc4c09f.tar.bz2 busybox-w32-4f82bdb050024b3475f5371a0612b1fb0fc4c09f.zip |
lpr,lpq: rework by dronnikov AT gmail.com
Diffstat (limited to 'printutils/lpr.c')
-rw-r--r-- | printutils/lpr.c | 364 |
1 files changed, 200 insertions, 164 deletions
diff --git a/printutils/lpr.c b/printutils/lpr.c index 865071011..ea0e21091 100644 --- a/printutils/lpr.c +++ b/printutils/lpr.c | |||
@@ -1,200 +1,236 @@ | |||
1 | /* vi: set sw=4 ts=4: */ | 1 | /* vi: set sw=4 ts=4: */ |
2 | /* | 2 | /* |
3 | * Copyright 2008 Walter Harms (WHarms@bfs.de) | 3 | * bare bones version of lpr & lpq: BSD printing utilities |
4 | * | 4 | * |
5 | * Licensed under the GPL v2, see the file LICENSE in this tarball. | 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 propocol description. | ||
6 | */ | 13 | */ |
7 | #include "libbb.h" | 14 | #include "libbb.h" |
8 | #include "lpr.h" | ||
9 | |||
10 | static char *mygethostname31(void) | ||
11 | { | ||
12 | char *s = xzalloc(32); | ||
13 | if (gethostname(s, 31) < 0) | ||
14 | bb_perror_msg_and_die("gethostname"); | ||
15 | /* gethostname() does not guarantee NUL-termination. xzalloc does. */ | ||
16 | return s; | ||
17 | } | ||
18 | |||
19 | static int xmkstemp(char *temp_name) | ||
20 | { | ||
21 | int fd; | ||
22 | 15 | ||
23 | fd = mkstemp(temp_name); | 16 | /* |
24 | if (fd < 0) | 17 | * LPD returns binary 0 on success. |
25 | bb_perror_msg_and_die("mkstemp"); | 18 | * Otherwise it returns error message. |
26 | return fd; | ||
27 | } | ||
28 | |||
29 | /* lpd server sends NUL byte on success. | ||
30 | * Else read the errormessage and exit. | ||
31 | */ | 19 | */ |
32 | static void get_response(int server_sock, const char *emsg) | 20 | static void get_response_or_say_and_die(const char *errmsg) |
33 | { | 21 | { |
34 | char buf = '\0'; | 22 | char buf = ' '; |
23 | |||
24 | fflush(stdout); | ||
35 | 25 | ||
36 | safe_read(server_sock, &buf, 1); | 26 | safe_read(STDOUT_FILENO, &buf, 1); |
37 | if (buf != '\0') { | 27 | if ('\0' != buf) { |
38 | bb_error_msg("%s. Server said:", emsg); | 28 | // request has failed |
39 | fputc(buf, stderr); | 29 | bb_error_msg("error while %s. Server said:", errmsg); |
30 | safe_write(STDERR_FILENO, &buf, 1); | ||
40 | logmode = 0; /* no errors from bb_copyfd_eof() */ | 31 | logmode = 0; /* no errors from bb_copyfd_eof() */ |
41 | bb_copyfd_eof(server_sock, STDERR_FILENO); | 32 | bb_copyfd_eof(STDOUT_FILENO, STDERR_FILENO); |
42 | xfunc_die(); | 33 | xfunc_die(); |
43 | } | 34 | } |
44 | } | 35 | } |
45 | 36 | ||
46 | int lpr_main(int argc, char *argv[]) MAIN_EXTERNALLY_VISIBLE; | 37 | int lpqr_main(int argc, char *argv[]) MAIN_EXTERNALLY_VISIBLE; |
47 | int lpr_main(int argc, char *argv[]) | 38 | int lpqr_main(int argc, char *argv[]) |
48 | { | 39 | { |
49 | struct netprint netprint; | ||
50 | char temp_name[] = "/tmp/lprXXXXXX"; /* for mkstemp */ | ||
51 | char *strings[5]; | ||
52 | const char *netopt; | ||
53 | const char *jobtitle; | ||
54 | const char *hostname; | ||
55 | const char *jobclass; | ||
56 | char *username; | ||
57 | int pid1000; | ||
58 | int server_sock, tmp_fd; | ||
59 | unsigned opt; | ||
60 | enum { | 40 | enum { |
61 | VERBOSE = 1 << 0, | 41 | OPT_P = 1 << 0, // -P queue[@host[:port]]. If no -P is given use $PRINTER, then "lp@localhost:515" |
62 | USE_HEADER = 1 << 1, /* -h: want banner printed */ | 42 | OPT_U = 1 << 1, // -U username |
63 | USE_MAIL = 1 << 2, /* -m: send mail back to user */ | 43 | |
64 | OPT_U = 1 << 3, /* -U username */ | 44 | LPR_V = 1 << 2, // -V: be verbose |
65 | OPT_J = 1 << 4, /* -J title: the job title for the banner page */ | 45 | LPR_h = 1 << 3, // -h: want banner printed |
66 | OPT_C = 1 << 5, /* -C class: job "class" (? supposedly printed on banner) */ | 46 | LPR_C = 1 << 4, // -C class: job "class" (? supposedly printed on banner) |
67 | OPT_P = 1 << 6, /* -P queue[@host[:port]] */ | 47 | LPR_J = 1 << 5, // -J title: the job title for the banner page |
68 | /* if no -P is given use $PRINTER, then "lp@localhost:515" */ | 48 | LPR_m = 1 << 6, // -m: send mail back to user |
49 | |||
50 | LPQ_SHORT_FMT = 1 << 2, // -s: short listing format | ||
51 | LPQ_DELETE = 1 << 3, // -d: delete job(s) | ||
52 | LPQ_FORCE = 1 << 4, // -f: force waiting job(s) to be printed | ||
69 | }; | 53 | }; |
70 | 54 | char tempfile[sizeof("/tmp/lprXXXXXX")]; | |
71 | /* Set defaults, parse options */ | 55 | const char *job_title; |
72 | hostname = mygethostname31(); | 56 | const char *printer_class = ""; // printer class, max 32 char |
73 | netopt = NULL; | 57 | const char *queue; // name of printer queue |
74 | username = getenv("LOGNAME"); | 58 | const char *server = "localhost"; // server[:port] of printer queue |
75 | if (username == NULL) | 59 | char *hostname; |
76 | username = (char*)"user"; /* TODO: getpwuid(getuid())->pw_name? */ | 60 | // N.B. IMHO getenv("USER") can be way easily spoofed! |
77 | opt = getopt32(argv, "VhmU:J:C:P:", | 61 | const char *user = bb_getpwuid(NULL, -1, getuid()); |
78 | &username, &jobtitle, &jobclass, &netopt); | 62 | unsigned job; |
63 | unsigned opts; | ||
64 | int old_stdout, fd; | ||
65 | |||
66 | // parse options | ||
67 | // TODO: set opt_complementary: s,d,f are mutually exclusive | ||
68 | opts = getopt32(argv, | ||
69 | (/*lp*/'r' == applet_name[2]) ? "P:U:VhC:J:m" : "P:U:sdf" | ||
70 | , &queue, &user | ||
71 | , &printer_class, &job_title | ||
72 | ); | ||
79 | argv += optind; | 73 | argv += optind; |
80 | parse_prt(netopt, &netprint); | 74 | |
81 | username = xstrndup(username, 31); | 75 | // if queue is not specified -> use $PRINTER |
82 | 76 | if (!(opts & OPT_P)) | |
83 | /* For stdin we need to save it to a tempfile, | 77 | queue = getenv("PRINTER"); |
84 | * otherwise we can't know the size. */ | 78 | // if queue is still not specified -> |
85 | tmp_fd = -1; | 79 | if (!queue) { |
86 | if (!*argv) { | 80 | // ... queue defaults to "lp" |
87 | if (jobtitle == NULL) | 81 | // server defaults to "localhost" |
88 | jobtitle = (char *) bb_msg_standard_input; | 82 | queue = "lp"; |
89 | 83 | // if queue is specified -> | |
90 | tmp_fd = xmkstemp(temp_name); | 84 | } else { |
91 | if (bb_copyfd_eof(STDIN_FILENO, tmp_fd) < 0) | 85 | // queue name is to the left of '@' |
92 | goto del_temp_file; | 86 | char *s = strchr(queue, '@'); |
93 | /* TODO: we actually can have deferred write errors... */ | 87 | if (s) { |
94 | close(tmp_fd); | 88 | // server name is to the right of '@' |
95 | argv--; /* back off from NULL */ | 89 | *s = '\0'; |
96 | *argv = temp_name; | 90 | server = s + 1; |
91 | } | ||
97 | } | 92 | } |
98 | 93 | ||
99 | if (opt & VERBOSE) | 94 | // do connect |
100 | puts("connect to server"); | 95 | fd = create_and_connect_stream_or_die(server, 515); |
101 | server_sock = xconnect_stream(netprint.lsa); | 96 | // play with descriptors to save space: fdprintf > printf |
97 | old_stdout = dup(STDOUT_FILENO); | ||
98 | xmove_fd(fd, STDOUT_FILENO); | ||
99 | |||
100 | // | ||
101 | // LPQ ------------------------ | ||
102 | // | ||
103 | if (/*lp*/'q' == applet_name[2]) { | ||
104 | char cmd; | ||
105 | // force printing of every job still in queue | ||
106 | if (opts & LPQ_FORCE) { | ||
107 | cmd = 1; | ||
108 | goto command; | ||
109 | // delete job(s) | ||
110 | } else if (opts & LPQ_DELETE) { | ||
111 | printf("\x5" "%s %s", queue, user); | ||
112 | while (*argv) { | ||
113 | printf(" %s", *argv++); | ||
114 | } | ||
115 | bb_putchar('\n'); | ||
116 | // dump current jobs status | ||
117 | // N.B. periodical polling should be achieved | ||
118 | // via "watch -n delay lpq" | ||
119 | // They say it's the UNIX-way :) | ||
120 | } else { | ||
121 | cmd = (opts & LPQ_SHORT_FMT) ? 3 : 4; | ||
122 | command: | ||
123 | printf("%c" "%s\n", cmd, queue); | ||
124 | bb_copyfd_eof(STDOUT_FILENO, old_stdout); | ||
125 | } | ||
102 | 126 | ||
103 | /* "Receive a printer job" command */ | 127 | return EXIT_SUCCESS; |
104 | fdprintf(server_sock, "\x2" "%s\n", netprint.queue); | 128 | } |
105 | get_response(server_sock, "set queue failed"); | ||
106 | 129 | ||
107 | pid1000 = getpid() % 1000; | 130 | // |
108 | while (*argv) { | 131 | // LPR ------------------------ |
109 | char dfa_name[sizeof("dfAnnn") + 32]; | 132 | // |
110 | struct stat st; | 133 | if (opts & LPR_V) |
111 | char **sptr; | 134 | bb_error_msg("connected to server"); |
112 | unsigned size; | ||
113 | int fd; | ||
114 | 135 | ||
115 | fd = xopen(*argv, O_RDONLY); | 136 | job = getpid() % 1000; |
137 | // TODO: when do finally we invent char *xgethostname()?!! | ||
138 | hostname = xzalloc(MAXHOSTNAMELEN+1); | ||
139 | gethostname(hostname, MAXHOSTNAMELEN); | ||
116 | 140 | ||
117 | /* "The name ... should start with ASCII "dfA", | 141 | // no files given on command line? -> use stdin |
118 | * followed by a three digit job number, followed | 142 | if (!*argv) |
119 | * by the host name which has constructed the file." */ | 143 | *--argv = (char *)"-"; |
120 | snprintf(dfa_name, sizeof(dfa_name), | 144 | |
121 | "dfA%03u%s", pid1000, hostname); | 145 | printf("\x2" "%s\n", queue); |
122 | pid1000 = (pid1000 + 1) % 1000; | 146 | get_response_or_say_and_die("setting queue"); |
123 | 147 | ||
124 | /* | 148 | // process files |
125 | * Generate control file contents | 149 | do { |
126 | */ | 150 | struct stat st; |
127 | /* H HOST, P USER, l DATA_FILE_NAME, J JOBNAME */ | 151 | char *c; |
128 | strings[0] = xasprintf("H%.32s\n" "P%.32s\n" "l%.32s\n" | 152 | char *remote_filename; |
129 | "J%.99s\n", | 153 | char *controlfile; |
130 | hostname, username, dfa_name, | 154 | |
131 | (opt & OPT_J) ? jobtitle : *argv); | 155 | // if data file is stdin, we need to dump it first |
132 | sptr = &strings[1]; | 156 | if (LONE_DASH(*argv)) { |
133 | /* C CLASS - printed on banner page (if L cmd is also given) */ | 157 | strcpy(tempfile, "/tmp/lprXXXXXX"); |
134 | if (opt & OPT_C) /* [1] */ | 158 | fd = mkstemp(tempfile); |
135 | *sptr++ = xasprintf("C%.32s\n", jobclass); | 159 | if (fd < 0) |
136 | /* M WHOM_TO_MAIL */ | 160 | bb_perror_msg_and_die("mkstemp"); |
137 | if (opt & USE_MAIL) /* [2] */ | 161 | bb_copyfd_eof(STDIN_FILENO, fd); |
138 | *sptr++ = xasprintf("M%.32s\n", username); | 162 | xlseek(fd, 0, SEEK_SET); |
139 | /* H USER - print banner page, with given user's name */ | 163 | *argv = (char*)bb_msg_standard_input; |
140 | if (opt & USE_HEADER) /* [3] */ | 164 | } else { |
141 | *sptr++ = xasprintf("L%.32s\n", username); | 165 | fd = xopen(*argv, O_RDONLY); |
142 | *sptr = NULL; /* [4] max */ | ||
143 | |||
144 | /* RFC 1179: "LPR servers MUST be able | ||
145 | * to receive the control file subcommand first | ||
146 | * and SHOULD be able to receive the data file | ||
147 | * subcommand first". | ||
148 | * Ok, we'll send control file first. */ | ||
149 | size = 0; | ||
150 | sptr = strings; | ||
151 | while (*sptr) | ||
152 | size += strlen(*sptr++); | ||
153 | if (opt & VERBOSE) | ||
154 | puts("send control file"); | ||
155 | /* 2: "Receive control file" subcommand */ | ||
156 | fdprintf(server_sock, "\x2" "%u c%s\n", size, dfa_name + 1); | ||
157 | sptr = strings; | ||
158 | while (*sptr) { | ||
159 | xwrite(server_sock, *sptr, strlen(*sptr)); | ||
160 | free(*sptr); | ||
161 | sptr++; | ||
162 | } | 166 | } |
163 | free(strings); | 167 | |
168 | /* "The name ... should start with ASCII "cfA", | ||
169 | * followed by a three digit job number, followed | ||
170 | * by the host name which has constructed the file." | ||
171 | * We supply 'c' or 'd' as needed for control/data file. */ | ||
172 | remote_filename = xasprintf("fA%03u%s", job, hostname); | ||
173 | |||
174 | // create control file | ||
175 | // TODO: all lines but 2 last are constants! How we can use this fact? | ||
176 | controlfile = xasprintf( | ||
177 | "H" "%.32s\n" "P" "%.32s\n" /* H HOST, P USER */ | ||
178 | "C" "%.32s\n" /* C CLASS - printed on banner page (if L cmd is also given) */ | ||
179 | "J" "%.99s\n" /* J JOBNAME */ | ||
180 | /* "class name for banner page and job name | ||
181 | * for banner page commands must precede L command" */ | ||
182 | "L" "%.32s\n" /* L USER - print banner page, with given user's name */ | ||
183 | "M" "%.32s\n" /* M WHOM_TO_MAIL */ | ||
184 | "l" "d%.31s\n" /* l DATA_FILE_NAME ("dfAxxx") */ | ||
185 | , hostname, user | ||
186 | , printer_class /* can be "" */ | ||
187 | , ((opts & LPR_J) ? job_title : *argv) | ||
188 | , (opts & LPR_h) ? user : "" | ||
189 | , (opts & LPR_m) ? user : "" | ||
190 | , remote_filename | ||
191 | ); | ||
192 | // delete possible "\nX\n" patterns | ||
193 | while ((c = strchr(controlfile, '\n')) != NULL && c[1] && c[2] == '\n') | ||
194 | memmove(c, c+2, strlen(c+1)); /* strlen(c+1) == strlen(c+2) + 1 */ | ||
195 | |||
196 | // send control file | ||
197 | if (opts & LPR_V) | ||
198 | bb_error_msg("sending control file"); | ||
164 | /* "Once all of the contents have | 199 | /* "Once all of the contents have |
165 | * been delivered, an octet of zero bits is sent as | 200 | * been delivered, an octet of zero bits is sent as |
166 | * an indication that the file being sent is complete. | 201 | * an indication that the file being sent is complete. |
167 | * A second level of acknowledgement processing | 202 | * A second level of acknowledgement processing |
168 | * must occur at this point." */ | 203 | * must occur at this point." */ |
169 | xwrite(server_sock, "", 1); | 204 | printf("\x2" "%u %s\n" "c%s" "%c", |
170 | get_response(server_sock, "send control file failed"); | 205 | (unsigned)strlen(controlfile), |
171 | 206 | remote_filename, controlfile, '\0'); | |
172 | /* Sending data */ | 207 | get_response_or_say_and_die("sending control file"); |
173 | st.st_size = 0; /* paranoia */ | 208 | |
209 | // send data file, with name "dfaXXX" | ||
210 | if (opts & LPR_V) | ||
211 | bb_error_msg("sending data file"); | ||
212 | st.st_size = 0; /* paranoia: fstat may theoretically fail */ | ||
174 | fstat(fd, &st); | 213 | fstat(fd, &st); |
175 | if (opt & VERBOSE) | 214 | printf("\x3" "%"OFF_FMT"u d%s\n", st.st_size, remote_filename); |
176 | puts("send data file"); | 215 | if (bb_copyfd_size(fd, STDOUT_FILENO, st.st_size) != st.st_size) { |
177 | /* 3: "Receive data file" subcommand */ | 216 | // We're screwed. We sent less bytes than we advertised. |
178 | fdprintf(server_sock, "\x3" "%"OFF_FMT"u %s\n", st.st_size, dfa_name); | 217 | bb_error_msg_and_die("local file changed size?!"); |
179 | /* TODO: if file shrank and we wrote less than st.st_size, | 218 | } |
180 | * pad output with NUL bytes? Otherwise server won't know | 219 | bb_putchar('\0'); |
181 | * that we are done. */ | 220 | get_response_or_say_and_die("sending data file"); |
182 | if (bb_copyfd_size(fd, server_sock, st.st_size) < 0) | ||
183 | xfunc_die(); | ||
184 | close(fd); | ||
185 | xwrite(server_sock, "", 1); | ||
186 | get_response(server_sock, "send file failed"); | ||
187 | 221 | ||
188 | argv++; | 222 | // delete temporary file if we dumped stdin |
189 | } | 223 | if (*argv == (char*)bb_msg_standard_input) |
224 | unlink(tempfile); | ||
190 | 225 | ||
191 | if (ENABLE_FEATURE_CLEAN_UP) | 226 | // cleanup |
192 | close(server_sock); | 227 | close(fd); |
228 | free(remote_filename); | ||
229 | free(controlfile); | ||
193 | 230 | ||
194 | if (tmp_fd >= 0) { | 231 | // next, please! |
195 | del_temp_file: | 232 | job = (job + 1) % 1000; |
196 | unlink(temp_name); | 233 | } while (*++argv); |
197 | } | ||
198 | 234 | ||
199 | return 0; | 235 | return EXIT_SUCCESS; |
200 | } | 236 | } |