Revert manifest to default one
[external/cups.git] / filter / commandtops.c
1 /*
2  * "$Id: commandtops.c 10373 2012-03-21 23:00:05Z mike $"
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: commandtops.c 10373 2012-03-21 23:00:05Z mike $".
538  */