1 /*
2 * "lpq" command for CUPS.
3 *
4 * Copyright © 2020-2024 by OpenPrinting.
5 * Copyright © 2007-2018 by Apple Inc.
6 * Copyright © 1997-2006 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 http_t *connect_server(const char *, http_t *);
24 static int show_jobs(const char *, http_t *, const char *,
25 const char *, const int, const int);
26 static void show_printer(const char *, http_t *, const char *);
27 static void usage(void) _CUPS_NORETURN;
28
29
30 /*
31 * 'main()' - Parse options and commands.
32 */
33
34 int
main(int argc,char * argv[])35 main(int argc, /* I - Number of command-line arguments */
36 char *argv[]) /* I - Command-line arguments */
37 {
38 int i; /* Looping var */
39 http_t *http; /* Connection to server */
40 const char *opt, /* Option pointer */
41 *dest, /* Desired printer */
42 *user, /* Desired user */
43 *val; /* Environment variable name */
44 char *instance; /* Printer instance */
45 int id, /* Desired job ID */
46 all, /* All printers */
47 interval, /* Reporting interval */
48 longstatus; /* Show file details */
49 cups_dest_t *named_dest; /* Named destination */
50
51
52 _cupsSetLocale(argv);
53
54 /*
55 * Check for command-line options...
56 */
57
58 http = NULL;
59 dest = NULL;
60 user = NULL;
61 id = 0;
62 interval = 0;
63 longstatus = 0;
64 all = 0;
65
66 for (i = 1; i < argc; i ++)
67 {
68 if (argv[i][0] == '+')
69 {
70 interval = atoi(argv[i] + 1);
71 }
72 else if (!strcmp(argv[i], "--help"))
73 usage();
74 else if (argv[i][0] == '-')
75 {
76 for (opt = argv[i] + 1; *opt; opt ++)
77 {
78 switch (*opt)
79 {
80 case 'E' : /* Encrypt */
81 #ifdef HAVE_TLS
82 cupsSetEncryption(HTTP_ENCRYPT_REQUIRED);
83
84 if (http)
85 httpEncryption(http, HTTP_ENCRYPT_REQUIRED);
86 #else
87 _cupsLangPrintf(stderr, _("%s: Sorry, no encryption support."), argv[0]);
88 #endif /* HAVE_TLS */
89 break;
90
91 case 'U' : /* Username */
92 if (opt[1] != '\0')
93 {
94 cupsSetUser(opt + 1);
95 opt += strlen(opt) - 1;
96 }
97 else
98 {
99 i ++;
100 if (i >= argc)
101 {
102 _cupsLangPrintf(stderr, _("%s: Error - expected username after \"-U\" option."), argv[0]);
103 return (1);
104 }
105
106 cupsSetUser(argv[i]);
107 }
108 break;
109
110 case 'P' : /* Printer */
111 if (opt[1] != '\0')
112 {
113 dest = opt + 1;
114 opt += strlen(opt) - 1;
115 }
116 else
117 {
118 i ++;
119
120 if (i >= argc)
121 {
122 httpClose(http);
123
124 usage();
125 }
126
127 dest = argv[i];
128 }
129
130 if ((instance = strchr(dest, '/')) != NULL)
131 *instance++ = '\0';
132
133 http = connect_server(argv[0], http);
134
135 if ((named_dest = cupsGetNamedDest(http, dest, instance)) == NULL)
136 {
137 if (cupsLastError() == IPP_STATUS_ERROR_BAD_REQUEST ||
138 cupsLastError() == IPP_STATUS_ERROR_VERSION_NOT_SUPPORTED)
139 _cupsLangPrintf(stderr, _("%s: Error - add '/version=1.1' to server name."), argv[0]);
140 else if (instance)
141 _cupsLangPrintf(stderr, _("%s: Error - unknown destination \"%s/%s\"."), argv[0], dest, instance);
142 else
143 _cupsLangPrintf(stderr, _("%s: Unknown destination \"%s\"."), argv[0], dest);
144
145 return (1);
146 }
147
148 cupsFreeDests(1, named_dest);
149 break;
150
151 case 'a' : /* All printers */
152 all = 1;
153 break;
154
155 case 'h' : /* Connect to host */
156 if (http)
157 {
158 httpClose(http);
159 http = NULL;
160 }
161
162 if (opt[1] != '\0')
163 {
164 cupsSetServer(opt + 1);
165 opt += strlen(opt) - 1;
166 }
167 else
168 {
169 i ++;
170
171 if (i >= argc)
172 {
173 _cupsLangPrintf(stderr, _("%s: Error - expected hostname after \"-h\" option."), argv[0]);
174 return (1);
175 }
176 else
177 cupsSetServer(argv[i]);
178 }
179 break;
180
181 case 'l' : /* Long status */
182 longstatus = 1;
183 break;
184
185 default :
186 httpClose(http);
187
188 usage();
189 }
190 }
191 }
192 else if (isdigit(argv[i][0] & 255))
193 {
194 id = atoi(argv[i]);
195 }
196 else
197 {
198 user = argv[i];
199 }
200 }
201
202 http = connect_server(argv[0], http);
203
204 if (dest == NULL && !all)
205 {
206 if ((named_dest = cupsGetNamedDest(http, NULL, NULL)) == NULL)
207 {
208 if (cupsLastError() == IPP_STATUS_ERROR_BAD_REQUEST ||
209 cupsLastError() == IPP_STATUS_ERROR_VERSION_NOT_SUPPORTED)
210 {
211 _cupsLangPrintf(stderr,
212 _("%s: Error - add '/version=1.1' to server name."),
213 argv[0]);
214 return (1);
215 }
216
217 val = NULL;
218
219 if ((dest = getenv("LPDEST")) == NULL)
220 {
221 if ((dest = getenv("PRINTER")) != NULL)
222 {
223 if (!strcmp(dest, "lp"))
224 dest = NULL;
225 else
226 val = "PRINTER";
227 }
228 }
229 else
230 val = "LPDEST";
231
232 if (dest && val)
233 _cupsLangPrintf(stderr,
234 _("%s: Error - %s environment variable names "
235 "non-existent destination \"%s\"."), argv[0], val,
236 dest);
237 else
238 _cupsLangPrintf(stderr,
239 _("%s: Error - no default destination available."),
240 argv[0]);
241 httpClose(http);
242 return (1);
243 }
244
245 dest = named_dest->name;
246 }
247
248 /*
249 * Show the status in a loop...
250 */
251
252 for (;;)
253 {
254 if (dest)
255 show_printer(argv[0], http, dest);
256
257 i = show_jobs(argv[0], http, dest, user, id, longstatus);
258
259 if (i && interval)
260 {
261 fflush(stdout);
262 sleep((unsigned)interval);
263 }
264 else
265 break;
266 }
267
268 /*
269 * Close the connection to the server and return...
270 */
271
272 httpClose(http);
273
274 return (0);
275 }
276
277
278 /*
279 * 'connect_server()' - Connect to the server as necessary...
280 */
281
282 static http_t * /* O - New HTTP connection */
connect_server(const char * command,http_t * http)283 connect_server(const char *command, /* I - Command name */
284 http_t *http) /* I - Current HTTP connection */
285 {
286 if (!http)
287 {
288 http = httpConnectEncrypt(cupsServer(), ippPort(),
289 cupsEncryption());
290
291 if (http == NULL)
292 {
293 _cupsLangPrintf(stderr, _("%s: Unable to connect to server."), command);
294 exit(1);
295 }
296 }
297
298 return (http);
299 }
300
301
302 /*
303 * 'show_jobs()' - Show jobs.
304 */
305
306 static int /* O - Number of jobs in queue */
show_jobs(const char * command,http_t * http,const char * dest,const char * user,const int id,const int longstatus)307 show_jobs(const char *command, /* I - Command name */
308 http_t *http, /* I - HTTP connection to server */
309 const char *dest, /* I - Destination */
310 const char *user, /* I - User */
311 const int id, /* I - Job ID */
312 const int longstatus) /* I - 1 if long report desired */
313 {
314 ipp_t *request, /* IPP Request */
315 *response; /* IPP Response */
316 ipp_attribute_t *attr; /* Current attribute */
317 const char *jobdest, /* Pointer into job-printer-uri */
318 *jobuser, /* Pointer to job-originating-user-name */
319 *jobname; /* Pointer to job-name */
320 ipp_jstate_t jobstate; /* job-state */
321 int jobid, /* job-id */
322 jobsize, /* job-k-octets */
323 jobcount, /* Number of jobs */
324 jobcopies, /* Number of copies */
325 rank; /* Rank of job */
326 char resource[1024]; /* Resource string */
327 char rankstr[255]; /* Rank string */
328 char namestr[1024]; /* Job name string */
329 static const char * const jobattrs[] =/* Job attributes we want to see */
330 {
331 "copies",
332 "job-id",
333 "job-k-octets",
334 "job-name",
335 "job-originating-user-name",
336 "job-printer-uri",
337 "job-priority",
338 "job-state"
339 };
340 static const char * const ranks[10] = /* Ranking strings */
341 {
342 "th",
343 "st",
344 "nd",
345 "rd",
346 "th",
347 "th",
348 "th",
349 "th",
350 "th",
351 "th"
352 };
353
354
355 if (http == NULL)
356 return (0);
357
358 /*
359 * Build an IPP_GET_JOBS or IPP_GET_JOB_ATTRIBUTES request, which requires
360 * the following attributes:
361 *
362 * attributes-charset
363 * attributes-natural-language
364 * job-uri or printer-uri
365 * requested-attributes
366 * requesting-user-name
367 */
368
369 request = ippNewRequest(id ? IPP_GET_JOB_ATTRIBUTES : IPP_GET_JOBS);
370
371 if (id)
372 {
373 snprintf(resource, sizeof(resource), "ipp://localhost/jobs/%d", id);
374 ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI, "job-uri",
375 NULL, resource);
376 }
377 else if (dest)
378 {
379 httpAssembleURIf(HTTP_URI_CODING_ALL, resource, sizeof(resource), "ipp",
380 NULL, "localhost", 0, "/printers/%s", dest);
381
382 ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI, "printer-uri",
383 NULL, resource);
384 }
385 else
386 ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI, "printer-uri",
387 NULL, "ipp://localhost/");
388
389 if (user)
390 {
391 ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_NAME,
392 "requesting-user-name", NULL, user);
393 ippAddBoolean(request, IPP_TAG_OPERATION, "my-jobs", 1);
394 }
395 else
396 ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_NAME,
397 "requesting-user-name", NULL, cupsUser());
398
399 ippAddStrings(request, IPP_TAG_OPERATION, IPP_TAG_KEYWORD,
400 "requested-attributes",
401 (int)(sizeof(jobattrs) / sizeof(jobattrs[0])), NULL, jobattrs);
402
403 /*
404 * Do the request and get back a response...
405 */
406
407 jobcount = 0;
408
409 if ((response = cupsDoRequest(http, request, "/")) != NULL)
410 {
411 if (response->request.status.status_code > IPP_OK_CONFLICT)
412 {
413 _cupsLangPrintf(stderr, "%s: %s", command, cupsLastErrorString());
414 ippDelete(response);
415 return (0);
416 }
417
418 rank = 1;
419
420 /*
421 * Loop through the job list and display them...
422 */
423
424 for (attr = response->attrs; attr != NULL; attr = attr->next)
425 {
426 /*
427 * Skip leading attributes until we hit a job...
428 */
429
430 while (attr != NULL && attr->group_tag != IPP_TAG_JOB)
431 attr = attr->next;
432
433 if (attr == NULL)
434 break;
435
436 /*
437 * Pull the needed attributes from this job...
438 */
439
440 jobid = 0;
441 jobsize = 0;
442 jobstate = IPP_JOB_PENDING;
443 jobname = "unknown";
444 jobuser = "unknown";
445 jobdest = NULL;
446 jobcopies = 1;
447
448 while (attr != NULL && attr->group_tag == IPP_TAG_JOB)
449 {
450 if (!strcmp(attr->name, "job-id") &&
451 attr->value_tag == IPP_TAG_INTEGER)
452 jobid = attr->values[0].integer;
453
454 if (!strcmp(attr->name, "job-k-octets") &&
455 attr->value_tag == IPP_TAG_INTEGER)
456 jobsize = attr->values[0].integer;
457
458 if (!strcmp(attr->name, "job-state") &&
459 attr->value_tag == IPP_TAG_ENUM)
460 jobstate = (ipp_jstate_t)attr->values[0].integer;
461
462 if (!strcmp(attr->name, "job-printer-uri") &&
463 attr->value_tag == IPP_TAG_URI)
464 if ((jobdest = strrchr(attr->values[0].string.text, '/')) != NULL)
465 jobdest ++;
466
467 if (!strcmp(attr->name, "job-originating-user-name") &&
468 attr->value_tag == IPP_TAG_NAME)
469 jobuser = attr->values[0].string.text;
470
471 if (!strcmp(attr->name, "job-name") &&
472 attr->value_tag == IPP_TAG_NAME)
473 jobname = attr->values[0].string.text;
474
475 if (!strcmp(attr->name, "copies") &&
476 attr->value_tag == IPP_TAG_INTEGER)
477 jobcopies = attr->values[0].integer;
478
479 attr = attr->next;
480 }
481
482 /*
483 * See if we have everything needed...
484 */
485
486 if (jobdest == NULL || jobid == 0)
487 {
488 if (attr == NULL)
489 break;
490 else
491 continue;
492 }
493
494 if (!longstatus && jobcount == 0)
495 _cupsLangPuts(stdout,
496 _("Rank Owner Job File(s)"
497 " Total Size"));
498
499 jobcount ++;
500
501 /*
502 * Display the job...
503 */
504
505 if (jobstate == IPP_JOB_PROCESSING)
506 strlcpy(rankstr, "active", sizeof(rankstr));
507 else
508 {
509 /*
510 * Make the rank show the "correct" suffix for each number
511 * (11-13 are the only special cases, for English anyways...)
512 */
513
514 if ((rank % 100) >= 11 && (rank % 100) <= 13)
515 snprintf(rankstr, sizeof(rankstr), "%dth", rank);
516 else
517 snprintf(rankstr, sizeof(rankstr), "%d%s", rank, ranks[rank % 10]);
518
519 rank ++;
520 }
521
522 if (longstatus)
523 {
524 _cupsLangPuts(stdout, "\n");
525
526 if (jobcopies > 1)
527 snprintf(namestr, sizeof(namestr), "%d copies of %s", jobcopies,
528 jobname);
529 else
530 strlcpy(namestr, jobname, sizeof(namestr));
531
532 _cupsLangPrintf(stdout, _("%s: %-33.33s [job %d localhost]"),
533 jobuser, rankstr, jobid);
534 _cupsLangPrintf(stdout, _(" %-39.39s %.0f bytes"),
535 namestr, 1024.0 * jobsize);
536 }
537 else
538 _cupsLangPrintf(stdout,
539 _("%-7s %-7.7s %-7d %-31.31s %.0f bytes"),
540 rankstr, jobuser, jobid, jobname, 1024.0 * jobsize);
541
542 if (attr == NULL)
543 break;
544 }
545
546 ippDelete(response);
547 }
548 else
549 {
550 _cupsLangPrintf(stderr, "%s: %s", command, cupsLastErrorString());
551 return (0);
552 }
553
554 if (jobcount == 0)
555 _cupsLangPuts(stdout, _("no entries"));
556
557 return (jobcount);
558 }
559
560
561 /*
562 * 'show_printer()' - Show printer status.
563 */
564
565 static void
show_printer(const char * command,http_t * http,const char * dest)566 show_printer(const char *command, /* I - Command name */
567 http_t *http, /* I - HTTP connection to server */
568 const char *dest) /* I - Destination */
569 {
570 ipp_t *request, /* IPP Request */
571 *response; /* IPP Response */
572 ipp_attribute_t *attr; /* Current attribute */
573 ipp_pstate_t state; /* Printer state */
574 char uri[HTTP_MAX_URI]; /* Printer URI */
575
576
577 if (http == NULL)
578 return;
579
580 /*
581 * Build an IPP_GET_PRINTER_ATTRIBUTES request, which requires the following
582 * attributes:
583 *
584 * attributes-charset
585 * attributes-natural-language
586 * printer-uri
587 */
588
589 request = ippNewRequest(IPP_GET_PRINTER_ATTRIBUTES);
590
591 httpAssembleURIf(HTTP_URI_CODING_ALL, uri, sizeof(uri), "ipp", NULL,
592 "localhost", 0, "/printers/%s", dest);
593 ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI,
594 "printer-uri", NULL, uri);
595
596 /*
597 * Do the request and get back a response...
598 */
599
600 if ((response = cupsDoRequest(http, request, "/")) != NULL)
601 {
602 if (response->request.status.status_code > IPP_OK_CONFLICT)
603 {
604 _cupsLangPrintf(stderr, "%s: %s", command, cupsLastErrorString());
605 ippDelete(response);
606 return;
607 }
608
609 if ((attr = ippFindAttribute(response, "printer-state", IPP_TAG_ENUM)) != NULL)
610 state = (ipp_pstate_t)attr->values[0].integer;
611 else
612 state = IPP_PRINTER_STOPPED;
613
614 switch (state)
615 {
616 case IPP_PRINTER_IDLE :
617 _cupsLangPrintf(stdout, _("%s is ready"), dest);
618 break;
619 case IPP_PRINTER_PROCESSING :
620 _cupsLangPrintf(stdout, _("%s is ready and printing"),
621 dest);
622 break;
623 case IPP_PRINTER_STOPPED :
624 _cupsLangPrintf(stdout, _("%s is not ready"), dest);
625 break;
626 }
627
628 ippDelete(response);
629 }
630 else
631 _cupsLangPrintf(stderr, "%s: %s", command, cupsLastErrorString());
632 }
633
634
635 /*
636 * 'usage()' - Show program usage.
637 */
638
639 static void
usage(void)640 usage(void)
641 {
642 _cupsLangPuts(stderr, _("Usage: lpq [options] [+interval]"));
643 _cupsLangPuts(stdout, _("Options:"));
644 _cupsLangPuts(stdout, _("-a Show jobs on all destinations"));
645 _cupsLangPuts(stdout, _("-E Encrypt the connection to the server"));
646 _cupsLangPuts(stdout, _("-h server[:port] Connect to the named server and port"));
647 _cupsLangPuts(stdout, _("-l Show verbose (long) output"));
648 _cupsLangPuts(stdout, _("-P destination Show status for the specified destination"));
649 _cupsLangPuts(stdout, _("-U username Specify the username to use for authentication"));
650
651 exit(1);
652 }
653