]> git.ipfire.org Git - thirdparty/cups-filters.git/commitdiff
libcupsfilters, libppd: cfFilterExternal() filter function
authorTill Kamppeter <till.kamppeter@gmail.com>
Sun, 16 Oct 2022 07:19:26 +0000 (09:19 +0200)
committerTill Kamppeter <till.kamppeter@gmail.com>
Sun, 16 Oct 2022 07:19:26 +0000 (09:19 +0200)
Moved the core functionality of the ppdFilterExternalCUPS() filter
function from libppd into libcupsfilters, as the new
cfFilterExternal() filter function.

This way also in applications without PPD file support, not using
libppd, external filter executables can be easily integrated in filter
function chains. This is for example needed if filters are not written
in C and conversion gets too awkward and error-prone, or is not even
possible (like with closed-source filters).

The Braille embosser printer driver for example contains filters which
are shell scripts.

External filters must use the call scheme of either CUPS filters or
System V interface scripts. Both call schemes are merely the same,
only in the CUPS filters there got added that input can come from
stdin instead of only via files named in the 6th and further command
line arguments.

ppdFilterExternalCUPS() is a wrapper around cfFilterExternal() now and
does not more than setting the cups-specific environment variables
CUPS_DATADIR, CUPS_SERVERBIN, CUPS_SERVERROOT, CUPS_STATEDIR,
SOFTWARE, CONTENT_TYPE, FINAL_CONTENT_TYPE, PRINTER, PPD, DEVICE_URI
to emulate a cUPS environment as good as possible. The PPD's file name
for the PPD environment variable is taken from the "libppd" extension
for the filter data.

For calling CUPS backends for job execution, the DEVICE_URI
environment variable must be set to the CUPS device URI of the
printer. This can be done by setting the variable right away or adding
it to the environment variable list in the parameters.

The parameters data structure for both cfFilterExternal() and
ppdFilterExternalCUPS() is cf_filter_external_t (in
cupsfilters/filter.h) now and not ppd_filter_external_cups_t any
more. Here the device_uri field got removed (the DEVICE_URI
environment variable has to be used instead) and the is_backend field
got replaced by exec_mode. exec_mode allows 4 setting: -1 for System V
interface scripts (input is file named by 6th command line argument),
0 for CUPS filter (input via stdin), 1 for CUPS backend in job
execution mode, 2 for CUPS backend in device discovery mode (no
command line arguments).

To allow easy handling of the environment variable list in the
parameters, the envp field, the internal helper functions
add_env_var() and get_env_var() got promoted into the API, as
cfFilterAddEnvVar() and cfFilterGetEnvVar(). This allows also libppd
to use these functions.

For testing and debugging any changes on cfFilterExternal() and
ppdFilterExternalCUPS() a new wrapper pseudo CUPS filter,
"test-external", source file filter/test-external.c, got added. This
one is mainly intended to be called manually. To tell which filter or
backend should be called and in which mode, one of the environment
variables INTERFACE, FILTER, CUPSFILTER, or CUPSBACKEND has to be set
to the filter/backend executable.

Makefile.am
backend/implicitclass.c
cupsfilters/filter.c
cupsfilters/filter.h
filter/test-external.c [new file with mode: 0644]
ppd/ppd-filter.c
ppd/ppd-filter.h

index bc5d05e11cb1594e6de3d15a3beb94795b760652..2029319c47d6fedac7f6e438c6627472bf25fad7 100644 (file)
@@ -376,7 +376,7 @@ test1284_CFLAGS = \
 
 testpdf1_SOURCES = \
        cupsfilters/testpdf1.c \
-       cupsfilters/fontembed.h
+       cupsfilters/fontembed-private.h
 testpdf1_CFLAGS = \
        -I$(srcdir)/cupsfilters/fontembed/ \
        -I$(srcdir)/cupsfilters/
@@ -385,7 +385,7 @@ testpdf1_LDADD = \
 
 testpdf2_SOURCES = \
        cupsfilters/testpdf2.c \
-       cupsfilters/fontembed.h
+       cupsfilters/fontembed-private.h
 testpdf2_CFLAGS = \
        -I$(srcdir)/cupsfilters/fontembed/ \
        -I$(srcdir)/cupsfilters/
@@ -848,6 +848,9 @@ pkgfilter_PROGRAMS += \
        imagetoraster
 endif
 
+check_PROGRAMS += \
+       test-external
+
 # Not reliable bash script
 #TESTS += filter/test.sh
 
@@ -1159,6 +1162,17 @@ universal_LDADD = \
        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
 # =====
