]> git.ipfire.org Git - thirdparty/cups.git/blob - filter/commandtops.c
Merge changes from CUPS 1.6svn-r10390.
[thirdparty/cups.git] / filter / commandtops.c
1 /*
2 * "$Id$"
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
39 static int auto_configure(ppd_file_t *ppd, const char *user);
40 static void begin_ps(ppd_file_t *ppd, const char *user);
41 static void end_ps(ppd_file_t *ppd);
42 static void print_self_test_page(ppd_file_t *ppd, const char *user);
43 static void report_levels(ppd_file_t *ppd, const char *user);
44
45
46 /*
47 * 'main()' - Process a CUPS command file.
48 */
49
50 int /* O - Exit status */
51 main(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
139 static int /* O - Exit status */
140 auto_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
430 static void
431 begin_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
453 static void
454 end_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
469 static void
470 print_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
513 static void
514 report_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$".
538 */