2 * "lp" command for CUPS.
4 * Copyright © 2020-2024 by OpenPrinting.
5 * Copyright © 2007-2021 by Apple Inc.
6 * Copyright © 1997-2007 by Easy Software Products.
8 * Licensed under Apache License v2.0. See the file "LICENSE" for more
13 * Include necessary headers...
16 #include <cups/cups-private.h>
23 static int restart_job(const char *command
, int job_id
, const char *job_hold_until
);
24 static int set_job_attrs(const char *command
, int job_id
, int num_options
, cups_option_t
*options
);
25 static void usage(void) _CUPS_NORETURN
;
29 * 'main()' - Parse options and send files for printing.
33 main(int argc
, /* I - Number of command-line arguments */
34 char *argv
[]) /* I - Command-line arguments */
36 int i
, j
; /* Looping vars */
37 int job_id
; /* Job ID */
38 char *printer
, /* Printer name */
39 *instance
, /* Instance name */
40 *opt
, /* Option pointer */
41 *val
, /* Option value */
42 *title
; /* Job title */
43 int priority
; /* Job priority (1-100) */
44 int num_copies
; /* Number of copies per file */
45 int num_files
; /* Number of files to print */
46 const char *files
[1000]; /* Files to print */
47 cups_dest_t
*dest
; /* Selected destination */
48 int num_options
; /* Number of options */
49 cups_option_t
*options
; /* Options */
50 int end_options
; /* No more options? */
51 int silent
; /* Silent or verbose output? */
52 char buffer
[8192]; /* Copy buffer */
57 * Solaris does some rather strange things to re-queue remote print
58 * jobs. On bootup, the "lp" command is run as "printd" to re-spool
59 * any remote jobs in /var/spool/print. Since CUPS doesn't need this
60 * nonsense, we just need to add the necessary check here to prevent
61 * lp from causing boot problems...
64 if ((val
= strrchr(argv
[0], '/')) != NULL
)
69 if (!strcmp(val
, "printd"))
85 for (i
= 1; i
< argc
; i
++)
87 if (!strcmp(argv
[i
], "--help"))
89 else if (argv
[i
][0] == '-' && argv
[i
][1] && !end_options
)
91 for (opt
= argv
[i
] + 1; *opt
; opt
++)
95 case 'E' : /* Encrypt */
96 cupsSetEncryption(HTTP_ENCRYPTION_REQUIRED
);
99 case 'U' : /* Username */
102 cupsSetUser(opt
+ 1);
103 opt
+= strlen(opt
) - 1;
110 _cupsLangPrintf(stderr
, _("%s: Error - expected username after \"-U\" option."), argv
[0]);
114 cupsSetUser(argv
[i
]);
118 case 'c' : /* Copy to spool dir (always enabled) */
121 case 'd' : /* Destination printer or class */
125 opt
+= strlen(opt
) - 1;
133 _cupsLangPrintf(stderr
, _("%s: Error - expected destination after \"-d\" option."), argv
[0]);
140 if ((instance
= strrchr(printer
, '/')) != NULL
)
143 if ((dest
= cupsGetNamedDest(CUPS_HTTP_DEFAULT
, printer
,
146 for (j
= 0; j
< dest
->num_options
; j
++)
147 if (cupsGetOption(dest
->options
[j
].name
, num_options
,
149 num_options
= cupsAddOption(dest
->options
[j
].name
,
150 dest
->options
[j
].value
,
151 num_options
, &options
);
153 else if (cupsGetError() == IPP_STATUS_ERROR_BAD_REQUEST
||
154 cupsGetError() == IPP_STATUS_ERROR_VERSION_NOT_SUPPORTED
)
156 _cupsLangPrintf(stderr
,
157 _("%s: Error - add '/version=1.1' to server "
161 else if (cupsGetError() == IPP_STATUS_ERROR_NOT_FOUND
)
163 _cupsLangPrintf(stderr
,
164 _("%s: Error - The printer or class does not exist."), argv
[0]);
169 case 'f' : /* Form */
172 opt
+= strlen(opt
) - 1;
180 _cupsLangPrintf(stderr
, _("%s: Error - expected form after \"-f\" option."), argv
[0]);
185 _cupsLangPrintf(stderr
, _("%s: Warning - form option ignored."), argv
[0]);
188 case 'h' : /* Destination host */
191 cupsSetServer(opt
+ 1);
192 opt
+= strlen(opt
) - 1;
200 _cupsLangPrintf(stderr
, _("%s: Error - expected hostname after \"-h\" option."), argv
[0]);
204 cupsSetServer(argv
[i
]);
208 case 'i' : /* Change job */
212 opt
+= strlen(opt
) - 1;
220 _cupsLangPrintf(stderr
, _("%s: Expected job ID after \"-i\" option."), argv
[0]);
229 _cupsLangPrintf(stderr
, _("%s: Error - cannot print files and alter jobs simultaneously."), argv
[0]);
233 if (strrchr(val
, '-') != NULL
)
234 job_id
= atoi(strrchr(val
, '-') + 1);
240 _cupsLangPrintf(stderr
, _("%s: Error - bad job ID."), argv
[0]);
245 case 'm' : /* Send email when job is done */
247 case 'p' : /* Notify on completion */
249 case 'w' : /* Write to console or email */
251 char email
[1024]; /* EMail address */
254 snprintf(email
, sizeof(email
), "mailto:%s@%s", cupsGetUser(), httpGetHostname(NULL
, buffer
, sizeof(buffer
)));
255 num_options
= cupsAddOption("notify-recipient-uri", email
, num_options
, &options
);
261 case 'n' : /* Number of copies */
264 num_copies
= atoi(opt
+ 1);
265 opt
+= strlen(opt
) - 1;
273 _cupsLangPrintf(stderr
, _("%s: Error - expected copies after \"-n\" option."), argv
[0]);
277 num_copies
= atoi(argv
[i
]);
282 _cupsLangPrintf(stderr
, _("%s: Error - copies must be 1 or more."), argv
[0]);
286 num_options
= cupsAddIntegerOption("copies", num_copies
, num_options
, &options
);
289 case 'o' : /* Option */
292 num_options
= cupsParseOptions(opt
+ 1, num_options
, &options
);
293 opt
+= strlen(opt
) - 1;
301 _cupsLangPrintf(stderr
, _("%s: Error - expected option=value after \"-o\" option."), argv
[0]);
305 num_options
= cupsParseOptions(argv
[i
], num_options
, &options
);
310 case 'p' : /* Queue priority */
312 case 'q' : /* Queue priority */
315 priority
= atoi(opt
+ 1);
316 opt
+= strlen(opt
) - 1;
322 _cupsLangPrintf(stderr
, _("%s: Error - expected priority after \"-%c\" option."), argv
[0], *opt
);
328 priority
= atoi(argv
[i
]);
332 * For 100% Solaris compatibility, need to add:
334 * priority = 99 * (39 - priority) / 39 + 1;
336 * However, to keep CUPS lp the same across all platforms
337 * we will break compatibility this far...
340 if (priority
< 1 || priority
> 100)
342 _cupsLangPrintf(stderr
, _("%s: Error - priority must be between 1 and 100."), argv
[0]);
346 num_options
= cupsAddIntegerOption("job-priority", priority
, num_options
, &options
);
349 case 's' : /* Silent */
353 case 't' : /* Title */
357 opt
+= strlen(opt
) - 1;
365 _cupsLangPrintf(stderr
, _("%s: Error - expected title after \"-t\" option."), argv
[0]);
373 case 'y' : /* mode-list */
376 opt
+= strlen(opt
) - 1;
384 _cupsLangPrintf(stderr
, _("%s: Error - expected mode list after \"-y\" option."), argv
[0]);
389 _cupsLangPrintf(stderr
, _("%s: Warning - mode option ignored."), argv
[0]);
392 case 'H' : /* Hold job */
396 opt
+= strlen(opt
) - 1;
404 _cupsLangPrintf(stderr
, _("%s: Error - expected hold name after \"-H\" option."), argv
[0]);
411 if (!strcmp(val
, "hold"))
412 num_options
= cupsAddOption("job-hold-until", "indefinite", num_options
, &options
);
413 else if (!strcmp(val
, "resume") || !strcmp(val
, "release"))
414 num_options
= cupsAddOption("job-hold-until", "no-hold", num_options
, &options
);
415 else if (!strcmp(val
, "immediate"))
417 num_options
= cupsAddOption("job-hold-until", "no-hold", num_options
, &options
);
418 num_options
= cupsAddOption("job-priority", "100", num_options
, &options
);
420 else if (!strcmp(val
, "restart"))
424 _cupsLangPrintf(stderr
, _("%s: Need job ID (\"-i jobid\") before \"-H restart\"."), argv
[0]);
428 if (restart_job(argv
[0], job_id
, cupsGetOption("job-hold-until", num_options
, options
)))
432 num_options
= cupsAddOption("job-hold-until", val
, num_options
, &options
);
435 case 'P' : /* Page list */
439 opt
+= strlen(opt
) - 1;
447 _cupsLangPrintf(stderr
, _("%s: Error - expected page list after \"-P\" option."), argv
[0]);
454 num_options
= cupsAddOption("page-ranges", val
, num_options
, &options
);
457 case 'S' : /* character set */
460 opt
+= strlen(opt
) - 1;
468 _cupsLangPrintf(stderr
, _("%s: Error - expected character set after \"-S\" option."), argv
[0]);
473 _cupsLangPrintf(stderr
, _("%s: Warning - character set option ignored."), argv
[0]);
476 case 'T' : /* Content-Type */
479 opt
+= strlen(opt
) - 1;
487 _cupsLangPrintf(stderr
, _("%s: Error - expected content type after \"-T\" option."), argv
[0]);
492 _cupsLangPrintf(stderr
, _("%s: Warning - content type option ignored."), argv
[0]);
495 case '-' : /* Stop processing options */
498 _cupsLangPrintf(stderr
, _("%s: Error - unknown option \"%s\"."), argv
[0], argv
[i
]);
506 _cupsLangPrintf(stderr
, _("%s: Error - unknown option \"%c\"."), argv
[0], *opt
);
511 else if (!strcmp(argv
[i
], "-"))
513 if (num_files
|| job_id
)
515 _cupsLangPrintf(stderr
,
516 _("%s: Error - cannot print from stdin if files or a "
517 "job ID are provided."), argv
[0]);
523 else if (num_files
< 1000 && job_id
== 0)
529 if (access(argv
[i
], R_OK
) != 0)
531 _cupsLangPrintf(stderr
, _("%s: Error - unable to access \"%s\" - %s"), argv
[0], argv
[i
], strerror(errno
));
535 files
[num_files
] = argv
[i
];
540 if ((title
= strrchr(argv
[i
], '/')) != NULL
)
548 _cupsLangPrintf(stderr
, _("%s: Error - too many files - \"%s\"."), argv
[0], argv
[i
]);
553 * See if we are altering an existing job...
557 return (set_job_attrs(argv
[0], job_id
, num_options
, options
));
560 * See if we have any files to print; if not, print from stdin...
565 if ((dest
= cupsGetNamedDest(NULL
, NULL
, NULL
)) != NULL
)
567 printer
= dest
->name
;
569 for (j
= 0; j
< dest
->num_options
; j
++)
570 if (cupsGetOption(dest
->options
[j
].name
, num_options
, options
) == NULL
)
571 num_options
= cupsAddOption(dest
->options
[j
].name
,
572 dest
->options
[j
].value
,
573 num_options
, &options
);
575 else if (cupsGetError() == IPP_STATUS_ERROR_BAD_REQUEST
||
576 cupsGetError() == IPP_STATUS_ERROR_VERSION_NOT_SUPPORTED
)
578 _cupsLangPrintf(stderr
,
579 _("%s: Error - add '/version=1.1' to server "
587 if (!cupsGetNamedDest(NULL
, NULL
, NULL
) && cupsGetError() == IPP_STATUS_ERROR_NOT_FOUND
)
588 _cupsLangPrintf(stderr
, _("%s: Error - %s"), argv
[0], cupsGetErrorString());
590 _cupsLangPrintf(stderr
, _("%s: Error - scheduler not responding."), argv
[0]);
596 job_id
= cupsPrintFiles(printer
, num_files
, files
, title
, num_options
, options
);
597 else if ((job_id
= cupsCreateJob(CUPS_HTTP_DEFAULT
, printer
,
598 title
? title
: "(stdin)",
599 num_options
, options
)) > 0)
601 http_status_t status
; /* Write status */
602 const char *format
; /* Document format */
603 ssize_t bytes
; /* Bytes read */
605 if (cupsGetOption("raw", num_options
, options
))
606 format
= CUPS_FORMAT_RAW
;
607 else if ((format
= cupsGetOption("document-format", num_options
,
609 format
= CUPS_FORMAT_AUTO
;
611 status
= cupsStartDocument(CUPS_HTTP_DEFAULT
, printer
, job_id
, NULL
,
614 while (status
== HTTP_STATUS_CONTINUE
&&
615 (bytes
= read(0, buffer
, sizeof(buffer
))) > 0)
616 status
= cupsWriteRequestData(CUPS_HTTP_DEFAULT
, buffer
, (size_t)bytes
);
618 if (status
!= HTTP_STATUS_CONTINUE
)
620 _cupsLangPrintf(stderr
, _("%s: Error - unable to queue from stdin - %s."),
621 argv
[0], httpStatus(status
));
622 cupsFinishDocument(CUPS_HTTP_DEFAULT
, printer
);
623 cupsCancelJob2(CUPS_HTTP_DEFAULT
, printer
, job_id
, 0);
627 if (cupsFinishDocument(CUPS_HTTP_DEFAULT
, printer
) != IPP_STATUS_OK
)
629 _cupsLangPrintf(stderr
, "%s: %s", argv
[0], cupsGetErrorString());
630 cupsCancelJob2(CUPS_HTTP_DEFAULT
, printer
, job_id
, 0);
637 _cupsLangPrintf(stderr
, "%s: %s", argv
[0], cupsGetErrorString());
641 _cupsLangPrintf(stdout
, _("request id is %s-%d (%d file(s))"),
642 printer
, job_id
, num_files
);
649 * 'restart_job()' - Restart a job.
652 static int /* O - Exit status */
653 restart_job(const char *command
, /* I - Command name */
654 int job_id
, /* I - Job ID */
655 const char *job_hold_until
) /* I - "job-hold-until" value, if any */
657 ipp_t
*request
; /* IPP request */
658 char uri
[HTTP_MAX_URI
]; /* URI for job */
661 request
= ippNewRequest(IPP_OP_RESTART_JOB
);
663 snprintf(uri
, sizeof(uri
), "ipp://localhost/jobs/%d", job_id
);
665 ippAddString(request
, IPP_TAG_OPERATION
, IPP_TAG_URI
,
666 "job-uri", NULL
, uri
);
668 ippAddString(request
, IPP_TAG_OPERATION
, IPP_TAG_NAME
,
669 "requesting-user-name", NULL
, cupsGetUser());
672 ippAddString(request
, IPP_TAG_OPERATION
, IPP_TAG_KEYWORD
, "job-hold-until", NULL
, job_hold_until
);
674 ippDelete(cupsDoRequest(CUPS_HTTP_DEFAULT
, request
, "/jobs"));
676 if (cupsGetError() == IPP_STATUS_ERROR_BAD_REQUEST
||
677 cupsGetError() == IPP_STATUS_ERROR_VERSION_NOT_SUPPORTED
)
679 _cupsLangPrintf(stderr
,
680 _("%s: Error - add '/version=1.1' to server "
684 else if (cupsGetError() > IPP_STATUS_OK_CONFLICTING
)
686 _cupsLangPrintf(stderr
, "%s: %s", command
, cupsGetErrorString());
695 * 'set_job_attrs()' - Set job attributes.
698 static int /* O - Exit status */
700 const char *command
, /* I - Command name */
701 int job_id
, /* I - Job ID */
702 int num_options
, /* I - Number of options */
703 cups_option_t
*options
) /* I - Options */
705 ipp_t
*request
; /* IPP request */
706 char uri
[HTTP_MAX_URI
]; /* URI for job */
709 if (num_options
== 0)
712 request
= ippNewRequest(IPP_OP_SET_JOB_ATTRIBUTES
);
714 snprintf(uri
, sizeof(uri
), "ipp://localhost/jobs/%d", job_id
);
716 ippAddString(request
, IPP_TAG_OPERATION
, IPP_TAG_URI
,
717 "job-uri", NULL
, uri
);
719 ippAddString(request
, IPP_TAG_OPERATION
, IPP_TAG_NAME
,
720 "requesting-user-name", NULL
, cupsGetUser());
722 cupsEncodeOptions(request
, num_options
, options
);
724 ippDelete(cupsDoRequest(CUPS_HTTP_DEFAULT
, request
, "/jobs"));
726 if (cupsGetError() == IPP_STATUS_ERROR_BAD_REQUEST
||
727 cupsGetError() == IPP_STATUS_ERROR_VERSION_NOT_SUPPORTED
)
729 _cupsLangPrintf(stderr
,
730 _("%s: Error - add '/version=1.1' to server "
734 else if (cupsGetError() > IPP_STATUS_OK_CONFLICTING
)
736 _cupsLangPrintf(stderr
, "%s: %s", command
, cupsGetErrorString());
745 * 'usage()' - Show program usage and exit.
751 _cupsLangPuts(stdout
, _("Usage: lp [options] [--] [file(s)]\n"
752 " lp [options] -i id"));
753 _cupsLangPuts(stdout
, _("Options:"));
754 _cupsLangPuts(stdout
, _("-c Make a copy of the print file(s)"));
755 _cupsLangPuts(stdout
, _("-d destination Specify the destination"));
756 _cupsLangPuts(stdout
, _("-E Encrypt the connection to the server"));
757 _cupsLangPuts(stdout
, _("-h server[:port] Connect to the named server and port"));
758 _cupsLangPuts(stdout
, _("-H HH:MM Hold the job until the specified UTC time"));
759 _cupsLangPuts(stdout
, _("-H hold Hold the job until released/resumed"));
760 _cupsLangPuts(stdout
, _("-H immediate Print the job as soon as possible"));
761 _cupsLangPuts(stdout
, _("-H restart Reprint the job"));
762 _cupsLangPuts(stdout
, _("-H resume Resume a held job"));
763 _cupsLangPuts(stdout
, _("-i id Specify an existing job ID to modify"));
764 _cupsLangPuts(stdout
, _("-m Send an email notification when the job completes"));
765 _cupsLangPuts(stdout
, _("-n num-copies Specify the number of copies to print"));
766 _cupsLangPuts(stdout
, _("-o option[=value] Specify a printer-specific option"));
767 _cupsLangPuts(stdout
, _("-o job-sheets=standard Print a banner page with the job"));
768 _cupsLangPuts(stdout
, _("-o media=size Specify the media size to use"));
769 _cupsLangPuts(stdout
, _("-o number-up=N Specify that input pages should be printed N-up (1, 2, 4, 6, 9, and 16 are supported)"));
770 _cupsLangPuts(stdout
, _("-o orientation-requested=N\n"
771 " Specify portrait (3) or landscape (4) orientation"));
772 _cupsLangPuts(stdout
, _("-o print-quality=N Specify the print quality - draft (3), normal (4), or best (5)"));
773 _cupsLangPuts(stdout
, _("-o sides=one-sided Specify 1-sided printing"));
774 _cupsLangPuts(stdout
, _("-o sides=two-sided-long-edge\n"
775 " Specify 2-sided portrait printing"));
776 _cupsLangPuts(stdout
, _("-o sides=two-sided-short-edge\n"
777 " Specify 2-sided landscape printing"));
778 _cupsLangPuts(stdout
, _("-P page-list Specify a list of pages to print"));
779 _cupsLangPuts(stdout
, _("-q priority Specify the priority from low (1) to high (100)"));
780 _cupsLangPuts(stdout
, _("-s Be silent"));
781 _cupsLangPuts(stdout
, _("-t title Specify the job title"));
782 _cupsLangPuts(stdout
, _("-U username Specify the username to use for authentication"));