]> git.ipfire.org Git - thirdparty/cups.git/commitdiff
Changes to support multiple files per job.
authormike <mike@7a7537e8-13f0-0310-91df-b6672ffda945>
Wed, 29 Dec 1999 02:15:43 +0000 (02:15 +0000)
committermike <mike@7a7537e8-13f0-0310-91df-b6672ffda945>
Wed, 29 Dec 1999 02:15:43 +0000 (02:15 +0000)
Added support for job persistence.

Added support for create-job, send-document operations.

Added start of get-devices and get-ppds.

git-svn-id: svn+ssh://src.apple.com/svn/cups/cups.org/trunk@833 7a7537e8-13f0-0310-91df-b6672ffda945

12 files changed:
config.h.in
cups/ipp.c
cups/ipp.h
scheduler/client.c
scheduler/conf.c
scheduler/conf.h
scheduler/cupsd.h
scheduler/ipp.c
scheduler/job.c
scheduler/job.h
scheduler/main.c
scheduler/printers.c

index d5da9906efffba96d230f51ab310fab762dc1ecd..e625a2557aceffbbf6b904c3a234009e6da9c8c5 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * "$Id: config.h.in,v 1.13 1999/12/15 15:18:04 mike Exp $"
+ * "$Id: config.h.in,v 1.14 1999/12/29 02:15:39 mike Exp $"
  *
  *   Configuration file for the Common UNIX Printing System (CUPS).
  *
  */
 
 #define CUPS_LOCALEDIR "/usr/lib/locale"
-#define CUPS_SERVERROOT        "/var/cups"
+#define CUPS_SERVERROOT        "/etc/cups"
+#define CUPS_REQUESTS  "/var/spool/cups"
+#define CUPS_SERVERBIN "/var/cups"
 #define CUPS_DATADIR   "/usr/share/cups"
+#define CUPS_DOCDIR    "/usr/share/doc/cups"
+
 
 /*
  * Do we have various image libraries?
 #undef HAVE_WAIT3
 
 /*
- * End of "$Id: config.h.in,v 1.13 1999/12/15 15:18:04 mike Exp $".
+ * End of "$Id: config.h.in,v 1.14 1999/12/29 02:15:39 mike Exp $".
  */
index 9f8fdb1352676098f642aab7ec5d629b4d3dfeb9..5e6f3a32d51a9d3093be40e6b53064e6eaeee1ff 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * "$Id: ipp.c,v 1.27 1999/12/21 02:26:45 mike Exp $"
+ * "$Id: ipp.c,v 1.28 1999/12/29 02:15:40 mike Exp $"
  *
  *   Internet Printing Protocol support functions for the Common UNIX
  *   Printing System (CUPS).
