]> git.ipfire.org Git - thirdparty/cups.git/blob - scheduler/cups-deviced.c
Merge changes from CUPS 1.4svn-r8162.
[thirdparty/cups.git] / scheduler / cups-deviced.c
1 /*
2 * "$Id: cups-deviced.c 7816 2008-07-30 20:53:31Z mike $"
3 *
4 * Device scanning mini-daemon for the Common UNIX Printing System (CUPS).
5 *
6 * Copyright 2007-2008 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
50 typedef 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
64 typedef 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
76 static int num_backends = 0,
77 /* Total backends */
78 active_backends = 0;
79 /* Active backends */
80 static cupsd_backend_t backends[MAX_BACKENDS];
81 /* Array of backends */
82 static struct pollfd backend_fds[MAX_BACKENDS];
83 /* Array for poll() */
84 static cups_array_t *devices; /* Array of devices */
85 static int normal_user; /* Normal user ID */
86 static int device_limit; /* Maximum number of devices */
87 static 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? */
94 static int dead_children = 0;
95 /* Dead children? */
96
97
98 /*
99 * Local functions...
100 */
101
102 static 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);
108 static int compare_devices(cupsd_device_t *p0,
109 cupsd_device_t *p1);
110 static double get_current_time(void);
111 static int get_device(cupsd_backend_t *backend);
112 static void process_children(void);
113 static void sigchld_handler(int sig);
114 static 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
125 int /* O - Exit code */
126 main(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 if (get_device(backends + i))
317 {
318 backend_fds[i].fd = 0;
319 backend_fds[i].events = 0;
320 }
321 }
322
323 /*
324 * Get exit status from children...
325 */
326
327 if (dead_children)
328 process_children();
329 }
330
331 cupsdSendIPPTrailer();
332
333 /*
334 * Terminate any remaining backends and exit...
335 */
336
337 if (active_backends > 0)
338 {
339 for (i = 0; i < num_backends; i ++)
340 if (backends[i].pid)
341 kill(backends[i].pid, SIGTERM);
342 }
343
344 return (0);
345 }
346
347
348 /*
349 * 'add_device()' - Add a new device to the list.
350 */
351
352 static int /* O - 0 on success, -1 on error */
353 add_device(
354 const char *device_class, /* I - Device class */
355 const char *device_make_and_model, /* I - Device make and model */
356 const char *device_info, /* I - Device information */
357 const char *device_uri, /* I - Device URI */
358 const char *device_id, /* I - 1284 device ID */
359 const char *device_location) /* I - Physical location */
360 {
361 cupsd_device_t *device; /* New device */
362
363
364 /*
365 * Allocate memory for the device record...
366 */
367
368 if ((device = calloc(1, sizeof(cupsd_device_t))) == NULL)
369 {
370 fputs("ERROR: [cups-deviced] Ran out of memory allocating a device!\n",
371 stderr);
372 return (-1);
373 }
374
375 /*
376 * Copy the strings over...
377 */
378
379 strlcpy(device->device_class, device_class, sizeof(device->device_class));
380 strlcpy(device->device_info, device_info, sizeof(device->device_info));
381 strlcpy(device->device_uri, device_uri, sizeof(device->device_uri));
382
383 /*
384 * Add the device to the array and return...
385 */
386
387 if (cupsArrayFind(devices, device))
388 {
389 /*
390 * Avoid duplicates!
391 */
392
393 free(device);
394 }
395 else
396 {
397 cupsArrayAdd(devices, device);
398
399 if (device_limit <= 0 || cupsArrayCount(devices) < device_limit)
400 {
401 /*
402 * Send device info...
403 */
404
405 cupsdSendIPPGroup(IPP_TAG_PRINTER);
406 if (send_class)
407 cupsdSendIPPString(IPP_TAG_KEYWORD, "device-class",
408 device_class);
409 if (send_info)
410 cupsdSendIPPString(IPP_TAG_TEXT, "device-info", device_info);
411 if (send_make_and_model)
412 cupsdSendIPPString(IPP_TAG_TEXT, "device-make-and-model",
413 device_make_and_model);
414 if (send_uri)
415 cupsdSendIPPString(IPP_TAG_URI, "device-uri", device_uri);
416 if (send_id)
417 cupsdSendIPPString(IPP_TAG_TEXT, "device-id",
418 device_id ? device_id : "");
419 if (send_location)
420 cupsdSendIPPString(IPP_TAG_TEXT, "device-location",
421 device_location ? device_location : "");
422
423 fflush(stdout);
424 fputs("DEBUG: Flushed attributes...\n", stderr);
425 }
426 }
427
428 return (0);
429 }
430
431
432 /*
433 * 'compare_devices()' - Compare device names to eliminate duplicates.
434 */
435
436 static int /* O - Result of comparison */
437 compare_devices(cupsd_device_t *d0, /* I - First device */
438 cupsd_device_t *d1) /* I - Second device */
439 {
440 int diff; /* Difference between strings */
441
442
443 /*
444 * Sort devices by device-info, device-class, and device-uri...
445 */
446
447 if ((diff = cupsdCompareNames(d0->device_info, d1->device_info)) != 0)
448 return (diff);
449 else if ((diff = strcasecmp(d0->device_class, d1->device_class)) != 0)
450 return (diff);
451 else
452 return (strcasecmp(d0->device_uri, d1->device_uri));
453 }
454
455
456 /*
457 * 'get_current_time()' - Get the current time as a double value in seconds.
458 */
459
460 static double /* O - Time in seconds */
461 get_current_time(void)
462 {
463 struct timeval curtime; /* Current time */
464
465
466 gettimeofday(&curtime, NULL);
467
468 return (curtime.tv_sec + 0.000001 * curtime.tv_usec);
469 }
470
471
472 /*
473 * 'get_device()' - Get a device from a backend.
474 */
475
476 static int /* O - 0 on success, -1 on error */
477 get_device(cupsd_backend_t *backend) /* I - Backend to read from */
478 {
479 char line[2048], /* Line from backend */
480 temp[2048], /* Copy of line */
481 *ptr, /* Pointer into line */
482 *dclass, /* Device class */
483 *uri, /* Device URI */
484 *make_model, /* Make and model */
485 *info, /* Device info */
486 *device_id, /* 1284 device ID */
487 *location; /* Physical location */
488
489
490 if (cupsFileGets(backend->pipe, line, sizeof(line)))
491 {
492 /*
493 * Each line is of the form:
494 *
495 * class URI "make model" "name" ["1284 device ID"] ["location"]
496 */
497
498 strlcpy(temp, line, sizeof(temp));
499
500 /*
501 * device-class
502 */
503
504 dclass = temp;
505
506 for (ptr = temp; *ptr; ptr ++)
507 if (isspace(*ptr & 255))
508 break;
509
510 while (isspace(*ptr & 255))
511 *ptr++ = '\0';
512
513 /*
514 * device-uri
515 */
516
517 if (!*ptr)
518 goto error;
519
520 for (uri = ptr; *ptr; ptr ++)
521 if (isspace(*ptr & 255))
522 break;
523
524 while (isspace(*ptr & 255))
525 *ptr++ = '\0';
526
527 /*
528 * device-make-and-model
529 */
530
531 if (*ptr != '\"')
532 goto error;
533
534 for (ptr ++, make_model = ptr; *ptr && *ptr != '\"'; ptr ++)
535 {
536 if (*ptr == '\\' && ptr[1])
537 _cups_strcpy(ptr, ptr + 1);
538 }
539
540 if (*ptr != '\"')
541 goto error;
542
543 for (*ptr++ = '\0'; isspace(*ptr & 255); *ptr++ = '\0');
544
545 /*
546 * device-info
547 */
548
549 if (*ptr != '\"')
550 goto error;
551
552 for (ptr ++, info = ptr; *ptr && *ptr != '\"'; ptr ++)
553 {
554 if (*ptr == '\\' && ptr[1])
555 _cups_strcpy(ptr, ptr + 1);
556 }
557
558 if (*ptr != '\"')
559 goto error;
560
561 for (*ptr++ = '\0'; isspace(*ptr & 255); *ptr++ = '\0');
562
563 /*
564 * device-id
565 */
566
567 if (*ptr == '\"')
568 {
569 for (ptr ++, device_id = ptr; *ptr && *ptr != '\"'; ptr ++)
570 {
571 if (*ptr == '\\' && ptr[1])
572 _cups_strcpy(ptr, ptr + 1);
573 }
574
575 if (*ptr != '\"')
576 goto error;
577
578 for (*ptr++ = '\0'; isspace(*ptr & 255); *ptr++ = '\0');
579
580 /*
581 * device-location
582 */
583
584 if (*ptr == '\"')
585 {
586 for (ptr ++, location = ptr; *ptr && *ptr != '\"'; ptr ++)
587 {
588 if (*ptr == '\\' && ptr[1])
589 _cups_strcpy(ptr, ptr + 1);
590 }
591
592 if (*ptr != '\"')
593 goto error;
594
595 *ptr = '\0';
596 }
597 else
598 location = NULL;
599 }
600 else
601 {
602 device_id = NULL;
603 location = NULL;
604 }
605
606 /*
607 * Add the device to the array of available devices...
608 */
609
610 if (!add_device(dclass, make_model, info, uri, device_id, location))
611 fprintf(stderr, "DEBUG: [cups-deviced] Found device \"%s\"...\n", uri);
612
613 return (0);
614 }
615
616 /*
617 * End of file...
618 */
619
620 cupsFileClose(backend->pipe);
621 backend->pipe = NULL;
622
623 return (-1);
624
625 /*
626 * Bad format; strip trailing newline and write an error message.
627 */
628
629 error:
630
631 if (line[strlen(line) - 1] == '\n')
632 line[strlen(line) - 1] = '\0';
633
634 fprintf(stderr, "ERROR: [cups-deviced] Bad line from \"%s\": %s\n",
635 backend->name, line);
636 return (0);
637 }
638
639
640 /*
641 * 'process_children()' - Process all dead children...
642 */
643
644 static void
645 process_children(void)
646 {
647 int i; /* Looping var */
648 int status; /* Exit status of child */
649 int pid; /* Process ID of child */
650 cupsd_backend_t *backend; /* Current backend */
651 const char *name; /* Name of process */
652
653
654 /*
655 * Reset the dead_children flag...
656 */
657
658 dead_children = 0;
659
660 /*
661 * Collect the exit status of some children...
662 */
663
664 #ifdef HAVE_WAITPID
665 while ((pid = waitpid(-1, &status, WNOHANG)) > 0)
666 #elif defined(HAVE_WAIT3)
667 while ((pid = wait3(&status, WNOHANG, NULL)) > 0)
668 #else
669 if ((pid = wait(&status)) > 0)
670 #endif /* HAVE_WAITPID */
671 {
672 if (status == SIGTERM)
673 status = 0;
674
675 for (i = num_backends, backend = backends; i > 0; i --, backend ++)
676 if (backend->pid == pid)
677 break;
678
679 if (i > 0)
680 {
681 name = backend->name;
682 backend->pid = 0;
683 backend->status = status;
684
685 active_backends --;
686 }
687 else
688 name = "Unknown";
689
690 if (status)
691 {
692 if (WIFEXITED(status))
693 fprintf(stderr,
694 "ERROR: [cups-deviced] PID %d (%s) stopped with status %d!\n",
695 pid, name, WEXITSTATUS(status));
696 else
697 fprintf(stderr,
698 "ERROR: [cups-deviced] PID %d (%s) crashed on signal %d!\n",
699 pid, name, WTERMSIG(status));
700 }
701 else
702 fprintf(stderr,
703 "DEBUG: [cups-deviced] PID %d (%s) exited with no errors.\n",
704 pid, name);
705 }
706 }
707
708
709 /*
710 * 'sigchld_handler()' - Handle 'child' signals from old processes.
711 */
712
713 static void
714 sigchld_handler(int sig) /* I - Signal number */
715 {
716 (void)sig;
717
718 /*
719 * Flag that we have dead children...
720 */
721
722 dead_children = 1;
723
724 /*
725 * Reset the signal handler as needed...
726 */
727
728 #if !defined(HAVE_SIGSET) && !defined(HAVE_SIGACTION)
729 signal(SIGCLD, sigchld_handler);
730 #endif /* !HAVE_SIGSET && !HAVE_SIGACTION */
731 }
732
733
734 /*
735 * 'start_backend()' - Run a backend to gather the available devices.
736 */
737
738 static int /* O - 0 on success, -1 on error */
739 start_backend(const char *name, /* I - Backend to run */
740 int root) /* I - Run as root? */
741 {
742 const char *server_bin; /* CUPS_SERVERBIN environment variable */
743 char program[1024]; /* Full path to backend */
744 cupsd_backend_t *backend; /* Current backend */
745 char *argv[2]; /* Command-line arguments */
746
747
748 if (num_backends >= MAX_BACKENDS)
749 {
750 fprintf(stderr, "ERROR: Too many backends (%d)!\n", num_backends);
751 return (-1);
752 }
753
754 if ((server_bin = getenv("CUPS_SERVERBIN")) == NULL)
755 server_bin = CUPS_SERVERBIN;
756
757 snprintf(program, sizeof(program), "%s/backend/%s", server_bin, name);
758
759 backend = backends + num_backends;
760
761 argv[0] = (char *)name;
762 argv[1] = NULL;
763
764 if ((backend->pipe = cupsdPipeCommand(&(backend->pid), program, argv,
765 root ? 0 : normal_user)) == NULL)
766 {
767 fprintf(stderr, "ERROR: [cups-deviced] Unable to execute \"%s\" - %s\n",
768 program, strerror(errno));
769 return (-1);
770 }
771
772 /*
773 * Fill in the rest of the backend information...
774 */
775
776 fprintf(stderr, "DEBUG: [cups-deviced] Started backend %s (PID %d)\n",
777 program, backend->pid);
778
779 backend_fds[num_backends].fd = cupsFileNumber(backend->pipe);
780 backend_fds[num_backends].events = POLLIN;
781
782 backend->name = strdup(name);
783 backend->status = 0;
784 backend->count = 0;
785
786 active_backends ++;
787 num_backends ++;
788
789 return (0);
790 }
791
792
793 /*
794 * End of "$Id: cups-deviced.c 7816 2008-07-30 20:53:31Z mike $".
795 */