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