testpdf1_SOURCES = \
cupsfilters/testpdf1.c \
- cupsfilters/fontembed.h
+ cupsfilters/fontembed-private.h
testpdf1_CFLAGS = \
-I$(srcdir)/cupsfilters/fontembed/ \
-I$(srcdir)/cupsfilters/
testpdf2_SOURCES = \
cupsfilters/testpdf2.c \
- cupsfilters/fontembed.h
+ cupsfilters/fontembed-private.h
testpdf2_CFLAGS = \
-I$(srcdir)/cupsfilters/fontembed/ \
-I$(srcdir)/cupsfilters/
imagetoraster
endif
+check_PROGRAMS += \
+ test-external
+
# Not reliable bash script
#TESTS += filter/test.sh
libppd.la \
$(CUPS_LIBS)
+test_external_SOURCES = \
+ filter/test-external.c
+test_external_CFLAGS = \
+ -I$(srcdir)/cupsfilters/ \
+ -I$(srcdir)/ppd/ \
+ $(CUPS_CFLAGS)
+test_external_LDADD = \
+ libcupsfilters.la \
+ libppd.la \
+ $(CUPS_LIBS)
+
# =====
# UTILS
# =====
int fd, nullfd;
cf_filter_data_t filter_data;
cf_filter_universal_parameter_t universal_parameters;
- ppd_filter_external_cups_t ipp_backend_params;
+ cf_filter_external_t ipp_backend_params;
cf_filter_filter_in_chain_t universal_in_chain,
ipp_in_chain;
cups_array_t *filter_chain;
/* Parameters for cfFilterExternalCUPS() call for IPP backend */
ipp_backend_params.filter = "ipp";
- ipp_backend_params.is_backend = 1;
- ipp_backend_params.device_uri = printer_uri;
+ ipp_backend_params.exec_mode = 1;
ipp_backend_params.num_options = 0;
ipp_backend_params.options = NULL;
ipp_backend_params.envp = NULL;
+ cfFilterAddEnvVar("DEVICE_URI", printer_uri, &ipp_backend_params.envp);
/* Filter chain entry for the ppdFilterUniversal() filter function call */
universal_in_chain.function = ppdFilterUniversal;
cfFilterCloseBackAndSidePipes(&filter_data);
/* Clean up */
+ if (ipp_backend_params.envp)
+ {
+ for (i = 0; ipp_backend_params.envp[i]; i ++)
+ free(ipp_backend_params.envp[i]);
+ free(ipp_backend_params.envp);
+ }
cupsArrayDelete(filter_chain);
ippDelete(response);
//
-// 'fcntl_add_nodelay()' - Add O_NODELAY flag to the flags
-// of a given file descriptor.
+// 'fcntl_add_nonblock()' - Add O_NONBLOCK flag to the flags
+// of a given file descriptor.
//
static int // Return value of fcntl()
}
+//
+// 'cfFilterGetEnvVar()' - Auxiliary function for cfFilterExternal(),
+// gets value of an environment variable in a
+// list of environment variables as used by
+// the execve() function
+//
+
+char * // O - The value, NULL if variable is not in
+ // list
+cfFilterGetEnvVar(char *name, // I - Name of environment variable to read
+ char **env) // I - List of environment variable serttings
+{
+ int i = 0;
+
+
+ if (env)
+ for (i = 0; env[i]; i ++)
+ if (strncmp(env[i], name, strlen(name)) == 0 &&
+ strlen(env[i]) > strlen(name) &&
+ env[i][strlen(name)] == '=')
+ return (env[i] + strlen(name) + 1);
+
+ return (NULL);
+}
+
+
+//
+// 'cfFilterAddEnvVar()' - Auxiliary function for cfFilterExternal(),
+// adds/sets an environment variable in a list of
+// environment variables as used by the execve()
+// function
+//
+
+int // O - Index of where the new value got
+ // inserted in the list
+cfFilterAddEnvVar(char *name, // I - Name of environment variable to set
+ char *value, // I - Value of environment variable to set
+ char ***env) // I - List of environment variable serttings
+{
+ char *p;
+ int i = 0,
+ name_len;
+
+
+ if (!name || !env || !name[0])
+ return (-1);
+
+ // Assemble a "VAR=VALUE" string and the string length of "VAR"
+ if ((p = strchr(name, '=')) != NULL)
+ {
+ // User supplied "VAR=VALUE" as name and NULL as value
+ if (value)
+ return (-1);
+ name_len = p - name;
+ p = strdup(name);
+ }
+ else
+ {
+ // User supplied variable name and value as the name and as the value
+ name_len = strlen(name);
+ p = (char *)calloc(strlen(name) + (value ? strlen(value) : 0) + 2,
+ sizeof(char));
+ sprintf(p, "%s=%s", name, (value ? value : ""));
+ }
+
+ // Check whether we already have this variable in the list and update its
+ // value if it is there
+ if (*env)
+ for (i = 0; (*env)[i]; i ++)
+ if (strncmp((*env)[i], p, name_len) == 0 && (*env)[i][name_len] == '=')
+ {
+ free((*env)[i]);
+ (*env)[i] = p;
+ return (i);
+ }
+
+ // Add the variable as new item to the list
+ *env = (char **)realloc(*env, (i + 2) * sizeof(char *));
+ (*env)[i] = p;
+ (*env)[i + 1] = NULL;
+ return (i);
+}
+
+
//
// 'cfFilterTee()' - This filter function is mainly for debugging. it
// resembles the "tee" utility, passing through the
}
+//
+// 'sanitize_device_uri()' - Remove authentication info from a device URI
+//
+
+static char * // O - Sanitized URI
+sanitize_device_uri(const char *uri, // I - Device URI
+ char *buf, // I - Buffer for output
+ size_t bufsize) // I - Size of buffer
+{
+ char *start, // Start of data after scheme
+ *slash, // First slash after scheme://
+ *ptr; // Pointer into user@host:port part
+
+
+ // URI not supplied
+ if (!uri)
+ return (NULL);
+
+ // Copy the device URI to a temporary buffer so we can sanitize any auth
+ // info in it...
+ strncpy(buf, uri, bufsize);
+
+ // Find the end of the scheme:// part...
+ if ((ptr = strchr(buf, ':')) != NULL)
+ {
+ for (start = ptr + 1; *start; start ++)
+ if (*start != '/')
+ break;
+
+ // Find the next slash (/) in the URI...
+ if ((slash = strchr(start, '/')) == NULL)
+ slash = start + strlen(start); // No slash, point to the end
+
+ // Check for an @ sign before the slash...
+ if ((ptr = strchr(start, '@')) != NULL && ptr < slash)
+ {
+ // Found an @ sign and it is before the resource part, so we have
+ // an authentication string. Copy the remaining URI over the
+ // authentication string...
+ memmove(start, ptr + 1, strlen(ptr + 1) + 1);
+ }
+ }
+
+ // Return the sanitized URI...
+ return (buf);
+}
+
+
+//
+// 'cfFilterExternal()' - Filter function which calls an external
+// classic CUPS filter or System V interface
+// script, for example a (proprietary) printer
+// driver which cannot be converted to a filter
+// function or if it is too awkward or risky to
+// convert for example when the printer
+// hardware is not available for testing
+//
+
+int // O - Error status
+cfFilterExternal(int inputfd, // I - File descriptor input stream
+ int outputfd, // I - File descriptor output stream
+ int inputseekable, // I - Is input stream seekable?
+ cf_filter_data_t *data, // I - Job and printer data
+ void *parameters) // I - Filter-specific parameters
+{
+ cf_filter_external_t *params = (cf_filter_external_t *)parameters;
+ int i;
+ int is_backend = 0; // Do we call a CUPS backend?
+ int pid, // Process ID of filter
+ stderrpid, // Process ID for stderr logging process
+ wpid; // PID reported as terminated
+ int fd; // Temporary file descriptor
+ int backfd, sidefd; // file descriptors for back and side
+ // channels
+ int stderrpipe[2]; // Pipe to log stderr
+ cups_file_t *fp; // File pointer to read log lines
+ char tmp_name[BUFSIZ] = "";
+ char buf[2048]; // Log line buffer
+ cf_loglevel_t log_level; // Log level of filter's log message
+ char *ptr1, *ptr2,
+ *msg, // Filter log message
+ *filter_name; // Filter name for logging
+ char filter_path[1024]; // Full path of the filter
+ char **argv, // Command line args for filter
+ **envp = NULL; // Environment variables for filter
+ int num_all_options = 0;
+ cups_option_t *all_options = NULL;
+ char job_id_str[16],
+ copies_str[16],
+ *options_str = NULL;
+ cups_option_t *opt;
+ int status = 65536;
+ int wstatus;
+ cf_logfunc_t log = data->logfunc;
+ void *ld = data->logdata;
+ cf_filter_iscanceledfunc_t iscanceled = data->iscanceledfunc;
+ void *icd = data->iscanceleddata;
+
+
+ if (!params->filter || !params->filter[0])
+ {
+ if (log) log(ld, CF_LOGLEVEL_ERROR,
+ "cfFilterExternal: Filter executable path/command not specified");
+ return (1);
+ }
+
+ // Check whether back/side channel FDs are valid and not all-zero
+ // from calloc'ed filter_data
+ if (data->back_pipe[0] == 0 && data->back_pipe[1] == 0)
+ data->back_pipe[0] = data->back_pipe[1] = -1;
+ if (data->side_pipe[0] == 0 && data->side_pipe[1] == 0)
+ data->side_pipe[0] = data->side_pipe[1] = -1;
+
+ // Select the correct end of the back/side channel pipes:
+ // [0] for filters, [1] for backends
+ is_backend = (params->exec_mode > 0 ? 1 : 0);
+ backfd = data->back_pipe[is_backend];
+ sidefd = data->side_pipe[is_backend];
+
+ // Filter name for logging
+ if ((filter_name = strrchr(params->filter, '/')) != NULL)
+ filter_name ++;
+ else
+ filter_name = (char *)params->filter;
+
+ //
+ // Ignore broken pipe signals...
+ //
+
+ signal(SIGPIPE, SIG_IGN);
+
+ //
+ // Copy the current environment variables and add the ones from the
+ // parameters
+ //
+
+ // Copy the environment in which the caller got started
+ if (environ)
+ for (i = 0; environ[i]; i ++)
+ cfFilterAddEnvVar(environ[i], NULL, &envp);
+
+ // Set the environment variables given by the parameters
+ if (params->envp)
+ for (i = 0; params->envp[i]; i ++)
+ cfFilterAddEnvVar(params->envp[i], NULL, &envp);
+
+ // Add CUPS_SERVERBIN to the beginning of PATH
+ ptr1 = cfFilterGetEnvVar("PATH", envp);
+ ptr2 = cfFilterGetEnvVar("CUPS_SERVERBIN", envp);
+ if (ptr2 && ptr2[0])
+ {
+ if (ptr1 && ptr1[0])
+ {
+ snprintf(buf, sizeof(buf), "%s/%s:%s",
+ ptr2, params->exec_mode > 0 ? "backend" : "filter", ptr1);
+ ptr1 = buf;
+ }
+ else
+ ptr1 = ptr2;
+ cfFilterAddEnvVar("PATH", ptr1, &envp);
+ }
+
+ // Determine full path for the filter
+ if (params->filter[0] == '/' ||
+ (ptr1 = cfFilterGetEnvVar("CUPS_SERVERBIN", envp)) == NULL || !ptr1[0])
+ strncpy(filter_path, params->filter, sizeof(filter_path) - 1);
+ else
+ snprintf(filter_path, sizeof(filter_path), "%s/%s/%s", ptr1,
+ params->exec_mode > 0 ? "backend" : "filter", params->filter);
+
+ // Log the resulting list of environment variable settings
+ // (with any authentication info removed)
+ if (log)
+ {
+ for (i = 0; envp[i]; i ++)
+ if (!strncmp(envp[i], "AUTH_", 5))
+ log(ld, CF_LOGLEVEL_DEBUG,
+ "cfFilterExternal (%s): envp[%d]: AUTH_%c****",
+ filter_name, i, envp[i][5]);
+ else if (!strncmp(envp[i], "DEVICE_URI=", 11))
+ log(ld, CF_LOGLEVEL_DEBUG,
+ "cfFilterExternal (%s): envp[%d]: DEVICE_URI=%s",
+ filter_name, i, sanitize_device_uri(envp[i] + 11,
+ buf, sizeof(buf)));
+ else
+ log(ld, CF_LOGLEVEL_DEBUG, "cfFilterExternal (%s): envp[%d]: %s",
+ filter_name, i, envp[i]);
+ }
+
+ if (params->exec_mode < 2)
+ {
+ //
+ // Filter or backend for job execution
+ //
+
+ //
+ // Join the options from the filter data and from the parameters
+ // If an option is present in both filter data and parameters, the
+ // value in the filter data has priority
+ //
+
+ for (i = 0, opt = params->options; i < params->num_options; i ++, opt ++)
+ num_all_options = cupsAddOption(opt->name, opt->value, num_all_options,
+ &all_options);
+ for (i = 0, opt = data->options; i < data->num_options; i ++, opt ++)
+ num_all_options = cupsAddOption(opt->name, opt->value, num_all_options,
+ &all_options);
+
+ //
+ // Create command line arguments for the CUPS filter
+ //
+
+ if (params->exec_mode >= 0)
+ // CUPS filter or backend
+
+ // CUPS filter allow input via stdin, so no 6th command line
+ // argument needed
+ argv = (char **)calloc(7, sizeof(char *));
+ else
+ {
+ // System V interface script
+
+ // Needs input via file name as 6th command line
+ // argument, not via stdin
+ argv = (char **)calloc(8, sizeof(char *));
+
+ fd = cupsTempFd(tmp_name, sizeof(tmp_name));
+ if (fd < 0)
+ {
+ if (log) log(ld, CF_LOGLEVEL_ERROR,
+ "cfFilterExternal: Can't create temporary file.");
+ goto out;
+ }
+ int bytes;
+ while ((bytes = read(inputfd, buf, sizeof(buf))) > 0)
+ {
+ if (write(fd, buf, bytes) != bytes)
+ {
+ if (log) log(ld, CF_LOGLEVEL_ERROR,
+ "cfFilterExternal: Can't copy input data to temporary file.");
+ close(fd);
+ goto out;
+ }
+ }
+ close(fd);
+ close(inputfd);
+ inputfd = open("/dev/null", O_RDONLY);
+ }
+
+ // Numeric parameters
+ snprintf(job_id_str, sizeof(job_id_str) - 1, "%d",
+ data->job_id > 0 ? data->job_id : 1);
+ snprintf(copies_str, sizeof(copies_str) - 1, "%d",
+ data->copies > 0 ? data->copies : 1);
+
+ // Options, build string of "Name1=Value1 Name2=Value2 ..." but use
+ // "Name" and "noName" instead for boolean options
+ for (i = 0, opt = all_options; i < num_all_options; i ++, opt ++)
+ {
+ if (strcasecmp(opt->value, "true") == 0 ||
+ strcasecmp(opt->value, "false") == 0)
+ {
+ options_str =
+ (char *)realloc(options_str,
+ ((options_str ? strlen(options_str) : 0) +
+ strlen(opt->name) +
+ (strcasecmp(opt->value, "false") == 0 ? 2 : 0) + 2) *
+ sizeof(char));
+ if (i == 0)
+ options_str[0] = '\0';
+ sprintf(options_str + strlen(options_str), " %s%s",
+ (strcasecmp(opt->value, "false") == 0 ? "no" : ""), opt->name);
+ }
+ else
+ {
+ options_str =
+ (char *)realloc(options_str,
+ ((options_str ? strlen(options_str) : 0) +
+ strlen(opt->name) + strlen(opt->value) + 3) *
+ sizeof(char));
+ if (i == 0)
+ options_str[0] = '\0';
+ sprintf(options_str + strlen(options_str), " %s=%s", opt->name,
+ opt->value);
+ }
+ }
+
+ // Find DEVICE_URI environment variable
+ if (params->exec_mode > 0)
+ for (i = 0; envp[i]; i ++)
+ if (strncmp(envp[i], "DEVICE_URI=", 11) == 0)
+ break;
+
+ // Add items to array
+ argv[0] = strdup((params->exec_mode > 0 && envp[i] ?
+ (char *)sanitize_device_uri(envp[i] + 11,
+ buf, sizeof(buf)) :
+ (data->printer ? data->printer :
+ (char *)params->filter)));
+ argv[1] = job_id_str;
+ argv[2] = data->job_user ? data->job_user : "Unknown";
+ argv[3] = data->job_title ? data->job_title : "Untitled";
+ argv[4] = copies_str;
+ argv[5] = options_str ? options_str + 1 : "";
+ if (params->exec_mode >= 0)
+ // CUPS Filter/backend: Input from stdin
+ argv[6] = NULL;
+ else
+ {
+ // System V Interface: Input file from 6th argument
+ argv[6] = tmp_name;
+ argv[7] = NULL;
+ }
+ }
+ else
+ {
+ //
+ // Backend in device discovery mode
+ //
+
+ argv = (char **)calloc(2, sizeof(char *));
+ argv[0] = strdup((char *)params->filter);
+ argv[1] = NULL;
+ }
+
+ // Log the arguments
+ if (log)
+ for (i = 0; argv[i]; i ++)
+ log(ld, CF_LOGLEVEL_DEBUG, "cfFilterExternal (%s): argv[%d]: %s",
+ filter_name, i, argv[i]);
+
+ //
+ // Execute the filter
+ //
+
+ if (pipe(stderrpipe) < 0)
+ {
+ if (log) log(ld, CF_LOGLEVEL_ERROR,
+ "cfFilterExternal (%s): Could not create pipe for stderr: %s",
+ filter_name, strerror(errno));
+ return (1);
+ }
+
+ if ((pid = fork()) == 0)
+ {
+ //
+ // Child process goes here...
+ //
+ // Update stdin/stdout/stderr as needed...
+ //
+
+ if (inputfd != 0)
+ {
+ if (inputfd < 0)
+ {
+ inputfd = open("/dev/null", O_RDONLY);
+ if (log) log(ld, CF_LOGLEVEL_ERROR,
+ "cfFilterExternal (%s): No input file descriptor supplied for CUPS filter - %s",
+ filter_name, strerror(errno));
+ }
+
+ if (inputfd > 0)
+ {
+ fcntl_add_cloexec(inputfd);
+ if (dup2(inputfd, 0) < 0)
+ {
+ if (log) log(ld, CF_LOGLEVEL_ERROR,
+ "cfFilterExternal (%s): Failed to connect input file descriptor with CUPS filter's stdin - %s",
+ filter_name, strerror(errno));
+ goto fd_error;
+ } else
+ if (log) log(ld, CF_LOGLEVEL_DEBUG,
+ "cfFilterExternal (%s): Connected input file descriptor %d to CUPS filter's stdin.",
+ filter_name, inputfd);
+ close(inputfd);
+ }
+ }
+ else
+ if (log) log(ld, CF_LOGLEVEL_DEBUG,
+ "cfFilterExternal (%s): Input file descriptor is stdin, no redirection needed.",
+ filter_name);
+
+ if (tmp_name[0])
+ if (log) log(ld, CF_LOGLEVEL_DEBUG,
+ "cfFilterExternal (%s): Input comes from temporary file %s, supplied as 6th command line argument.",
+ filter_name, tmp_name);
+
+ if (outputfd != 1)
+ {
+ if (outputfd < 0)
+ outputfd = open("/dev/null", O_WRONLY);
+
+ if (outputfd > 1) {
+ fcntl_add_cloexec(outputfd);
+ dup2(outputfd, 1);
+ close(outputfd);
+ }
+ }
+
+ if (strcasestr(params->filter, "gziptoany"))
+ {
+ // Send stderr to the Nirwana if we are running gziptoany, as
+ // gziptoany emits a false "PAGE: 1 1"
+ if ((fd = open("/dev/null", O_RDWR)) > 2)
+ {
+ fcntl_add_cloexec(fd);
+ dup2(fd, 2);
+ close(fd);
+ } else
+ close(fd);
+ }
+ else
+ {
+ // Send stderr into pipe for logging
+ fcntl_add_cloexec(stderrpipe[1]);
+ dup2(stderrpipe[1], 2);
+ fcntl_add_nonblock(2);
+ }
+ close(stderrpipe[0]);
+ close(stderrpipe[1]);
+
+ if (params->exec_mode < 2) // Not needed in discovery mode of backend
+ {
+ // Back channel
+ if (backfd != 3 && backfd >= 0)
+ {
+ dup2(backfd, 3);
+ close(backfd);
+ fcntl_add_nonblock(3);
+ }
+ else if (backfd < 0)
+ {
+ if ((backfd = open("/dev/null", O_RDWR)) > 3)
+ {
+ dup2(backfd, 3);
+ close(backfd);
+ }
+ else
+ close(backfd);
+ fcntl_add_nonblock(3);
+ }
+
+ // Side channel
+ if (sidefd != 4 && sidefd >= 0)
+ {
+ dup2(sidefd, 4);
+ close(sidefd);
+ fcntl_add_nonblock(4);
+ }
+ else if (sidefd < 0)
+ {
+ if ((sidefd = open("/dev/null", O_RDWR)) > 4)
+ {
+ dup2(sidefd, 4);
+ close(sidefd);
+ } else
+ close(sidefd);
+ fcntl_add_nonblock(4);
+ }
+ }
+
+ //
+ // Execute command...
+ //
+
+ execve(filter_path, argv, envp);
+
+ if (log) log(ld, CF_LOGLEVEL_ERROR,
+ "cfFilterExternal (%s): Execution of %s %s failed - %s",
+ filter_name, params->exec_mode > 0 ? "backend" : "filter",
+ filter_path, strerror(errno));
+
+ fd_error:
+ exit(errno);
+ }
+ else if (pid > 0)
+ {
+ if (log) log(ld, CF_LOGLEVEL_INFO,
+ "cfFilterExternal (%s): %s (PID %d) started.",
+ filter_name, filter_path, pid);
+ }
+ else
+ {
+ if (log) log(ld, CF_LOGLEVEL_ERROR,
+ "cfFilterExternal (%s): Unable to fork process for %s %s",
+ filter_name, params->exec_mode > 0 ? "backend" : "filter",
+ filter_path);
+ close(stderrpipe[0]);
+ close(stderrpipe[1]);
+ status = 1;
+ goto out;
+ }
+ if (inputfd >= 0)
+ close(inputfd);
+ if (outputfd >= 0)
+ close(outputfd);
+
+ //
+ // Log the filter's stderr
+ //
+
+ if ((stderrpid = fork()) == 0)
+ {
+ //
+ // Child process goes here...
+ //
+
+ close(stderrpipe[1]);
+ fp = cupsFileOpenFd(stderrpipe[0], "r");
+ while (cupsFileGets(fp, buf, sizeof(buf)))
+ if (log)
+ {
+ if (strncmp(buf, "DEBUG: ", 7) == 0)
+ {
+ log_level = CF_LOGLEVEL_DEBUG;
+ msg = buf + 7;
+ }
+ else if (strncmp(buf, "DEBUG2: ", 8) == 0)
+ {
+ log_level = CF_LOGLEVEL_DEBUG;
+ msg = buf + 8;
+ }
+ else if (strncmp(buf, "INFO: ", 6) == 0)
+ {
+ log_level = CF_LOGLEVEL_INFO;
+ msg = buf + 6;
+ }
+ else if (strncmp(buf, "WARNING: ", 9) == 0)
+ {
+ log_level = CF_LOGLEVEL_WARN;
+ msg = buf + 9;
+ }
+ else if (strncmp(buf, "ERROR: ", 7) == 0)
+ {
+ log_level = CF_LOGLEVEL_ERROR;
+ msg = buf + 7;
+ }
+ else if (strncmp(buf, "PAGE: ", 6) == 0 ||
+ strncmp(buf, "ATTR: ", 6) == 0 ||
+ strncmp(buf, "STATE: ", 7) == 0 ||
+ strncmp(buf, "PPD: ", 5) == 0)
+ {
+ log_level = CF_LOGLEVEL_CONTROL;
+ msg = buf;
+ }
+ else
+ {
+ log_level = CF_LOGLEVEL_DEBUG;
+ msg = buf;
+ }
+ if (log_level == CF_LOGLEVEL_CONTROL)
+ log(ld, log_level, msg);
+ else
+ log(ld, log_level, "cfFilterExternal (%s): %s",
+ filter_name, msg);
+ }
+ cupsFileClose(fp);
+ // No need to close the fd stderrpipe[0], as cupsFileClose(fp) does this
+ // already
+ // Ignore errors of the logging process
+ exit(0);
+ }
+ else if (stderrpid > 0)
+ {
+ if (log) log(ld, CF_LOGLEVEL_INFO,
+ "cfFilterExternal (%s): Logging (PID %d) started.",
+ filter_name, stderrpid);
+ }
+ else
+ {
+ if (log) log(ld, CF_LOGLEVEL_ERROR,
+ "cfFilterExternal (%s): Unable to fork process for logging",
+ filter_name);
+ close(stderrpipe[0]);
+ close(stderrpipe[1]);
+ status = 1;
+ goto out;
+ }
+
+ close(stderrpipe[0]);
+ close(stderrpipe[1]);
+
+ //
+ // Wait for filter and logging processes to finish
+ //
+
+ status = 0;
+
+ while (pid > 0 || stderrpid > 0)
+ {
+ if ((wpid = wait(&wstatus)) < 0)
+ {
+ if (errno == EINTR && iscanceled && iscanceled(icd))
+ {
+ if (log) log(ld, CF_LOGLEVEL_DEBUG,
+ "cfFilterExternal (%s): Job canceled, killing %s ...",
+ filter_name, params->exec_mode > 0 ? "backend" : "filter");
+ kill(pid, SIGTERM);
+ pid = -1;
+ kill(stderrpid, SIGTERM);
+ stderrpid = -1;
+ break;
+ }
+ else
+ continue;
+ }
+
+ // How did the filter terminate
+ if (wstatus)
+ {
+ if (WIFEXITED(wstatus))
+ {
+ // Via exit() anywhere or return() in the main() function
+ if (log) log(ld, CF_LOGLEVEL_ERROR,
+ "cfFilterExternal (%s): %s (PID %d) stopped with status %d",
+ filter_name,
+ (wpid == pid ?
+ (params->exec_mode > 0 ? "Backend" : "Filter") :
+ "Logging"),
+ wpid, WEXITSTATUS(wstatus));
+ }
+ else
+ {
+ // Via signal
+ if (log) log(ld, CF_LOGLEVEL_ERROR,
+ "cfFilterExternal (%s): %s (PID %d) crashed on signal %d",
+ filter_name,
+ (wpid == pid ?
+ (params->exec_mode > 0 ? "Backend" : "Filter") :
+ "Logging"),
+ wpid, WTERMSIG(wstatus));
+ }
+ status = 1;
+ }
+ else
+ {
+ if (log) log(ld, CF_LOGLEVEL_INFO,
+ "cfFilterExternal (%s): %s (PID %d) exited with no errors.",
+ filter_name,
+ (wpid == pid ?
+ (params->exec_mode > 0 ? "Backend" : "Filter") : "Logging"),
+ wpid);
+ }
+ if (wpid == pid)
+ pid = -1;
+ else if (wpid == stderrpid)
+ stderrpid = -1;
+ }
+
+ //
+ // Clean up
+ //
+
+ out:
+ if (params->exec_mode < 0)
+ unlink(tmp_name);
+ cupsFreeOptions(num_all_options, all_options);
+ if (options_str)
+ free(options_str);
+ free(argv[0]);
+ free(argv);
+ for (i = 0; envp[i]; i ++)
+ free(envp[i]);
+ free(envp);
+
+ return (status);
+}
+
+
//
// 'cfFilterOpenBackAndSidePipes()' - Open the pipes for the back
// channel and the side channel, so
// needed if a CUPS backend (either
// implemented as filter function
// or called via
-// cfFilterExternalCUPS()) is
-// called with the same filter_data
-// record as the filters. Usually
-// to be called when populating the
+// cfFilterExternal()) is called
+// with the same filter_data record
+// as the filters. Usually to be
+// called when populating the
// filter_data record.
//
typedef int (*cf_filter_iscanceledfunc_t)(void *data);
-typedef struct cf_filter_data_s {
+typedef struct cf_filter_data_s
+{
char *printer; // Print queue name or NULL
int job_id; // Job ID or 0
char *job_user; // Job user or NULL
// NULL
} cf_filter_data_t;
-typedef struct cf_filter_data_ext_s {
+typedef struct cf_filter_data_ext_s
+{
char *name;
void *ext;
} cf_filter_data_ext_t;
int inputseekable, cf_filter_data_t *data,
void *parameters);
-typedef enum cf_filter_out_format_e { // Possible output formats for filter
+typedef enum cf_filter_out_format_e // Possible output formats for filter
// functions
+{
CF_FILTER_OUT_FORMAT_PDF, // PDF
CF_FILTER_OUT_FORMAT_PDF_IMAGE, // Raster-only PDF
CF_FILTER_OUT_FORMAT_PCLM, // PCLM
CF_FILTER_OUT_FORMAT_PXL // PCL-XL
} cf_filter_out_format_t;
-typedef struct cf_filter_filter_in_chain_s { // filter entry for CUPS array to
- // be supplied to cfFilterChain()
- // filter function
+typedef struct cf_filter_filter_in_chain_s // filter entry for CUPS array to
+ // be supplied to cfFilterChain()
+ // filter function
+{
cf_filter_function_t function; // Filter function to be called
void *parameters; // Parameters for this filter function call
char *name; // Name/comment, only for logging
} cf_filter_filter_in_chain_t;
-typedef struct cf_filter_texttopdf_parameter_s { // parameters container of
- // environemnt variables needed
- // by texttopdf filter
- // function
+typedef struct cf_filter_external_s // Parameters for the
+ // cfFilterExternal() filter
+ // function
+{
+ const char *filter; // Path/Name of the CUPS filter to be called by
+ // this filter function, required
+ int exec_mode; // 0 if we call a CUPS filter, -1 if we call
+ // a System V interface script, 1 if we call a CUPS
+ // backend, 2 if we call a CUPS backend in
+ // device discovery mode
+ int num_options; // Extra options for the 5th command line
+ cups_option_t *options; // argument, options of filter_data have
+ // priority, 0/NULL if none
+ char **envp; // Additional environment variables, the already
+ // defined ones stay valid but can be overwritten
+ // by these ones, NULL if none
+} cf_filter_external_t;
+
+typedef struct cf_filter_texttopdf_parameter_s // parameters container of
+ // environemnt variables needed
+ // by texttopdf filter
+ // function
+{
char *data_dir;
char *char_set;
char *content_type;
char *classification;
} cf_filter_texttopdf_parameter_t;
-typedef struct cf_filter_universal_parameter_s { // Contains input and output
- // type to be supplied to the
- // universal function, and also
- // parameters for
- // cfFilterTextToPDF()
+typedef struct cf_filter_universal_parameter_s // Contains input and output
+ // type to be supplied to the
+ // universal function, and also
+ // parameters for
+ // cfFilterTextToPDF()
+{
char *actual_output_type;
cf_filter_texttopdf_parameter_t texttopdf_params;
const char *bannertopdf_template_dir;
extern void *cfFilterDataRemoveExt(cf_filter_data_t *data, const char *name);
+extern char *cfFilterGetEnvVar(char *name, char **env);
+
+
+extern int cfFilterAddEnvVar(char *name, char *value, char ***env);
+
+
extern int cfFilterTee(int inputfd,
int outputfd,
int inputseekable,
// are supplied individually in the array
+extern int cfFilterExternal(int inputfd,
+ int outputfd,
+ int inputseekable,
+ cf_filter_data_t *data,
+ void *parameters);
+
+// Parameters: cf_filter_external_t*
+//
+// Path/Name of the external CUPS/System V filter or backend to be
+// called by this filter function, specification whether we call a
+// filter or a backend, and in case of backend, whether in job
+// processing or discovery mode, extra options for the 5th command
+// line argument, and extra environment variables
+//
+// CUPS filter:
+// See "man filter"
+//
+// CUPS Backend:
+// See "man backend"
+//
+// System V interface script:
+// https://www.ibm.com/docs/en/aix/7.2?topic=configuration-printer-interface-scripts
+
+
extern int cfFilterOpenBackAndSidePipes(cf_filter_data_t *data);
--- /dev/null
+//
+// Include necessary headers...
+//
+
+#include <cupsfilters/filter.h>
+#include <ppd/ppd-filter.h>
+#include <signal.h>
+#include <config.h>
+
+//
+// Local globals...
+//
+
+static int JobCanceled = 0; // Set to 1 on SIGTERM
+
+
+//
+// Local functions...
+//
+
+static void cancel_job(int sig);
+
+//
+// 'main()' - Main entry and processing of driver.
+//
+
+int // O - Exit status
+main(int argc, // I - Number of command-line arguments
+ char *argv[]) // I - Command-line arguments
+{
+ int ret = 1;
+#if defined(HAVE_SIGACTION) && !defined(HAVE_SIGSET)
+ struct sigaction action; // Actions for POSIX signals
+#endif // HAVE_SIGACTION && !HAVE_SIGSET
+
+ //
+ // Register a signal handler to cleanly cancel a job.
+ //
+
+#ifdef HAVE_SIGSET // Use System V signals over POSIX to avoid bugs
+ sigset(SIGTERM, cancel_job);
+#elif defined(HAVE_SIGACTION)
+ memset(&action, 0, sizeof(action));
+
+ sigemptyset(&action.sa_mask);
+ action.sa_handler = cancel_job;
+ sigaction(SIGTERM, &action, NULL);
+#else
+ signal(SIGTERM, cancel_job);
+#endif // HAVE_SIGSET
+
+ //
+ // Fire up the cfFilterExternal()/ppdFilterExternalCUPS() filter function
+ //
+
+ cf_filter_external_t parameters;
+ char *val;
+
+ parameters.num_options = 0;
+ parameters.options = NULL;
+ parameters.envp = NULL;
+ if ((val = getenv("INTERFACE")) != NULL)
+ {
+ parameters.filter = val;
+ parameters.exec_mode = -1;
+ ret = ppdFilterCUPSWrapper(argc, argv, cfFilterExternal, ¶meters,
+ &JobCanceled);
+ }
+ else if ((val = getenv("FILTER")) != NULL)
+ {
+ parameters.filter = val;
+ parameters.exec_mode = 0;
+ ret = ppdFilterCUPSWrapper(argc, argv, cfFilterExternal, ¶meters,
+ &JobCanceled);
+ }
+ else if ((val = getenv("CUPSFILTER")) != NULL)
+ {
+ parameters.filter = val;
+ parameters.exec_mode = 0;
+ ret = ppdFilterCUPSWrapper(argc, argv, ppdFilterExternalCUPS, ¶meters,
+ &JobCanceled);
+ }
+ else if ((val = getenv("CUPSBACKEND")) != NULL)
+ {
+ parameters.filter = val;
+ if (argc < 6)
+ parameters.exec_mode = 2;
+ else
+ parameters.exec_mode = 1;
+ ret = ppdFilterCUPSWrapper(argc, argv, ppdFilterExternalCUPS, ¶meters,
+ &JobCanceled);
+ }
+ else
+ fprintf(stderr, "ERROR: No filter executable specified. Specify with one of INTERFACE, FILTER, CUPSFILTER, or CUPSBACKEND environment variables.\n");
+
+ if (ret)
+ fprintf(stderr, "ERROR: cfFilterExternal()/ppdFilterExternalCUPS() filter function failed.\n");
+
+ return (ret);
+}
+
+
+//
+// 'cancel_job()' - Flag the job as canceled.
+//
+
+static void
+cancel_job(int sig) // I - Signal number (unused)
+{
+ (void)sig;
+
+ JobCanceled = 1;
+}
int inputfd; // Print file descriptor
int inputseekable; // Is the input seekable (actual file
// not stdin)?
- int num_options; // Number of print options
- cups_option_t *options; // Print options
+ int num_options = 0; // Number of print options
+ cups_option_t *options = NULL; // Print options
cf_filter_data_t filter_data;
const char *val;
char buf[256];
// Check command-line...
//
- if (argc < 6 || argc > 7)
+ if ((argc < 6 || argc > 7) && argc != 1)
{
fprintf(stderr, "Usage: %s job-id user title copies options [file]\n",
argv[0]);
// Otherwise, send stdin instead...
//
- if (argc == 6)
+ if (argc <= 6)
{
inputfd = 0; // stdin
inputseekable = 0;
//
options = NULL;
- num_options = cupsParseOptions(argv[5], 0, &options);
+ if (argc > 5)
+ num_options = cupsParseOptions(argv[5], 0, &options);
if ((filter_data.printer = getenv("PRINTER")) == NULL)
filter_data.printer = argv[0];
- filter_data.job_id = atoi(argv[1]);
- filter_data.job_user = argv[2];
- filter_data.job_title = argv[3];
- filter_data.copies = atoi(argv[4]);
+ filter_data.job_id = argc > 1 ? atoi(argv[1]) : 0;
+ filter_data.job_user = argc > 2 ? argv[2] : NULL;
+ filter_data.job_title = argc > 3 ? argv[3] : NULL;
+ filter_data.copies = argc > 4 ? atoi(argv[4]) : 1;
filter_data.content_type = getenv("CONTENT_TYPE");
filter_data.final_content_type = getenv("FINAL_CONTENT_TYPE");
filter_data.job_attrs = NULL; // We use command line options
// to the filter_data structure
//
- retval = ppdFilterLoadPPDFile(&filter_data, getenv("PPD"));
+ if (getenv("PPD"))
+ retval = ppdFilterLoadPPDFile(&filter_data, getenv("PPD"));
//
// Fire up the filter function (output to stdout, file descriptor 1)
//
-// 'get_env_var()' - Auxiliary function for ppdFilterExternalCUPS(),
-// gets value of an environment variable in a list
-// of environment variables as used by the execve()
-// function
+// 'ppdFilterExternalCUPS()' - Filter function which calls an external
+// classic CUPS filter or System V
+// interface script, for example a
+// (proprietary) printer driver which
+// cannot be converted to a filter
+// function or if it is too awkward or
+// risky to convert for example when the
+// printer hardware is not available for
+// testing
//
-static char * // O - The value, NULL if variable is not in list
-get_env_var(char *name, // I - Name of environment variable to read
- char **env) // I - List of environment variable serttings
-{
- int i = 0;
-
-
- if (env)
- for (i = 0; env[i]; i ++)
- if (strncmp(env[i], name, strlen(name)) == 0 &&
- strlen(env[i]) > strlen(name) &&
- env[i][strlen(name)] == '=')
- return (env[i] + strlen(name) + 1);
-
- return (NULL);
-}
-
-
-//
-// 'add_env_var()' - Auxiliary function for ppdFilterExternalCUPS(),
-// adds/sets an environment variable in a list of
-// environment variables as used by the execve()
-// function
-//
-
-static int // O - Index of where the new value got inserted in
- // the list
-add_env_var(char *name, // I - Name of environment variable to set
- char *value, // I - Value of environment variable to set
- char ***env) // I - List of environment variable serttings
-{
- char *p;
- int i = 0,
- name_len;
-
-
- if (!name || !env || !name[0])
- return (-1);
-
- // Assemble a "VAR=VALUE" string and the string length of "VAR"
- if ((p = strchr(name, '=')) != NULL)
- {
- // User supplied "VAR=VALUE" as name and NULL as value
- if (value)
- return (-1);
- name_len = p - name;
- p = strdup(name);
- }
- else
- {
- // User supplied variable name and value as the name and as the value
- name_len = strlen(name);
- p = (char *)calloc(strlen(name) + (value ? strlen(value) : 0) + 2,
- sizeof(char));
- sprintf(p, "%s=%s", name, (value ? value : ""));
- }
-
- // Check whether we already have this variable in the list and update its
- // value if it is there
- if (*env)
- for (i = 0; (*env)[i]; i ++)
- if (strncmp((*env)[i], p, name_len) == 0 && (*env)[i][name_len] == '=')
- {
- free((*env)[i]);
- (*env)[i] = p;
- return (i);
- }
-
- // Add the variable as new item to the list
- *env = (char **)realloc(*env, (i + 2) * sizeof(char *));
- (*env)[i] = p;
- (*env)[i + 1] = NULL;
- return (i);
-}
-
-
-//
-// 'sanitize_device_uri()' - Remove authentication info from a device URI
-//
-
-static char * // O - Sanitized URI
-sanitize_device_uri(const char *uri, // I - Device URI
- char *buf, // I - Buffer for output
- size_t bufsize) // I - Size of buffer
-{
- char *start, // Start of data after scheme
- *slash, // First slash after scheme://
- *ptr; // Pointer into user@host:port part
-
-
- // URI not supplied
- if (!uri)
- return (NULL);
-
- // Copy the device URI to a temporary buffer so we can sanitize any auth
- // info in it...
- strncpy(buf, uri, bufsize);
-
- // Find the end of the scheme:// part...
- if ((ptr = strchr(buf, ':')) != NULL)
- {
- for (start = ptr + 1; *start; start ++)
- if (*start != '/')
- break;
-
- // Find the next slash (/) in the URI...
- if ((slash = strchr(start, '/')) == NULL)
- slash = start + strlen(start); // No slash, point to the end
-
- // Check for an @ sign before the slash...
- if ((ptr = strchr(start, '@')) != NULL && ptr < slash)
- {
- // Found an @ sign and it is before the resource part, so we have
- // an authentication string. Copy the remaining URI over the
- // authentication string...
- memmove(start, ptr + 1, strlen(ptr + 1) + 1);
- }
- }
-
- // Return the sanitized URI...
- return (buf);
-}
-
-
-//
-// 'fcntl_add_cloexec()' - Add FD_CLOEXEC flag to the flags
-// of a given file descriptor.
-//
-
-static int // Return value of fcntl()
-fcntl_add_cloexec(int fd) // File descriptor to add FD_CLOEXEC to
-{
- return fcntl(fd, F_SETFD, fcntl(fd, F_GETFD) | FD_CLOEXEC);
-}
-
-
-//
-// 'fcntl_add_nonblock()' - Add O_NONBLOCK flag to the flags
-// of a given file descriptor.
-
-
-static int // Return value of fcntl()
-fcntl_add_nonblock(int fd) // File descriptor to add O_NONBLOCK to
-{
- return fcntl(fd, F_SETFL, fcntl(fd, F_GETFL) | O_NONBLOCK);
-}
-
-
-//
-// 'ppdFilterExternalCUPS()' - Filter function which calls an
-// external, classic CUPS filter, for
-// example a (proprietary) printer driver
-// which cannot be converted to a filter
-// function or is to awkward or risky to
-// convert for example when the printer
-// hardware is not available for testing
-
-
int // O - Error status
ppdFilterExternalCUPS(int inputfd, // I - File descriptor input stream
int outputfd, // I - File descriptor output stream
ppd_filter_data_ext_t *filter_data_ext =
(ppd_filter_data_ext_t *)cfFilterDataGetExt(data,
PPD_FILTER_DATA_EXT);
- ppd_filter_external_cups_t *params = (ppd_filter_external_cups_t *)parameters;
+ cf_filter_external_t params = *((cf_filter_external_t *)parameters);
int i;
- int is_backend = 0; // Do we call a CUPS backend?
- int pid, // Process ID of filter
- stderrpid, // Process ID for stderr logging process
- wpid; // PID reported as terminated
- int fd; // Temporary file descriptor
- int backfd, sidefd; // file descriptors for back and side
- // channels
- int stderrpipe[2]; // Pipe to log stderr
- cups_file_t *fp; // File pointer to read log lines
- char buf[2048]; // Log line buffer
- cf_loglevel_t log_level; // Log level of filter's log message
- char *ptr1, *ptr2,
- *msg, // Filter log message
- *filter_name; // Filter name for logging
- char filter_path[1024]; // Full path of the filter
- char **argv, // Command line args for filter
- **envp = NULL; // Environment variables for filter
- int num_all_options = 0;
- cups_option_t *all_options = NULL;
- char job_id_str[16],
- copies_str[16],
- *options_str = NULL;
- cups_option_t *opt;
- int status = 65536;
- int wstatus;
+ char *filter_name; // Filter name for logging
+ char **envp = NULL; // Environment variables for filter
+ int status;
cf_logfunc_t log = data->logfunc;
void *ld = data->logdata;
- cf_filter_iscanceledfunc_t iscanceled = data->iscanceledfunc;
- void *icd = data->iscanceleddata;
- if (!params->filter || !params->filter[0])
+ if (!params.filter || !params.filter[0])
{
if (log) log(ld, CF_LOGLEVEL_ERROR,
"ppdFilterExternalCUPS: Filter executable path/command not specified");
return (1);
}
- // Check whether back/side channel FDs are valid and not all-zero
- // from calloc'ed filter_data
- if (data->back_pipe[0] == 0 && data->back_pipe[1] == 0)
- data->back_pipe[0] = data->back_pipe[1] = -1;
- if (data->side_pipe[0] == 0 && data->side_pipe[1] == 0)
- data->side_pipe[0] = data->side_pipe[1] = -1;
-
- // Select the correct end of the back/side channel pipes:
- // [0] for filters, [1] for backends
- is_backend = (params->is_backend ? 1 : 0);
- backfd = data->back_pipe[is_backend];
- sidefd = data->side_pipe[is_backend];
-
// Filter name for logging
- if ((filter_name = strrchr(params->filter, '/')) != NULL)
+ if ((filter_name = strrchr(params.filter, '/')) != NULL)
filter_name ++;
else
- filter_name = (char *)params->filter;
+ filter_name = (char *)params.filter;
//
// Ignore broken pipe signals...
signal(SIGPIPE, SIG_IGN);
//
- // Copy the current environment variables and add some important ones
- // needed for correct execution of the CUPS filter (which is not running
- // out of CUPS here)
+ // Copy the environment variables given by the parameters
//
- // Some default environment variables from CUPS, will get overwritten
- // if also defined in the environment in which the caller is started
- // or in the parameters
- add_env_var("CUPS_DATADIR", CUPS_DATADIR, &envp);
- add_env_var("CUPS_SERVERBIN", CUPS_SERVERBIN, &envp);
- add_env_var("CUPS_SERVERROOT", CUPS_SERVERROOT, &envp);
- add_env_var("CUPS_STATEDIR", CUPS_STATEDIR, &envp);
- add_env_var("SOFTWARE", "CUPS/2.5.99", &envp); // Last CUPS with PPDs
- if (data->content_type)
- add_env_var("CONTENT_TYPE", data->content_type, &envp);
- if (data->final_content_type)
- add_env_var("FINAL_CONTENT_TYPE", data->final_content_type, &envp);
-
- // Copy the environment in which the caller got started
- if (environ)
- for (i = 0; environ[i]; i ++)
- add_env_var(environ[i], NULL, &envp);
-
- // Set the environment variables given by the parameters
- if (params->envp)
- for (i = 0; params->envp[i]; i ++)
- add_env_var(params->envp[i], NULL, &envp);
-
- // Add CUPS_SERVERBIN to the beginning of PATH
- ptr1 = get_env_var("PATH", envp);
- ptr2 = get_env_var("CUPS_SERVERBIN", envp);
- if (ptr2 && ptr2[0])
- {
- if (ptr1 && ptr1[0])
- {
- snprintf(buf, sizeof(buf), "%s/%s:%s",
- ptr2, params->is_backend ? "backend" : "filter", ptr1);
- ptr1 = buf;
- }
- else
- ptr1 = ptr2;
- add_env_var("PATH", ptr1, &envp);
- }
+ if (params.envp)
+ for (i = 0; params.envp[i]; i ++)
+ cfFilterAddEnvVar(params.envp[i], NULL, &envp);
+
+ //
+ // Some default environment variables from CUPS, will be not set if
+ // also defined in the environment in which the caller is started or
+ // if already set in the parameters. These are needed for correct
+ // execution of the CUPS filter or backend (which is not running out
+ // of CUPS here)
+ //
- if (params->is_backend < 2) // Not needed in discovery mode of backend
+ if (log) log(ld, CF_LOGLEVEL_DEBUG,
+ "ppdFilterExternalCUPS: Setting CUPS-specific environment environment variables: CUPS_DATADIR, CUPS_SERVERBIN, CUPS_SERVERROOT, CUPS_STATEDIR, SOFTWARE, CONTENT_TYPE, FINAL_CONTENT_TYPE");
+
+ if (!getenv("CUPS_DATADIR") &&
+ !cfFilterGetEnvVar("CUPS_DATADIR", envp))
+ cfFilterAddEnvVar("CUPS_DATADIR", CUPS_DATADIR, &envp);
+ if (!getenv("CUPS_SERVERBIN") &&
+ !cfFilterGetEnvVar("CUPS_SERVERBIN", envp))
+ cfFilterAddEnvVar("CUPS_SERVERBIN", CUPS_SERVERBIN, &envp);
+ if (!getenv("CUPS_SERVERROOT") &&
+ !cfFilterGetEnvVar("CUPS_SERVERROOT", envp))
+ cfFilterAddEnvVar("CUPS_SERVERROOT", CUPS_SERVERROOT, &envp);
+ if (!getenv("CUPS_STATEDIR") &&
+ !cfFilterGetEnvVar("CUPS_STATEDIR", envp))
+ cfFilterAddEnvVar("CUPS_STATEDIR", CUPS_STATEDIR, &envp);
+ if (!getenv("SOFTWARE") &&
+ !cfFilterGetEnvVar("SOFTWARE", envp))
+ cfFilterAddEnvVar("SOFTWARE", "CUPS/2.5.99", &envp);
+ if (data->content_type &&
+ !getenv("CONTENT_TYPE") &&
+ !cfFilterGetEnvVar("CONTENT_TYPE", envp))
+ cfFilterAddEnvVar("CONTENT_TYPE", data->content_type, &envp);
+ if (data->final_content_type &&
+ !getenv("FINAL_CONTENT_TYPE") &&
+ !cfFilterGetEnvVar("FINAL_CONTENT_TYPE", envp))
+ cfFilterAddEnvVar("FINAL_CONTENT_TYPE", data->final_content_type,
+ &envp);
+
+ if (params.exec_mode < 2) // Not needed in discovery mode of backend
{
+ if (log) log(ld, CF_LOGLEVEL_DEBUG,
+ "ppdFilterExternalCUPS: Setting CUPS-specific environment environment variables: PRINTER, PPD, DEVICE_URI");
+
// Print queue name from filter data
if (data->printer)
- add_env_var("PRINTER", data->printer, &envp);
+ cfFilterAddEnvVar("PRINTER", data->printer, &envp);
else
- add_env_var("PRINTER", "Unknown", &envp);
+ cfFilterAddEnvVar("PRINTER", "Unknown", &envp);
// PPD file path/name from filter data, required for most CUPS filters
if (filter_data_ext && filter_data_ext->ppdfile)
- add_env_var("PPD", filter_data_ext->ppdfile, &envp);
-
- // Device URI from parameters
- if (params->is_backend && params->device_uri)
- add_env_var("DEVICE_URI", (char *)params->device_uri, &envp);
- }
-
- // Determine full path for the filter
- if (params->filter[0] == '/' ||
- (ptr1 = get_env_var("CUPS_SERVERBIN", envp)) == NULL || !ptr1[0])
- strncpy(filter_path, params->filter, sizeof(filter_path) - 1);
- else
- snprintf(filter_path, sizeof(filter_path), "%s/%s/%s", ptr1,
- params->is_backend ? "backend" : "filter", params->filter);
-
- // Log the resulting list of environment variable settings
- // (with any authentication info removed)
- if (log)
- {
- for (i = 0; envp[i]; i ++)
- if (!strncmp(envp[i], "AUTH_", 5))
- log(ld, CF_LOGLEVEL_DEBUG,
- "ppdFilterExternalCUPS (%s): envp[%d]: AUTH_%c****",
- filter_name, i, envp[i][5]);
- else if (!strncmp(envp[i], "DEVICE_URI=", 11))
- log(ld, CF_LOGLEVEL_DEBUG,
- "ppdFilterExternalCUPS (%s): envp[%d]: DEVICE_URI=%s",
- filter_name, i, sanitize_device_uri(envp[i] + 11,
- buf, sizeof(buf)));
- else
- log(ld, CF_LOGLEVEL_DEBUG, "ppdFilterExternalCUPS (%s): envp[%d]: %s",
- filter_name, i, envp[i]);
- }
-
- if (params->is_backend < 2)
- {
- //
- // Filter or backend for job execution
- //
-
- //
- // Join the options from the filter data and from the parameters
- // If an option is present in both filter data and parameters, the
- // value in the filter data has priority
- //
-
- for (i = 0, opt = params->options; i < params->num_options; i ++, opt ++)
- num_all_options = cupsAddOption(opt->name, opt->value, num_all_options,
- &all_options);
- for (i = 0, opt = data->options; i < data->num_options; i ++, opt ++)
- num_all_options = cupsAddOption(opt->name, opt->value, num_all_options,
- &all_options);
-
- //
- // Create command line arguments for the CUPS filter
- //
-
- argv = (char **)calloc(7, sizeof(char *));
-
- // Numeric parameters
- snprintf(job_id_str, sizeof(job_id_str) - 1, "%d",
- data->job_id > 0 ? data->job_id : 1);
- snprintf(copies_str, sizeof(copies_str) - 1, "%d",
- data->copies > 0 ? data->copies : 1);
-
- // Options, build string of "Name1=Value1 Name2=Value2 ..." but use
- // "Name" and "noName" instead for boolean options
- for (i = 0, opt = all_options; i < num_all_options; i ++, opt ++)
- {
- if (strcasecmp(opt->value, "true") == 0 ||
- strcasecmp(opt->value, "false") == 0)
- {
- options_str =
- (char *)realloc(options_str,
- ((options_str ? strlen(options_str) : 0) +
- strlen(opt->name) +
- (strcasecmp(opt->value, "false") == 0 ? 2 : 0) + 2) *
- sizeof(char));
- if (i == 0)
- options_str[0] = '\0';
- sprintf(options_str + strlen(options_str), " %s%s",
- (strcasecmp(opt->value, "false") == 0 ? "no" : ""), opt->name);
- }
- else
- {
- options_str =
- (char *)realloc(options_str,
- ((options_str ? strlen(options_str) : 0) +
- strlen(opt->name) + strlen(opt->value) + 3) *
- sizeof(char));
- if (i == 0)
- options_str[0] = '\0';
- sprintf(options_str + strlen(options_str), " %s=%s", opt->name,
- opt->value);
- }
- }
-
- // Find DEVICE_URI environment variable
- if (params->is_backend && !params->device_uri)
- for (i = 0; envp[i]; i ++)
- if (strncmp(envp[i], "DEVICE_URI=", 11) == 0)
- break;
-
- // Add items to array
- argv[0] = strdup((params->is_backend && params->device_uri ?
- (char *)sanitize_device_uri(params->device_uri,
- buf, sizeof(buf)) :
- (params->is_backend && envp[i] ?
- (char *)sanitize_device_uri(envp[i] + 11,
- buf, sizeof(buf)) :
- (data->printer ? data->printer :
- (char *)params->filter))));
- argv[1] = job_id_str;
- argv[2] = data->job_user ? data->job_user : "Unknown";
- argv[3] = data->job_title ? data->job_title : "Untitled";
- argv[4] = copies_str;
- argv[5] = options_str ? options_str + 1 : "";
- argv[6] = NULL;
-
- // Log the arguments
- if (log)
- for (i = 0; argv[i]; i ++)
- log(ld, CF_LOGLEVEL_DEBUG, "ppdFilterExternalCUPS (%s): argv[%d]: %s",
- filter_name, i, argv[i]);
- }
- else
- {
- //
- // Backend in device discovery mode
- //
-
- argv = (char **)calloc(2, sizeof(char *));
- argv[0] = strdup((char *)params->filter);
- argv[1] = NULL;
+ cfFilterAddEnvVar("PPD", filter_data_ext->ppdfile, &envp);
+
+ // Do we have the DEVICE_URI env variable set?
+ if (params.exec_mode > 0 &&
+ !getenv("DEVICE_URI") &&
+ !cfFilterGetEnvVar("DEVICE_URI", envp))
+ if (log) log(ld, CF_LOGLEVEL_WARN,
+ "ppdFilterExternalCUPS: Running backend and DEVICE_URI environment variable is not set.");
}
//
- // Execute the filter
+ // Insert new environment variable list into copy of parameters
//
- if (pipe(stderrpipe) < 0)
- {
- if (log) log(ld, CF_LOGLEVEL_ERROR,
- "ppdFilterExternalCUPS (%s): Could not create pipe for stderr: %s",
- filter_name, strerror(errno));
- return (1);
- }
-
- if ((pid = fork()) == 0)
- {
- //
- // Child process goes here...
- //
- // Update stdin/stdout/stderr as needed...
- //
-
- if (inputfd != 0)
- {
- if (inputfd < 0)
- {
- inputfd = open("/dev/null", O_RDONLY);
- if (log) log(ld, CF_LOGLEVEL_ERROR,
- "ppdFilterExternalCUPS (%s): No input file descriptor supplied for CUPS filter - %s",
- filter_name, strerror(errno));
- }
-
- if (inputfd > 0)
- {
- fcntl_add_cloexec(inputfd);
- if (dup2(inputfd, 0) < 0)
- {
- if (log) log(ld, CF_LOGLEVEL_ERROR,
- "ppdFilterExternalCUPS (%s): Failed to connect input file descriptor with CUPS filter's stdin - %s",
- filter_name, strerror(errno));
- goto fd_error;
- } else
- if (log) log(ld, CF_LOGLEVEL_DEBUG,
- "ppdFilterExternalCUPS (%s): Connected input file descriptor %d to CUPS filter's stdin.",
- filter_name, inputfd);
- close(inputfd);
- }
- }
- else
- if (log) log(ld, CF_LOGLEVEL_DEBUG,
- "ppdFilterExternalCUPS (%s): Input comes from stdin, letting the filter grab stdin directly",
- filter_name);
-
- if (outputfd != 1)
- {
- if (outputfd < 0)
- outputfd = open("/dev/null", O_WRONLY);
-
- if (outputfd > 1) {
- fcntl_add_cloexec(outputfd);
- dup2(outputfd, 1);
- close(outputfd);
- }
- }
-
- if (strcasestr(params->filter, "gziptoany"))
- {
- // Send stderr to the Nirwana if we are running gziptoany, as
- // gziptoany emits a false "PAGE: 1 1"
- if ((fd = open("/dev/null", O_RDWR)) > 2)
- {
- fcntl_add_cloexec(fd);
- dup2(fd, 2);
- close(fd);
- } else
- close(fd);
- }
- else
- {
- // Send stderr into pipe for logging
- fcntl_add_cloexec(stderrpipe[1]);
- dup2(stderrpipe[1], 2);
- fcntl_add_nonblock(2);
- }
- close(stderrpipe[0]);
- close(stderrpipe[1]);
-
- if (params->is_backend < 2) // Not needed in discovery mode of backend
- {
- // Back channel
- if (backfd != 3 && backfd >= 0)
- {
- dup2(backfd, 3);
- close(backfd);
- fcntl_add_nonblock(3);
- }
- else if (backfd < 0)
- {
- if ((backfd = open("/dev/null", O_RDWR)) > 3)
- {
- dup2(backfd, 3);
- close(backfd);
- }
- else
- close(backfd);
- fcntl_add_nonblock(3);
- }
-
- // Side channel
- if (sidefd != 4 && sidefd >= 0)
- {
- dup2(sidefd, 4);
- close(sidefd);
- fcntl_add_nonblock(4);
- }
- else if (sidefd < 0)
- {
- if ((sidefd = open("/dev/null", O_RDWR)) > 4)
- {
- dup2(sidefd, 4);
- close(sidefd);
- } else
- close(sidefd);
- fcntl_add_nonblock(4);
- }
- }
-
- //
- // Execute command...
- //
-
- execve(filter_path, argv, envp);
-
- if (log) log(ld, CF_LOGLEVEL_ERROR,
- "ppdFilterExternalCUPS (%s): Execution of %s %s failed - %s",
- filter_name, params->is_backend ? "backend" : "filter",
- filter_path, strerror(errno));
-
- fd_error:
- exit(errno);
- }
- else if (pid > 0)
- {
- if (log) log(ld, CF_LOGLEVEL_INFO,
- "ppdFilterExternalCUPS (%s): %s (PID %d) started.",
- filter_name, filter_path, pid);
- }
- else
- {
- if (log) log(ld, CF_LOGLEVEL_ERROR,
- "ppdFilterExternalCUPS (%s): Unable to fork process for %s %s",
- filter_name, params->is_backend ? "backend" : "filter",
- filter_path);
- close(stderrpipe[0]);
- close(stderrpipe[1]);
- status = 1;
- goto out;
- }
- if (inputfd >= 0)
- close(inputfd);
- if (outputfd >= 0)
- close(outputfd);
+ params.envp = envp;
//
- // Log the filter's stderr
+ // Call cfFilterExternal() to do the actual work
//
- if ((stderrpid = fork()) == 0)
- {
- //
- // Child process goes here...
- //
-
- close(stderrpipe[1]);
- fp = cupsFileOpenFd(stderrpipe[0], "r");
- while (cupsFileGets(fp, buf, sizeof(buf)))
- if (log)
- {
- if (strncmp(buf, "DEBUG: ", 7) == 0)
- {
- log_level = CF_LOGLEVEL_DEBUG;
- msg = buf + 7;
- }
- else if (strncmp(buf, "DEBUG2: ", 8) == 0)
- {
- log_level = CF_LOGLEVEL_DEBUG;
- msg = buf + 8;
- }
- else if (strncmp(buf, "INFO: ", 6) == 0)
- {
- log_level = CF_LOGLEVEL_INFO;
- msg = buf + 6;
- }
- else if (strncmp(buf, "WARNING: ", 9) == 0)
- {
- log_level = CF_LOGLEVEL_WARN;
- msg = buf + 9;
- }
- else if (strncmp(buf, "ERROR: ", 7) == 0)
- {
- log_level = CF_LOGLEVEL_ERROR;
- msg = buf + 7;
- }
- else if (strncmp(buf, "PAGE: ", 6) == 0 ||
- strncmp(buf, "ATTR: ", 6) == 0 ||
- strncmp(buf, "STATE: ", 7) == 0 ||
- strncmp(buf, "PPD: ", 5) == 0)
- {
- log_level = CF_LOGLEVEL_CONTROL;
- msg = buf;
- }
- else
- {
- log_level = CF_LOGLEVEL_DEBUG;
- msg = buf;
- }
- if (log_level == CF_LOGLEVEL_CONTROL)
- log(ld, log_level, msg);
- else
- log(ld, log_level, "ppdFilterExternalCUPS (%s): %s",
- filter_name, msg);
- }
- cupsFileClose(fp);
- // No need to close the fd stderrpipe[0], as cupsFileClose(fp) does this
- // already
- // Ignore errors of the logging process
- exit(0);
- }
- else if (stderrpid > 0)
- {
- if (log) log(ld, CF_LOGLEVEL_INFO,
- "ppdFilterExternalCUPS (%s): Logging (PID %d) started.",
- filter_name, stderrpid);
- }
- else
- {
- if (log) log(ld, CF_LOGLEVEL_ERROR,
- "ppdFilterExternalCUPS (%s): Unable to fork process for logging",
- filter_name);
- close(stderrpipe[0]);
- close(stderrpipe[1]);
- status = 1;
- goto out;
- }
+ if (log) log(ld, CF_LOGLEVEL_DEBUG,
+ "ppdFilterExternalCUPS: Calling cfFilterExternal().");
- close(stderrpipe[0]);
- close(stderrpipe[1]);
+ status = cfFilterExternal(inputfd, outputfd, inputseekable, data, ¶ms);
//
- // Wait for filter and logging processes to finish
+ // Clean up
//
- status = 0;
-
- while (pid > 0 || stderrpid > 0)
+ if (envp)
{
- if ((wpid = wait(&wstatus)) < 0)
- {
- if (errno == EINTR && iscanceled && iscanceled(icd))
- {
- if (log) log(ld, CF_LOGLEVEL_DEBUG,
- "ppdFilterExternalCUPS (%s): Job canceled, killing %s ...",
- filter_name, params->is_backend ? "backend" : "filter");
- kill(pid, SIGTERM);
- pid = -1;
- kill(stderrpid, SIGTERM);
- stderrpid = -1;
- break;
- }
- else
- continue;
- }
-
- // How did the filter terminate
- if (wstatus)
- {
- if (WIFEXITED(wstatus))
- {
- // Via exit() anywhere or return() in the main() function
- if (log) log(ld, CF_LOGLEVEL_ERROR,
- "ppdFilterExternalCUPS (%s): %s (PID %d) stopped with status %d",
- filter_name,
- (wpid == pid ?
- (params->is_backend ? "Backend" : "Filter") :
- "Logging"),
- wpid, WEXITSTATUS(wstatus));
- }
- else
- {
- // Via signal
- if (log) log(ld, CF_LOGLEVEL_ERROR,
- "ppdFilterExternalCUPS (%s): %s (PID %d) crashed on signal %d",
- filter_name,
- (wpid == pid ?
- (params->is_backend ? "Backend" : "Filter") :
- "Logging"),
- wpid, WTERMSIG(wstatus));
- }
- status = 1;
- }
- else
- {
- if (log) log(ld, CF_LOGLEVEL_INFO,
- "ppdFilterExternalCUPS (%s): %s (PID %d) exited with no errors.",
- filter_name,
- (wpid == pid ?
- (params->is_backend ? "Backend" : "Filter") : "Logging"),
- wpid);
- }
- if (wpid == pid)
- pid = -1;
- else if (wpid == stderrpid)
- stderrpid = -1;
+ for (i = 0; envp[i]; i ++)
+ free(envp[i]);
+ free(envp);
}
- //
- // Clean up
- //
+ return (status);
+}
- out:
- cupsFreeOptions(num_all_options, all_options);
- if (options_str)
- free(options_str);
- free(argv[0]);
- free(argv);
- for (i = 0; envp[i]; i ++)
- free(envp[i]);
- free(envp);
- return (status);
+//
+// 'fcntl_add_cloexec()' - Add FD_CLOEXEC flag to the flags
+// of a given file descriptor.
+//
+
+static int // Return value of fcntl()
+fcntl_add_cloexec(int fd) // File descriptor to add FD_CLOEXEC to
+{
+ return fcntl(fd, F_SETFD, fcntl(fd, F_GETFD) | FD_CLOEXEC);
}
ppd_file_t *ppd; // PPD file data
} ppd_filter_data_ext_t;
-typedef struct ppd_filter_external_cups_s { // Parameters for the
- // ppdFilterExternalCUPS() filter
- // function
- const char *filter; // Path/Name of the CUPS filter to be called by
- // this filter function, required
- int is_backend; // 0 if we call a filter, 1 if we call a CUPS
- // backend, 2 if we call a CUPS backend in
- // device discovery mode
- const char *device_uri; // Device URI when calling a CUPS Backend for
- // processing a job, optional, alternatively
- // DEVICE_URI environment variable can get set
- // in envp
- int num_options; // Extra options for the 5th command line
- cups_option_t *options; // argument, options of filter_data have
- // priority, 0/NULL if none
- char **envp; // Additional environment variables, the already
- // defined ones stay valid but can be overwritten
- // by these ones, NULL if none
-} ppd_filter_external_cups_t;
-
-
//
// Prototypes...
//
cf_filter_data_t *data,
void *parameters);
-// Parameters: ppd_filter_external_cups_t*
-// Path/Name of the CUPS filter to be called by this filter function,
-// specification whether we call a filter or a backend, an in case of
-// backend, whether in job processing or discovery mode, extra options
-// for the 5th command line argument, and extra environment
-// variables
+// Parameters: cf_filter_external_t*
+//
+// Path/Name of the external CUPS/System V filter or backend to be
+// called by this filter function, specification whether we call a
+// filter or a backend, and in case of backend, whether in job
+// processing or discovery mode, extra options for the 5th command
+// line argument, and extra environment variables
+//
+// CUPS filter:
+// See "man filter"
+//
+// CUPS Backend:
+// See "man backend"
extern int ppdFilterEmitJCL(int inputfd,