index 8ee4e10517f3565d6925437103cdce48870cb2a2..0bf420c0742a9d7089265585fdac8d6032e2f816 100644 (file)
@@ -241,7 +241,7 @@ main(int  argc,                             /* I - Number of command-line args */
       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;
@@ -354,11 +354,11 @@ main(int  argc,                           /* I - Number of command-line args */
 
       /* 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;
@@ -391,6 +391,12 @@ main(int  argc,                            /* I - Number of command-line args */
       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);
 
index 36a05bdf580bfb52a565239d8145f94096be55a4..fb6f46fcbce9813eeb361fafe98a37e4615dbcd6 100644 (file)
@@ -49,8 +49,8 @@ fcntl_add_cloexec(int fd) // File descriptor to add FD_CLOEXEC to
 
 
 //
-// '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()
@@ -226,6 +226,90 @@ cfFilterDataRemoveExt(cf_filter_data_t *data,
 }
 
 
+//
+// '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
@@ -754,6 +838,675 @@ cfFilterChain(int inputfd,         // I - File descriptor input stream
 }
 
 
+//
+// '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
@@ -762,10 +1515,10 @@ cfFilterChain(int inputfd,         // I - File descriptor input stream
 //                                    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.
 //
 
index 9f618209221b2aeee6354f8155430b1537e963fb..e0d4607cd129819dadd3b558bb16829725eca570 100644 (file)
@@ -43,7 +43,8 @@ extern "C" {
 
 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
@@ -74,7 +75,8 @@ typedef struct cf_filter_data_s {
                             // 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;
@@ -83,8 +85,9 @@ typedef int (*cf_filter_function_t)(int inputfd, int outputfd,
                                    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
@@ -94,29 +97,50 @@ typedef enum cf_filter_out_format_e { // Possible output formats for filter
   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;
@@ -146,6 +170,12 @@ extern void *cfFilterDataGetExt(cf_filter_data_t *data, const char *name);
 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,
@@ -182,6 +212,30 @@ extern int cfFilterChain(int inputfd,
 // 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);
 
 
diff --git a/filter/test-external.c b/filter/test-external.c
new file mode 100644 (file)
index 0000000..dbe02ae
--- /dev/null
@@ -0,0 +1,113 @@
+//
+// 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, &parameters,
+                              &JobCanceled);
+  }
+  else if ((val = getenv("FILTER")) != NULL)
+  {
+    parameters.filter = val;
+    parameters.exec_mode = 0;
+    ret = ppdFilterCUPSWrapper(argc, argv, cfFilterExternal, &parameters,
+                              &JobCanceled);
+  }
+  else if ((val = getenv("CUPSFILTER")) != NULL)
+  {
+    parameters.filter = val;
+    parameters.exec_mode = 0;
+    ret = ppdFilterCUPSWrapper(argc, argv, ppdFilterExternalCUPS, &parameters,
+                              &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, &parameters,
+                              &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;
+}
index 532a5fa4f1ebe8aa1c95621dbec3700212d63fa0..d5fa680ac1ccb32bffa04db35f8ecd9b34bc0561 100644 (file)
@@ -42,8 +42,8 @@ ppdFilterCUPSWrapper(
   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];
@@ -66,7 +66,7 @@ ppdFilterCUPSWrapper(
   // 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]);
@@ -78,7 +78,7 @@ ppdFilterCUPSWrapper(
   // Otherwise, send stdin instead...
   //
 
-  if (argc == 6)
+  if (argc <= 6)
   {
     inputfd = 0; // stdin
     inputseekable = 0;
@@ -109,14 +109,15 @@ ppdFilterCUPSWrapper(
   //
 
   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
@@ -160,7 +161,8 @@ ppdFilterCUPSWrapper(
   // 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)
@@ -1008,170 +1010,17 @@ ppdFilterFreePPD(cf_filter_data_t *data) // I - Job and printer data
 
 
 //
-// '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
@@ -1182,64 +1031,27 @@ ppdFilterExternalCUPS(int inputfd,         // I - File descriptor input 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...
@@ -1248,524 +1060,111 @@ ppdFilterExternalCUPS(int inputfd,         // I - File descriptor input stream
   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, &params);
 
   //
-  // 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);
 }
 
 
index 9b271dc2bc36fd2ff83bb5f173a4ed393b60a031..17fd33326cce8da73ca51a0dc8a2c51339f02faf 100644 (file)
@@ -50,27 +50,6 @@ typedef struct ppd_filter_data_ext_s {
   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...
 //
@@ -100,12 +79,19 @@ extern int ppdFilterExternalCUPS(int inputfd,
                                 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,