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