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