1/* 2 * "$Id: cups-deviced.c 11791 2014-04-02 16:56:54Z msweet $" 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 39typedef 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 53typedef 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 65static int num_backends = 0, 66 /* Total backends */ 67 active_backends = 0; 68 /* Active backends */ 69static cupsd_backend_t backends[MAX_BACKENDS]; 70 /* Array of backends */ 71static struct pollfd backend_fds[MAX_BACKENDS]; 72 /* Array for poll() */ 73static cups_array_t *devices; /* Array of devices */ 74static uid_t normal_user; /* Normal user ID */ 75static int device_limit; /* Maximum number of devices */ 76static 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? */ 83static int dead_children = 0; 84 /* Dead children? */ 85 86 87/* 88 * Local functions... 89 */ 90 91static 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); 97static int compare_devices(cupsd_device_t *p0, 98 cupsd_device_t *p1); 99static double get_current_time(void); 100static int get_device(cupsd_backend_t *backend); 101static void process_children(void); 102static void sigchld_handler(int sig); 103static 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 114int /* O - Exit code */ 115main(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 350static int /* O - 0 on success, -1 on error */ 351add_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 434static int /* O - Result of comparison */ 435compare_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 458static double /* O - Time in seconds */ 459get_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 474static int /* O - 0 on success, -1 on error */ 475get_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 642static void 643process_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 711static void 712sigchld_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 736static int /* O - 0 on success, -1 on error */ 737start_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: cups-deviced.c 11791 2014-04-02 16:56:54Z msweet $". 797 */ 798