/*
- * "$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.
*
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);
* 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;
}
* 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
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;
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;
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);
}
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;
}
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;
}
* 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;
}
}
+/*
+ * '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.
*/
}
+/*
+ * '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.
*/
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 */
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
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));
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;
}
* 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;
}
* 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;
}
}
+/*
+ * '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.
*/
/*
- * '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 */
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);
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?
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';
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';
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...
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;
}
}
+/*
+ * '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.
*/
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;
}
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;
}
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;
}
/*
- * 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 $".
*/
/*
- * "$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);
*/
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));
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;
}
}
*/
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)
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()");
{
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));
* '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)
}
+/*
+ * '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)
}
+/*
+ * '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.
*/
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;
}
+/*
+ * '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));
{
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
{
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",
/*
- * 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 $".
*/