1 /*
2 * "lpr" command for CUPS.
3 *
4 * Copyright © 2020-2024 by OpenPrinting.
5 * Copyright © 2007-2019 by Apple Inc.
6 * Copyright © 1997-2007 by Easy Software Products.
7 *
8 * Licensed under Apache License v2.0. See the file "LICENSE" for more
9 * information.
10 */
11
12 /*
13 * Include necessary headers...
14 */
15
16 #include <cups/cups-private.h>
17
18
19 /*
20 * Local functions...
21 */
22
23 static void usage(void) _CUPS_NORETURN;
24
25
26 /*
27 * 'main()' - Parse options and send files for printing.
28 */
29
30 int
main(int argc,char * argv[])31 main(int argc, /* I - Number of command-line arguments */
32 char *argv[]) /* I - Command-line arguments */
33 {
34 int i, j; /* Looping var */
35 int job_id; /* Job ID */
36 char ch; /* Option character */
37 char *printer, /* Destination printer or class */
38 *instance, /* Instance */
39 *opt; /* Option pointer */
40 const char *title; /* Job title */
41 int num_copies; /* Number of copies per file */
42 int num_files; /* Number of files to print */
43 const char *files[1000]; /* Files to print */
44 cups_dest_t *dest; /* Selected destination */
45 int num_options; /* Number of options */
46 cups_option_t *options; /* Options */
47 int deletefile; /* Delete file after print? */
48 char buffer[8192]; /* Copy buffer */
49
50
51 _cupsSetLocale(argv);
52
53 deletefile = 0;
54 printer = NULL;
55 dest = NULL;
56 num_options = 0;
57 options = NULL;
58 num_files = 0;
59 title = NULL;
60
61 for (i = 1; i < argc; i ++)
62 {
63 if (!strcmp(argv[i], "--help"))
64 usage();
65 else if (argv[i][0] == '-')
66 {
67 for (opt = argv[i] + 1; *opt; opt ++)
68 {
69 switch (ch = *opt)
70 {
71 case 'E' : /* Encrypt */
72 #ifdef HAVE_TLS
73 cupsSetEncryption(HTTP_ENCRYPT_REQUIRED);
74 #else
75 _cupsLangPrintf(stderr, _("%s: Sorry, no encryption support."), argv[0]);
76 #endif /* HAVE_TLS */
77 break;
78
79 case 'U' : /* Username */
80 if (opt[1] != '\0')
81 {
82 cupsSetUser(opt + 1);
83 opt += strlen(opt) - 1;
84 }
85 else
86 {
87 i ++;
88 if (i >= argc)
89 {
90 _cupsLangPrintf(stderr, _("%s: Error - expected username after \"-U\" option."), argv[0]);
91 usage();
92 }
93
94 cupsSetUser(argv[i]);
95 }
96 break;
97
98 case 'H' : /* Connect to host */
99 if (opt[1] != '\0')
100 {
101 cupsSetServer(opt + 1);
102 opt += strlen(opt) - 1;
103 }
104 else
105 {
106 i ++;
107
108 if (i >= argc)
109 {
110 _cupsLangPrintf(stderr, _("%s: Error - expected hostname after \"-H\" option."), argv[0]);
111 usage();
112 }
113 else
114 cupsSetServer(argv[i]);
115 }
116 break;
117
118 case '1' : /* TROFF font set 1 */
119 case '2' : /* TROFF font set 2 */
120 case '3' : /* TROFF font set 3 */
121 case '4' : /* TROFF font set 4 */
122 case 'i' : /* indent */
123 case 'w' : /* width */
124 if (opt[1] != '\0')
125 {
126 opt += strlen(opt) - 1;
127 }
128 else
129 {
130 i ++;
131
132 if (i >= argc)
133 {
134 _cupsLangPrintf(stderr,
135 _("%s: Error - expected value after \"-%c\" "
136 "option."), argv[0], ch);
137 usage();
138 }
139 }
140
141 case 'c' : /* CIFPLOT */
142 case 'd' : /* DVI */
143 case 'f' : /* FORTRAN */
144 case 'g' : /* plot */
145 case 'n' : /* Ditroff */
146 case 't' : /* Troff */
147 case 'v' : /* Raster image */
148 _cupsLangPrintf(stderr, _("%s: Warning - \"%c\" format modifier not supported - output may not be correct."), argv[0], ch);
149 break;
150
151 case 'o' : /* Option */
152 if (opt[1] != '\0')
153 {
154 num_options = cupsParseOptions(opt + 1, num_options, &options);
155 opt += strlen(opt) - 1;
156 }
157 else
158 {
159 i ++;
160 if (i >= argc)
161 {
162 _cupsLangPrintf(stderr, _("%s: Error - expected option=value after \"-o\" option."), argv[0]);
163 usage();
164 }
165
166 num_options = cupsParseOptions(argv[i], num_options, &options);
167 }
168 break;
169
170 case 'l' : /* Literal/raw */
171 num_options = cupsAddOption("raw", "true", num_options, &options);
172 break;
173
174 case 'p' : /* Prettyprint */
175 num_options = cupsAddOption("prettyprint", "true", num_options, &options);
176 break;
177
178 case 'h' : /* Suppress burst page */
179 num_options = cupsAddOption("job-sheets", "none", num_options, &options);
180 break;
181
182 case 's' : /* Don't use symlinks */
183 break;
184
185 case 'm' : /* Mail on completion */
186 {
187 char email[1024]; /* EMail address */
188
189 snprintf(email, sizeof(email), "mailto:%s@%s", cupsUser(), httpGetHostname(NULL, buffer, sizeof(buffer)));
190 num_options = cupsAddOption("notify-recipient-uri", email, num_options, &options);
191 }
192 break;
193
194 case 'q' : /* Queue file but don't print */
195 num_options = cupsAddOption("job-hold-until", "indefinite", num_options, &options);
196 break;
197
198 case 'r' : /* Remove file after printing */
199 deletefile = 1;
200 break;
201
202 case 'P' : /* Destination printer or class */
203 if (opt[1] != '\0')
204 {
205 printer = opt + 1;
206 opt += strlen(opt) - 1;
207 }
208 else
209 {
210 i ++;
211 if (i >= argc)
212 {
213 _cupsLangPrintf(stderr, _("%s: Error - expected destination after \"-P\" option."), argv[0]);
214 usage();
215 }
216
217 printer = argv[i];
218 }
219
220 if ((instance = strrchr(printer, '/')) != NULL)
221 *instance++ = '\0';
222
223 if ((dest = cupsGetNamedDest(NULL, printer, instance)) != NULL)
224 {
225 for (j = 0; j < dest->num_options; j ++)
226 if (cupsGetOption(dest->options[j].name, num_options,
227 options) == NULL)
228 num_options = cupsAddOption(dest->options[j].name,
229 dest->options[j].value,
230 num_options, &options);
231 }
232 else if (cupsLastError() == IPP_STATUS_ERROR_BAD_REQUEST ||
233 cupsLastError() == IPP_STATUS_ERROR_VERSION_NOT_SUPPORTED)
234 {
235 _cupsLangPrintf(stderr, _("%s: Error - add '/version=1.1' to server name."), argv[0]);
236 return (1);
237 }
238 else if (cupsLastError() == IPP_STATUS_ERROR_NOT_FOUND)
239 {
240 _cupsLangPrintf(stderr,
241 _("%s: Error - The printer or class does not exist."), argv[0]);
242 return (1);
243 }
244 break;
245
246 case '#' : /* Number of copies */
247 if (opt[1] != '\0')
248 {
249 num_copies = atoi(opt + 1);
250 opt += strlen(opt) - 1;
251 }
252 else
253 {
254 i ++;
255 if (i >= argc)
256 {
257 _cupsLangPrintf(stderr, _("%s: Error - expected copies after \"-#\" option."), argv[0]);
258 usage();
259 }
260
261 num_copies = atoi(argv[i]);
262 }
263
264 if (num_copies < 1)
265 {
266 _cupsLangPrintf(stderr, _("%s: Error - copies must be 1 or more."), argv[0]);
267 return (1);
268 }
269
270 num_options = cupsAddIntegerOption("copies", num_copies, num_options, &options);
271 break;
272
273 case 'C' : /* Class */
274 case 'J' : /* Job name */
275 case 'T' : /* Title */
276 if (opt[1] != '\0')
277 {
278 title = opt + 1;
279 opt += strlen(opt) - 1;
280 }
281 else
282 {
283 i ++;
284 if (i >= argc)
285 {
286 _cupsLangPrintf(stderr, _("%s: Error - expected name after \"-%c\" option."), argv[0], ch);
287 usage();
288 }
289
290 title = argv[i];
291 }
292 break;
293
294 default :
295 _cupsLangPrintf(stderr, _("%s: Error - unknown option \"%c\"."), argv[0], *opt);
296 return (1);
297 }
298 }
299 }
300 else if (num_files < 1000)
301 {
302 /*
303 * Print a file...
304 */
305
306 if (access(argv[i], R_OK) != 0)
307 {
308 _cupsLangPrintf(stderr,
309 _("%s: Error - unable to access \"%s\" - %s"),
310 argv[0], argv[i], strerror(errno));
311 return (1);
312 }
313
314 files[num_files] = argv[i];
315 num_files ++;
316
317 if (title == NULL)
318 {
319 if ((title = strrchr(argv[i], '/')) != NULL)
320 title ++;
321 else
322 title = argv[i];
323 }
324 }
325 else
326 {
327 _cupsLangPrintf(stderr, _("%s: Error - too many files - \"%s\"."), argv[0], argv[i]);
328 }
329 }
330
331 /*
332 * See if we have any files to print; if not, print from stdin...
333 */
334
335 if (printer == NULL)
336 {
337 if ((dest = cupsGetNamedDest(NULL, NULL, NULL)) != NULL)
338 {
339 printer = dest->name;
340
341 for (j = 0; j < dest->num_options; j ++)
342 if (cupsGetOption(dest->options[j].name, num_options, options) == NULL)
343 num_options = cupsAddOption(dest->options[j].name,
344 dest->options[j].value,
345 num_options, &options);
346 }
347 else if (cupsLastError() == IPP_STATUS_ERROR_BAD_REQUEST ||
348 cupsLastError() == IPP_STATUS_ERROR_VERSION_NOT_SUPPORTED)
349 {
350 _cupsLangPrintf(stderr,
351 _("%s: Error - add '/version=1.1' to server "
352 "name."), argv[0]);
353 return (1);
354 }
355 }
356
357 if (printer == NULL)
358 {
359 if (!cupsGetNamedDest(NULL, NULL, NULL) && cupsLastError() == IPP_STATUS_ERROR_NOT_FOUND)
360 _cupsLangPrintf(stderr, _("%s: Error - %s"), argv[0], cupsLastErrorString());
361 else
362 _cupsLangPrintf(stderr, _("%s: Error - scheduler not responding."), argv[0]);
363
364 return (1);
365 }
366
367 if (num_files > 0)
368 {
369 job_id = cupsPrintFiles(printer, num_files, files, title, num_options, options);
370
371 if (deletefile && job_id > 0)
372 {
373 /*
374 * Delete print files after printing...
375 */
376
377 for (i = 0; i < num_files; i ++)
378 unlink(files[i]);
379 }
380 }
381 else if ((job_id = cupsCreateJob(CUPS_HTTP_DEFAULT, printer,
382 title ? title : "(stdin)",
383 num_options, options)) > 0)
384 {
385 http_status_t status; /* Write status */
386 const char *format; /* Document format */
387 ssize_t bytes; /* Bytes read */
388
389 if (cupsGetOption("raw", num_options, options))
390 format = CUPS_FORMAT_RAW;
391 else if ((format = cupsGetOption("document-format", num_options,
392 options)) == NULL)
393 format = CUPS_FORMAT_AUTO;
394
395 status = cupsStartDocument(CUPS_HTTP_DEFAULT, printer, job_id, NULL,
396 format, 1);
397
398 while (status == HTTP_CONTINUE &&
399 (bytes = read(0, buffer, sizeof(buffer))) > 0)
400 status = cupsWriteRequestData(CUPS_HTTP_DEFAULT, buffer, (size_t)bytes);
401
402 if (status != HTTP_CONTINUE)
403 {
404 _cupsLangPrintf(stderr, _("%s: Error - unable to queue from stdin - %s."),
405 argv[0], httpStatus(status));
406 cupsFinishDocument(CUPS_HTTP_DEFAULT, printer);
407 cupsCancelJob2(CUPS_HTTP_DEFAULT, printer, job_id, 0);
408 return (1);
409 }
410
411 if (cupsFinishDocument(CUPS_HTTP_DEFAULT, printer) != IPP_OK)
412 {
413 _cupsLangPrintf(stderr, "%s: %s", argv[0], cupsLastErrorString());
414 cupsCancelJob2(CUPS_HTTP_DEFAULT, printer, job_id, 0);
415 return (1);
416 }
417 }
418
419 if (job_id < 1)
420 {
421 _cupsLangPrintf(stderr, "%s: %s", argv[0], cupsLastErrorString());
422 return (1);
423 }
424
425 return (0);
426 }
427
428
429 /*
430 * 'usage()' - Show program usage and exit.
431 */
432
433 static void
usage(void)434 usage(void)
435 {
436 _cupsLangPuts(stdout, _("Usage: lpr [options] [file(s)]"));
437 _cupsLangPuts(stdout, _("Options:"));
438 _cupsLangPuts(stdout, _("-# num-copies Specify the number of copies to print"));
439 _cupsLangPuts(stdout, _("-E Encrypt the connection to the server"));
440 _cupsLangPuts(stdout, _("-H server[:port] Connect to the named server and port"));
441 _cupsLangPuts(stdout, _("-m Send an email notification when the job completes"));
442 _cupsLangPuts(stdout, _("-o option[=value] Specify a printer-specific option"));
443 _cupsLangPuts(stdout, _("-o job-sheets=standard Print a banner page with the job"));
444 _cupsLangPuts(stdout, _("-o media=size Specify the media size to use"));
445 _cupsLangPuts(stdout, _("-o number-up=N Specify that input pages should be printed N-up (1, 2, 4, 6, 9, and 16 are supported)"));
446 _cupsLangPuts(stdout, _("-o orientation-requested=N\n"
447 " Specify portrait (3) or landscape (4) orientation"));
448 _cupsLangPuts(stdout, _("-o print-quality=N Specify the print quality - draft (3), normal (4), or best (5)"));
449 _cupsLangPuts(stdout, _("-o sides=one-sided Specify 1-sided printing"));
450 _cupsLangPuts(stdout, _("-o sides=two-sided-long-edge\n"
451 " Specify 2-sided portrait printing"));
452 _cupsLangPuts(stdout, _("-o sides=two-sided-short-edge\n"
453 " Specify 2-sided landscape printing"));
454 _cupsLangPuts(stdout, _("-P destination Specify the destination"));
455 _cupsLangPuts(stdout, _("-q Specify the job should be held for printing"));
456 _cupsLangPuts(stdout, _("-r Remove the file(s) after submission"));
457 _cupsLangPuts(stdout, _("-T title Specify the job title"));
458 _cupsLangPuts(stdout, _("-U username Specify the username to use for authentication"));
459
460 exit(1);
461 }
462