source: branches/3.2/mindi-busybox/printutils/lpr.c @ 3232

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