1/* 2 * "$Id: testbackend.c 11645 2014-02-27 16:35:53Z msweet $" 3 * 4 * Backend test program for CUPS. 5 * 6 * Copyright 2007-2014 by Apple Inc. 7 * Copyright 1997-2005 by Easy Software Products, all rights reserved. 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 * "LICENSE" which should have been included with this file. If this 13 * file is missing or damaged, see the license at "http://www.cups.org/". 14 * 15 * This file is subject to the Apple OS-Developed Software exception. 16 */ 17 18/* 19 * Include necessary headers. 20 */ 21 22#include <cups/string-private.h> 23#include <cups/cups.h> 24#include <cups/sidechannel.h> 25#include <unistd.h> 26#include <fcntl.h> 27#include <sys/wait.h> 28#include <signal.h> 29 30 31/* 32 * Local globals... 33 */ 34 35static int job_canceled = 0; 36 37 38/* 39 * Local functions... 40 */ 41 42static void sigterm_handler(int sig); 43static void usage(void) __attribute__((noreturn)); 44static void walk_cb(const char *oid, const char *data, int datalen, 45 void *context); 46 47 48/* 49 * 'main()' - Run the named backend. 50 * 51 * Usage: 52 * 53 * testbackend [-s] [-t] device-uri job-id user title copies options [file] 54 */ 55 56int /* O - Exit status */ 57main(int argc, /* I - Number of command-line args */ 58 char *argv[]) /* I - Command-line arguments */ 59{ 60 int first_arg, /* First argument for backend */ 61 do_cancel = 0, /* Simulate a cancel-job via SIGTERM */ 62 do_ps = 0, /* Do PostScript query+test? */ 63 do_pcl = 0, /* Do PCL query+test? */ 64 do_side_tests = 0, /* Test side-channel ops? */ 65 do_trickle = 0, /* Trickle data to backend */ 66 do_walk = 0, /* Do OID lookup (0) or walking (1) */ 67 show_log = 0; /* Show log messages from backends? */ 68 const char *oid = ".1.3.6.1.2.1.43.10.2.1.4.1.1"; 69 /* OID to lookup or walk */ 70 char scheme[255], /* Scheme in URI == backend */ 71 backend[1024], /* Backend path */ 72 libpath[1024], /* Path for libcups */ 73 *ptr; /* Pointer into path */ 74 const char *serverbin; /* CUPS_SERVERBIN environment variable */ 75 int fd, /* Temporary file descriptor */ 76 back_fds[2], /* Back-channel pipe */ 77 side_fds[2], /* Side-channel socket */ 78 data_fds[2], /* Data pipe */ 79 back_pid = -1, /* Backend process ID */ 80 data_pid = -1, /* Trickle process ID */ 81 pid, /* Process ID */ 82 status; /* Exit status */ 83 84 85 /* 86 * Get the current directory and point the run-time linker at the "cups" 87 * subdirectory... 88 */ 89 90 if (getcwd(libpath, sizeof(libpath)) && 91 (ptr = strrchr(libpath, '/')) != NULL && !strcmp(ptr, "/backend")) 92 { 93 strlcpy(ptr, "/cups", sizeof(libpath) - (size_t)(ptr - libpath)); 94 if (!access(libpath, 0)) 95 { 96#ifdef __APPLE__ 97 fprintf(stderr, "Setting DYLD_LIBRARY_PATH to \"%s\".\n", libpath); 98 setenv("DYLD_LIBRARY_PATH", libpath, 1); 99#else 100 fprintf(stderr, "Setting LD_LIBRARY_PATH to \"%s\".\n", libpath); 101 setenv("LD_LIBRARY_PATH", libpath, 1); 102#endif /* __APPLE__ */ 103 } 104 else 105 perror(libpath); 106 } 107 108 /* 109 * See if we have side-channel tests to do... 110 */ 111 112 for (first_arg = 1; 113 argv[first_arg] && argv[first_arg][0] == '-'; 114 first_arg ++) 115 if (!strcmp(argv[first_arg], "-d")) 116 show_log = 1; 117 else if (!strcmp(argv[first_arg], "-cancel")) 118 do_cancel = 1; 119 else if (!strcmp(argv[first_arg], "-pcl")) 120 do_pcl = 1; 121 else if (!strcmp(argv[first_arg], "-ps")) 122 do_ps = 1; 123 else if (!strcmp(argv[first_arg], "-s")) 124 do_side_tests = 1; 125 else if (!strcmp(argv[first_arg], "-t")) 126 do_trickle = 1; 127 else if (!strcmp(argv[first_arg], "-get") && (first_arg + 1) < argc) 128 { 129 first_arg ++; 130 131 do_side_tests = 1; 132 oid = argv[first_arg]; 133 } 134 else if (!strcmp(argv[first_arg], "-walk") && (first_arg + 1) < argc) 135 { 136 first_arg ++; 137 138 do_side_tests = 1; 139 do_walk = 1; 140 oid = argv[first_arg]; 141 } 142 else 143 usage(); 144 145 argc -= first_arg; 146 if (argc < 6 || argc > 7 || (argc == 7 && do_trickle)) 147 usage(); 148 149 /* 150 * Extract the scheme from the device-uri - that's the program we want to 151 * execute. 152 */ 153 154 if (sscanf(argv[first_arg], "%254[^:]", scheme) != 1) 155 { 156 fputs("testbackend: Bad device-uri - no colon!\n", stderr); 157 return (1); 158 } 159 160 if (!access(scheme, X_OK)) 161 strlcpy(backend, scheme, sizeof(backend)); 162 else 163 { 164 if ((serverbin = getenv("CUPS_SERVERBIN")) == NULL) 165 serverbin = CUPS_SERVERBIN; 166 167 snprintf(backend, sizeof(backend), "%s/backend/%s", serverbin, scheme); 168 if (access(backend, X_OK)) 169 { 170 fprintf(stderr, "testbackend: Unknown device scheme \"%s\"!\n", scheme); 171 return (1); 172 } 173 } 174 175 /* 176 * Create the back-channel pipe and side-channel socket... 177 */ 178 179 open("/dev/null", O_WRONLY); /* Make sure fd 3 and 4 are used */ 180 open("/dev/null", O_WRONLY); 181 182 pipe(back_fds); 183 fcntl(back_fds[0], F_SETFL, fcntl(back_fds[0], F_GETFL) | O_NONBLOCK); 184 fcntl(back_fds[1], F_SETFL, fcntl(back_fds[1], F_GETFL) | O_NONBLOCK); 185 186 socketpair(AF_LOCAL, SOCK_STREAM, 0, side_fds); 187 fcntl(side_fds[0], F_SETFL, fcntl(side_fds[0], F_GETFL) | O_NONBLOCK); 188 fcntl(side_fds[1], F_SETFL, fcntl(side_fds[1], F_GETFL) | O_NONBLOCK); 189 190 /* 191 * Execute the trickle process as needed... 192 */ 193 194 if (do_trickle || do_pcl || do_ps || do_cancel) 195 { 196 pipe(data_fds); 197 198 signal(SIGTERM, sigterm_handler); 199 200 if ((data_pid = fork()) == 0) 201 { 202 /* 203 * Trickle/query child comes here. Rearrange file descriptors so that 204 * FD 1, 3, and 4 point to the backend... 205 */ 206 207 if ((fd = open("/dev/null", O_RDONLY)) != 0) 208 { 209 dup2(fd, 0); 210 close(fd); 211 } 212 213 if (data_fds[1] != 1) 214 { 215 dup2(data_fds[1], 1); 216 close(data_fds[1]); 217 } 218 close(data_fds[0]); 219 220 if (back_fds[0] != 3) 221 { 222 dup2(back_fds[0], 3); 223 close(back_fds[0]); 224 } 225 close(back_fds[1]); 226 227 if (side_fds[0] != 4) 228 { 229 dup2(side_fds[0], 4); 230 close(side_fds[0]); 231 } 232 close(side_fds[1]); 233 234 if (do_trickle) 235 { 236 /* 237 * Write 10 spaces, 1 per second... 238 */ 239 240 int i; /* Looping var */ 241 242 for (i = 0; i < 10; i ++) 243 { 244 write(1, " ", 1); 245 sleep(1); 246 } 247 } 248 else if (do_cancel) 249 { 250 /* 251 * Write PS or PCL lines until we see SIGTERM... 252 */ 253 254 int line = 0, page = 0; /* Current line and page */ 255 ssize_t bytes; /* Number of bytes of response data */ 256 char buffer[1024]; /* Output buffer */ 257 258 259 if (do_pcl) 260 write(1, "\033E", 2); 261 else 262 write(1, "%!\n/Courier findfont 12 scalefont setfont 0 setgray\n", 52); 263 264 while (!job_canceled) 265 { 266 if (line == 0) 267 { 268 page ++; 269 270 if (do_pcl) 271 snprintf(buffer, sizeof(buffer), "PCL Page %d\r\n\r\n", page); 272 else 273 snprintf(buffer, sizeof(buffer), 274 "18 732 moveto (PS Page %d) show\n", page); 275 276 write(1, buffer, strlen(buffer)); 277 } 278 279 line ++; 280 281 if (do_pcl) 282 snprintf(buffer, sizeof(buffer), "Line %d\r\n", line); 283 else 284 snprintf(buffer, sizeof(buffer), "18 %d moveto (Line %d) show\n", 285 720 - line * 12, line); 286 287 write(1, buffer, strlen(buffer)); 288 289 if (line >= 55) 290 { 291 /* 292 * Eject after 55 lines... 293 */ 294 295 line = 0; 296 if (do_pcl) 297 write(1, "\014", 1); 298 else 299 write(1, "showpage\n", 9); 300 } 301 302 /* 303 * Check for back-channel data... 304 */ 305 306 if ((bytes = cupsBackChannelRead(buffer, sizeof(buffer), 0)) > 0) 307 write(2, buffer, (size_t)bytes); 308 309 /* 310 * Throttle output to ~100hz... 311 */ 312 313 usleep(10000); 314 } 315 316 /* 317 * Eject current page with info... 318 */ 319 320 if (do_pcl) 321 snprintf(buffer, sizeof(buffer), 322 "Canceled on line %d of page %d\r\n\014\033E", line, page); 323 else 324 snprintf(buffer, sizeof(buffer), 325 "\n18 %d moveto (Canceled on line %d of page %d)\nshowpage\n", 326 720 - line * 12, line, page); 327 328 write(1, buffer, strlen(buffer)); 329 330 /* 331 * See if we get any back-channel data... 332 */ 333 334 while ((bytes = cupsBackChannelRead(buffer, sizeof(buffer), 5.0)) > 0) 335 write(2, buffer, (size_t)bytes); 336 337 exit(0); 338 } 339 else 340 { 341 /* 342 * Do PS or PCL query + test pages. 343 */ 344 345 char buffer[1024]; /* Buffer for response data */ 346 ssize_t bytes; /* Number of bytes of response data */ 347 double timeout; /* Timeout */ 348 const char *data; /* Data to send */ 349 static const char *pcl_data = /* PCL data */ 350 "\033%-12345X@PJL\r\n" 351 "@PJL JOB NAME = \"Hello, World!\"\r\n" 352 "@PJL INFO USTATUS\r\n" 353 "@PJL ENTER LANGUAGE = PCL\r\n" 354 "\033E" 355 "Hello, World!\n" 356 "\014" 357 "\033%-12345X@PJL\r\n" 358 "@PJL EOJ NAME=\"Hello, World!\"\r\n" 359 "\033%-12345X"; 360 static const char *ps_data = /* PostScript data */ 361 "%!\n" 362 "save\n" 363 "product = flush\n" 364 "currentpagedevice /PageSize get aload pop\n" 365 "2 copy gt {exch} if\n" 366 "(Unknown)\n" 367 "19 dict\n" 368 "dup [612 792] (Letter) put\n" 369 "dup [612 1008] (Legal) put\n" 370 "dup [612 935] (w612h935) put\n" 371 "dup [522 756] (Executive) put\n" 372 "dup [595 842] (A4) put\n" 373 "dup [420 595] (A5) put\n" 374 "dup [499 709] (ISOB5) put\n" 375 "dup [516 728] (B5) put\n" 376 "dup [612 936] (w612h936) put\n" 377 "dup [284 419] (Postcard) put\n" 378 "dup [419.5 567] (DoublePostcard) put\n" 379 "dup [558 774] (w558h774) put\n" 380 "dup [553 765] (w553h765) put\n" 381 "dup [522 737] (w522h737) put\n" 382 "dup [499 709] (EnvISOB5) put\n" 383 "dup [297 684] (Env10) put\n" 384 "dup [459 649] (EnvC5) put\n" 385 "dup [312 624] (EnvDL) put\n" 386 "dup [279 540] (EnvMonarch) put\n" 387 "{ exch aload pop 4 index sub abs 5 le exch\n" 388 " 5 index sub abs 5 le and\n" 389 " {exch pop exit} {pop} ifelse\n" 390 "} bind forall\n" 391 "= flush pop pop\n" 392 "/Courier findfont 12 scalefont setfont\n" 393 "0 setgray 36 720 moveto (Hello, ) show product show (!) show\n" 394 "showpage\n" 395 "restore\n" 396 "\004"; 397 398 399 if (do_pcl) 400 data = pcl_data; 401 else 402 data = ps_data; 403 404 write(1, data, strlen(data)); 405 write(2, "DEBUG: START\n", 13); 406 timeout = 60.0; 407 while ((bytes = cupsBackChannelRead(buffer, sizeof(buffer), 408 timeout)) > 0) 409 { 410 write(2, buffer, (size_t)bytes); 411 timeout = 5.0; 412 } 413 write(2, "\nDEBUG: END\n", 12); 414 } 415 416 exit(0); 417 } 418 else if (data_pid < 0) 419 { 420 perror("testbackend: Unable to fork"); 421 return (1); 422 } 423 } 424 else 425 data_fds[0] = data_fds[1] = -1; 426 427 /* 428 * Execute the backend... 429 */ 430 431 if ((back_pid = fork()) == 0) 432 { 433 /* 434 * Child comes here... 435 */ 436 437 if (do_trickle || do_ps || do_pcl || do_cancel) 438 { 439 if (data_fds[0] != 0) 440 { 441 dup2(data_fds[0], 0); 442 close(data_fds[0]); 443 } 444 close(data_fds[1]); 445 } 446 447 if (!show_log) 448 { 449 if ((fd = open("/dev/null", O_WRONLY)) != 2) 450 { 451 dup2(fd, 2); 452 close(fd); 453 } 454 } 455 456 if (back_fds[1] != 3) 457 { 458 dup2(back_fds[1], 3); 459 close(back_fds[0]); 460 } 461 close(back_fds[1]); 462 463 if (side_fds[1] != 4) 464 { 465 dup2(side_fds[1], 4); 466 close(side_fds[0]); 467 } 468 close(side_fds[1]); 469 470 execv(backend, argv + first_arg); 471 fprintf(stderr, "testbackend: Unable to execute \"%s\": %s\n", backend, 472 strerror(errno)); 473 return (errno); 474 } 475 else if (back_pid < 0) 476 { 477 perror("testbackend: Unable to fork"); 478 return (1); 479 } 480 481 /* 482 * Parent comes here, setup back and side channel file descriptors... 483 */ 484 485 if (do_trickle || do_ps || do_pcl || do_cancel) 486 { 487 close(data_fds[0]); 488 close(data_fds[1]); 489 } 490 491 if (back_fds[0] != 3) 492 { 493 dup2(back_fds[0], 3); 494 close(back_fds[0]); 495 } 496 close(back_fds[1]); 497 498 if (side_fds[0] != 4) 499 { 500 dup2(side_fds[0], 4); 501 close(side_fds[0]); 502 } 503 close(side_fds[1]); 504 505 /* 506 * Do side-channel tests as needed, then wait for the backend... 507 */ 508 509 if (do_side_tests) 510 { 511 int length; /* Length of buffer */ 512 char buffer[2049]; /* Buffer for reponse */ 513 cups_sc_status_t scstatus; /* Status of side-channel command */ 514 static const char * const statuses[] = 515 { 516 "CUPS_SC_STATUS_NONE", /* No status */ 517 "CUPS_SC_STATUS_OK", /* Operation succeeded */ 518 "CUPS_SC_STATUS_IO_ERROR", /* An I/O error occurred */ 519 "CUPS_SC_STATUS_TIMEOUT", /* The backend did not respond */ 520 "CUPS_SC_STATUS_NO_RESPONSE", /* The device did not respond */ 521 "CUPS_SC_STATUS_BAD_MESSAGE", /* The command/response message was invalid */ 522 "CUPS_SC_STATUS_TOO_BIG", /* Response too big */ 523 "CUPS_SC_STATUS_NOT_IMPLEMENTED" /* Command not implemented */ 524 }; 525 526 527 sleep(2); 528 529 length = 0; 530 scstatus = cupsSideChannelDoRequest(CUPS_SC_CMD_DRAIN_OUTPUT, buffer, 531 &length, 60.0); 532 printf("CUPS_SC_CMD_DRAIN_OUTPUT returned %s\n", statuses[scstatus]); 533 534 length = 1; 535 scstatus = cupsSideChannelDoRequest(CUPS_SC_CMD_GET_BIDI, buffer, 536 &length, 5.0); 537 printf("CUPS_SC_CMD_GET_BIDI returned %s, %d\n", statuses[scstatus], buffer[0]); 538 539 length = sizeof(buffer) - 1; 540 scstatus = cupsSideChannelDoRequest(CUPS_SC_CMD_GET_DEVICE_ID, buffer, 541 &length, 5.0); 542 buffer[length] = '\0'; 543 printf("CUPS_SC_CMD_GET_DEVICE_ID returned %s, \"%s\"\n", 544 statuses[scstatus], buffer); 545 546 length = 1; 547 scstatus = cupsSideChannelDoRequest(CUPS_SC_CMD_GET_STATE, buffer, 548 &length, 5.0); 549 printf("CUPS_SC_CMD_GET_STATE returned %s, %02X\n", statuses[scstatus], 550 buffer[0] & 255); 551 552 if (do_walk) 553 { 554 /* 555 * Walk the OID tree... 556 */ 557 558 scstatus = cupsSideChannelSNMPWalk(oid, 5.0, walk_cb, NULL); 559 printf("CUPS_SC_CMD_SNMP_WALK returned %s\n", statuses[scstatus]); 560 } 561 else 562 { 563 /* 564 * Lookup the same OID twice... 565 */ 566 567 length = sizeof(buffer); 568 scstatus = cupsSideChannelSNMPGet(oid, buffer, &length, 5.0); 569 printf("CUPS_SC_CMD_SNMP_GET %s returned %s, %d bytes (%s)\n", oid, 570 statuses[scstatus], (int)length, buffer); 571 572 length = sizeof(buffer); 573 scstatus = cupsSideChannelSNMPGet(oid, buffer, &length, 5.0); 574 printf("CUPS_SC_CMD_SNMP_GET %s returned %s, %d bytes (%s)\n", oid, 575 statuses[scstatus], (int)length, buffer); 576 } 577 578 length = 0; 579 scstatus = cupsSideChannelDoRequest(CUPS_SC_CMD_SOFT_RESET, buffer, 580 &length, 5.0); 581 printf("CUPS_SC_CMD_SOFT_RESET returned %s\n", statuses[scstatus]); 582 } 583 584 if (do_cancel) 585 { 586 sleep(1); 587 kill(data_pid, SIGTERM); 588 kill(back_pid, SIGTERM); 589 } 590 591 while ((pid = wait(&status)) > 0) 592 { 593 if (status) 594 { 595 if (WIFEXITED(status)) 596 printf("%s exited with status %d!\n", 597 pid == back_pid ? backend : "test", 598 WEXITSTATUS(status)); 599 else 600 printf("%s crashed with signal %d!\n", 601 pid == back_pid ? backend : "test", 602 WTERMSIG(status)); 603 } 604 } 605 606 /* 607 * Exit accordingly... 608 */ 609 610 return (status != 0); 611} 612 613 614/* 615 * 'sigterm_handler()' - Flag when we get SIGTERM. 616 */ 617 618static void 619sigterm_handler(int sig) /* I - Signal */ 620{ 621 (void)sig; 622 623 job_canceled = 1; 624} 625 626 627/* 628 * 'usage()' - Show usage information. 629 */ 630 631static void 632usage(void) 633{ 634 puts("Usage: testbackend [-cancel] [-d] [-ps | -pcl] [-s [-get OID] " 635 "[-walk OID]] [-t] device-uri job-id user title copies options [file]"); 636 puts(""); 637 puts("Options:"); 638 puts(" -cancel Simulate a canceled print job after 2 seconds."); 639 puts(" -d Show log messages from backend."); 640 puts(" -get OID Lookup the specified SNMP OID."); 641 puts(" (.1.3.6.1.2.1.43.10.2.1.4.1.1 is a good one for printers)"); 642 puts(" -pcl Send PCL+PJL query and test page to backend."); 643 puts(" -ps Send PostScript query and test page to backend."); 644 puts(" -s Do side-channel + SNMP tests."); 645 puts(" -t Send spaces slowly to backend ('trickle')."); 646 puts(" -walk OID Walk the specified SNMP OID."); 647 puts(" (.1.3.6.1.2.1.43 is a good one for printers)"); 648 649 exit(1); 650} 651 652 653/* 654 * 'walk_cb()' - Show results of cupsSideChannelSNMPWalk... 655 */ 656 657static void 658walk_cb(const char *oid, /* I - OID */ 659 const char *data, /* I - Data */ 660 int datalen, /* I - Length of data */ 661 void *context) /* I - Context (unused) */ 662{ 663 char temp[80]; 664 665 (void)context; 666 667 if ((size_t)datalen > (sizeof(temp) - 1)) 668 { 669 memcpy(temp, data, sizeof(temp) - 1); 670 temp[sizeof(temp) - 1] = '\0'; 671 } 672 else 673 { 674 memcpy(temp, data, (size_t)datalen); 675 temp[datalen] = '\0'; 676 } 677 678 printf("CUPS_SC_CMD_SNMP_WALK %s, %d bytes (%s)\n", oid, datalen, temp); 679} 680 681 682/* 683 * End of "$Id: testbackend.c 11645 2014-02-27 16:35:53Z msweet $". 684 */ 685