@@ -1206,6 +1206,17 @@ ippWrite(http_t *http,           /* I - HTTP data */
                   DEBUG_printf(("ippWrite: writing string = %d, \'%s\'\n", n,
                                attr->values[i].string.text));
 
+                  if ((sizeof(buffer) - (bufptr - buffer)) < (n + 2)
+                 {
+                    if (httpWrite(http, (char *)buffer, bufptr - buffer) < 0)
+                   {
+                     DEBUG_puts("ippWrite: Could not write IPP attribute...");
+                     return (IPP_ERROR);
+                   }
+
+                   bufptr = buffer;
+                 }
+
                  *bufptr++ = n >> 8;
                  *bufptr++ = n;
                  memcpy(bufptr, attr->values[i].string.text, n);
@@ -1309,12 +1320,36 @@ ippWrite(http_t *http,          /* I - HTTP data */
                  }
 
                   n = strlen(attr->values[i].string.charset);
+
+                  if ((sizeof(buffer) - (bufptr - buffer)) < (n + 2)
+                 {
+                    if (httpWrite(http, (char *)buffer, bufptr - buffer) < 0)
+                   {
+                     DEBUG_puts("ippWrite: Could not write IPP attribute...");
+                     return (IPP_ERROR);
+                   }
+
+                   bufptr = buffer;
+                 }
+
                  *bufptr++ = n >> 8;
                  *bufptr++ = n;
                  memcpy(bufptr, attr->values[i].string.charset, n);
                  bufptr += n;
 
                   n = strlen(attr->values[i].string.text);
+
+                  if ((sizeof(buffer) - (bufptr - buffer)) < (n + 2)
+                 {
+                    if (httpWrite(http, (char *)buffer, bufptr - buffer) < 0)
+                   {
+                     DEBUG_puts("ippWrite: Could not write IPP attribute...");
+                     return (IPP_ERROR);
+                   }
+
+                   bufptr = buffer;
+                 }
+
                  *bufptr++ = n >> 8;
                  *bufptr++ = n;
                  memcpy(bufptr, attr->values[i].string.text, n);
@@ -1453,5 +1488,5 @@ ipp_read(http_t        *http,     /* I - Client connection */
 
 
 /*
- * End of "$Id: ipp.c,v 1.27 1999/12/21 02:26:45 mike Exp $".
+ * End of "$Id: ipp.c,v 1.28 1999/12/29 02:15:40 mike Exp $".
  */
index e0090eb6a024347caf4f22fbac0b43bc9c644ced..b182851f1f817fb898dd3fa5c5f1d67da4f190ba 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * "$Id: ipp.h,v 1.19 1999/10/12 17:10:04 mike Exp $"
+ * "$Id: ipp.h,v 1.20 1999/12/29 02:15:40 mike Exp $"
  *
  *   Internet Printing Protocol definitions for the Common UNIX Printing
  *   System (CUPS).
@@ -193,7 +193,9 @@ typedef enum                        /**** IPP operations... ****/
   CUPS_DELETE_CLASS,
   CUPS_ACCEPT_JOBS,
   CUPS_REJECT_JOBS,
-  CUPS_SET_DEFAULT
+  CUPS_SET_DEFAULT,
+  CUPS_GET_DEVICES,
+  CUPS_GET_PPDS
 } ipp_op_t;
 
 typedef enum                   /**** IPP status codes... ****/
@@ -339,5 +341,5 @@ extern int          ippPort(void);
 #endif /* !_CUPS_IPP_H_ */
 
 /*
- * End of "$Id: ipp.h,v 1.19 1999/10/12 17:10:04 mike Exp $".
+ * End of "$Id: ipp.h,v 1.20 1999/12/29 02:15:40 mike Exp $".
  */
index 53dc27c2aa1c2c789111223e6bea5c31748f4542..1acc6b397b93b03990d843cb1304a1d67835dd3e 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * "$Id: client.c,v 1.40 1999/12/08 13:15:30 mike Exp $"
+ * "$Id: client.c,v 1.41 1999/12/29 02:15:40 mike Exp $"
  *
  *   Client routines for the Common UNIX Printing System (CUPS) scheduler.
  *
@@ -407,7 +407,7 @@ ReadClient(client_t *con)   /* I - Client to read from */
        return (0);
       }
     }
-    else if (strncmp(con->uri, "..", 2) == 0)
+    else if (strstr(con->uri, "..") != NULL)
     {
      /*
       * Protect against malicious users!
@@ -464,7 +464,8 @@ ReadClient(client_t *con)   /* I - Client to read from */
            }
          }
 
-         if (strncmp(con->uri, "/printers", 9) == 0 ||
+         if (strncmp(con->uri, "/admin", 6) == 0 ||
+             strncmp(con->uri, "/printers", 9) == 0 ||
              strncmp(con->uri, "/classes", 8) == 0 ||
              strncmp(con->uri, "/jobs", 5) == 0)
          {
@@ -472,19 +473,24 @@ ReadClient(client_t *con) /* I - Client to read from */
            * Send CGI output...
            */
 
-            if (strncmp(con->uri, "/printers", 9) == 0)
+            if (strncmp(con->uri, "/admin", 6) == 0)
            {
-             snprintf(command, sizeof(command), "%s/cgi-bin/printers.cgi", ServerRoot);
+             snprintf(command, sizeof(command), "%s/cgi-bin/admin.cgi", ServerBin);
+             options = con->uri + 9;
+           }
+            else if (strncmp(con->uri, "/printers", 9) == 0)
+           {
+             snprintf(command, sizeof(command), "%s/cgi-bin/printers.cgi", ServerBin);
              options = con->uri + 9;
            }
            else if (strncmp(con->uri, "/classes", 8) == 0)
            {
-             snprintf(command, sizeof(command), "%s/cgi-bin/classes.cgi", ServerRoot);
+             snprintf(command, sizeof(command), "%s/cgi-bin/classes.cgi", ServerBin);
              options = con->uri + 8;
            }
            else
            {
-             snprintf(command, sizeof(command), "%s/cgi-bin/jobs.cgi", ServerRoot);
+             snprintf(command, sizeof(command), "%s/cgi-bin/jobs.cgi", ServerBin);
              options = con->uri + 5;
            }
 
@@ -575,7 +581,8 @@ ReadClient(client_t *con)   /* I - Client to read from */
          if (strcmp(con->http.fields[HTTP_FIELD_CONTENT_TYPE], "application/ipp") == 0)
             con->request = ippNew();
          else if (strcmp(con->http.fields[HTTP_FIELD_CONTENT_TYPE], "application/x-www-form-urlencoded") == 0 &&
-                  (strncmp(con->uri, "/printers", 9) == 0 ||
+                  (strncmp(con->uri, "/admin", 6) == 0 ||
+                   strncmp(con->uri, "/printers", 9) == 0 ||
                    strncmp(con->uri, "/classes", 8) == 0 ||
                    strncmp(con->uri, "/jobs", 5) == 0))
          {
@@ -583,19 +590,24 @@ ReadClient(client_t *con) /* I - Client to read from */
            * CGI request...
            */
 
-            if (strncmp(con->uri, "/printers", 9) == 0)
+            if (strncmp(con->uri, "/admin", 9) == 0)
+           {
+             snprintf(command, sizeof(command), "%s/cgi-bin/admin.cgi", ServerBin);
+             options = con->uri + 9;
+           }
+            else if (strncmp(con->uri, "/printers", 9) == 0)
            {
-             snprintf(command, sizeof(command), "%s/cgi-bin/printers", ServerRoot);
+             snprintf(command, sizeof(command), "%s/cgi-bin/printers.cgi", ServerBin);
              options = con->uri + 9;
            }
            else if (strncmp(con->uri, "/classes", 8) == 0)
            {
-             snprintf(command, sizeof(command), "%s/cgi-bin/classes", ServerRoot);
+             snprintf(command, sizeof(command), "%s/cgi-bin/classes.cgi", ServerBin);
              options = con->uri + 8;
            }
            else
            {
-             snprintf(command, sizeof(command), "%s/cgi-bin/jobs", ServerRoot);
+             snprintf(command, sizeof(command), "%s/cgi-bin/jobs.cgi", ServerBin);
              options = con->uri + 5;
            }
 
@@ -644,7 +656,8 @@ ReadClient(client_t *con)   /* I - Client to read from */
            strcpy(con->uri, command);
          }
 
-         if (strncmp(con->uri, "/printers/", 10) == 0 ||
+         if (strncmp(con->uri, "/admin/", 7) == 0 ||
+             strncmp(con->uri, "/printers/", 10) == 0 ||
              strncmp(con->uri, "/classes/", 9) == 0 ||
              strncmp(con->uri, "/jobs/", 6) == 0)
          {
@@ -773,7 +786,7 @@ ReadClient(client_t *con)   /* I - Client to read from */
            * Create a file as needed for the request data...
            */
 
-            snprintf(con->filename, sizeof(con->filename), "%s/requests/XXXXXX", ServerRoot);
+            snprintf(con->filename, sizeof(con->filename), "%s/XXXXXX", RequestRoot);
            con->file = mkstemp(con->filename);
            fchmod(con->file, 0640);
            fchown(con->file, User, Group);
@@ -1044,7 +1057,7 @@ SendHeader(client_t    *con,      /* I - Client to send to */
     return (0);
   if (httpPrintf(HTTP(con), "Date: %s\r\n", httpGetDateString(time(NULL))) < 0)
     return (0);
-  if (httpPrintf(HTTP(con), "Server: CUPS/1.0\r\n") < 0)
+  if (httpPrintf(HTTP(con), "Server: CUPS/1.1\r\n") < 0)
     return (0);
   if (con->http.keep_alive && con->http.version >= HTTP_1_0)
   {
@@ -1429,7 +1442,7 @@ pipe_command(client_t *con,       /* I - Client connection */
   sprintf(tmpdir, "TMPDIR=%s", TempDir);
 
   envp[0]  = "PATH=/bin:/usr/bin";
-  envp[1]  = "SERVER_SOFTWARE=CUPS/1.0";
+  envp[1]  = "SERVER_SOFTWARE=CUPS/1.1";
   envp[2]  = "GATEWAY_INTERFACE=CGI/1.1";
   envp[3]  = "SERVER_PROTOCOL=HTTP/1.1";
   envp[4]  = ipp_port;
@@ -1533,5 +1546,5 @@ pipe_command(client_t *con,       /* I - Client connection */
 
 
 /*
- * End of "$Id: client.c,v 1.40 1999/12/08 13:15:30 mike Exp $".
+ * End of "$Id: client.c,v 1.41 1999/12/29 02:15:40 mike Exp $".
  */
index bf433f82e81915577ac305578c9f42c9c079ae16..44c1a3e6fde6aaf6b3f34939fc05e0a938e6395e 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * "$Id: conf.c,v 1.35 1999/12/07 18:10:18 mike Exp $"
+ * "$Id: conf.c,v 1.36 1999/12/29 02:15:41 mike Exp $"
  *
  *   Configuration routines for the Common UNIX Printing System (CUPS).
  *
@@ -75,7 +75,9 @@ static var_t  variables[] =
   { "ServerName",      ServerName,             VAR_STRING,     sizeof(ServerName) },
   { "ServerAdmin",     ServerAdmin,            VAR_STRING,     sizeof(ServerAdmin) },
   { "ServerRoot",      ServerRoot,             VAR_STRING,     sizeof(ServerRoot) },
+  { "ServerBin",       ServerBin,              VAR_STRING,     sizeof(ServerBin) },
   { "DocumentRoot",    DocumentRoot,           VAR_STRING,     sizeof(DocumentRoot) },
+  { "RequestRoot",     RequestRoot,            VAR_STRING,     sizeof(RequestRoot) },
   { "SystemGroup",     SystemGroup,            VAR_STRING,     sizeof(SystemGroup) },
   { "AccessLog",       AccessLog,              VAR_STRING,     sizeof(AccessLog) },
   { "ErrorLog",                ErrorLog,               VAR_STRING,     sizeof(ErrorLog) },
@@ -95,7 +97,9 @@ static var_t  variables[] =
   { "BrowseTimeout",   &BrowseTimeout,         VAR_INTEGER,    0 },
   { "MaxClients",      &MaxClients,            VAR_INTEGER,    0 },
   { "MaxLogSize",      &MaxLogSize,            VAR_INTEGER,    0 },
-  { "MaxRequestSize",  &MaxRequestSize,        VAR_INTEGER,    0 }
+  { "MaxRequestSize",  &MaxRequestSize,        VAR_INTEGER,    0 },
+  { "PreserveJobHistory", &JobHistory,         VAR_BOOLEAN,    0 },
+  { "PreserveJobFiles",        &JobFiles,              VAR_BOOLEAN,    0 }
 };
 #define NUM_VARS       (sizeof(variables) / sizeof(variables[0]))
 
@@ -140,6 +144,8 @@ ReadConfiguration(void)
     Clients = NULL;
   }
 
+  StopAllJobs();
+
   if (AccessFile != NULL)
   {
     fclose(AccessFile);
@@ -170,7 +176,9 @@ ReadConfiguration(void)
   gethostname(ServerName, sizeof(ServerName));
   sprintf(ServerAdmin, "root@%s", ServerName);
   strcpy(ServerRoot, CUPS_SERVERROOT);
-  strcpy(DocumentRoot, CUPS_DATADIR "/doc");
+  strcpy(ServerBin, CUPS_SERVERBIN);
+  strcpy(RequestRoot, CUPS_REQUESTS);
+  strcpy(DocumentRoot, CUPS_DOCDIR);
   strcpy(AccessLog, "logs/access_log");
   strcpy(ErrorLog, "logs/error_log");
 
@@ -270,6 +278,9 @@ ReadConfiguration(void)
   if (MimeDatabase != NULL)
     mimeDelete(MimeDatabase);
 
+  JobHistory       = DEFAULT_HISTORY;
+  JobFiles         = DEFAULT_FILES;
+
   if ((fp = fopen(ConfigurationFile, "r")) == NULL)
     return (0);
 
@@ -280,6 +291,24 @@ ReadConfiguration(void)
   if (!status)
     return (0);
 
+  if (DocumentRoot[0] != '/')
+  {
+    snprintf(directory, sizeof(directory), "%s/%s", ServerRoot, DocumentRoot);
+    strcpy(DocumentRoot, directory);
+  }
+
+  if (RequestRoot[0] != '/')
+  {
+    snprintf(directory, sizeof(directory), "%s/%s", ServerRoot, RequestRoot);
+    strcpy(RequestRoot, directory);
+  }
+
+  if (ServerBin[0] != '/')
+  {
+    snprintf(directory, sizeof(directory), "%s/%s", ServerRoot, ServerBin);
+    strcpy(ServerBin, directory);
+  }
+
   LogMessage(LOG_DEBUG, "ReadConfiguration() ConfigurationFile=\"%s\"",
              ConfigurationFile);
 
@@ -306,17 +335,18 @@ ReadConfiguration(void)
   * Read the MIME type and conversion database...
   */
 
-  sprintf(directory, "%s/conf", ServerRoot);
+  snprintf(directory, sizeof(directory), "%s/conf", ServerRoot);
 
   MimeDatabase = mimeNew();
   mimeMerge(MimeDatabase, directory);
 
  /*
-  * Load printers and classes...
+  * Load printers, classes, and jobs...
   */
 
   LoadAllPrinters();
   LoadAllClasses();
+  LoadAllJobs();
 
  /*
   * Add a default browser if browsing is enabled and no browser addresses
@@ -344,6 +374,7 @@ ReadConfiguration(void)
   * Check for queued jobs...
   */
 
+  LoadJobs();
   CheckJobs();
 
   return (1);
@@ -941,5 +972,5 @@ get_address(char               *value,              /* I - Value string */
 
 
 /*
- * End of "$Id: conf.c,v 1.35 1999/12/07 18:10:18 mike Exp $".
+ * End of "$Id: conf.c,v 1.36 1999/12/29 02:15:41 mike Exp $".
  */
index 047764163c8af3988ca40dfbd730848ef0c6a9cf..5e2513dbe9f1f1e41316e947b1820a35f6ef08e5 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * "$Id: conf.h,v 1.16 1999/12/07 18:26:15 mike Exp $"
+ * "$Id: conf.h,v 1.17 1999/12/29 02:15:41 mike Exp $"
  *
  *   Configuration file definitions for the Common UNIX Printing System (CUPS)
  *   scheduler.
@@ -47,6 +47,10 @@ VAR char             ConfigurationFile[256]  VALUE(CUPS_SERVERROOT "/conf/cupsd.conf"),
                                        /* Administrator's email */
                        ServerRoot[1024]        VALUE(CUPS_SERVERROOT),
                                        /* Root directory for scheduler */
+                       ServerBin[1024]         VALUE(CUPS_SERVERBIN),
+                                       /* Root directory for binaries */
+                       RequestRoot[1024]       VALUE(CUPS_REQUESTS),
+                                       /* Directory for request files */
                        DocumentRoot[1024]      VALUE(CUPS_DATADIR "/doc"),
                                        /* Root directory for documents */
                        SystemGroup[32],
@@ -108,5 +112,5 @@ extern int  LogPage(job_t *job, char *page);
 
 
 /*
- * End of "$Id: conf.h,v 1.16 1999/12/07 18:26:15 mike Exp $".
+ * End of "$Id: conf.h,v 1.17 1999/12/29 02:15:41 mike Exp $".
  */
index 7c7ed10b0f15a220650027f7259f839c0eeb7990..8572d4c2b4b4b755b82887f70ec9c1904eb324c1 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * "$Id: cupsd.h,v 1.15 1999/12/07 18:26:15 mike Exp $"
+ * "$Id: cupsd.h,v 1.16 1999/12/29 02:15:41 mike Exp $"
  *
  *   Main header file for the Common UNIX Printing System (CUPS) scheduler.
  *
 #define MAX_USERPASS           16      /* Maximum size of username/password */
 #define MAX_FILTERS            20      /* Maximum number of filters */
 
+
 /*
  * Defaults...
  */
 
+#define DEFAULT_HISTORY                1       /* Preserve job history? */
+#define DEFAULT_FILES          0       /* Preserve job files? */
 #define DEFAULT_TIMEOUT                300     /* Timeout during requests/updates */
 #define DEFAULT_KEEPALIVE      60      /* Timeout between requests */
 #define DEFAULT_INTERVAL       30      /* Interval between browse updates */
@@ -129,6 +132,11 @@ VAR int                    NeedReload      VALUE(TRUE);
 VAR char               TZ[1024]        VALUE("TZ=GMT");
                                        /* Timezone configuration */
 
+VAR ipp_t              *Devices        VALUE(NULL),
+                                       /* Available devices */
+                       *PPDs           VALUE(NULL);
+                                       /* Available PPDs */
+
 
 /*
  * Prototypes...
@@ -136,5 +144,5 @@ VAR char            TZ[1024]        VALUE("TZ=GMT");
 
 
 /*
- * End of "$Id: cupsd.h,v 1.15 1999/12/07 18:26:15 mike Exp $".
+ * End of "$Id: cupsd.h,v 1.16 1999/12/29 02:15:41 mike Exp $".
  */
index ba2d8d043f82e4948b3821ddc84f9d2af4c88a0c..f1fa392afdecedf667eed7fbef8ffd7a634d53a7 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * "$Id: ipp.c,v 1.37 1999/12/14 20:41:27 mike Exp $"
+ * "$Id: ipp.c,v 1.38 1999/12/29 02:15:41 mike Exp $"
  *
  *   IPP routines for the Common UNIX Printing System (CUPS) scheduler.
  *
@@ -64,14 +64,20 @@ static void add_printer(client_t *con, ipp_attribute_t *uri);
 static void    cancel_all_jobs(client_t *con, ipp_attribute_t *uri);
 static void    cancel_job(client_t *con, ipp_attribute_t *uri);
 static void    copy_attrs(ipp_t *to, ipp_t *from, ipp_attribute_t *req);
+static void    create_job(client_t *con, ipp_attribute_t *uri);
 static void    delete_printer(client_t *con, ipp_attribute_t *uri);
 static void    get_default(client_t *con);
+static void    get_devices(client_t *con);
 static void    get_jobs(client_t *con, ipp_attribute_t *uri);
 static void    get_job_attrs(client_t *con, ipp_attribute_t *uri);
+static void    get_ppds(client_t *con);
 static void    get_printers(client_t *con, int type);
 static void    get_printer_attrs(client_t *con, ipp_attribute_t *uri);
+static void    hold_job(client_t *con, ipp_attribute_t *uri);
 static void    print_job(client_t *con, ipp_attribute_t *uri);
 static void    reject_jobs(client_t *con, ipp_attribute_t *uri);
+static void    restart_job(client_t *con, ipp_attribute_t *uri);
+static void    send_document(client_t *con, ipp_attribute_t *uri);
 static void    send_ipp_error(client_t *con, ipp_status_t status);
 static void    set_default(client_t *con, ipp_attribute_t *uri);
 static void    start_printer(client_t *con, ipp_attribute_t *uri);
@@ -136,7 +142,7 @@ ProcessIPPRequest(client_t *con)    /* I - Client connection */
        * Out of order; return an error...
        */
 
-       DEBUG_puts("ProcessIPPRequest: attribute groups are out of order!");
+       LogMessage(LOG_ERROR, "ProcessIPPRequest: attribute groups are out of order!");
        send_ipp_error(con, IPP_BAD_REQUEST);
        break;
       }
@@ -193,8 +199,15 @@ ProcessIPPRequest(client_t *con)   /* I - Client connection */
        * for all operations.
        */
 
-       DEBUG_printf(("ProcessIPPRequest: missing attributes (%08x, %08x, %08x)!\n",
-                      charset, language, uri));
+        if (charset == NULL)
+         LogMessage(LOG_ERROR, "ProcessIPPRequest: missing attributes-charset attribute!");
+
+        if (language == NULL)
+         LogMessage(LOG_ERROR, "ProcessIPPRequest: missing attributes-natural-language attribute!");
+
+        if (uri == NULL)
+         LogMessage(LOG_ERROR, "ProcessIPPRequest: missing printer-uri or job-uri attribute!");
+
        send_ipp_error(con, IPP_BAD_REQUEST);
       }
       else
@@ -213,6 +226,14 @@ ProcessIPPRequest(client_t *con)   /* I - Client connection */
               validate_job(con, uri);
               break;
 
+         case IPP_CREATE_JOB :
+              create_job(con, uri);
+              break;
+
+         case IPP_SEND_DOCUMENT :
+              send_document(con, uri);
+              break;
+
          case IPP_CANCEL_JOB :
               cancel_job(con, uri);
               break;
@@ -229,6 +250,14 @@ ProcessIPPRequest(client_t *con)   /* I - Client connection */
               get_printer_attrs(con, uri);
               break;
 
+         case IPP_HOLD_JOB :
+              hold_job(con, uri);
+              break;
+
+         case IPP_RESTART_JOB :
+              restart_job(con, uri);
+              break;
+
          case IPP_PAUSE_PRINTER :
               stop_printer(con, uri);
              break;
@@ -281,6 +310,14 @@ ProcessIPPRequest(client_t *con)   /* I - Client connection */
               set_default(con, uri);
               break;
 
+         case CUPS_GET_DEVICES :
+              get_devices(con);
+              break;
+
+         case CUPS_GET_PPDS :
+              get_ppds(con);
+              break;
+
          default :
               send_ipp_error(con, IPP_OPERATION_NOT_SUPPORTED);
        }
@@ -929,8 +966,8 @@ cancel_all_jobs(client_t        *con,       /* I - Client connection */
 
   if (strcmp(uri->name, "printer-uri") != 0)
   {
-    DEBUG_printf(("cancel_all_jobs: bad %s attribute \'%s\'!\n",
-                  uri->name, uri->values[0].string.text));
+    LogMessage(LOG_ERROR, "cancel_all_jobs: bad %s attribute \'%s\'!",
+               uri->name, uri->values[0].string.text);
     send_ipp_error(con, IPP_BAD_REQUEST);
     return;
   }
@@ -1018,7 +1055,7 @@ cancel_job(client_t        *con,  /* I - Client connection */
 
     if ((attr = ippFindAttribute(con->request, "job-id", IPP_TAG_INTEGER)) == NULL)
     {
-      DEBUG_puts("cancel_job: got a printer-uri attribute but no job-id!");
+      LogMessage(LOG_ERROR, "cancel_job: got a printer-uri attribute but no job-id!");
       send_ipp_error(con, IPP_BAD_REQUEST);
       return;
     }
@@ -1039,8 +1076,8 @@ cancel_job(client_t        *con,  /* I - Client connection */
       * Not a valid URI!
       */
 
-      DEBUG_printf(("cancel_job: bad job-uri attribute \'%s\'!\n",
-                    uri->values[0].string.text));
+      LogMessage(LOG_ERROR, "cancel_job: bad job-uri attribute \'%s\'!",
+                 uri->values[0].string.text);
       send_ipp_error(con, IPP_BAD_REQUEST);
       return;
     }
@@ -1252,6 +1289,143 @@ copy_attrs(ipp_t           *to,         /* I - Destination request */
 }
 
 
+/*
+ * 'create_job()' - Print a file to a printer or class.
+ */
+
+static void
+create_job(client_t        *con,       /* I - Client connection */
+          ipp_attribute_t *uri)        /* I - Printer URI */
+{
+  ipp_attribute_t      *attr;          /* Current attribute */
+  char                 *dest;          /* Destination */
+  cups_ptype_t         dtype;          /* Destination type (printer or class) */
+  int                  priority;       /* Job priority */
+  char                 *title;         /* Job name/title */
+  job_t                        *job;           /* Current job */
+  char                 job_uri[HTTP_MAX_URI],
+                                       /* Job URI */
+                       method[HTTP_MAX_URI],
+                                       /* Method portion of URI */
+                       username[HTTP_MAX_URI],
+                                       /* Username portion of URI */
+                       host[HTTP_MAX_URI],
+                                       /* Host portion of URI */
+                       resource[HTTP_MAX_URI];
+                                       /* Resource portion of URI */
+  int                  port;           /* Port portion of URI */
+  printer_t            *printer;       /* Printer data */
+
+
+  DEBUG_printf(("create_job(%08x, %08x)\n", con, uri));
+
+ /*
+  * Verify that the POST operation was done to a valid URI.
+  */
+
+  if (strncmp(con->uri, "/classes/", 9) != 0 &&
+      strncmp(con->uri, "/printers/", 10) != 0)
+  {
+    LogMessage(LOG_ERROR, "create_job: cancel request on bad resource \'%s\'!",
+               con->uri);
+    send_ipp_error(con, IPP_NOT_AUTHORIZED);
+    return;
+  }
+
+ /*
+  * Is the destination valid?
+  */
+
+  httpSeparate(uri->values[0].string.text, method, username, host, &port, resource);
+
+  if ((dest = validate_dest(resource, &dtype)) == NULL)
+  {
+   /*
+    * Bad URI...
+    */
+
+    LogMessage(LOG_ERROR, "create_job: resource name \'%s\' no good!", resource);
+    send_ipp_error(con, IPP_NOT_FOUND);
+    return;
+  }
+
+ /*
+  * See if the printer is accepting jobs...
+  */
+
+  if (dtype == CUPS_PRINTER_CLASS)
+    printer = FindClass(dest);
+  else
+    printer = FindPrinter(dest);
+
+  if (!printer->accepting)
+  {
+    LogMessage(LOG_INFO, "create_job: destination \'%s\' is not accepting jobs.",
+               dest);
+    send_ipp_error(con, IPP_NOT_ACCEPTING);
+    return;
+  }
+
+ /*
+  * Create the job and set things up...
+  */
+
+  if ((attr = ippFindAttribute(con->request, "job-priority", IPP_TAG_INTEGER)) != NULL)
+    priority = attr->values[0].integer;
+  else
+    priority = 50;
+
+  if ((attr = ippFindAttribute(con->request, "job-name", IPP_TAG_NAME)) != NULL)
+    title = attr->values[0].string.text;
+  else
+    title = "Untitled";
+
+  if ((job = AddJob(priority, printer->name)) == NULL)
+  {
+    LogMessage(LOG_ERROR, "create_job: unable to add job for destination \'%s\'!",
+               dest);
+    send_ipp_error(con, IPP_INTERNAL_ERROR);
+    return;
+  }
+
+  job->dtype   = dtype;
+  job->attrs   = con->request;
+  con->request = NULL;
+
+  strncpy(job->title, title, sizeof(job->title) - 1);
+
+  strcpy(job->username, con->username);
+  if ((attr = ippFindAttribute(job->attrs, "requesting-user-name", IPP_TAG_NAME)) != NULL)
+  {
+    LogMessage(LOG_DEBUG, "create_job: requesting-user-name = \'%s\'",
+               attr->values[0].string.text);
+
+    strncpy(job->username, attr->values[0].string.text, sizeof(job->username) - 1);
+    job->username[sizeof(job->username) - 1] = '\0';
+  }
+
+  if (job->username[0] == '\0')
+    strcpy(job->username, "guest");
+
+  LogMessage(LOG_INFO, "Job %d created on \'%s\' by \'%s\'.", job->id,
+             job->dest, job->username);
+
+ /*
+  * Fill in the response info...
+  */
+
+  sprintf(job_uri, "http://%s:%d/jobs/%d", ServerName,
+         ntohs(con->http.hostaddr.sin_port), job->id);
+  ippAddString(con->response, IPP_TAG_JOB, IPP_TAG_URI, "job-uri", NULL, job_uri);
+
+  ippAddInteger(con->response, IPP_TAG_JOB, IPP_TAG_INTEGER, "job-id", job->id);
+
+  ippAddInteger(con->response, IPP_TAG_JOB, IPP_TAG_ENUM, "job-state", job->state);
+
+  con->response->request.status.status_code = IPP_OK;
+}
+
+
 /*
  * 'delete_printer()' - Remove a printer or class from the system.
  */
@@ -1366,6 +1540,26 @@ get_default(client_t *con)               /* I - Client connection */
 }
 
 
+/*
+ * 'get_devices()' - Get the list of available devices on the local system.
+ */
+
+static void
+get_devices(client_t *con)             /* I - Client connection */
+{
+ /*
+  * Copy the device attributes to the response using the requested-attributes
+  * attribute that may be provided by the client.
+  */
+
+  copy_attrs(con->response, Devices,
+             ippFindAttribute(con->request, "requested-attributes",
+                             IPP_TAG_KEYWORD));
+
+  con->response->request.status.status_code = IPP_OK;
+}
+
+
 /*
  * 'get_jobs()' - Get a list of jobs for the specified printer.
  */
@@ -1386,6 +1580,7 @@ get_jobs(client_t        *con,            /* I - Client connection */
                        resource[HTTP_MAX_URI];
                                        /* Resource portion of URI */
   int                  port;           /* Port portion of URI */
+  int                  completed;      /* Completed jobs? */
   int                  limit;          /* Maximum number of jobs to return */
   int                  count;          /* Number of jobs that match */
   job_t                        *job;           /* Current job pointer */
@@ -1433,10 +1628,9 @@ get_jobs(client_t        *con,           /* I - Client connection */
 
   if ((attr = ippFindAttribute(con->request, "which-jobs", IPP_TAG_KEYWORD)) != NULL &&
       strcmp(attr->values[0].string.text, "completed") == 0)
-  {
-    con->response->request.status.status_code = IPP_OK;
-    return;
-  }
+    completed = 1;
+  else
+    completed = 0;
 
  /*
   * See if they want to limit the number of jobs reported; if not, limit
@@ -1486,6 +1680,11 @@ get_jobs(client_t        *con,           /* I - Client connection */
     if (username[0] != '\0' && strcmp(username, job->username) != 0)
       continue;
 
+    if (completed && job->state <= IPP_JOB_STOPPED)
+      continue;
+    if (!completed && job->state > IPP_JOB_STOPPED)
+      continue;
+
     count ++;
 
     DEBUG_printf(("get_jobs: count = %d\n", count));
@@ -1596,7 +1795,7 @@ get_job_attrs(client_t        *con,               /* I - Client connection */
 
     if ((attr = ippFindAttribute(con->request, "job-id", IPP_TAG_INTEGER)) == NULL)
     {
-      DEBUG_puts("get_job_attrs: got a printer-uri attribute but no job-id!");
+      LogMessage(LOG_ERROR, "get_job_attrs: got a printer-uri attribute but no job-id!");
       send_ipp_error(con, IPP_BAD_REQUEST);
       return;
     }
@@ -1617,8 +1816,8 @@ get_job_attrs(client_t        *con,               /* I - Client connection */
       * Not a valid URI!
       */
 
-      DEBUG_printf(("get_job_attrs: bad job-uri attribute \'%s\'!\n",
-                    uri->values[0].string.text));
+      LogMessage(LOG_ERROR, "get_job_attrs: bad job-uri attribute \'%s\'!\n",
+                 uri->values[0].string.text);
       send_ipp_error(con, IPP_BAD_REQUEST);
       return;
     }
@@ -1636,7 +1835,7 @@ get_job_attrs(client_t        *con,               /* I - Client connection */
     * Nope - return a "not found" error...
     */
 
-    DEBUG_printf(("get_job_attrs: job #%d doesn't exist!\n", jobid));
+    LogMessage(LOG_ERROR, "get_job_attrs: job #%d doesn't exist!", jobid);
     send_ipp_error(con, IPP_NOT_FOUND);
     return;
   }
@@ -1692,6 +1891,26 @@ get_job_attrs(client_t        *con,              /* I - Client connection */
 }
 
 
+/*
+ * 'get_ppds()' - Get the list of PPD files on the local system.
+ */
+
+static void
+get_ppds(client_t *con)                        /* I - Client connection */
+{
+ /*
+  * Copy the PPD attributes to the response using the requested-attributes
+  * attribute that may be provided by the client.
+  */
+
+  copy_attrs(con->response, PPDs,
+             ippFindAttribute(con->request, "requested-attributes",
+                             IPP_TAG_KEYWORD));
+
+  con->response->request.status.status_code = IPP_OK;
+}
+
+
 /*
  * 'get_printers()' - Get a list of printers.
  */
@@ -1843,23 +2062,17 @@ get_printer_attrs(client_t        *con, /* I - Client connection */
 
 
 /*
- * 'print_job()' - Print a file to a printer or class.
+ * 'hold_job()' - Cancel a print job.
  */
 
 static void
-print_job(client_t        *con,                /* I - Client connection */
-         ipp_attribute_t *uri)         /* I - Printer URI */
+hold_job(client_t        *con, /* I - Client connection */
+         ipp_attribute_t *uri) /* I - Job or Printer URI */
 {
+  int                  i;              /* Looping var */
   ipp_attribute_t      *attr;          /* Current attribute */
-  ipp_attribute_t      *format;        /* Document-format attribute */
-  char                 *dest;          /* Destination */
-  cups_ptype_t         dtype;          /* Destination type (printer or class) */
-  int                  priority;       /* Job priority */
-  char                 *title;         /* Job name/title */
-  job_t                        *job;           /* Current job */
-  char                 job_uri[HTTP_MAX_URI],
-                                       /* Job URI */
-                       method[HTTP_MAX_URI],
+  int                  jobid;          /* Job ID */
+  char                 method[HTTP_MAX_URI],
                                        /* Method portion of URI */
                        username[HTTP_MAX_URI],
                                        /* Username portion of URI */
@@ -1868,98 +2081,264 @@ print_job(client_t        *con,                /* I - Client connection */
                        resource[HTTP_MAX_URI];
                                        /* Resource portion of URI */
   int                  port;           /* Port portion of URI */
-  mime_type_t          *filetype;      /* Type of file */
-  char                 super[MIME_MAX_SUPER],
-                                       /* Supertype of file */
-                       type[MIME_MAX_TYPE],
-                                       /* Subtype of file */
-                       mimetype[MIME_MAX_SUPER + MIME_MAX_TYPE + 2];
-                                       /* Textual name of mime type */
-  printer_t            *printer;       /* Printer data */
+  job_t                        *job;           /* Job information */
+  struct passwd                *user;          /* User info */
+  struct group         *group;         /* System group info */
 
 
-  DEBUG_printf(("print_job(%08x, %08x)\n", con, uri));
+  DEBUG_printf(("hold_job(%08x, %08x)\n", con, uri));
 
  /*
   * Verify that the POST operation was done to a valid URI.
   */
 
   if (strncmp(con->uri, "/classes/", 9) != 0 &&
+      strncmp(con->uri, "/jobs/", 5) != 0 &&
       strncmp(con->uri, "/printers/", 10) != 0)
   {
-    LogMessage(LOG_ERROR, "print_job: cancel request on bad resource \'%s\'!",
+    LogMessage(LOG_ERROR, "hold_job: hold request on bad resource \'%s\'!",
                con->uri);
     send_ipp_error(con, IPP_NOT_AUTHORIZED);
     return;
   }
 
  /*
-  * OK, see if the client is sending the document compressed - CUPS
-  * doesn't support compression yet...
-  */
-
-  if ((attr = ippFindAttribute(con->request, "compression", IPP_TAG_KEYWORD)) != NULL)
-  {
-    DEBUG_puts("print_job: Unsupported compression attribute!");
-    send_ipp_error(con, IPP_ATTRIBUTES);
-    attr = ippAddString(con->response, IPP_TAG_UNSUPPORTED_GROUP, IPP_TAG_KEYWORD,
-                       "compression", NULL, attr->values[0].string.text);
-    return;
-  }
-
- /*
-  * Do we have a file to print?
-  */
-
-  if (con->filename[0] == '\0')
-  {
-    DEBUG_puts("print_job: No filename!?!");
-    send_ipp_error(con, IPP_BAD_REQUEST);
-    return;
-  }
-
- /*
-  * Is it a format we support?
+  * See if we have a job URI or a printer URI...
   */
 
-  if ((format = ippFindAttribute(con->request, "document-format", IPP_TAG_MIMETYPE)) != NULL)
+  if (strcmp(uri->name, "printer-uri") == 0)
   {
    /*
-    * Grab format from client...
+    * Got a printer URI; see if we also have a job-id attribute...
     */
 
-    if (sscanf(format->values[0].string.text, "%15[^/]/%31[^;]", super, type) != 2)
+    if ((attr = ippFindAttribute(con->request, "job-id", IPP_TAG_INTEGER)) == NULL)
     {
-      DEBUG_printf(("print_job: could not scan type \'%s\'!\n",
-                   format->values[0].string.text));
+      LogMessage(LOG_ERROR, "hold_job: got a printer-uri attribute but no job-id!");
       send_ipp_error(con, IPP_BAD_REQUEST);
       return;
     }
-  }
-  else
-  {
-   /*
-    * No document format attribute?  Auto-type it!
-    */
 
-    strcpy(super, "application");
-    strcpy(type, "octet-stream");
+    jobid = attr->values[0].integer;
   }
-
-  if (strcmp(super, "application") == 0 &&
-      strcmp(type, "octet-stream") == 0)
+  else
   {
    /*
-    * Auto-type the file...
+    * Got a job URI; parse it to get the job ID...
     */
 
-    DEBUG_puts("print_job: auto-typing request using magic rules.");
-    filetype = mimeFileType(MimeDatabase, con->filename);
-
-    if (filetype != NULL)
+    httpSeparate(uri->values[0].string.text, method, username, host, &port, resource);
+    if (strncmp(resource, "/jobs/", 6) != 0)
     {
      /*
-      * Replace the document-format attribute value with the auto-typed one.
+      * Not a valid URI!
+      */
+
+      LogMessage(LOG_ERROR, "hold_job: bad job-uri attribute \'%s\'!",
+                 uri->values[0].string.text);
+      send_ipp_error(con, IPP_BAD_REQUEST);
+      return;
+    }
+
+    jobid = atoi(resource + 6);
+  }
+
+ /*
+  * See if the job exists...
+  */
+
+  if ((job = FindJob(jobid)) == NULL)
+  {
+   /*
+    * Nope - return a "not found" error...
+    */
+
+    LogMessage(LOG_ERROR, "hold_job: job #%d doesn't exist!", jobid);
+    send_ipp_error(con, IPP_NOT_FOUND);
+    return;
+  }
+
+ /*
+  * See if the job is owned by the requesting user...
+  */
+
+  if ((attr = ippFindAttribute(con->request, "requesting-user-name", IPP_TAG_NAME)) != NULL)
+  {
+    strncpy(username, attr->values[0].string.text, sizeof(username) - 1);
+    username[sizeof(username) - 1] = '\0';
+  }
+  else if (con->username[0])
+    strcpy(username, con->username);
+  else
+    username[0] = '\0';
+
+  if (strcmp(username, job->username) != 0 && strcmp(username, "root") != 0)
+  {
+   /*
+    * Not the owner or root; check to see if the user is a member of the
+    * system group...
+    */
+
+    user = getpwnam(username);
+    endpwent();
+
+    group = getgrnam(SystemGroup);
+    endgrent();
+
+    if (group != NULL)
+      for (i = 0; group->gr_mem[i]; i ++)
+        if (strcmp(username, group->gr_mem[i]) == 0)
+         break;
+
+    if (user == NULL || group == NULL ||
+        (group->gr_mem[i] == NULL && group->gr_gid != user->pw_gid))
+    {
+     /*
+      * Username not found, group not found, or user is not part of the
+      * system group...
+      */
+
+      LogMessage(LOG_ERROR, "hold_job: \"%s\" not authorized to hold job id %d owned by \"%s\"!",
+                username, jobid, job->username);
+      send_ipp_error(con, IPP_FORBIDDEN);
+      return;
+    }
+  }
+
+ /*
+  * Hold the job and return...
+  */
+
+  HoldJob(jobid);
+
+  LogMessage(LOG_INFO, "Job %d was held by \'%s\'.", jobid,
+             con->username[0] ? con->username : "unknown");
+
+  con->response->request.status.status_code = IPP_OK;
+}
+
+
+/*
+ * 'print_job()' - Print a file to a printer or class.
+ */
+
+static void
+print_job(client_t        *con,                /* I - Client connection */
+         ipp_attribute_t *uri)         /* I - Printer URI */
+{
+  ipp_attribute_t      *attr;          /* Current attribute */
+  ipp_attribute_t      *format;        /* Document-format attribute */
+  char                 *dest;          /* Destination */
+  cups_ptype_t         dtype;          /* Destination type (printer or class) */
+  int                  priority;       /* Job priority */
+  char                 *title;         /* Job name/title */
+  job_t                        *job;           /* Current job */
+  char                 job_uri[HTTP_MAX_URI],
+                                       /* Job URI */
+                       method[HTTP_MAX_URI],
+                                       /* Method portion of URI */
+                       username[HTTP_MAX_URI],
+                                       /* Username portion of URI */
+                       host[HTTP_MAX_URI],
+                                       /* Host portion of URI */
+                       resource[HTTP_MAX_URI],
+                                       /* Resource portion of URI */
+                       filename[1024]; /* Job filename */
+  int                  port;           /* Port portion of URI */
+  mime_type_t          *filetype;      /* Type of file */
+  char                 super[MIME_MAX_SUPER],
+                                       /* Supertype of file */
+                       type[MIME_MAX_TYPE],
+                                       /* Subtype of file */
+                       mimetype[MIME_MAX_SUPER + MIME_MAX_TYPE + 2];
+                                       /* Textual name of mime type */
+  printer_t            *printer;       /* Printer data */
+
+
+  DEBUG_printf(("print_job(%08x, %08x)\n", con, uri));
+
+ /*
+  * Verify that the POST operation was done to a valid URI.
+  */
+
+  if (strncmp(con->uri, "/classes/", 9) != 0 &&
+      strncmp(con->uri, "/printers/", 10) != 0)
+  {
+    LogMessage(LOG_ERROR, "print_job: cancel request on bad resource \'%s\'!",
+               con->uri);
+    send_ipp_error(con, IPP_NOT_AUTHORIZED);
+    return;
+  }
+
+ /*
+  * OK, see if the client is sending the document compressed - CUPS
+  * doesn't support compression yet...
+  */
+
+  if ((attr = ippFindAttribute(con->request, "compression", IPP_TAG_KEYWORD)) != NULL)
+  {
+    LogMessage(LOG_ERROR, "print_job: Unsupported compression attribute!");
+    send_ipp_error(con, IPP_ATTRIBUTES);
+    ippAddString(con->response, IPP_TAG_UNSUPPORTED_GROUP, IPP_TAG_KEYWORD,
+                "compression", NULL, attr->values[0].string.text);
+    return;
+  }
+
+ /*
+  * Do we have a file to print?
+  */
+
+  if (con->filename[0] == '\0')
+  {
+    LogMessage(LOG_ERROR, "print_job: No file!?!");
+    send_ipp_error(con, IPP_BAD_REQUEST);
+    return;
+  }
+
+ /*
+  * Is it a format we support?
+  */
+
+  if ((format = ippFindAttribute(con->request, "document-format", IPP_TAG_MIMETYPE)) != NULL)
+  {
+   /*
+    * Grab format from client...
+    */
+
+    if (sscanf(format->values[0].string.text, "%15[^/]/%31[^;]", super, type) != 2)
+    {
+      LogMessage(LOG_ERROR, "print_job: could not scan type \'%s\'!",
+                format->values[0].string.text);
+      send_ipp_error(con, IPP_BAD_REQUEST);
+      return;
+    }
+  }
+  else
+  {
+   /*
+    * No document format attribute?  Auto-type it!
+    */
+
+    strcpy(super, "application");
+    strcpy(type, "octet-stream");
+  }
+
+  if (strcmp(super, "application") == 0 &&
+      strcmp(type, "octet-stream") == 0)
+  {
+   /*
+    * Auto-type the file...
+    */
+
+    LogMessage(LOG_DEBUG, "print_job: auto-typing file...");
+
+    filetype = mimeFileType(MimeDatabase, con->filename);
+
+    if (filetype != NULL)
+    {
+     /*
+      * Replace the document-format attribute value with the auto-typed one.
       */
 
       sprintf(mimetype, "%s/%s", filetype->super, filetype->type);
@@ -1979,16 +2358,16 @@ print_job(client_t        *con,         /* I - Client connection */
 
   if (filetype == NULL)
   {
-    DEBUG_printf(("print_job: Unsupported format \'%s\'!\n",
-                 format->values[0].string.text));
+    LogMessage(LOG_ERROR, "print_job: Unsupported format \'%s\'!",
+              format->values[0].string.text);
     send_ipp_error(con, IPP_DOCUMENT_FORMAT);
-    attr = ippAddString(con->response, IPP_TAG_UNSUPPORTED_GROUP, IPP_TAG_MIMETYPE,
-                        "document-format", NULL, format->values[0].string.text);
+    ippAddString(con->response, IPP_TAG_UNSUPPORTED_GROUP, IPP_TAG_MIMETYPE,
+                 "document-format", NULL, format->values[0].string.text);
     return;
   }
 
-  DEBUG_printf(("print_job: request file type is %s/%s.\n",
-               filetype->super, filetype->type));
+  LogMessage(LOG_DEBUG, "print_job: request file type is %s/%s.",
+            filetype->super, filetype->type);
 
  /*
   * Is the destination valid?
@@ -2048,11 +2427,24 @@ print_job(client_t        *con,         /* I - Client connection */
 
   job->dtype    = dtype;
   job->state    = IPP_JOB_PENDING;
-  job->filetype = filetype;
   job->attrs    = con->request;
   con->request  = NULL;
 
-  strcpy(job->filename, con->filename);
+  if ((filetypes = (mimetype_t **)malloc(sizeof(mimetype_t *))) == NULL)
+  {
+    CancelJob(job->id);
+    LogMessage(LOG_ERROR, "print_job: unable to allocate memory for file types!");
+    send_ipp_error(con, IPP_INTERNAL_ERROR);
+    return;
+  }
+
+  job->filetypes = filetypes;
+  job->filetypes[job->num_files] = filetype;
+
+  job->num_files ++;
+  sprintf(filename, "%s/d%05d-%03d", RequestRoot, job->id, job->num_files);
+  rename(con->filename, filename);
+
   strncpy(job->title, title, sizeof(job->title) - 1);
 
   con->filename[0] = '\0';
@@ -2060,8 +2452,8 @@ print_job(client_t        *con,           /* I - Client connection */
   strcpy(job->username, con->username);
   if ((attr = ippFindAttribute(job->attrs, "requesting-user-name", IPP_TAG_NAME)) != NULL)
   {
-    DEBUG_printf(("print_job: requesting-user-name = \'%s\'\n",
-                  attr->values[0].string.text));
+    LogMessage(LOG_DEBUG, "print_job: requesting-user-name = \'%s\'",
+               attr->values[0].string.text);
 
     strncpy(job->username, attr->values[0].string.text, sizeof(job->username) - 1);
     job->username[sizeof(job->username) - 1] = '\0';
@@ -2070,8 +2462,8 @@ print_job(client_t        *con,           /* I - Client connection */
   if (job->username[0] == '\0')
     strcpy(job->username, "guest");
 
-  DEBUG_printf(("print_job: job->username = \'%s\', attr = %08x\n",
-                job->username, attr));
+  LogMessage(LOG_INFO, "Job %d queued on \'%s\' by \'%s\'.", job->id,
+             job->dest, job->username);
 
  /*
   * Start the job if possible...
@@ -2079,23 +2471,17 @@ print_job(client_t        *con,         /* I - Client connection */
 
   CheckJobs();
 
-  LogMessage(LOG_INFO, "Job %d queued on \'%s\' by \'%s\'.", job->id,
-             job->dest, job->username);
-
  /*
   * Fill in the response info...
   */
 
   sprintf(job_uri, "http://%s:%d/jobs/%d", ServerName,
          ntohs(con->http.hostaddr.sin_port), job->id);
-  attr = ippAddString(con->response, IPP_TAG_JOB, IPP_TAG_URI, "job-uri",
-                      NULL, job_uri);
+  ippAddString(con->response, IPP_TAG_JOB, IPP_TAG_URI, "job-uri", NULL, job_uri);
 
-  attr = ippAddInteger(con->response, IPP_TAG_JOB, IPP_TAG_INTEGER,
-                       "job-id", job->id);
+  ippAddInteger(con->response, IPP_TAG_JOB, IPP_TAG_INTEGER, "job-id", job->id);
 
-  attr = ippAddInteger(con->response, IPP_TAG_JOB, IPP_TAG_ENUM,
-                       "job-state", job->state);
+  ippAddInteger(con->response, IPP_TAG_JOB, IPP_TAG_ENUM, "job-state", job->state);
 
   con->response->request.status.status_code = IPP_OK;
 }
@@ -2191,6 +2577,469 @@ reject_jobs(client_t        *con,       /* I - Client connection */
 }
 
 
+/*
+ * 'restart_job()' - Cancel a print job.
+ */
+
+static void
+restart_job(client_t        *con,      /* I - Client connection */
+         ipp_attribute_t *uri) /* I - Job or Printer URI */
+{
+  int                  i;              /* Looping var */
+  ipp_attribute_t      *attr;          /* Current attribute */
+  int                  jobid;          /* Job ID */
+  char                 method[HTTP_MAX_URI],
+                                       /* Method portion of URI */
+                       username[HTTP_MAX_URI],
+                                       /* Username portion of URI */
+                       host[HTTP_MAX_URI],
+                                       /* Host portion of URI */
+                       resource[HTTP_MAX_URI];
+                                       /* Resource portion of URI */
+  int                  port;           /* Port portion of URI */
+  job_t                        *job;           /* Job information */
+  struct passwd                *user;          /* User info */
+  struct group         *group;         /* System group info */
+
+
+  DEBUG_printf(("restart_job(%08x, %08x)\n", con, uri));
+
+ /*
+  * Verify that the POST operation was done to a valid URI.
+  */
+
+  if (strncmp(con->uri, "/classes/", 9) != 0 &&
+      strncmp(con->uri, "/jobs/", 5) != 0 &&
+      strncmp(con->uri, "/printers/", 10) != 0)
+  {
+    LogMessage(LOG_ERROR, "restart_job: restart request on bad resource \'%s\'!",
+               con->uri);
+    send_ipp_error(con, IPP_NOT_AUTHORIZED);
+    return;
+  }
+
+ /*
+  * See if we have a job URI or a printer URI...
+  */
+
+  if (strcmp(uri->name, "printer-uri") == 0)
+  {
+   /*
+    * Got a printer URI; see if we also have a job-id attribute...
+    */
+
+    if ((attr = ippFindAttribute(con->request, "job-id", IPP_TAG_INTEGER)) == NULL)
+    {
+      LogMessage(LOG_ERROR, "restart_job: got a printer-uri attribute but no job-id!");
+      send_ipp_error(con, IPP_BAD_REQUEST);
+      return;
+    }
+
+    jobid = attr->values[0].integer;
+  }
+  else
+  {
+   /*
+    * Got a job URI; parse it to get the job ID...
+    */
+
+    httpSeparate(uri->values[0].string.text, method, username, host, &port, resource);
+    if (strncmp(resource, "/jobs/", 6) != 0)
+    {
+     /*
+      * Not a valid URI!
+      */
+
+      LogMessage(LOG_ERROR, "restart_job: bad job-uri attribute \'%s\'!",
+                 uri->values[0].string.text);
+      send_ipp_error(con, IPP_BAD_REQUEST);
+      return;
+    }
+
+    jobid = atoi(resource + 6);
+  }
+
+ /*
+  * See if the job exists...
+  */
+
+  if ((job = FindJob(jobid)) == NULL)
+  {
+   /*
+    * Nope - return a "not found" error...
+    */
+
+    LogMessage(LOG_ERROR, "restart_job: job #%d doesn't exist!", jobid);
+    send_ipp_error(con, IPP_NOT_FOUND);
+    return;
+  }
+
+ /*
+  * See if the job is owned by the requesting user...
+  */
+
+  if ((attr = ippFindAttribute(con->request, "requesting-user-name", IPP_TAG_NAME)) != NULL)
+  {
+    strncpy(username, attr->values[0].string.text, sizeof(username) - 1);
+    username[sizeof(username) - 1] = '\0';
+  }
+  else if (con->username[0])
+    strcpy(username, con->username);
+  else
+    username[0] = '\0';
+
+  if (strcmp(username, job->username) != 0 && strcmp(username, "root") != 0)
+  {
+   /*
+    * Not the owner or root; check to see if the user is a member of the
+    * system group...
+    */
+
+    user = getpwnam(username);
+    endpwent();
+
+    group = getgrnam(SystemGroup);
+    endgrent();
+
+    if (group != NULL)
+      for (i = 0; group->gr_mem[i]; i ++)
+        if (strcmp(username, group->gr_mem[i]) == 0)
+         break;
+
+    if (user == NULL || group == NULL ||
+        (group->gr_mem[i] == NULL && group->gr_gid != user->pw_gid))
+    {
+     /*
+      * Username not found, group not found, or user is not part of the
+      * system group...
+      */
+
+      LogMessage(LOG_ERROR, "restart_job: \"%s\" not authorized to restart job id %d owned by \"%s\"!",
+                username, jobid, job->username);
+      send_ipp_error(con, IPP_FORBIDDEN);
+      return;
+    }
+  }
+
+ /*
+  * Restart the job and return...
+  */
+
+  RestartJob(jobid);
+
+  LogMessage(LOG_INFO, "Job %d was restarted by \'%s\'.", jobid,
+             con->username[0] ? con->username : "unknown");
+
+  con->response->request.status.status_code = IPP_OK;
+}
+
+
+/*
+ * 'send_document()' - Send a file to a printer or class.
+ */
+
+static void
+send_document(client_t        *con,    /* I - Client connection */
+             ipp_attribute_t *uri)     /* I - Printer URI */
+{
+  ipp_attribute_t      *attr;          /* Current attribute */
+  ipp_attribute_t      *format;        /* Document-format attribute */
+  char                 *dest;          /* Destination */
+  cups_ptype_t         dtype;          /* Destination type (printer or class) */
+  int                  priority;       /* Job priority */
+  char                 *title;         /* Job name/title */
+  int                  jobid;          /* Job ID number */
+  job_t                        *job;           /* Current job */
+  char                 job_uri[HTTP_MAX_URI],
+                                       /* Job URI */
+                       method[HTTP_MAX_URI],
+                                       /* Method portion of URI */
+                       username[HTTP_MAX_URI],
+                                       /* Username portion of URI */
+                       host[HTTP_MAX_URI],
+                                       /* Host portion of URI */
+                       resource[HTTP_MAX_URI];
+                                       /* Resource portion of URI */
+  int                  port;           /* Port portion of URI */
+  mime_type_t          *filetype,      /* Type of file */
+                       **filetypes;    /* File types array */
+  char                 super[MIME_MAX_SUPER],
+                                       /* Supertype of file */
+                       type[MIME_MAX_TYPE],
+                                       /* Subtype of file */
+                       mimetype[MIME_MAX_SUPER + MIME_MAX_TYPE + 2];
+                                       /* Textual name of mime type */
+  printer_t            *printer;       /* Printer data */
+
+
+  DEBUG_printf(("send_document(%08x, %08x)\n", con, uri));
+
+ /*
+  * Verify that the POST operation was done to a valid URI.
+  */
+
+  if (strncmp(con->uri, "/classes/", 9) != 0 &&
+      strncmp(con->uri, "/jobs/", 6) != 0 &&
+      strncmp(con->uri, "/printers/", 10) != 0)
+  {
+    LogMessage(LOG_ERROR, "send_document: print request on bad resource \'%s\'!",
+               con->uri);
+    send_ipp_error(con, IPP_NOT_AUTHORIZED);
+    return;
+  }
+
+ /*
+  * See if we have a job URI or a printer URI...
+  */
+
+  if (strcmp(uri->name, "printer-uri") == 0)
+  {
+   /*
+    * Got a printer URI; see if we also have a job-id attribute...
+    */
+
+    if ((attr = ippFindAttribute(con->request, "job-id", IPP_TAG_INTEGER)) == NULL)
+    {
+      LogMessage(LOG_ERROR, "send_document: got a printer-uri attribute but no job-id!");
+      send_ipp_error(con, IPP_BAD_REQUEST);
+      return;
+    }
+
+    jobid = attr->values[0].integer;
+  }
+  else
+  {
+   /*
+    * Got a job URI; parse it to get the job ID...
+    */
+
+    httpSeparate(uri->values[0].string.text, method, username, host, &port, resource);
+    if (strncmp(resource, "/jobs/", 6) != 0)
+    {
+     /*
+      * Not a valid URI!
+      */
+
+      LogMessage(LOG_ERROR, "send_document: bad job-uri attribute \'%s\'!",
+                 uri->values[0].string.text);
+      send_ipp_error(con, IPP_BAD_REQUEST);
+      return;
+    }
+
+    jobid = atoi(resource + 6);
+  }
+
+ /*
+  * See if the job exists...
+  */
+
+  if ((job = FindJob(jobid)) == NULL)
+  {
+   /*
+    * Nope - return a "not found" error...
+    */
+
+    LogMessage(LOG_ERROR, "send_document: job #%d doesn't exist!", jobid);
+    send_ipp_error(con, IPP_NOT_FOUND);
+    return;
+  }
+
+ /*
+  * See if the job is owned by the requesting user...
+  */
+
+  if ((attr = ippFindAttribute(con->request, "requesting-user-name", IPP_TAG_NAME)) != NULL)
+  {
+    strncpy(username, attr->values[0].string.text, sizeof(username) - 1);
+    username[sizeof(username) - 1] = '\0';
+  }
+  else if (con->username[0])
+    strcpy(username, con->username);
+  else
+    username[0] = '\0';
+
+  if (strcmp(username, job->username) != 0 && strcmp(username, "root") != 0)
+  {
+   /*
+    * Not the owner or root; check to see if the user is a member of the
+    * system group...
+    */
+
+    user = getpwnam(username);
+    endpwent();
+
+    group = getgrnam(SystemGroup);
+    endgrent();
+
+    if (group != NULL)
+      for (i = 0; group->gr_mem[i]; i ++)
+        if (strcmp(username, group->gr_mem[i]) == 0)
+         break;
+
+    if (user == NULL || group == NULL ||
+        (group->gr_mem[i] == NULL && group->gr_gid != user->pw_gid))
+    {
+     /*
+      * Username not found, group not found, or user is not part of the
+      * system group...
+      */
+
+      LogMessage(LOG_ERROR, "send_document: \"%s\" not authorized to send document for job id %d owned by \"%s\"!",
+                username, jobid, job->username);
+      send_ipp_error(con, IPP_FORBIDDEN);
+      return;
+    }
+  }
+
+ /*
+  * OK, see if the client is sending the document compressed - CUPS
+  * doesn't support compression yet...
+  */
+
+  if ((attr = ippFindAttribute(con->request, "compression", IPP_TAG_KEYWORD)) != NULL)
+  {
+    LogMessage(LOG_ERROR, "send_document: Unsupported compression attribute!");
+    send_ipp_error(con, IPP_ATTRIBUTES);
+    ippAddString(con->response, IPP_TAG_UNSUPPORTED_GROUP, IPP_TAG_KEYWORD,
+                "compression", NULL, attr->values[0].string.text);
+    return;
+  }
+
+ /*
+  * Do we have a file to print?
+  */
+
+  if (con->filename[0] == '\0')
+  {
+    LogMessage(LOG_ERROR, "send_document: No file!?!");
+    send_ipp_error(con, IPP_BAD_REQUEST);
+    return;
+  }
+
+ /*
+  * Is it a format we support?
+  */
+
+  if ((format = ippFindAttribute(con->request, "document-format", IPP_TAG_MIMETYPE)) != NULL)
+  {
+   /*
+    * Grab format from client...
+    */
+
+    if (sscanf(format->values[0].string.text, "%15[^/]/%31[^;]", super, type) != 2)
+    {
+      LogMessage(LOG_ERROR, "send_document: could not scan type \'%s\'!",
+                format->values[0].string.text);
+      send_ipp_error(con, IPP_BAD_REQUEST);
+      return;
+    }
+  }
+  else
+  {
+   /*
+    * No document format attribute?  Auto-type it!
+    */
+
+    strcpy(super, "application");
+    strcpy(type, "octet-stream");
+  }
+
+  if (strcmp(super, "application") == 0 &&
+      strcmp(type, "octet-stream") == 0)
+  {
+   /*
+    * Auto-type the file...
+    */
+
+    LogMessage(LOG_DEBUG, "send_document: auto-typing file...");
+
+    filetype = mimeFileType(MimeDatabase, con->filename);
+
+    if (filetype != NULL)
+    {
+     /*
+      * Replace the document-format attribute value with the auto-typed one.
+      */
+
+      sprintf(mimetype, "%s/%s", filetype->super, filetype->type);
+
+      if (format != NULL)
+      {
+       free(format->values[0].string.text);
+       format->values[0].string.text = strdup(mimetype);
+      }
+      else
+        ippAddString(con->request, IPP_TAG_JOB, IPP_TAG_MIMETYPE,
+                    "document-format", NULL, mimetype);
+    }
+  }
+  else
+    filetype = mimeType(MimeDatabase, super, type);
+
+  if (filetype == NULL)
+  {
+    LogMessage(LOG_ERROR, "send_document: Unsupported format \'%s\'!",
+              format->values[0].string.text);
+    send_ipp_error(con, IPP_DOCUMENT_FORMAT);
+    ippAddString(con->response, IPP_TAG_UNSUPPORTED_GROUP, IPP_TAG_MIMETYPE,
+                 "document-format", NULL, format->values[0].string.text);
+    return;
+  }
+
+  LogMessage(LOG_DEBUG, "send_document: request file type is %s/%s.",
+            filetype->super, filetype->type);
+
+ /*
+  * Add the file to the job...
+  */
+
+  if (job->num_files == 0)
+    filetypes = (mimetype_t **)malloc(sizeof(mimetype_t *));
+  else
+    filetypes = (mimetype_t **)realloc(job->filetypes,
+                                       (job->num_files + 1) *
+                                      sizeof(mimetype_t));
+
+  if (filetypes == NULL)
+  {
+    CancelJob(job->id);
+    LogMessage(LOG_ERROR, "send_document: unable to allocate memory for file types!");
+    send_ipp_error(con, IPP_INTERNAL_ERROR);
+    return;
+  }
+
+  job->filetypes = filetypes;
+  job->filetypes[job->num_files] = filetype;
+
+  job->num_files ++;
+  sprintf(filename, "%s/d%05d-%03d", RequestRoot, job->id, job->num_files);
+  rename(con->filename, filename);
+
+  strncpy(job->title, title, sizeof(job->title) - 1);
+
+  con->filename[0] = '\0';
+
+  LogMessage(LOG_INFO, "File queued in job #%d by \'%s\'.", job->id,
+             job->username);
+
+ /*
+  * Fill in the response info...
+  */
+
+  sprintf(job_uri, "http://%s:%d/jobs/%d", ServerName,
+         ntohs(con->http.hostaddr.sin_port), job->id);
+  ippAddString(con->response, IPP_TAG_JOB, IPP_TAG_URI, "job-uri", NULL, job_uri);
+
+  ippAddInteger(con->response, IPP_TAG_JOB, IPP_TAG_INTEGER, "job-id", job->id);
+
+  ippAddInteger(con->response, IPP_TAG_JOB, IPP_TAG_ENUM, "job-state", job->state);
+
+  con->response->request.status.status_code = IPP_OK;
+}
+
+
 /*
  * 'send_ipp_error()' - Send an error status back to the IPP client.
  */
@@ -2557,10 +3406,10 @@ validate_job(client_t        *con,      /* I - Client connection */
 
   if ((attr = ippFindAttribute(con->request, "compression", IPP_TAG_KEYWORD)) != NULL)
   {
-    DEBUG_puts("validate_job: Unsupported compression attribute!");
+    LogMessage(LOG_ERROR, "validate_job: Unsupported compression attribute!");
     send_ipp_error(con, IPP_ATTRIBUTES);
-    attr = ippAddString(con->response, IPP_TAG_UNSUPPORTED_GROUP, IPP_TAG_KEYWORD,
-                       "compression", NULL, attr->values[0].string.text);
+    ippAddString(con->response, IPP_TAG_UNSUPPORTED_GROUP, IPP_TAG_KEYWORD,
+                "compression", NULL, attr->values[0].string.text);
     return;
   }
 
@@ -2570,15 +3419,15 @@ validate_job(client_t        *con,      /* I - Client connection */
 
   if ((format = ippFindAttribute(con->request, "document-format", IPP_TAG_MIMETYPE)) == NULL)
   {
-    DEBUG_puts("validate_job: missing document-format attribute!");
+    LogError(LOG_ERROR, "validate_job: missing document-format attribute!");
     send_ipp_error(con, IPP_BAD_REQUEST);
     return;
   }
 
   if (sscanf(format->values[0].string.text, "%15[^/]/%31[^;]", super, type) != 2)
   {
-    DEBUG_printf(("validate_job: could not scan type \'%s\'!\n",
-                 format->values[0].string.text));
+    LogMessage(LOG_ERROR, "validate_job: could not scan type \'%s\'!\n",
+              format->values[0].string.text);
     send_ipp_error(con, IPP_BAD_REQUEST);
     return;
   }
@@ -2587,11 +3436,11 @@ validate_job(client_t        *con,      /* I - Client connection */
        strcmp(type, "octet-stream") != 0) &&
       mimeType(MimeDatabase, super, type) == NULL)
   {
-    DEBUG_printf(("validate_job: Unsupported format \'%s\'!\n",
-                 format->values[0].string.text));
+    LogMessage(LOG_ERROR, "validate_job: Unsupported format \'%s\'!\n",
+              format->values[0].string.text);
     send_ipp_error(con, IPP_DOCUMENT_FORMAT);
-    attr = ippAddString(con->response, IPP_TAG_UNSUPPORTED_GROUP, IPP_TAG_MIMETYPE,
-                        "document-format", NULL, format->values[0].string.text);
+    ippAddString(con->response, IPP_TAG_UNSUPPORTED_GROUP, IPP_TAG_MIMETYPE,
+                 "document-format", NULL, format->values[0].string.text);
     return;
   }
 
@@ -2621,5 +3470,5 @@ validate_job(client_t        *con,        /* I - Client connection */
 
 
 /*
- * End of "$Id: ipp.c,v 1.37 1999/12/14 20:41:27 mike Exp $".
+ * End of "$Id: ipp.c,v 1.38 1999/12/29 02:15:41 mike Exp $".
  */
index 82ad65b9cb0692d2397596d51df7e621c3203071..23ad091e56672199f09638aabcd19f92e6cb3d4a 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * "$Id: job.c,v 1.43 1999/12/21 02:26:48 mike Exp $"
+ * "$Id: job.c,v 1.44 1999/12/29 02:15:42 mike Exp $"
  *
  *   Job management routines for the Common UNIX Printing System (CUPS).
  *
  *
  * Contents:
  *
- *   AddJob()        - Add a new job to the job queue...
- *   CancelJob()     - Cancel the specified print job.
- *   CancelJobs()    - Cancel all jobs on the given printer or class.
- *   CheckJobs()     - Check the pending jobs and start any if the destination
- *                     is available.
- *   FindJob()       - Find the specified job.
- *   MoveJob()       - Move the specified job to a different destination.
- *   StartJob()      - Start a print job.
- *   StopJob()       - Stop a print job.
- *   UpdateJob()     - Read a status update from a job's filters.
- *   start_process() - Start a background process.
+ *   AddJob()         - Add a new job to the job queue...
+ *   CancelJob()      - Cancel the specified print job.
+ *   CancelJobs()     - Cancel all jobs on the given printer or class.
+ *   CheckJobs()      - Check the pending jobs and start any if the destination
+ *                      is available.
+ *   FindJob()        - Find the specified job.
+ *   HoldJob()        - Hold the specified job.
+ *   LoadAllJobs()    - Load all jobs from disk.
+ *   LoadJob()        - Load a job from disk.
+ *   MoveJob()        - Move the specified job to a different destination.
+ *   RestartJob()     - Resume the specified job.
+ *   SaveJob()        - Save a job to disk.
+ *   StartJob()       - Start a print job.
+ *   StopAllJobs()    - Stop all print jobs.
+ *   StopJob()        - Stop a print job.
+ *   UpdateJob()      - Read a status update from a job's filters.
+ *   ipp_read_file()  - Read an IPP request from a file.
+ *   ipp_write_file() - Write an IPP request to a file.
+ *   start_process()  - Start a background process.
  */
 
 /*
  * Local functions...
  */
 
-static int     start_process(char *command, char *argv[], char *envp[],
-                             int in, int out, int err);
+static ipp_state_t     ipp_read_file(const char *filename, ipp_t *request);
+static ipp_state_t     ipp_write_file(const char *filename, ipp_t *request);
+static int             start_process(const char *command, const char *argv[],
+                                     const char *envp[], int in, int out, int err);
 
 
 /*
  * 'AddJob()' - Add a new job to the job queue...
  */
 
-job_t *                        /* O - New job record */
-AddJob(int  priority,  /* I - Job priority */
-       char *dest)     /* I - Job destination */
+job_t *                                /* O - New job record */
+AddJob(int        priority,    /* I - Job priority */
+       const char *dest)       /* I - Job destination */
 {
-  job_t        *job,           /* New job record */
-       *current,       /* Current job in queue */
-       *prev;          /* Previous job in queue */
+  job_t        *job,                   /* New job record */
+       *current,               /* Current job in queue */
+       *prev;                  /* Previous job in queue */
 
 
   job = calloc(sizeof(job_t), 1);
@@ -92,10 +102,12 @@ AddJob(int  priority,      /* I - Job priority */
  */
 
 void
-CancelJob(int id)      /* I - Job to cancel */
+CancelJob(int id)              /* I - Job to cancel */
 {
-  job_t        *current,       /* Current job */
-       *prev;          /* Previous job in list */
+  int  i;                      /* Looping var */
+  job_t        *current,               /* Current job */
+       *prev;                  /* Previous job in list */
+  char filename[1024];         /* Job filename */
 
 
   DEBUG_printf(("CancelJob(%d)\n", id));
@@ -110,30 +122,53 @@ CancelJob(int id) /* I - Job to cancel */
       DEBUG_puts("CancelJob: found job in list.");
 
       if (current->state == IPP_JOB_PROCESSING)
+      {
        StopJob(current->id);
+       current->state = IPP_JOB_CANCELLED;
+      }
 
      /*
-      * Update pointers...
+      * Remove the print file for good if we aren't preserving jobs or
+      * files...
       */
 
-      if (prev == NULL)
-        Jobs = current->next;
+      if (!JobHistory || !JobFiles)
+        for (i = 1; i <= current->num_files; i ++)
+       {
+         snprintf(filename, sizeof(filename), "%s/d%05d-%03d", RequestRoot,
+                  current->id, i);
+          unlink(filename);
+       }
+
+      if (JobHistory)
+      {
+       /*
+        * Save job state info...
+       */
+
+        SaveJob(current->id);
+      }
       else
-        prev->next = current->next;
+      {
+       /*
+        * Update pointers if we aren't preserving jobs...
+        */
 
-     /*
-      * Free all memory used...
-      */
+        if (prev == NULL)
+          Jobs = current->next;
+        else
+          prev->next = current->next;
 
-      if (current->attrs != NULL)
-        ippDelete(current->attrs);
+       /*
+        * Free all memory used...
+        */
 
-     /*
-      * Remove the print file for good...
-      */
+        if (current->attrs != NULL)
+          ippDelete(current->attrs);
+
+        free(current);
+      }
 
-      unlink(current->filename);
-      free(current);
       return;
     }
 }
@@ -144,10 +179,10 @@ CancelJob(int id) /* I - Job to cancel */
  */
 
 void
-CancelJobs(char *dest) /* I - Destination to cancel */
+CancelJobs(const char *dest)   /* I - Destination to cancel */
 {
-  job_t        *current,       /* Current job */
-       *prev;          /* Previous job in list */
+  job_t        *current,               /* Current job */
+       *prev;                  /* Previous job in list */
 
 
   for (current = Jobs, prev = NULL; current != NULL; prev = current)
@@ -179,9 +214,9 @@ CancelJobs(char *dest)      /* I - Destination to cancel */
 void
 CheckJobs(void)
 {
-  job_t                *current,
-               *prev;
-  printer_t    *printer;
+  job_t                *current,       /* Current job in queue */
+               *prev;          /* Previous job in queue */
+  printer_t    *printer;       /* Printer/class destination */
 
 
   DEBUG_puts("CheckJobs()");
@@ -190,7 +225,11 @@ CheckJobs(void)
   {
     DEBUG_printf(("CheckJobs: current->state = %d\n", current->state));
 
-    if (current->state != IPP_JOB_PROCESSING)
+   /*
+    * Start pending jobs if the destination is available...
+    */
+
+    if (current->state == IPP_JOB_PENDING)
     {
       DEBUG_printf(("CheckJobs: current->dest = \'%s\'\n", current->dest));
 
@@ -241,10 +280,10 @@ CheckJobs(void)
  * 'FindJob()' - Find the specified job.
  */
 
-job_t *                        /* O - Job data */
-FindJob(int id)                /* I - Job ID */
+job_t *                                /* O - Job data */
+FindJob(int id)                        /* I - Job ID */
 {
-  job_t        *current;       /* Current job */
+  job_t        *current;               /* Current job */
 
 
   for (current = Jobs; current != NULL; current = current->next)
@@ -255,14 +294,57 @@ FindJob(int id)           /* I - Job ID */
 }
 
 
+/*
+ * 'HoldJob()' - Hold the specified job.
+ */
+
+void
+HoldJob(int id)                        /* I - Job ID */
+{
+  job_t        *job;                   /* Job data */
+
+
+  if ((job = FindJob(id)) == NULL)
+    return;
+
+  if (job->state == IPP_JOB_PROCESSING)
+    StopJob(id);
+
+  job->state = IPP_JOB_HELD;
+
+  CheckJobs();
+}
+
+
+/*
+ * 'LoadAllJobs()' - Load all jobs from disk.
+ */
+
+void
+LoadAllJobs(void)
+{
+}
+
+
+/*
+ * 'LoadJob()' - Load a job from disk.
+ */
+
+void
+LoadJob(int id)                        /* I - Job ID */
+{
+}
+
+
 /*
  * 'MoveJob()' - Move the specified job to a different destination.
  */
 
 void
-MoveJob(int id, char *dest)
+MoveJob(int        id,         /* I - Job ID */
+        const char *dest)      /* I - Destination */
 {
-  job_t        *current;       /* Current job */
+  job_t        *current;               /* Current job */
 
 
   for (current = Jobs; current != NULL; current = current->next)
@@ -276,6 +358,37 @@ MoveJob(int id, char *dest)
 }
 
 
+/*
+ * 'RestartJob()' - Resume the specified job.
+ */
+
+void
+RestartJob(int id)             /* I - Job ID */
+{
+  job_t        *job;                   /* Job data */
+
+
+  if ((job = FindJob(id)) == NULL)
+    return;
+
+  if (job->state == IPP_JOB_HELD)
+  {
+    job->state = IPP_JOB_PENDING;
+    CheckJobs();
+  }
+}
+
+
+/*
+ * 'SaveJob()' - Save a job to disk.
+ */
+
+void
+SaveJob(int id)                        /* I - Job ID */
+{
+}
+
+
 /*
  * 'StartJob()' - Start a print job.
  */
@@ -540,7 +653,7 @@ StartJob(int       id,              /* I - Job ID */
   sprintf(tmpdir, "TMPDIR=%s", TempDir);
 
   envp[0]  = "PATH=/bin:/usr/bin";
-  envp[1]  = "SOFTWARE=CUPS/1.0";
+  envp[1]  = "SOFTWARE=CUPS/1.1";
   envp[2]  = "TZ=GMT";
   envp[3]  = "USER=root";
   envp[4]  = charset;
@@ -718,15 +831,25 @@ StartJob(int       id,            /* I - Job ID */
 }
 
 
+/*
+ * 'StopAllJobs()' - Stop all print jobs.
+ */
+
+void
+StopAllJobs(void)
+{
+}
+
+
 /*
  * 'StopJob()' - Stop a print job.
  */
 
 void
-StopJob(int id)
+StopJob(int id)                        /* I - Job ID */
 {
-  int  i;              /* Looping var */
-  job_t        *current;       /* Current job */
+  int  i;                      /* Looping var */
+  job_t        *current;               /* Current job */
 
 
   DEBUG_printf(("StopJob(%d)\n", id));
@@ -877,13 +1000,27 @@ UpdateJob(job_t *job)            /* I - Job to check */
   {
     DEBUG_printf(("UpdateJob: job %d is complete.\n", job->id));
 
-    if (job->status)
+    if (job->status > 0)
     {
      /*
-      * Job had errors; stop it...
+      * Backend had errors; stop it...
       */
 
       StopJob(job->id);
+      job->state = IPP_JOB_PENDING;
+    }
+    else if (job->status > 0)
+    {
+     /*
+      * Filter had errors; cancel it...
+      */
+
+      CancelJob(job->id);
+
+      if (JobHistory)
+        job->state = IPP_JOB_ABORTED;
+
+      CheckJobs();
     }
     else
     {
@@ -894,26 +1031,729 @@ UpdateJob(job_t *job)           /* I - Job to check */
       job->printer->state_message[0] = '\0';
 
       CancelJob(job->id);
+
+      if (JobHistory)
+        job->state = IPP_JOB_COMPLETED;
+
       CheckJobs();
     }
   }
 }
 
 
+/*
+ * 'ipp_read_file()' - Read an IPP request from a file.
+ */
+
+static ipp_state_t                     /* O - State */
+ipp_read_file(const char *filename,    /* I - File to read from */
+              ipp_t      *request)     /* I - Request to read into */
+{
+  int                  fd;             /* File descriptor for file */
+  int                  n;              /* Length of data */
+  unsigned char                buffer[8192];   /* Data buffer */
+  ipp_attribute_t      *attr;          /* Current attribute */
+  ipp_tag_t            tag;            /* Current tag */
+
+
+ /*
+  * Open the file if possible...
+  */
+
+  if (filename == NULL || request == NULL)
+    return (IPP_ERROR);
+
+  if ((fd = open(filename, O_RDONLY)) == -1)
+    return (IPP_ERROR);
+
+ /*
+  * Read the IPP request...
+  */
+
+  ipp->state = IPP_IDLE;
+
+  switch (ipp->state)
+  {
+    case IPP_IDLE :
+        ipp->state ++; /* Avoid common problem... */
+
+    case IPP_HEADER :
+       /*
+        * Get the request header...
+       */
+
+        if ((n = read(fd, buffer, 8)) < 8)
+       {
+         DEBUG_printf(("ippRead: Unable to read header (%d bytes read)!\n", n));
+         close(fd);
+         return (n == 0 ? IPP_IDLE : IPP_ERROR);
+       }
+
+       /*
+        * Verify the major version number...
+       */
+
+       if (buffer[0] != 1)
+       {
+         DEBUG_printf(("ippRead: version number (%d.%d) is bad.\n", buffer[0],
+                       buffer[1]));
+         close(fd);
+         return (IPP_ERROR);
+       }
+
+       /*
+        * Then copy the request header over...
+       */
+
+        ipp->request.any.version[0]  = buffer[0];
+        ipp->request.any.version[1]  = buffer[1];
+        ipp->request.any.op_status   = (buffer[2] << 8) | buffer[3];
+        ipp->request.any.request_id  = (((((buffer[4] << 8) | buffer[5]) << 8) |
+                                      buffer[6]) << 8) | buffer[7];
+
+        ipp->state   = IPP_ATTRIBUTE;
+       ipp->current = NULL;
+       ipp->curtag  = IPP_TAG_ZERO;
+
+    case IPP_ATTRIBUTE :
+        while (read(fd, buffer, 1) > 0)
+       {
+        /*
+         * Read this attribute...
+         */
+
+          tag = (ipp_tag_t)buffer[0];
+
+         if (tag == IPP_TAG_END)
+         {
+          /*
+           * No more attributes left...
+           */
+
+            DEBUG_puts("ippRead: IPP_TAG_END!");
+
+           ipp->state = IPP_DATA;
+           break;
+         }
+          else if (tag < IPP_TAG_UNSUPPORTED_VALUE)
+         {
+          /*
+           * Group tag...  Set the current group and continue...
+           */
+
+            if (ipp->curtag == tag)
+             ippAddSeparator(ipp);
+
+           ipp->curtag  = tag;
+           ipp->current = NULL;
+           DEBUG_printf(("ippRead: group tag = %x\n", tag));
+           continue;
+         }
+
+          DEBUG_printf(("ippRead: value tag = %x\n", tag));
+
+         /*
+         * Get the name...
+         */
+
+          if (read(fd, buffer, 2) < 2)
+         {
+           DEBUG_puts("ippRead: unable to read name length!");
+           close(fd);
+           return (IPP_ERROR);
+         }
+
+          n = (buffer[0] << 8) | buffer[1];
+
+          DEBUG_printf(("ippRead: name length = %d\n", n));
+
+          if (n == 0)
+         {
+          /*
+           * More values for current attribute...
+           */
+
+            if (ipp->current == NULL)
+           {
+             close(fd);
+              return (IPP_ERROR);
+           }
+
+            attr = ipp->current;
+
+           if (attr->num_values >= IPP_MAX_VALUES)
+           {
+             close(fd);
+              return (IPP_ERROR);
+           }
+         }
+         else
+         {
+          /*
+           * New attribute; read the name and add it...
+           */
+
+           if (read(fd, buffer, n) < n)
+           {
+             DEBUG_puts("ippRead: unable to read name!");
+             close(fd);
+             return (IPP_ERROR);
+           }
+
+           buffer[n] = '\0';
+           DEBUG_printf(("ippRead: name = \'%s\'\n", buffer));
+
+           attr = ipp->current = add_attr(ipp, IPP_MAX_VALUES);
+
+           attr->group_tag  = ipp->curtag;
+           attr->value_tag  = tag;
+           attr->name       = strdup((char *)buffer);
+           attr->num_values = 0;
+         }
+
+         if (read(fd, buffer, 2) < 2)
+         {
+           DEBUG_puts("ippRead: unable to read value length!");
+           close(fd);
+           return (IPP_ERROR);
+         }
+
+         n = (buffer[0] << 8) | buffer[1];
+          DEBUG_printf(("ippRead: value length = %d\n", n));
+
+         switch (tag)
+         {
+           case IPP_TAG_INTEGER :
+           case IPP_TAG_ENUM :
+               if (read(fd, buffer, 4) < 4)
+               {
+                 close(fd);
+                  return (IPP_ERROR);
+               }
+
+               n = (((((buffer[0] << 8) | buffer[1]) << 8) | buffer[2]) << 8) |
+                   buffer[3];
+
+                attr->values[attr->num_values].integer = n;
+               break;
+           case IPP_TAG_BOOLEAN :
+               if (read(fd, buffer, 1) < 1)
+               {
+                 close(fd);
+                  return (IPP_ERROR);
+               }
+
+                attr->values[attr->num_values].boolean = buffer[0];
+               break;
+           case IPP_TAG_TEXT :
+           case IPP_TAG_NAME :
+           case IPP_TAG_KEYWORD :
+           case IPP_TAG_STRING :
+           case IPP_TAG_URI :
+           case IPP_TAG_URISCHEME :
+           case IPP_TAG_CHARSET :
+           case IPP_TAG_LANGUAGE :
+           case IPP_TAG_MIMETYPE :
+               if (read(fd, buffer, n) < n)
+               {
+                 close(fd);
+                  return (IPP_ERROR);
+               }
+
+                buffer[n] = '\0';
+               DEBUG_printf(("ippRead: value = \'%s\'\n", buffer));
+
+                attr->values[attr->num_values].string.text = strdup((char *)buffer);
+               break;
+           case IPP_TAG_DATE :
+               if (read(fd, buffer, 11) < 11)
+               {
+                 close(fd);
+                  return (IPP_ERROR);
+               }
+
+                memcpy(attr->values[attr->num_values].date, buffer, 11);
+               break;
+           case IPP_TAG_RESOLUTION :
+               if (read(fd, buffer, 9) < 9)
+               {
+                 close(fd);
+                  return (IPP_ERROR);
+               }
+
+                attr->values[attr->num_values].resolution.xres =
+                   (((((buffer[0] << 8) | buffer[1]) << 8) | buffer[2]) << 8) |
+                   buffer[3];
+                attr->values[attr->num_values].resolution.yres =
+                   (((((buffer[4] << 8) | buffer[5]) << 8) | buffer[6]) << 8) |
+                   buffer[7];
+                attr->values[attr->num_values].resolution.units =
+                   (ipp_res_t)buffer[8];
+               break;
+           case IPP_TAG_RANGE :
+               if (read(fd, buffer, 8) < 8)
+               {
+                 close(fd);
+                  return (IPP_ERROR);
+               }
+
+                attr->values[attr->num_values].range.lower =
+                   (((((buffer[0] << 8) | buffer[1]) << 8) | buffer[2]) << 8) |
+                   buffer[3];
+                attr->values[attr->num_values].range.upper =
+                   (((((buffer[4] << 8) | buffer[5]) << 8) | buffer[6]) << 8) |
+                   buffer[7];
+               break;
+           case IPP_TAG_TEXTLANG :
+           case IPP_TAG_NAMELANG :
+               if (read(fd, buffer, n) < n)
+               {
+                 close(fd);
+                  return (IPP_ERROR);
+               }
+
+                buffer[n] = '\0';
+
+                attr->values[attr->num_values].string.charset = strdup((char *)buffer);
+
+               if (read(fd, buffer, 2) < 2)
+               {
+                 close(fd);
+                  return (IPP_ERROR);
+               }
+
+               n = (buffer[0] << 8) | buffer[1];
+
+               if (read(fd, buffer, n) < n)
+               {
+                 close(fd);
+                  return (IPP_ERROR);
+               }
+
+                buffer[n] = '\0';
+
+                attr->values[attr->num_values].string.text = strdup((char *)buffer);
+               break;
+         }
+
+          attr->num_values ++;
+       }
+        break;
+
+    case IPP_DATA :
+        break;
+  }
+
+ /*
+  * Close the file and return...
+  */
+
+  close(fd);
+
+  return (ipp->state);
+}
+
+
+/*
+ * 'ipp_write_file()' - Write an IPP request to a file.
+ */
+
+static ipp_state_t                     /* O - State */
+ipp_write_file(const char *filename,   /* I - File to write to */
+               ipp_t      *request)    /* I - Request to write */
+{
+  int                  fd;             /* File descriptor */
+  int                  i;              /* Looping var */
+  int                  n;              /* Length of data */
+  unsigned char                buffer[8192],   /* Data buffer */
+                       *bufptr;        /* Pointer into buffer */
+  ipp_attribute_t      *attr;          /* Current attribute */
+
+
+ /*
+  * Open the file if possible...
+  */
+
+  if (filename == NULL || request == NULL)
+    return (IPP_ERROR);
+
+  if ((fd = open(filename, O_WRONLY | O_CREATE | O_TRUNC, 0640)) == -1)
+    return (IPP_ERROR);
+
+  fchmod(fd, 0640);
+  fchown(fd, User, Group);
+
+ /*
+  * Write the IPP request...
+  */
+
+  ipp->state = IPP_IDLE;
+
+  switch (ipp->state)
+  {
+    case IPP_IDLE :
+        ipp->state ++; /* Avoid common problem... */
+
+    case IPP_HEADER :
+       /*
+        * Send the request header...
+       */
+
+        bufptr = buffer;
+
+       *bufptr++ = 1;
+       *bufptr++ = 0;
+       *bufptr++ = ipp->request.any.op_status >> 8;
+       *bufptr++ = ipp->request.any.op_status;
+       *bufptr++ = ipp->request.any.request_id >> 24;
+       *bufptr++ = ipp->request.any.request_id >> 16;
+       *bufptr++ = ipp->request.any.request_id >> 8;
+       *bufptr++ = ipp->request.any.request_id;
+
+        if (write(fd, (char *)buffer, bufptr - buffer) < 0)
+       {
+         DEBUG_puts("ippWrite: Could not write IPP header...");
+         close(fd);
+         return (IPP_ERROR);
+       }
+
+        ipp->state   = IPP_ATTRIBUTE;
+       ipp->current = ipp->attrs;
+       ipp->curtag  = IPP_TAG_ZERO;
+
+    case IPP_ATTRIBUTE :
+        while (ipp->current != NULL)
+       {
+        /*
+         * Write this attribute...
+         */
+
+         bufptr = buffer;
+         attr   = ipp->current;
+
+         ipp->current = ipp->current->next;
+
+          if (ipp->curtag != attr->group_tag)
+         {
+          /*
+           * Send a group operation tag...
+           */
+
+           ipp->curtag = attr->group_tag;
+
+            if (attr->group_tag == IPP_TAG_ZERO)
+             continue;
+
+            DEBUG_printf(("ippWrite: wrote group tag = %x\n", attr->group_tag));
+           *bufptr++ = attr->group_tag;
+         }
+
+          n = strlen(attr->name);
+
+          DEBUG_printf(("ippWrite: writing value tag = %x\n", attr->value_tag));
+          DEBUG_printf(("ippWrite: writing name = %d, \'%s\'\n", n, attr->name));
+
+          *bufptr++ = attr->value_tag;
+         *bufptr++ = n >> 8;
+         *bufptr++ = n;
+         memcpy(bufptr, attr->name, n);
+         bufptr += n;
+
+         switch (attr->value_tag)
+         {
+           case IPP_TAG_INTEGER :
+           case IPP_TAG_ENUM :
+               for (i = 0; i < attr->num_values; i ++)
+               {
+                 if (i)
+                 {
+                  /*
+                   * Arrays and sets are done by sending additional
+                   * values with a zero-length name...
+                   */
+
+                    *bufptr++ = attr->value_tag;
+                   *bufptr++ = 0;
+                   *bufptr++ = 0;
+                 }
+
+                 *bufptr++ = 0;
+                 *bufptr++ = 4;
+                 *bufptr++ = attr->values[i].integer >> 24;
+                 *bufptr++ = attr->values[i].integer >> 16;
+                 *bufptr++ = attr->values[i].integer >> 8;
+                 *bufptr++ = attr->values[i].integer;
+               }
+               break;
+
+           case IPP_TAG_BOOLEAN :
+               for (i = 0; i < attr->num_values; i ++)
+               {
+                 if (i)
+                 {
+                  /*
+                   * Arrays and sets are done by sending additional
+                   * values with a zero-length name...
+                   */
+
+                    *bufptr++ = attr->value_tag;
+                   *bufptr++ = 0;
+                   *bufptr++ = 0;
+                 }
+
+                 *bufptr++ = 0;
+                 *bufptr++ = 1;
+                 *bufptr++ = attr->values[i].boolean;
+               }
+               break;
+
+           case IPP_TAG_TEXT :
+           case IPP_TAG_NAME :
+           case IPP_TAG_KEYWORD :
+           case IPP_TAG_STRING :
+           case IPP_TAG_URI :
+           case IPP_TAG_URISCHEME :
+           case IPP_TAG_CHARSET :
+           case IPP_TAG_LANGUAGE :
+           case IPP_TAG_MIMETYPE :
+               for (i = 0; i < attr->num_values; i ++)
+               {
+                 if (i)
+                 {
+                  /*
+                   * Arrays and sets are done by sending additional
+                   * values with a zero-length name...
+                   */
+
+                   DEBUG_printf(("ippWrite: writing value tag = %x\n",
+                                 attr->value_tag));
+                   DEBUG_printf(("ippWrite: writing name = 0, \'\'\n"));
+
+                    *bufptr++ = attr->value_tag;
+                   *bufptr++ = 0;
+                   *bufptr++ = 0;
+                 }
+
+                  n = strlen(attr->values[i].string.text);
+
+                  DEBUG_printf(("ippWrite: writing string = %d, \'%s\'\n", n,
+                               attr->values[i].string.text));
+
+                  if ((sizeof(buffer) - (bufptr - buffer)) < (n + 2))
+                 {
+                    if (write(fd, (char *)buffer, bufptr - buffer) < 0)
+                   {
+                     DEBUG_puts("ippWrite: Could not write IPP attribute...");
+                     close(fd);
+                     return (IPP_ERROR);
+                   }
+
+                   bufptr = buffer;
+                 }
+
+                 *bufptr++ = n >> 8;
+                 *bufptr++ = n;
+                 memcpy(bufptr, attr->values[i].string.text, n);
+                 bufptr += n;
+               }
+               break;
+
+           case IPP_TAG_DATE :
+               for (i = 0; i < attr->num_values; i ++)
+               {
+                 if (i)
+                 {
+                  /*
+                   * Arrays and sets are done by sending additional
+                   * values with a zero-length name...
+                   */
+
+                    *bufptr++ = attr->value_tag;
+                   *bufptr++ = 0;
+                   *bufptr++ = 0;
+                 }
+
+                 *bufptr++ = 0;
+                 *bufptr++ = 11;
+                 memcpy(bufptr, attr->values[i].date, 11);
+                 bufptr += 11;
+               }
+               break;
+
+           case IPP_TAG_RESOLUTION :
+               for (i = 0; i < attr->num_values; i ++)
+               {
+                 if (i)
+                 {
+                  /*
+                   * Arrays and sets are done by sending additional
+                   * values with a zero-length name...
+                   */
+
+                    *bufptr++ = attr->value_tag;
+                   *bufptr++ = 0;
+                   *bufptr++ = 0;
+                 }
+
+                 *bufptr++ = 0;
+                 *bufptr++ = 9;
+                 *bufptr++ = attr->values[i].resolution.xres >> 24;
+                 *bufptr++ = attr->values[i].resolution.xres >> 16;
+                 *bufptr++ = attr->values[i].resolution.xres >> 8;
+                 *bufptr++ = attr->values[i].resolution.xres;
+                 *bufptr++ = attr->values[i].resolution.yres >> 24;
+                 *bufptr++ = attr->values[i].resolution.yres >> 16;
+                 *bufptr++ = attr->values[i].resolution.yres >> 8;
+                 *bufptr++ = attr->values[i].resolution.yres;
+                 *bufptr++ = attr->values[i].resolution.units;
+               }
+               break;
+
+           case IPP_TAG_RANGE :
+               for (i = 0; i < attr->num_values; i ++)
+               {
+                 if (i)
+                 {
+                  /*
+                   * Arrays and sets are done by sending additional
+                   * values with a zero-length name...
+                   */
+
+                    *bufptr++ = attr->value_tag;
+                   *bufptr++ = 0;
+                   *bufptr++ = 0;
+                 }
+
+                 *bufptr++ = 0;
+                 *bufptr++ = 8;
+                 *bufptr++ = attr->values[i].range.lower >> 24;
+                 *bufptr++ = attr->values[i].range.lower >> 16;
+                 *bufptr++ = attr->values[i].range.lower >> 8;
+                 *bufptr++ = attr->values[i].range.lower;
+                 *bufptr++ = attr->values[i].range.upper >> 24;
+                 *bufptr++ = attr->values[i].range.upper >> 16;
+                 *bufptr++ = attr->values[i].range.upper >> 8;
+                 *bufptr++ = attr->values[i].range.upper;
+               }
+               break;
+
+           case IPP_TAG_TEXTLANG :
+           case IPP_TAG_NAMELANG :
+               for (i = 0; i < attr->num_values; i ++)
+               {
+                 if (i)
+                 {
+                  /*
+                   * Arrays and sets are done by sending additional
+                   * values with a zero-length name...
+                   */
+
+                    *bufptr++ = attr->value_tag;
+                   *bufptr++ = 0;
+                   *bufptr++ = 0;
+                 }
+
+                  n = strlen(attr->values[i].string.charset);
+
+                  if ((sizeof(buffer) - (bufptr - buffer)) < (n + 2))
+                 {
+                    if (write(fd, (char *)buffer, bufptr - buffer) < 0)
+                   {
+                     DEBUG_puts("ippWrite: Could not write IPP attribute...");
+                     close(fd);
+                     return (IPP_ERROR);
+                   }
+
+                   bufptr = buffer;
+                 }
+
+                 *bufptr++ = n >> 8;
+                 *bufptr++ = n;
+                 memcpy(bufptr, attr->values[i].string.charset, n);
+                 bufptr += n;
+
+                  n = strlen(attr->values[i].string.text);
+
+                  if ((sizeof(buffer) - (bufptr - buffer)) < (n + 2))
+                 {
+                    if (write(fd, (char *)buffer, bufptr - buffer) < 0)
+                   {
+                     DEBUG_puts("ippWrite: Could not write IPP attribute...");
+                     close(fd);
+                     return (IPP_ERROR);
+                   }
+
+                   bufptr = buffer;
+                 }
+
+                 *bufptr++ = n >> 8;
+                 *bufptr++ = n;
+                 memcpy(bufptr, attr->values[i].string.text, n);
+                 bufptr += n;
+               }
+               break;
+         }
+
+         /*
+         * Write the data out...
+         */
+
+          if (write(fd, (char *)buffer, bufptr - buffer) < 0)
+         {
+           DEBUG_puts("ippWrite: Could not write IPP attribute...");
+           close(fd);
+           return (IPP_ERROR);
+         }
+
+          DEBUG_printf(("ippWrite: wrote %d bytes\n", bufptr - buffer));
+       }
+
+       if (ipp->current == NULL)
+       {
+         /*
+         * Done with all of the attributes; add the end-of-attributes tag...
+         */
+
+          buffer[0] = IPP_TAG_END;
+         if (write(fd, (char *)buffer, 1) < 0)
+         {
+           DEBUG_puts("ippWrite: Could not write IPP end-tag...");
+           close(fd);
+           return (IPP_ERROR);
+         }
+
+         ipp->state = IPP_DATA;
+       }
+        break;
+
+    case IPP_DATA :
+        break;
+  }
+
+ /*
+  * Close the file and return...
+  */
+
+  close(fd);
+
+  return (ipp->state);
+}
+
+
 /*
  * 'start_process()' - Start a background process.
  */
 
-static int                     /* O - Process ID or 0 */
-start_process(char *command,   /* I - Full path to command */
-              char *argv[],    /* I - Command-line arguments */
-             char *envp[],     /* I - Environment */
-              int  infd,       /* I - Standard input file descriptor */
-             int  outfd,       /* I - Standard output file descriptor */
-             int  errfd)       /* I - Standard error file descriptor */
+static int                             /* O - Process ID or 0 */
+start_process(const char *command,     /* I - Full path to command */
+              const char *argv[],      /* I - Command-line arguments */
+             const char *envp[],       /* I - Environment */
+              int        infd,         /* I - Standard input file descriptor */
+             int        outfd,         /* I - Standard output file descriptor */
+             int        errfd)         /* I - Standard error file descriptor */
 {
-  int  fd;                     /* Looping var */
-  int  pid;                    /* Process ID */
+  int  fd;                             /* Looping var */
+  int  pid;                            /* Process ID */
 
 
   DEBUG_printf(("start_process(\"%s\", %08x, %08x, %d, %d, %d)\n",
@@ -979,5 +1819,5 @@ start_process(char *command,       /* I - Full path to command */
 
 
 /*
- * End of "$Id: job.c,v 1.43 1999/12/21 02:26:48 mike Exp $".
+ * End of "$Id: job.c,v 1.44 1999/12/29 02:15:42 mike Exp $".
  */
index d5eb7cd167e5ccb4f682eccb0c31c7fd28d132de..3f164e3eccf4164dad8ba8f648358a0142edb5c7 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * "$Id: job.h,v 1.9 1999/06/18 18:36:48 mike Exp $"
+ * "$Id: job.h,v 1.10 1999/12/29 02:15:43 mike Exp $"
  *
  *   Print job definitions for the Common UNIX Printing System (CUPS) scheduler.
  *
@@ -36,8 +36,9 @@ typedef struct job_str
   char         dest[IPP_MAX_NAME];     /* Destination printer or class */
   char         title[IPP_MAX_NAME];    /* Job name/title */
   cups_ptype_t dtype;                  /* Destination type (class/remote bits) */
-  char         filename[HTTP_MAX_URI]; /* Name of job file */
-  mime_type_t  *filetype;              /* File type */
+  int          num_files;              /* Number of files in job */
+  int          current_file;           /* Current file in job */
+  mime_type_t  **filetypes;            /* File types */
   ipp_t                *attrs;                 /* Job attributes */
   int          pipe;                   /* Status pipe for this job */
   int          procs[MAX_FILTERS + 2]; /* Process IDs, 0 terminated */
@@ -50,26 +51,34 @@ typedef struct job_str
  * Globals...
  */
 
+VAR int                JobHistory      VALUE(1);       /* Preserve job history? */
+VAR int                JobFiles        VALUE(0);       /* Preserve job files? */
 VAR int                NumJobs         VALUE(0);       /* Number of jobs in queue */
 VAR job_t      *Jobs           VALUE(NULL);    /* List of current jobs */
 VAR int                NextJobId       VALUE(1);       /* Next job ID to use */
 
+
 /*
  * Prototypes...
  */
 
-extern job_t   *AddJob(int priority, char *dest);
+extern job_t   *AddJob(int priority, const char *dest);
 extern void    CancelJob(int id);
-extern void    CancelJobs(char *dest);
+extern void    CancelJobs(const char *dest);
 extern void    CheckJobs(void);
 extern void    DeleteJob(int id);
 extern job_t   *FindJob(int id);
-extern void    LoadJobs(void);
-extern void    MoveJob(int id, char *dest);
+extern void    HoldJob(int id);
+extern void    LoadAllJobs(void);
+extern void    LoadJob(int id);
+extern void    MoveJob(int id, const char *dest);
+extern void    RestartJob(int id);
+extern void    SaveJob(int id);
 extern void    StartJob(int id, printer_t *printer);
+extern void    StopAllJobs(void);
 extern void    StopJob(int id);
 extern void    UpdateJob(job_t *job);
 
 /*
- * End of "$Id: job.h,v 1.9 1999/06/18 18:36:48 mike Exp $".
+ * End of "$Id: job.h,v 1.10 1999/12/29 02:15:43 mike Exp $".
  */
index 4ecaf601d0a979fd4955a0f4ceebaa2b203c5994..57c6d3e16870294d826c510aa9b3953749e8140a 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * "$Id: main.c,v 1.30 1999/12/07 18:10:18 mike Exp $"
+ * "$Id: main.c,v 1.31 1999/12/29 02:15:43 mike Exp $"
  *
  *   Scheduler main loop for the Common UNIX Printing System (CUPS).
  *
@@ -391,14 +391,20 @@ sigchld_handler(int sig)  /* I - Signal number */
 
           job->procs[i] = -pid;
 
-          if (status && !job->procs[i + 1])
+          if (status && job->status >= 0)
          {
           /*
-           * A fatal error occurred; save the exit status so we know to stop
-           * the printer when all of the filters finish...
+           * An error occurred; save the exit status so we know to stop
+           * the printer or cancel the job when all of the filters finish...
+           *
+           * A negative status indicates that the backend failed and the
+           * printer needs to be stopped.
            */
 
-           job->status = status;
+            if (!job->procs[i + 1])
+             job->status = -status;    /* Backend failed */
+           else
+             job->status = status;     /* Filter failed */
          }
          break;
        }
@@ -439,5 +445,5 @@ usage(void)
 
 
 /*
- * End of "$Id: main.c,v 1.30 1999/12/07 18:10:18 mike Exp $".
+ * End of "$Id: main.c,v 1.31 1999/12/29 02:15:43 mike Exp $".
  */
index 7cd0b2ca5606e612b1a0f372867aa3e7f93be4d3..a314135365e4e8ed2778bcd79ebc5f2d2d03b249 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * "$Id: printers.c,v 1.46 1999/12/14 23:19:11 mike Exp $"
+ * "$Id: printers.c,v 1.47 1999/12/29 02:15:43 mike Exp $"
  *
  *   Printer routines for the Common UNIX Printing System (CUPS).
  *
@@ -660,10 +660,14 @@ SetPrinterAttrs(printer_t *p)             /* I - Printer to setup */
                {
                  IPP_PRINT_JOB,
                  IPP_VALIDATE_JOB,
+                 IPP_CREATE_JOB,
+                 IPP_SEND_DOCUMENT,
                  IPP_CANCEL_JOB,
                  IPP_GET_JOB_ATTRIBUTES,
                  IPP_GET_JOBS,
                  IPP_GET_PRINTER_ATTRIBUTES,
+                 IPP_HOLD_JOB,
+                 IPP_RELEASE_JOB,
                  IPP_PAUSE_PRINTER,
                  IPP_RESUME_PRINTER,
                  IPP_PURGE_JOBS,
@@ -675,7 +679,9 @@ SetPrinterAttrs(printer_t *p)               /* I - Printer to setup */
                  CUPS_ADD_CLASS,
                  CUPS_DELETE_CLASS,
                  CUPS_ACCEPT_JOBS,
-                 CUPS_REJECT_JOBS
+                 CUPS_REJECT_JOBS,
+                 CUPS_GET_DEVICES,
+                 CUPS_GET_PPDS
                };
   const char   *charsets[] =           /* charset-supported values */
                {
@@ -1233,5 +1239,5 @@ write_printcap(void)
 
 
 /*
- * End of "$Id: printers.c,v 1.46 1999/12/14 23:19:11 mike Exp $".
+ * End of "$Id: printers.c,v 1.47 1999/12/29 02:15:43 mike Exp $".
  */