1/*
2 * "$Id: cups-deviced.c 11093 2013-07-03 20:48:42Z msweet $"
3 *
4 *   Device scanning mini-daemon for CUPS.
5 *
6 *   Copyright 2007-2011 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 * Contents:
16 *
17 *   main()                 - Scan for devices and return an IPP response.
18 *   add_device()           - Add a new device to the list.
19 *   compare_devices()      - Compare device names to eliminate duplicates.
20 *   get_current_time()     - Get the current time as a double value in seconds.
21 *   get_device()           - Get a device from a backend.
22 *   process_children()     - Process all dead children...
23 *   sigchld_handler()      - Handle 'child' signals from old processes.
24 *   start_backend()        - Run a backend to gather the available devices.
25 */
26
27/*
28 * Include necessary headers...
29 */
30
31#include "util.h"
32#include <cups/array.h>
33#include <cups/dir.h>
34#include <fcntl.h>
35#include <sys/wait.h>
36#include <poll.h>
37
38
39/*
40 * Constants...
41 */
42
43#define MAX_BACKENDS	200		/* Maximum number of backends we'll run */
44
45
46/*
47 * Backend information...
48 */
49
50typedef struct
51{
52  char		*name;			/* Name of backend */
53  int		pid,			/* Process ID */
54		status;			/* Exit status */
55  cups_file_t	*pipe;			/* Pipe from backend stdout */
56  int		count;			/* Number of devices found */
57} cupsd_backend_t;
58
59
60/*
61 * Device information structure...
62 */
63
64typedef struct
65{
66  char	device_class[128],		/* Device class */
67	device_info[128],		/* Device info/description */
68	device_uri[1024];		/* Device URI */
69} cupsd_device_t;
70
71
72/*
73 * Local globals...
74 */
75
76static int		num_backends = 0,
77					/* Total backends */
78			active_backends = 0;
79					/* Active backends */
80static cupsd_backend_t	backends[MAX_BACKENDS];
81					/* Array of backends */
82static struct pollfd	backend_fds[MAX_BACKENDS];
83					/* Array for poll() */
84static cups_array_t	*devices;	/* Array of devices */
85static int		normal_user;	/* Normal user ID */
86static int		device_limit;	/* Maximum number of devices */
87static int		send_class,	/* Send device-class attribute? */
88			send_info,	/* Send device-info attribute? */
89			send_make_and_model,
90					/* Send device-make-and-model attribute? */
91			send_uri,	/* Send device-uri attribute? */
92			send_id,	/* Send device-id attribute? */
93			send_location;	/* Send device-location attribute? */
94static int		dead_children = 0;
95					/* Dead children? */
96
97
98/*
99 * Local functions...
100 */
101
102static int		add_device(const char *device_class,
103				   const char *device_make_and_model,
104				   const char *device_info,
105				   const char *device_uri,
106				   const char *device_id,
107				   const char *device_location);
108static int		compare_devices(cupsd_device_t *p0,
109			                cupsd_device_t *p1);
110static double		get_current_time(void);
111static int		get_device(cupsd_backend_t *backend);
112static void		process_children(void);
113static void		sigchld_handler(int sig);
114static int		start_backend(const char *backend, int root);
115
116
117/*
118 * 'main()' - Scan for devices and return an IPP response.
119 *
120 * Usage:
121 *
122 *    cups-deviced request_id limit options
123 */
124
125int					/* O - Exit code */
126main(int  argc,				/* I - Number of command-line args */
127     char *argv[])			/* I - Command-line arguments */
128{
129  int		i;			/* Looping var */
130  int		request_id;		/* Request ID */
131  int		timeout;		/* Timeout in seconds */
132  const char	*server_bin;		/* CUPS_SERVERBIN environment variable */
133  char		filename[1024];		/* Backend directory filename */
134  cups_dir_t	*dir;			/* Directory pointer */
135  cups_dentry_t *dent;			/* Directory entry */
136  double	current_time,		/* Current time */
137		end_time;		/* Ending time */
138  int		num_options;		/* Number of options */
139  cups_option_t	*options;		/* Options */
140  cups_array_t	*requested,		/* requested-attributes values */
141		*exclude,		/* exclude-schemes values */
142		*include;		/* include-schemes values */
143#if defined(HAVE_SIGACTION) && !defined(HAVE_SIGSET)
144  struct sigaction action;		/* Actions for POSIX signals */
145#endif /* HAVE_SIGACTION && !HAVE_SIGSET */
146
147
148  setbuf(stderr, NULL);
149
150 /*
151  * Check the command-line...
152  */
153
154  if (argc != 6)
155  {
156    fputs("Usage: cups-deviced request-id limit timeout user-id options\n", stderr);
157
158    return (1);
159  }
160
161  request_id = atoi(argv[1]);
162  if (request_id < 1)
163  {
164    fprintf(stderr, "ERROR: [cups-deviced] Bad request ID %d!\n", request_id);
165
166    return (1);
167  }
168
169  device_limit = atoi(argv[2]);
170  if (device_limit < 0)
171  {
172    fprintf(stderr, "ERROR: [cups-deviced] Bad limit %d!\n", device_limit);
173
174    return (1);
175  }
176
177  timeout = atoi(argv[3]);
178  if (timeout < 1)
179  {
180    fprintf(stderr, "ERROR: [cups-deviced] Bad timeout %d!\n", timeout);
181
182    return (1);
183  }
184
185  normal_user = atoi(argv[4]);
186  if (normal_user <= 0)
187  {
188    fprintf(stderr, "ERROR: [cups-deviced] Bad user %d!\n", normal_user);
189
190    return (1);
191  }
192
193  num_options = cupsParseOptions(argv[5], 0, &options);
194  requested   = cupsdCreateStringsArray(cupsGetOption("requested-attributes",
195                                                      num_options, options));
196  exclude     = cupsdCreateStringsArray(cupsGetOption("exclude-schemes",
197                                                      num_options, options));
198  include     = cupsdCreateStringsArray(cupsGetOption("include-schemes",
199                                                      num_options, options));
200
201  if (!requested || cupsArrayFind(requested, "all") != NULL)
202  {
203    send_class = send_info = send_make_and_model = send_uri = send_id =
204        send_location = 1;
205  }
206  else
207  {
208    send_class          = cupsArrayFind(requested, "device-class") != NULL;
209    send_info           = cupsArrayFind(requested, "device-info") != NULL;
210    send_make_and_model = cupsArrayFind(requested, "device-make-and-model") != NULL;
211    send_uri            = cupsArrayFind(requested, "device-uri") != NULL;
212    send_id             = cupsArrayFind(requested, "device-id") != NULL;
213    send_location       = cupsArrayFind(requested, "device-location") != NULL;
214  }
215
216 /*
217  * Listen to child signals...
218  */
219
220#ifdef HAVE_SIGSET /* Use System V signals over POSIX to avoid bugs */
221  sigset(SIGCHLD, sigchld_handler);
222#elif defined(HAVE_SIGACTION)
223  memset(&action, 0, sizeof(action));
224
225  sigemptyset(&action.sa_mask);
226  sigaddset(&action.sa_mask, SIGCHLD);
227  action.sa_handler = sigchld_handler;
228  sigaction(SIGCHLD, &action, NULL);
229#else
230  signal(SIGCLD, sigchld_handler);	/* No, SIGCLD isn't a typo... */
231#endif /* HAVE_SIGSET */
232
233 /*
234  * Try opening the backend directory...
235  */
236
237  if ((server_bin = getenv("CUPS_SERVERBIN")) == NULL)
238    server_bin = CUPS_SERVERBIN;
239
240  snprintf(filename, sizeof(filename), "%s/backend", server_bin);
241
242  if ((dir = cupsDirOpen(filename)) == NULL)
243  {
244    fprintf(stderr, "ERROR: [cups-deviced] Unable to open backend directory "
245                    "\"%s\": %s", filename, strerror(errno));
246
247    return (1);
248  }
249
250 /*
251  * Setup the devices array...
252  */
253
254  devices = cupsArrayNew((cups_array_func_t)compare_devices, NULL);
255
256 /*
257  * Loop through all of the device backends...
258  */
259
260  while ((dent = cupsDirRead(dir)) != NULL)
261  {
262   /*
263    * Skip entries that are not executable files...
264    */
265
266    if (!S_ISREG(dent->fileinfo.st_mode) ||
267        !isalnum(dent->filename[0] & 255) ||
268        (dent->fileinfo.st_mode & (S_IRUSR | S_IXUSR)) != (S_IRUSR | S_IXUSR))
269      continue;
270
271   /*
272    * Skip excluded or not included backends...
273    */
274
275    if (cupsArrayFind(exclude, dent->filename) ||
276        (include && !cupsArrayFind(include, dent->filename)))
277      continue;
278
279   /*
280    * Backends without permissions for normal users run as root,
281    * all others run as the unprivileged user...
282    */
283
284    start_backend(dent->filename,
285                  !(dent->fileinfo.st_mode & (S_IRWXG | S_IRWXO)));
286  }
287
288  cupsDirClose(dir);
289
290 /*
291  * Collect devices...
292  */
293
294  if (getenv("SOFTWARE"))
295    puts("Content-Type: application/ipp\n");
296
297  cupsdSendIPPHeader(IPP_OK, request_id);
298  cupsdSendIPPGroup(IPP_TAG_OPERATION);
299  cupsdSendIPPString(IPP_TAG_CHARSET, "attributes-charset", "utf-8");
300  cupsdSendIPPString(IPP_TAG_LANGUAGE, "attributes-natural-language", "en-US");
301
302  end_time = get_current_time() + timeout;
303
304  while (active_backends > 0 && (current_time = get_current_time()) < end_time)
305  {
306   /*
307    * Collect the output from the backends...
308    */
309
310    timeout = (int)(1000 * (end_time - current_time));
311
312    if (poll(backend_fds, num_backends, timeout) > 0)
313    {
314      for (i = 0; i < num_backends; i ++)
315        if (backend_fds[i].revents && backends[i].pipe)
316	{
317	  cups_file_t *bpipe = backends[i].pipe;
318					/* Copy of pipe for backend... */
319
320	  do
321	  {
322	    if (get_device(backends + i))
323	    {
324	      backend_fds[i].fd     = 0;
325	      backend_fds[i].events = 0;
326	      break;
327	    }
328	  }
329	  while (bpipe->ptr &&
330	         memchr(bpipe->ptr, '\n', bpipe->end - bpipe->ptr));
331        }
332    }
333
334   /*
335    * Get exit status from children...
336    */
337
338    if (dead_children)
339      process_children();
340  }
341
342  cupsdSendIPPTrailer();
343
344 /*
345  * Terminate any remaining backends and exit...
346  */
347
348  if (active_backends > 0)
349  {
350    for (i = 0; i < num_backends; i ++)
351      if (backends[i].pid)
352	kill(backends[i].pid, SIGTERM);
353  }
354
355  return (0);
356}
357
358
359/*
360 * 'add_device()' - Add a new device to the list.
361 */
362
363static int				/* O - 0 on success, -1 on error */
364add_device(
365    const char *device_class,		/* I - Device class */
366    const char *device_make_and_model,	/* I - Device make and model */
367    const char *device_info,		/* I - Device information */
368    const char *device_uri,		/* I - Device URI */
369    const char *device_id,		/* I - 1284 device ID */
370    const char *device_location)	/* I - Physical location */
371{
372  cupsd_device_t	*device;	/* New device */
373
374
375 /*
376  * Allocate memory for the device record...
377  */
378
379  if ((device = calloc(1, sizeof(cupsd_device_t))) == NULL)
380  {
381    fputs("ERROR: [cups-deviced] Ran out of memory allocating a device!\n",
382          stderr);
383    return (-1);
384  }
385
386 /*
387  * Copy the strings over...
388  */
389
390  strlcpy(device->device_class, device_class, sizeof(device->device_class));
391  strlcpy(device->device_info, device_info, sizeof(device->device_info));
392  strlcpy(device->device_uri, device_uri, sizeof(device->device_uri));
393
394 /*
395  * Add the device to the array and return...
396  */
397
398  if (cupsArrayFind(devices, device))
399  {
400   /*
401    * Avoid duplicates!
402    */
403
404    free(device);
405  }
406  else
407  {
408    cupsArrayAdd(devices, device);
409
410    if (device_limit <= 0 || cupsArrayCount(devices) < device_limit)
411    {
412     /*
413      * Send device info...
414      */
415
416      cupsdSendIPPGroup(IPP_TAG_PRINTER);
417      if (send_class)
418	cupsdSendIPPString(IPP_TAG_KEYWORD, "device-class",
419	                   device_class);
420      if (send_info)
421	cupsdSendIPPString(IPP_TAG_TEXT, "device-info", device_info);
422      if (send_make_and_model)
423	cupsdSendIPPString(IPP_TAG_TEXT, "device-make-and-model",
424			   device_make_and_model);
425      if (send_uri)
426	cupsdSendIPPString(IPP_TAG_URI, "device-uri", device_uri);
427      if (send_id)
428	cupsdSendIPPString(IPP_TAG_TEXT, "device-id",
429	                   device_id ? device_id : "");
430      if (send_location)
431	cupsdSendIPPString(IPP_TAG_TEXT, "device-location",
432	                   device_location ? device_location : "");
433
434      fflush(stdout);
435      fputs("DEBUG: Flushed attributes...\n", stderr);
436    }
437  }
438
439  return (0);
440}
441
442
443/*
444 * 'compare_devices()' - Compare device names to eliminate duplicates.
445 */
446
447static int				/* O - Result of comparison */
448compare_devices(cupsd_device_t *d0,	/* I - First device */
449                cupsd_device_t *d1)	/* I - Second device */
450{
451  int		diff;			/* Difference between strings */
452
453
454 /*
455  * Sort devices by device-info, device-class, and device-uri...
456  */
457
458  if ((diff = cupsdCompareNames(d0->device_info, d1->device_info)) != 0)
459    return (diff);
460  else if ((diff = _cups_strcasecmp(d0->device_class, d1->device_class)) != 0)
461    return (diff);
462  else
463    return (_cups_strcasecmp(d0->device_uri, d1->device_uri));
464}
465
466
467/*
468 * 'get_current_time()' - Get the current time as a double value in seconds.
469 */
470
471static double				/* O - Time in seconds */
472get_current_time(void)
473{
474  struct timeval	curtime;	/* Current time */
475
476
477  gettimeofday(&curtime, NULL);
478
479  return (curtime.tv_sec + 0.000001 * curtime.tv_usec);
480}
481
482
483/*
484 * 'get_device()' - Get a device from a backend.
485 */
486
487static int				/* O - 0 on success, -1 on error */
488get_device(cupsd_backend_t *backend)	/* I - Backend to read from */
489{
490  char	line[2048],			/* Line from backend */
491	temp[2048],			/* Copy of line */
492	*ptr,				/* Pointer into line */
493	*dclass,			/* Device class */
494	*uri,				/* Device URI */
495	*make_model,			/* Make and model */
496	*info,				/* Device info */
497	*device_id,			/* 1284 device ID */
498	*location;			/* Physical location */
499
500
501  if (cupsFileGets(backend->pipe, line, sizeof(line)))
502  {
503   /*
504    * Each line is of the form:
505    *
506    *   class URI "make model" "name" ["1284 device ID"] ["location"]
507    */
508
509    strlcpy(temp, line, sizeof(temp));
510
511   /*
512    * device-class
513    */
514
515    dclass = temp;
516
517    for (ptr = temp; *ptr; ptr ++)
518      if (isspace(*ptr & 255))
519        break;
520
521    while (isspace(*ptr & 255))
522      *ptr++ = '\0';
523
524   /*
525    * device-uri
526    */
527
528    if (!*ptr)
529      goto error;
530
531    for (uri = ptr; *ptr; ptr ++)
532      if (isspace(*ptr & 255))
533        break;
534
535    while (isspace(*ptr & 255))
536      *ptr++ = '\0';
537
538   /*
539    * device-make-and-model
540    */
541
542    if (*ptr != '\"')
543      goto error;
544
545    for (ptr ++, make_model = ptr; *ptr && *ptr != '\"'; ptr ++)
546    {
547      if (*ptr == '\\' && ptr[1])
548        _cups_strcpy(ptr, ptr + 1);
549    }
550
551    if (*ptr != '\"')
552      goto error;
553
554    for (*ptr++ = '\0'; isspace(*ptr & 255); *ptr++ = '\0');
555
556   /*
557    * device-info
558    */
559
560    if (*ptr != '\"')
561      goto error;
562
563    for (ptr ++, info = ptr; *ptr && *ptr != '\"'; ptr ++)
564    {
565      if (*ptr == '\\' && ptr[1])
566        _cups_strcpy(ptr, ptr + 1);
567    }
568
569    if (*ptr != '\"')
570      goto error;
571
572    for (*ptr++ = '\0'; isspace(*ptr & 255); *ptr++ = '\0');
573
574   /*
575    * device-id
576    */
577
578    if (*ptr == '\"')
579    {
580      for (ptr ++, device_id = ptr; *ptr && *ptr != '\"'; ptr ++)
581      {
582	if (*ptr == '\\' && ptr[1])
583	  _cups_strcpy(ptr, ptr + 1);
584      }
585
586      if (*ptr != '\"')
587	goto error;
588
589      for (*ptr++ = '\0'; isspace(*ptr & 255); *ptr++ = '\0');
590
591     /*
592      * device-location
593      */
594
595      if (*ptr == '\"')
596      {
597	for (ptr ++, location = ptr; *ptr && *ptr != '\"'; ptr ++)
598	{
599	  if (*ptr == '\\' && ptr[1])
600	    _cups_strcpy(ptr, ptr + 1);
601	}
602
603	if (*ptr != '\"')
604	  goto error;
605
606	*ptr = '\0';
607      }
608      else
609        location = NULL;
610    }
611    else
612    {
613      device_id = NULL;
614      location  = NULL;
615    }
616
617   /*
618    * Add the device to the array of available devices...
619    */
620
621    if (!add_device(dclass, make_model, info, uri, device_id, location))
622      fprintf(stderr, "DEBUG: [cups-deviced] Found device \"%s\"...\n", uri);
623
624    return (0);
625  }
626
627 /*
628  * End of file...
629  */
630
631  cupsFileClose(backend->pipe);
632  backend->pipe = NULL;
633
634  return (-1);
635
636 /*
637  * Bad format; strip trailing newline and write an error message.
638  */
639
640  error:
641
642  if (line[strlen(line) - 1] == '\n')
643    line[strlen(line) - 1] = '\0';
644
645  fprintf(stderr, "ERROR: [cups-deviced] Bad line from \"%s\": %s\n",
646	  backend->name, line);
647  return (0);
648}
649
650
651/*
652 * 'process_children()' - Process all dead children...
653 */
654
655static void
656process_children(void)
657{
658  int			i;		/* Looping var */
659  int			status;		/* Exit status of child */
660  int			pid;		/* Process ID of child */
661  cupsd_backend_t	*backend;	/* Current backend */
662  const char		*name;		/* Name of process */
663
664
665 /*
666  * Reset the dead_children flag...
667  */
668
669  dead_children = 0;
670
671 /*
672  * Collect the exit status of some children...
673  */
674
675#ifdef HAVE_WAITPID
676  while ((pid = waitpid(-1, &status, WNOHANG)) > 0)
677#elif defined(HAVE_WAIT3)
678  while ((pid = wait3(&status, WNOHANG, NULL)) > 0)
679#else
680  if ((pid = wait(&status)) > 0)
681#endif /* HAVE_WAITPID */
682  {
683    if (status == SIGTERM)
684      status = 0;
685
686    for (i = num_backends, backend = backends; i > 0; i --, backend ++)
687      if (backend->pid == pid)
688        break;
689
690    if (i > 0)
691    {
692      name            = backend->name;
693      backend->pid    = 0;
694      backend->status = status;
695
696      active_backends --;
697    }
698    else
699      name = "Unknown";
700
701    if (status)
702    {
703      if (WIFEXITED(status))
704	fprintf(stderr,
705	        "ERROR: [cups-deviced] PID %d (%s) stopped with status %d!\n",
706		pid, name, WEXITSTATUS(status));
707      else
708	fprintf(stderr,
709	        "ERROR: [cups-deviced] PID %d (%s) crashed on signal %d!\n",
710		pid, name, WTERMSIG(status));
711    }
712    else
713      fprintf(stderr,
714              "DEBUG: [cups-deviced] PID %d (%s) exited with no errors.\n",
715	      pid, name);
716  }
717}
718
719
720/*
721 * 'sigchld_handler()' - Handle 'child' signals from old processes.
722 */
723
724static void
725sigchld_handler(int sig)		/* I - Signal number */
726{
727  (void)sig;
728
729 /*
730  * Flag that we have dead children...
731  */
732
733  dead_children = 1;
734
735 /*
736  * Reset the signal handler as needed...
737  */
738
739#if !defined(HAVE_SIGSET) && !defined(HAVE_SIGACTION)
740  signal(SIGCLD, sigchld_handler);
741#endif /* !HAVE_SIGSET && !HAVE_SIGACTION */
742}
743
744
745/*
746 * 'start_backend()' - Run a backend to gather the available devices.
747 */
748
749static int				/* O - 0 on success, -1 on error */
750start_backend(const char *name,		/* I - Backend to run */
751              int        root)		/* I - Run as root? */
752{
753  const char		*server_bin;	/* CUPS_SERVERBIN environment variable */
754  char			program[1024];	/* Full path to backend */
755  cupsd_backend_t	*backend;	/* Current backend */
756  char			*argv[2];	/* Command-line arguments */
757
758
759  if (num_backends >= MAX_BACKENDS)
760  {
761    fprintf(stderr, "ERROR: Too many backends (%d)!\n", num_backends);
762    return (-1);
763  }
764
765  if ((server_bin = getenv("CUPS_SERVERBIN")) == NULL)
766    server_bin = CUPS_SERVERBIN;
767
768  snprintf(program, sizeof(program), "%s/backend/%s", server_bin, name);
769
770  if (_cupsFileCheck(program, _CUPS_FILE_CHECK_PROGRAM, !geteuid(),
771                     _cupsFileCheckFilter, NULL))
772    return (-1);
773
774  backend = backends + num_backends;
775
776  argv[0] = (char *)name;
777  argv[1] = NULL;
778
779  if ((backend->pipe = cupsdPipeCommand(&(backend->pid), program, argv,
780                                        root ? 0 : normal_user)) == NULL)
781  {
782    fprintf(stderr, "ERROR: [cups-deviced] Unable to execute \"%s\" - %s\n",
783            program, strerror(errno));
784    return (-1);
785  }
786
787 /*
788  * Fill in the rest of the backend information...
789  */
790
791  fprintf(stderr, "DEBUG: [cups-deviced] Started backend %s (PID %d)\n",
792          program, backend->pid);
793
794  backend_fds[num_backends].fd     = cupsFileNumber(backend->pipe);
795  backend_fds[num_backends].events = POLLIN;
796
797  backend->name   = strdup(name);
798  backend->status = 0;
799  backend->count  = 0;
800
801  active_backends ++;
802  num_backends ++;
803
804  return (0);
805}
806
807
808/*
809 * End of "$Id: cups-deviced.c 11093 2013-07-03 20:48:42Z msweet $".
810 */
811