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