mirror of https://gitee.com/openkylin/cups.git
515 lines
11 KiB
C
515 lines
11 KiB
C
|
/*
|
||
|
* PostScript command filter for CUPS.
|
||
|
*
|
||
|
* Copyright 2008-2014 by Apple Inc.
|
||
|
*
|
||
|
* Licensed under Apache License v2.0. See the file "LICENSE" for more information.
|
||
|
*/
|
||
|
|
||
|
/*
|
||
|
* Include necessary headers...
|
||
|
*/
|
||
|
|
||
|
#include <cups/cups-private.h>
|
||
|
#include <cups/ppd.h>
|
||
|
#include <cups/sidechannel.h>
|
||
|
|
||
|
|
||
|
/*
|
||
|
* Local functions...
|
||
|
*/
|
||
|
|
||
|
static int auto_configure(ppd_file_t *ppd, const char *user);
|
||
|
static void begin_ps(ppd_file_t *ppd, const char *user);
|
||
|
static void end_ps(ppd_file_t *ppd);
|
||
|
static void print_self_test_page(ppd_file_t *ppd, const char *user);
|
||
|
static void report_levels(ppd_file_t *ppd, const char *user);
|
||
|
|
||
|
|
||
|
/*
|
||
|
* 'main()' - Process a CUPS command file.
|
||
|
*/
|
||
|
|
||
|
int /* O - Exit status */
|
||
|
main(int argc, /* I - Number of command-line arguments */
|
||
|
char *argv[]) /* I - Command-line arguments */
|
||
|
{
|
||
|
int status = 0; /* Exit status */
|
||
|
cups_file_t *fp; /* Command file */
|
||
|
char line[1024], /* Line from file */
|
||
|
*value; /* Value on line */
|
||
|
int linenum; /* Line number in file */
|
||
|
ppd_file_t *ppd; /* PPD file */
|
||
|
|
||
|
|
||
|
/*
|
||
|
* Check for valid arguments...
|
||
|
*/
|
||
|
|
||
|
if (argc < 6 || argc > 7)
|
||
|
{
|
||
|
/*
|
||
|
* We don't have the correct number of arguments; write an error message
|
||
|
* and return.
|
||
|
*/
|
||
|
|
||
|
_cupsLangPrintf(stderr,
|
||
|
_("Usage: %s job-id user title copies options [file]"),
|
||
|
argv[0]);
|
||
|
return (1);
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Open the PPD file...
|
||
|
*/
|
||
|
|
||
|
if ((ppd = ppdOpenFile(getenv("PPD"))) == NULL)
|
||
|
{
|
||
|
fputs("ERROR: Unable to open PPD file!\n", stderr);
|
||
|
return (1);
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Open the command file as needed...
|
||
|
*/
|
||
|
|
||
|
if (argc == 7)
|
||
|
{
|
||
|
if ((fp = cupsFileOpen(argv[6], "r")) == NULL)
|
||
|
{
|
||
|
perror("ERROR: Unable to open command file - ");
|
||
|
return (1);
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
fp = cupsFileStdin();
|
||
|
|
||
|
/*
|
||
|
* Read the commands from the file and send the appropriate commands...
|
||
|
*/
|
||
|
|
||
|
linenum = 0;
|
||
|
|
||
|
while (cupsFileGetConf(fp, line, sizeof(line), &value, &linenum))
|
||
|
{
|
||
|
/*
|
||
|
* Parse the command...
|
||
|
*/
|
||
|
|
||
|
if (!_cups_strcasecmp(line, "AutoConfigure"))
|
||
|
status |= auto_configure(ppd, argv[2]);
|
||
|
else if (!_cups_strcasecmp(line, "PrintSelfTestPage"))
|
||
|
print_self_test_page(ppd, argv[2]);
|
||
|
else if (!_cups_strcasecmp(line, "ReportLevels"))
|
||
|
report_levels(ppd, argv[2]);
|
||
|
else
|
||
|
{
|
||
|
_cupsLangPrintFilter(stderr, "ERROR",
|
||
|
_("Invalid printer command \"%s\"."), line);
|
||
|
status = 1;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return (status);
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
* 'auto_configure()' - Automatically configure the printer using PostScript
|
||
|
* query commands and/or SNMP lookups.
|
||
|
*/
|
||
|
|
||
|
static int /* O - Exit status */
|
||
|
auto_configure(ppd_file_t *ppd, /* I - PPD file */
|
||
|
const char *user) /* I - Printing user */
|
||
|
{
|
||
|
int status = 0; /* Exit status */
|
||
|
ppd_option_t *option; /* Current option in PPD */
|
||
|
ppd_attr_t *attr; /* Query command attribute */
|
||
|
const char *valptr; /* Pointer into attribute value */
|
||
|
char buffer[1024], /* String buffer */
|
||
|
*bufptr; /* Pointer into buffer */
|
||
|
ssize_t bytes; /* Number of bytes read */
|
||
|
int datalen; /* Side-channel data length */
|
||
|
|
||
|
|
||
|
/*
|
||
|
* See if the backend supports bidirectional I/O...
|
||
|
*/
|
||
|
|
||
|
datalen = 1;
|
||
|
if (cupsSideChannelDoRequest(CUPS_SC_CMD_GET_BIDI, buffer, &datalen,
|
||
|
30.0) != CUPS_SC_STATUS_OK ||
|
||
|
buffer[0] != CUPS_SC_BIDI_SUPPORTED)
|
||
|
{
|
||
|
fputs("DEBUG: Unable to auto-configure PostScript Printer - no "
|
||
|
"bidirectional I/O available!\n", stderr);
|
||
|
return (1);
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Put the printer in PostScript mode...
|
||
|
*/
|
||
|
|
||
|
begin_ps(ppd, user);
|
||
|
|
||
|
/*
|
||
|
* (STR #4028)
|
||
|
*
|
||
|
* As a lot of PPDs contain bad PostScript query code, we need to prevent one
|
||
|
* bad query sequence from affecting all auto-configuration. The following
|
||
|
* error handler allows us to log PostScript errors to cupsd.
|
||
|
*/
|
||
|
|
||
|
puts("/cups_handleerror {\n"
|
||
|
" $error /newerror false put\n"
|
||
|
" (:PostScript error in \") print cups_query_keyword print (\": ) "
|
||
|
"print\n"
|
||
|
" $error /errorname get 128 string cvs print\n"
|
||
|
" (; offending command:) print $error /command get 128 string cvs "
|
||
|
"print (\n) print flush\n"
|
||
|
"} bind def\n"
|
||
|
"errordict /timeout {} put\n"
|
||
|
"/cups_query_keyword (?Unknown) def\n");
|
||
|
fflush(stdout);
|
||
|
|
||
|
/*
|
||
|
* Wait for the printer to become connected...
|
||
|
*/
|
||
|
|
||
|
do
|
||
|
{
|
||
|
sleep(1);
|
||
|
datalen = 1;
|
||
|
}
|
||
|
while (cupsSideChannelDoRequest(CUPS_SC_CMD_GET_CONNECTED, buffer, &datalen,
|
||
|
5.0) == CUPS_SC_STATUS_OK && !buffer[0]);
|
||
|
|
||
|
/*
|
||
|
* Then loop through every option in the PPD file and ask for the current
|
||
|
* value...
|
||
|
*/
|
||
|
|
||
|
fputs("DEBUG: Auto-configuring PostScript printer...\n", stderr);
|
||
|
|
||
|
for (option = ppdFirstOption(ppd); option; option = ppdNextOption(ppd))
|
||
|
{
|
||
|
/*
|
||
|
* See if we have a query command for this option...
|
||
|
*/
|
||
|
|
||
|
snprintf(buffer, sizeof(buffer), "?%s", option->keyword);
|
||
|
|
||
|
if ((attr = ppdFindAttr(ppd, buffer, NULL)) == NULL || !attr->value)
|
||
|
{
|
||
|
fprintf(stderr, "DEBUG: Skipping %s option...\n", option->keyword);
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Send the query code to the printer...
|
||
|
*/
|
||
|
|
||
|
fprintf(stderr, "DEBUG: Querying %s...\n", option->keyword);
|
||
|
|
||
|
for (bufptr = buffer, valptr = attr->value; *valptr; valptr ++)
|
||
|
{
|
||
|
/*
|
||
|
* Log the query code, breaking at newlines...
|
||
|
*/
|
||
|
|
||
|
if (*valptr == '\n')
|
||
|
{
|
||
|
*bufptr = '\0';
|
||
|
fprintf(stderr, "DEBUG: %s\\n\n", buffer);
|
||
|
bufptr = buffer;
|
||
|
}
|
||
|
else if (*valptr < ' ')
|
||
|
{
|
||
|
if (bufptr >= (buffer + sizeof(buffer) - 4))
|
||
|
{
|
||
|
*bufptr = '\0';
|
||
|
fprintf(stderr, "DEBUG: %s\n", buffer);
|
||
|
bufptr = buffer;
|
||
|
}
|
||
|
|
||
|
if (*valptr == '\r')
|
||
|
{
|
||
|
*bufptr++ = '\\';
|
||
|
*bufptr++ = 'r';
|
||
|
}
|
||
|
else if (*valptr == '\t')
|
||
|
{
|
||
|
*bufptr++ = '\\';
|
||
|
*bufptr++ = 't';
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
*bufptr++ = '\\';
|
||
|
*bufptr++ = '0' + ((*valptr / 64) & 7);
|
||
|
*bufptr++ = '0' + ((*valptr / 8) & 7);
|
||
|
*bufptr++ = '0' + (*valptr & 7);
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
if (bufptr >= (buffer + sizeof(buffer) - 1))
|
||
|
{
|
||
|
*bufptr = '\0';
|
||
|
fprintf(stderr, "DEBUG: %s\n", buffer);
|
||
|
bufptr = buffer;
|
||
|
}
|
||
|
|
||
|
*bufptr++ = *valptr;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (bufptr > buffer)
|
||
|
{
|
||
|
*bufptr = '\0';
|
||
|
fprintf(stderr, "DEBUG: %s\n", buffer);
|
||
|
}
|
||
|
|
||
|
printf("/cups_query_keyword (?%s) def\n", option->keyword);
|
||
|
/* Set keyword for error reporting */
|
||
|
fputs("{ (", stdout);
|
||
|
for (valptr = attr->value; *valptr; valptr ++)
|
||
|
{
|
||
|
if (*valptr == '(' || *valptr == ')' || *valptr == '\\')
|
||
|
putchar('\\');
|
||
|
putchar(*valptr);
|
||
|
}
|
||
|
fputs(") cvx exec } stopped { cups_handleerror } if clear\n", stdout);
|
||
|
/* Send query code */
|
||
|
fflush(stdout);
|
||
|
|
||
|
datalen = 0;
|
||
|
cupsSideChannelDoRequest(CUPS_SC_CMD_DRAIN_OUTPUT, buffer, &datalen, 5.0);
|
||
|
|
||
|
/*
|
||
|
* Read the response data...
|
||
|
*/
|
||
|
|
||
|
bufptr = buffer;
|
||
|
buffer[0] = '\0';
|
||
|
while ((bytes = cupsBackChannelRead(bufptr, sizeof(buffer) - (size_t)(bufptr - buffer) - 1, 10.0)) > 0)
|
||
|
{
|
||
|
/*
|
||
|
* No newline at the end? Go on reading ...
|
||
|
*/
|
||
|
|
||
|
bufptr += bytes;
|
||
|
*bufptr = '\0';
|
||
|
|
||
|
if (bytes == 0 ||
|
||
|
(bufptr > buffer && bufptr[-1] != '\r' && bufptr[-1] != '\n'))
|
||
|
continue;
|
||
|
|
||
|
/*
|
||
|
* Trim whitespace and control characters from both ends...
|
||
|
*/
|
||
|
|
||
|
bytes = bufptr - buffer;
|
||
|
|
||
|
for (bufptr --; bufptr >= buffer; bufptr --)
|
||
|
if (isspace(*bufptr & 255) || iscntrl(*bufptr & 255))
|
||
|
*bufptr = '\0';
|
||
|
else
|
||
|
break;
|
||
|
|
||
|
for (bufptr = buffer; isspace(*bufptr & 255) || iscntrl(*bufptr & 255);
|
||
|
bufptr ++);
|
||
|
|
||
|
if (bufptr > buffer)
|
||
|
{
|
||
|
_cups_strcpy(buffer, bufptr);
|
||
|
bufptr = buffer;
|
||
|
}
|
||
|
|
||
|
fprintf(stderr, "DEBUG: Got %d bytes.\n", (int)bytes);
|
||
|
|
||
|
/*
|
||
|
* Skip blank lines...
|
||
|
*/
|
||
|
|
||
|
if (!buffer[0])
|
||
|
continue;
|
||
|
|
||
|
/*
|
||
|
* Check the response...
|
||
|
*/
|
||
|
|
||
|
if ((bufptr = strchr(buffer, ':')) != NULL)
|
||
|
{
|
||
|
/*
|
||
|
* PostScript code for this option in the PPD is broken; show the
|
||
|
* interpreter's error message that came back...
|
||
|
*/
|
||
|
|
||
|
fprintf(stderr, "DEBUG%s\n", bufptr);
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Verify the result is a valid option choice...
|
||
|
*/
|
||
|
|
||
|
if (!ppdFindChoice(option, buffer))
|
||
|
{
|
||
|
if (!strcasecmp(buffer, "Unknown"))
|
||
|
break;
|
||
|
|
||
|
bufptr = buffer;
|
||
|
buffer[0] = '\0';
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Write out the result and move on to the next option...
|
||
|
*/
|
||
|
|
||
|
fprintf(stderr, "PPD: Default%s=%s\n", option->keyword, buffer);
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Printer did not answer this option's query
|
||
|
*/
|
||
|
|
||
|
if (bytes <= 0)
|
||
|
{
|
||
|
fprintf(stderr,
|
||
|
"DEBUG: No answer to query for option %s within 10 seconds.\n",
|
||
|
option->keyword);
|
||
|
status = 1;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Finish the job...
|
||
|
*/
|
||
|
|
||
|
fflush(stdout);
|
||
|
end_ps(ppd);
|
||
|
|
||
|
/*
|
||
|
* Return...
|
||
|
*/
|
||
|
|
||
|
if (status)
|
||
|
_cupsLangPrintFilter(stderr, "WARNING",
|
||
|
_("Unable to configure printer options."));
|
||
|
|
||
|
return (0);
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
* 'begin_ps()' - Send the standard PostScript prolog.
|
||
|
*/
|
||
|
|
||
|
static void
|
||
|
begin_ps(ppd_file_t *ppd, /* I - PPD file */
|
||
|
const char *user) /* I - Username */
|
||
|
{
|
||
|
(void)user;
|
||
|
|
||
|
if (ppd->jcl_begin)
|
||
|
{
|
||
|
fputs(ppd->jcl_begin, stdout);
|
||
|
fputs(ppd->jcl_ps, stdout);
|
||
|
}
|
||
|
|
||
|
puts("%!");
|
||
|
puts("userdict dup(\\004)cvn{}put (\\004\\004)cvn{}put\n");
|
||
|
|
||
|
fflush(stdout);
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
* 'end_ps()' - Send the standard PostScript trailer.
|
||
|
*/
|
||
|
|
||
|
static void
|
||
|
end_ps(ppd_file_t *ppd) /* I - PPD file */
|
||
|
{
|
||
|
if (ppd->jcl_end)
|
||
|
fputs(ppd->jcl_end, stdout);
|
||
|
else
|
||
|
putchar(0x04);
|
||
|
|
||
|
fflush(stdout);
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
* 'print_self_test_page()' - Print a self-test page.
|
||
|
*/
|
||
|
|
||
|
static void
|
||
|
print_self_test_page(ppd_file_t *ppd, /* I - PPD file */
|
||
|
const char *user) /* I - Printing user */
|
||
|
{
|
||
|
/*
|
||
|
* Put the printer in PostScript mode...
|
||
|
*/
|
||
|
|
||
|
begin_ps(ppd, user);
|
||
|
|
||
|
/*
|
||
|
* Send a simple file the draws a box around the imageable area and shows
|
||
|
* the product/interpreter information...
|
||
|
*/
|
||
|
|
||
|
puts("\r%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%"
|
||
|
"%%%%%%%%%%%%%\n"
|
||
|
"\r%%%% If you can read this, you are using the wrong driver for your "
|
||
|
"printer. %%%%\n"
|
||
|
"\r%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%"
|
||
|
"%%%%%%%%%%%%%\n"
|
||
|
"0 setgray\n"
|
||
|
"2 setlinewidth\n"
|
||
|
"initclip newpath clippath gsave stroke grestore pathbbox\n"
|
||
|
"exch pop exch pop exch 9 add exch 9 sub moveto\n"
|
||
|
"/Courier findfont 12 scalefont setfont\n"
|
||
|
"0 -12 rmoveto gsave product show grestore\n"
|
||
|
"0 -12 rmoveto gsave version show ( ) show revision 20 string cvs show "
|
||
|
"grestore\n"
|
||
|
"0 -12 rmoveto gsave serialnumber 20 string cvs show grestore\n"
|
||
|
"showpage");
|
||
|
|
||
|
/*
|
||
|
* Finish the job...
|
||
|
*/
|
||
|
|
||
|
end_ps(ppd);
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
* 'report_levels()' - Report supply levels.
|
||
|
*/
|
||
|
|
||
|
static void
|
||
|
report_levels(ppd_file_t *ppd, /* I - PPD file */
|
||
|
const char *user) /* I - Printing user */
|
||
|
{
|
||
|
/*
|
||
|
* Put the printer in PostScript mode...
|
||
|
*/
|
||
|
|
||
|
begin_ps(ppd, user);
|
||
|
|
||
|
/*
|
||
|
* Don't bother sending any additional PostScript commands, since we just
|
||
|
* want the backend to have enough time to collect the supply info.
|
||
|
*/
|
||
|
|
||
|
/*
|
||
|
* Finish the job...
|
||
|
*/
|
||
|
|
||
|
end_ps(ppd);
|
||
|
}
|