1/* 2 * "$Id: commandtops.c 3796 2012-04-23 22:54:48Z msweet $" 3 * 4 * PostScript command filter for CUPS. 5 * 6 * Copyright 2008-2012 by Apple Inc. 7 * 8 * These coded instructions, statements, and computer programs are the 9 * property of Apple Inc. and are protected by Federal copyright 10 * law. Distribution and use rights are outlined in the file "LICENSE.txt" 11 * which should have been included with this file. If this file is 12 * file is missing or damaged, see the license at "http://www.cups.org/". 13 * 14 * 15 * Contents: 16 * 17 * main() - Process a CUPS command file. 18 * auto_configure() - Automatically configure the printer using 19 * PostScript query commands and/or SNMP lookups. 20 * begin_ps() - Send the standard PostScript prolog. 21 * end_ps() - Send the standard PostScript trailer. 22 * print_self_test_page() - Print a self-test page. 23 * report_levels() - Report supply levels. 24 */ 25 26/* 27 * Include necessary headers... 28 */ 29 30#include <cups/cups-private.h> 31#include <cups/ppd.h> 32#include <cups/sidechannel.h> 33 34 35/* 36 * Local functions... 37 */ 38 39static int auto_configure(ppd_file_t *ppd, const char *user); 40static void begin_ps(ppd_file_t *ppd, const char *user); 41static void end_ps(ppd_file_t *ppd); 42static void print_self_test_page(ppd_file_t *ppd, const char *user); 43static void report_levels(ppd_file_t *ppd, const char *user); 44 45 46/* 47 * 'main()' - Process a CUPS command file. 48 */ 49 50int /* O - Exit status */ 51main(int argc, /* I - Number of command-line arguments */ 52 char *argv[]) /* I - Command-line arguments */ 53{ 54 int status = 0; /* Exit status */ 55 cups_file_t *fp; /* Command file */ 56 char line[1024], /* Line from file */ 57 *value; /* Value on line */ 58 int linenum; /* Line number in file */ 59 ppd_file_t *ppd; /* PPD file */ 60 61 62 /* 63 * Check for valid arguments... 64 */ 65 66 if (argc < 6 || argc > 7) 67 { 68 /* 69 * We don't have the correct number of arguments; write an error message 70 * and return. 71 */ 72 73 _cupsLangPrintf(stderr, 74 _("Usage: %s job-id user title copies options [file]"), 75 argv[0]); 76 return (1); 77 } 78 79 /* 80 * Open the PPD file... 81 */ 82 83 if ((ppd = ppdOpenFile(getenv("PPD"))) == NULL) 84 { 85 fputs("ERROR: Unable to open PPD file!\n", stderr); 86 return (1); 87 } 88 89 /* 90 * Open the command file as needed... 91 */ 92 93 if (argc == 7) 94 { 95 if ((fp = cupsFileOpen(argv[6], "r")) == NULL) 96 { 97 perror("ERROR: Unable to open command file - "); 98 return (1); 99 } 100 } 101 else 102 fp = cupsFileStdin(); 103 104 /* 105 * Read the commands from the file and send the appropriate commands... 106 */ 107 108 linenum = 0; 109 110 while (cupsFileGetConf(fp, line, sizeof(line), &value, &linenum)) 111 { 112 /* 113 * Parse the command... 114 */ 115 116 if (!_cups_strcasecmp(line, "AutoConfigure")) 117 status |= auto_configure(ppd, argv[2]); 118 else if (!_cups_strcasecmp(line, "PrintSelfTestPage")) 119 print_self_test_page(ppd, argv[2]); 120 else if (!_cups_strcasecmp(line, "ReportLevels")) 121 report_levels(ppd, argv[2]); 122 else 123 { 124 _cupsLangPrintFilter(stderr, "ERROR", 125 _("Invalid printer command \"%s\"."), line); 126 status = 1; 127 } 128 } 129 130 return (status); 131} 132 133 134/* 135 * 'auto_configure()' - Automatically configure the printer using PostScript 136 * query commands and/or SNMP lookups. 137 */ 138 139static int /* O - Exit status */ 140auto_configure(ppd_file_t *ppd, /* I - PPD file */ 141 const char *user) /* I - Printing user */ 142{ 143 int status = 0; /* Exit status */ 144 ppd_option_t *option; /* Current option in PPD */ 145 ppd_attr_t *attr; /* Query command attribute */ 146 const char *valptr; /* Pointer into attribute value */ 147 char buffer[1024], /* String buffer */ 148 *bufptr; /* Pointer into buffer */ 149 ssize_t bytes; /* Number of bytes read */ 150 int datalen; /* Side-channel data length */ 151 152 153 /* 154 * See if the backend supports bidirectional I/O... 155 */ 156 157 datalen = 1; 158 if (cupsSideChannelDoRequest(CUPS_SC_CMD_GET_BIDI, buffer, &datalen, 159 30.0) != CUPS_SC_STATUS_OK || 160 buffer[0] != CUPS_SC_BIDI_SUPPORTED) 161 { 162 fputs("DEBUG: Unable to auto-configure PostScript Printer - no " 163 "bidirectional I/O available!\n", stderr); 164 return (1); 165 } 166 167 /* 168 * Put the printer in PostScript mode... 169 */ 170 171 begin_ps(ppd, user); 172 173 /* 174 * (STR #4028) 175 * 176 * As a lot of PPDs contain bad PostScript query code, we need to prevent one 177 * bad query sequence from affecting all auto-configuration. The following 178 * error handler allows us to log PostScript errors to cupsd. 179 */ 180 181 puts("/cups_handleerror {\n" 182 " $error /newerror false put\n" 183 " (:PostScript error in \") print cups_query_keyword print (\": ) " 184 "print\n" 185 " $error /errorname get 128 string cvs print\n" 186 " (; offending command:) print $error /command get 128 string cvs " 187 "print (\n) print flush\n" 188 "} bind def\n" 189 "errordict /timeout {} put\n" 190 "/cups_query_keyword (?Unknown) def\n"); 191 fflush(stdout); 192 193 /* 194 * Wait for the printer to become connected... 195 */ 196 197 do 198 { 199 sleep(1); 200 datalen = 1; 201 } 202 while (cupsSideChannelDoRequest(CUPS_SC_CMD_GET_CONNECTED, buffer, &datalen, 203 5.0) == CUPS_SC_STATUS_OK && !buffer[0]); 204 205 /* 206 * Then loop through every option in the PPD file and ask for the current 207 * value... 208 */ 209 210 fputs("DEBUG: Auto-configuring PostScript printer...\n", stderr); 211 212 for (option = ppdFirstOption(ppd); option; option = ppdNextOption(ppd)) 213 { 214 /* 215 * See if we have a query command for this option... 216 */ 217 218 snprintf(buffer, sizeof(buffer), "?%s", option->keyword); 219 220 if ((attr = ppdFindAttr(ppd, buffer, NULL)) == NULL || !attr->value) 221 { 222 fprintf(stderr, "DEBUG: Skipping %s option...\n", option->keyword); 223 continue; 224 } 225 226 /* 227 * Send the query code to the printer... 228 */ 229 230 fprintf(stderr, "DEBUG: Querying %s...\n", option->keyword); 231 232 for (bufptr = buffer, valptr = attr->value; *valptr; valptr ++) 233 { 234 /* 235 * Log the query code, breaking at newlines... 236 */ 237 238 if (*valptr == '\n') 239 { 240 *bufptr = '\0'; 241 fprintf(stderr, "DEBUG: %s\\n\n", buffer); 242 bufptr = buffer; 243 } 244 else if (*valptr < ' ') 245 { 246 if (bufptr >= (buffer + sizeof(buffer) - 4)) 247 { 248 *bufptr = '\0'; 249 fprintf(stderr, "DEBUG: %s\n", buffer); 250 bufptr = buffer; 251 } 252 253 if (*valptr == '\r') 254 { 255 *bufptr++ = '\\'; 256 *bufptr++ = 'r'; 257 } 258 else if (*valptr == '\t') 259 { 260 *bufptr++ = '\\'; 261 *bufptr++ = 't'; 262 } 263 else 264 { 265 *bufptr++ = '\\'; 266 *bufptr++ = '0' + ((*valptr / 64) & 7); 267 *bufptr++ = '0' + ((*valptr / 8) & 7); 268 *bufptr++ = '0' + (*valptr & 7); 269 } 270 } 271 else 272 { 273 if (bufptr >= (buffer + sizeof(buffer) - 1)) 274 { 275 *bufptr = '\0'; 276 fprintf(stderr, "DEBUG: %s\n", buffer); 277 bufptr = buffer; 278 } 279 280 *bufptr++ = *valptr; 281 } 282 } 283 284 if (bufptr > buffer) 285 { 286 *bufptr = '\0'; 287 fprintf(stderr, "DEBUG: %s\n", buffer); 288 } 289 290 printf("/cups_query_keyword (?%s) def\n", option->keyword); 291 /* Set keyword for error reporting */ 292 fputs("{ (", stdout); 293 for (valptr = attr->value; *valptr; valptr ++) 294 { 295 if (*valptr == '(' || *valptr == ')' || *valptr == '\\') 296 putchar('\\'); 297 putchar(*valptr); 298 } 299 fputs(") cvx exec } stopped { cups_handleerror } if clear\n", stdout); 300 /* Send query code */ 301 fflush(stdout); 302 303 datalen = 0; 304 cupsSideChannelDoRequest(CUPS_SC_CMD_DRAIN_OUTPUT, buffer, &datalen, 5.0); 305 306 /* 307 * Read the response data... 308 */ 309 310 bufptr = buffer; 311 buffer[0] = '\0'; 312 while ((bytes = cupsBackChannelRead(bufptr, 313 sizeof(buffer) - (bufptr - buffer) - 1, 314 10.0)) > 0) 315 { 316 /* 317 * No newline at the end? Go on reading ... 318 */ 319 320 bufptr += bytes; 321 *bufptr = '\0'; 322 323 if (bytes == 0 || 324 (bufptr > buffer && bufptr[-1] != '\r' && bufptr[-1] != '\n')) 325 continue; 326 327 /* 328 * Trim whitespace and control characters from both ends... 329 */ 330 331 bytes = bufptr - buffer; 332 333 for (bufptr --; bufptr >= buffer; bufptr --) 334 if (isspace(*bufptr & 255) || iscntrl(*bufptr & 255)) 335 *bufptr = '\0'; 336 else 337 break; 338 339 for (bufptr = buffer; isspace(*bufptr & 255) || iscntrl(*bufptr & 255); 340 bufptr ++); 341 342 if (bufptr > buffer) 343 { 344 _cups_strcpy(buffer, bufptr); 345 bufptr = buffer; 346 } 347 348 fprintf(stderr, "DEBUG: Got %d bytes.\n", (int)bytes); 349 350 /* 351 * Skip blank lines... 352 */ 353 354 if (!buffer[0]) 355 continue; 356 357 /* 358 * Check the response... 359 */ 360 361 if ((bufptr = strchr(buffer, ':')) != NULL) 362 { 363 /* 364 * PostScript code for this option in the PPD is broken; show the 365 * interpreter's error message that came back... 366 */ 367 368 fprintf(stderr, "DEBUG%s\n", bufptr); 369 break; 370 } 371 372 /* 373 * Verify the result is a valid option choice... 374 */ 375 376 if (!ppdFindChoice(option, buffer)) 377 { 378 if (!strcasecmp(buffer, "Unknown")) 379 break; 380 381 bufptr = buffer; 382 buffer[0] = '\0'; 383 continue; 384 } 385 386 /* 387 * Write out the result and move on to the next option... 388 */ 389 390 fprintf(stderr, "PPD: Default%s=%s\n", option->keyword, buffer); 391 break; 392 } 393 394 /* 395 * Printer did not answer this option's query 396 */ 397 398 if (bytes <= 0) 399 { 400 fprintf(stderr, 401 "DEBUG: No answer to query for option %s within 10 seconds.\n", 402 option->keyword); 403 status = 1; 404 } 405 } 406 407 /* 408 * Finish the job... 409 */ 410 411 fflush(stdout); 412 end_ps(ppd); 413 414 /* 415 * Return... 416 */ 417 418 if (status) 419 _cupsLangPrintFilter(stderr, "WARNING", 420 _("Unable to configure printer options.")); 421 422 return (0); 423} 424 425 426/* 427 * 'begin_ps()' - Send the standard PostScript prolog. 428 */ 429 430static void 431begin_ps(ppd_file_t *ppd, /* I - PPD file */ 432 const char *user) /* I - Username */ 433{ 434 (void)user; 435 436 if (ppd->jcl_begin) 437 { 438 fputs(ppd->jcl_begin, stdout); 439 fputs(ppd->jcl_ps, stdout); 440 } 441 442 puts("%!"); 443 puts("userdict dup(\\004)cvn{}put (\\004\\004)cvn{}put\n"); 444 445 fflush(stdout); 446} 447 448 449/* 450 * 'end_ps()' - Send the standard PostScript trailer. 451 */ 452 453static void 454end_ps(ppd_file_t *ppd) /* I - PPD file */ 455{ 456 if (ppd->jcl_end) 457 fputs(ppd->jcl_end, stdout); 458 else 459 putchar(0x04); 460 461 fflush(stdout); 462} 463 464 465/* 466 * 'print_self_test_page()' - Print a self-test page. 467 */ 468 469static void 470print_self_test_page(ppd_file_t *ppd, /* I - PPD file */ 471 const char *user) /* I - Printing user */ 472{ 473 /* 474 * Put the printer in PostScript mode... 475 */ 476 477 begin_ps(ppd, user); 478 479 /* 480 * Send a simple file the draws a box around the imageable area and shows 481 * the product/interpreter information... 482 */ 483 484 puts("\r%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%" 485 "%%%%%%%%%%%%%\n" 486 "\r%%%% If you can read this, you are using the wrong driver for your " 487 "printer. %%%%\n" 488 "\r%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%" 489 "%%%%%%%%%%%%%\n" 490 "0 setgray\n" 491 "2 setlinewidth\n" 492 "initclip newpath clippath gsave stroke grestore pathbbox\n" 493 "exch pop exch pop exch 9 add exch 9 sub moveto\n" 494 "/Courier findfont 12 scalefont setfont\n" 495 "0 -12 rmoveto gsave product show grestore\n" 496 "0 -12 rmoveto gsave version show ( ) show revision 20 string cvs show " 497 "grestore\n" 498 "0 -12 rmoveto gsave serialnumber 20 string cvs show grestore\n" 499 "showpage"); 500 501 /* 502 * Finish the job... 503 */ 504 505 end_ps(ppd); 506} 507 508 509/* 510 * 'report_levels()' - Report supply levels. 511 */ 512 513static void 514report_levels(ppd_file_t *ppd, /* I - PPD file */ 515 const char *user) /* I - Printing user */ 516{ 517 /* 518 * Put the printer in PostScript mode... 519 */ 520 521 begin_ps(ppd, user); 522 523 /* 524 * Don't bother sending any additional PostScript commands, since we just 525 * want the backend to have enough time to collect the supply info. 526 */ 527 528 /* 529 * Finish the job... 530 */ 531 532 end_ps(ppd); 533} 534 535 536/* 537 * End of "$Id: commandtops.c 3796 2012-04-23 22:54:48Z msweet $". 538 */ 539