• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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