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