/*
 * "$Id: job.c,v 1.5.2.3 2003/02/27 23:17:39 jlovell Exp $"
 *
 *   Job management routines for the Common UNIX Printing System (CUPS).
 *
 *   Copyright 1997-2002 by Easy Software Products, all rights reserved.
 *
 *   These coded instructions, statements, and computer programs are the
 *   property of Easy Software Products and are protected by Federal
 *   copyright law.  Distribution and use rights are outlined in the file
 *   "LICENSE.txt" which should have been included with this file.  If this
 *   file is missing or damaged please contact Easy Software Products
 *   at:
 *
 *       Attn: CUPS Licensing Information
 *       Easy Software Products
 *       44141 Airport View Drive, Suite 204
 *       Hollywood, Maryland 20636-3111 USA
 *
 *       Voice: (301) 373-9603
 *       EMail: cups-info@cups.org
 *         WWW: http://www.cups.org
 *
 * 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.
 *   CleanJobs()          - Clean out old jobs.
 *   FreeAllJobs()        - Free all jobs from memory.
 *   FindJob()            - Find the specified job.
 *   GetPrinterJobCount() - Get the number of pending, processing,
 *                          or held jobs in a printer or class.
 *   GetUserJobCount()    - Get the number of pending, processing,
 *                          or held jobs for a user.
 *   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.
 *   ReleaseJob()         - Release the specified job.
 *   RestartJob()         - Restart the specified job.
 *   SaveJob()            - Save a job to disk.
 *   SetJobHoldUntil()    - Set the hold time for a job...
 *   SetJobPriority()     - Set the priority of a job, moving it up/down
 *                          in the list as needed.
 *   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.
 */

/*
 * Include necessary headers...
 */

#include "cupsd.h"
#include <grp.h>


/*
 * Local functions...
 */

static ipp_state_t	ipp_read_file(const char *filename, ipp_t *ipp);
static ipp_state_t	ipp_write_file(const char *filename, ipp_t *ipp);
static void		set_time(job_t *job, const char *name);
static int		start_process(const char *command, char *argv[],
			              char *envp[], int in, int out, int err,
				      int root, int *pid);


/*
 * 'AddJob()' - Add a new job to the job queue...
 */

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 = calloc(sizeof(job_t), 1);

  job->id       = NextJobId ++;
  job->priority = priority;
  strlcpy(job->dest, dest, sizeof(job->dest));

  NumJobs ++;

  for (current = Jobs, prev = NULL;
       current != NULL;
       prev = current, current = current->next)
    if (job->priority > current->priority)
      break;

  job->next = current;
  if (prev != NULL)
    prev->next = job;
  else
    Jobs = job;

  return (job);
}


/*
 * 'CancelJob()' - Cancel the specified print job.
 */

void
CancelJob(int id,		/* I - Job to cancel */
          int purge)		/* I - Purge jobs? */
{
  int	i;			/* Looping var */
  job_t	*current,		/* Current job */
	*prev;			/* Previous job in list */
  char	filename[1024];		/* Job filename */


  LogMessage(L_DEBUG, "CancelJob: id = %d", id);

  for (current = Jobs, prev = NULL; current != NULL; prev = current, current = current->next)
    if (current->id == id)
    {
     /*
      * Stop any processes that are working on the current...
      */

      DEBUG_puts("CancelJob: found job in list.");

      if (current->state->values[0].integer == IPP_JOB_PROCESSING)
	StopJob(current->id, 0);

      current->state->values[0].integer = IPP_JOB_CANCELLED;

      set_time(current, "time-at-completed");

     /*
      * Remove the print file for good if we aren't preserving jobs or
      * files...
      */

      current->current_file = 0;

      if (!JobHistory || !JobFiles || purge ||
          (current->dtype & CUPS_PRINTER_REMOTE))
        for (i = 1; i <= current->num_files; i ++)
	{
	  snprintf(filename, sizeof(filename), "%s/d%05d-%03d", RequestRoot,
	           current->id, i);
          unlink(filename);
	}

      if (JobHistory && !purge && !(current->dtype & CUPS_PRINTER_REMOTE))
      {
       /*
        * Save job state info...
	*/

        SaveJob(current->id);
      }
      else
      {
       /*
        * Remove the job info file...
	*/

	snprintf(filename, sizeof(filename), "%s/c%05d", RequestRoot,
	         current->id);
	unlink(filename);

       /*
        * Update pointers if we aren't preserving jobs...
        */

        if (prev == NULL)
          Jobs = current->next;
        else
          prev->next = current->next;

       /*
        * Free all memory used...
        */

        if (current->attrs != NULL)
          ippDelete(current->attrs);

        free(current->filetypes);

        free(current);

	NumJobs --;
      }

      return;
    }
}


/*
 * 'CancelJobs()' - Cancel all jobs on the given printer or class.
 */

void
CancelJobs(const char *dest)	/* I - Destination to cancel */
{
  job_t	*current;		/* Current job */


  for (current = Jobs; current != NULL;)
    if (strcmp(current->dest, dest) == 0)
    {
     /*
      * Cancel all jobs matching this destination...
      */

      CancelJob(current->id, 1);

      current = Jobs;
    }
    else
      current = current->next;

  CheckJobs();
}


/*
 * 'CheckJobs()' - Check the pending jobs and start any if the destination
 *                 is available.
 */

void
CheckJobs(void)
{
  job_t		*current,	/* Current job in queue */
		*next;		/* Next job in queue */
  printer_t	*printer,	/* Printer destination */
		*pclass;	/* Printer class destination */


  DEBUG_puts("CheckJobs()");

  for (current = Jobs; current != NULL; current = next)
  {
   /*
    * Save next pointer in case the job is cancelled en-route.
    */

    next = current->next;

   /*
    * Start held jobs if they are ready...
    */

    if (current->state->values[0].integer == IPP_JOB_HELD &&
        current->hold_until &&
	current->hold_until < time(NULL))
      current->state->values[0].integer = IPP_JOB_PENDING;

   /*
    * Start pending jobs if the destination is available...
    */

    if (current->state->values[0].integer == IPP_JOB_PENDING)
    {
      if ((pclass = FindClass(current->dest)) != NULL)
      {
       /*
        * If the class is remote, just pass it to the remote server...
	*/

        if (pclass->type & CUPS_PRINTER_REMOTE)
	  printer = pclass;
	else if (pclass->state != IPP_PRINTER_STOPPED)
	  printer = FindAvailablePrinter(current->dest);
	else
	  printer = NULL;
      }
      else
        printer = FindPrinter(current->dest);

      if (printer != NULL && (printer->type & CUPS_PRINTER_IMPLICIT))
      {
       /*
        * Handle implicit classes...
	*/

        pclass = printer;

	if (pclass->state != IPP_PRINTER_STOPPED)
	  printer = FindAvailablePrinter(current->dest);
	else
	  printer = NULL;
      }

      if (printer == NULL && pclass == NULL)
      {
       /*
        * Whoa, the printer and/or class for this destination went away;
	* cancel the job...
	*/

        LogMessage(L_WARN, "Printer/class %s has gone away; cancelling job %d!",
	           current->dest, current->id);
        CancelJob(current->id, 1);
      }
      else if (printer != NULL)
      {
       /*
        * See if the printer is available or remote and not printing a job;
	* if so, start the job...
	*/

        if (printer->state == IPP_PRINTER_IDLE ||	/* Printer is idle */
	    ((printer->type & CUPS_PRINTER_REMOTE) &&	/* Printer is remote */
	     !printer->job))				/* and not printing a job */
	  StartJob(current->id, printer);
      }
    }
  }
}


/*
 * 'CleanJobs()' - Clean out old jobs.
 */

void
CleanJobs(void)
{
  job_t	*job,		/* Current job */
	*next;		/* Next job */


  if (MaxJobs == 0)
    return;

  for (job = Jobs; job && NumJobs >= MaxJobs; job = next)
  {
    next = job->next;

    if (job->state->values[0].integer >= IPP_JOB_CANCELLED)
      CancelJob(job->id, 1);
  }
}


/*
 * 'FreeAllJobs()' - Free all jobs from memory.
 */

void
FreeAllJobs(void)
{
  job_t	*job,		/* Current job */
	*next;		/* Next job */


  StopAllJobs();

  for (job = Jobs; job; job = next)
  {
    next = job->next;

    ippDelete(job->attrs);
    free(job->filetypes);
    free(job);
  }

  Jobs = NULL;
}


/*
 * 'FindJob()' - Find the specified job.
 */

job_t *				/* O - Job data */
FindJob(int id)			/* I - Job ID */
{
  job_t	*current;		/* Current job */


  for (current = Jobs; current != NULL; current = current->next)
    if (current->id == id)
      break;

  return (current);
}


/*
 * 'GetPrinterJobCount()' - Get the number of pending, processing,
 *                          or held jobs in a printer or class.
 */

int					/* O - Job count */
GetPrinterJobCount(const char *dest)	/* I - Printer or class name */
{
  int	count;				/* Job count */
  job_t	*job;				/* Current job */


  for (job = Jobs, count = 0; job != NULL; job = job->next)
    if (job->state->values[0].integer <= IPP_JOB_PROCESSING &&
        strcasecmp(job->dest, dest) == 0)
      count ++;

  return (count);
}


/*
 * 'GetUserJobCount()' - Get the number of pending, processing,
 *                       or held jobs for a user.
 */

int					/* O - Job count */
GetUserJobCount(const char *username)	/* I - Username */
{
  int	count;				/* Job count */
  job_t	*job;				/* Current job */


  for (job = Jobs, count = 0; job != NULL; job = job->next)
    if (job->state->values[0].integer <= IPP_JOB_PROCESSING &&
        strcmp(job->username, username) == 0)
      count ++;

  return (count);
}


/*
 * 'HoldJob()' - Hold the specified job.
 */

void
HoldJob(int id)			/* I - Job ID */
{
  job_t	*job;			/* Job data */


  LogMessage(L_DEBUG, "HoldJob: id = %d", id);

  if ((job = FindJob(id)) == NULL)
    return;

  if (job->state->values[0].integer == IPP_JOB_PROCESSING)
    StopJob(id, 0);

  DEBUG_puts("HoldJob: setting state to held...");

  job->state->values[0].integer = IPP_JOB_HELD;

  SaveJob(id);

  CheckJobs();
}


/*
 * 'LoadAllJobs()' - Load all jobs from disk.
 */

void
LoadAllJobs(void)
{
  DIR		*dir;		/* Directory */
  DIRENT	*dent;		/* Directory entry */
  char		filename[1024];	/* Full filename of job file */
  job_t		*job,		/* New job */
		*current,	/* Current job */
		*prev;		/* Previous job */
  int		jobid,		/* Current job ID */
		fileid;		/* Current file ID */
  ipp_attribute_t *attr;	/* Job attribute */
  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 */
  printer_t	*p;		/* Printer or class */
  const char	*dest;		/* Destination */
  mime_type_t	**filetypes;	/* New filetypes array */


 /*
  * First open the requests directory...
  */

  if ((dir = opendir(RequestRoot)) == NULL)
    return;

 /*
  * Read all the c##### files...
  */

  while ((dent = readdir(dir)) != NULL)
    if (NAMLEN(dent) == 6 && dent->d_name[0] == 'c')
    {
     /*
      * Allocate memory for the job...
      */

      if ((job = calloc(sizeof(job_t), 1)) == NULL)
      {
        LogMessage(L_ERROR, "LoadAllJobs: Ran out of memory for jobs!");
	closedir(dir);
	return;
      }

      if ((job->attrs = ippNew()) == NULL)
      {
        free(job);
        LogMessage(L_ERROR, "LoadAllJobs: Ran out of memory for job attributes!");
	closedir(dir);
	return;
      }

     /*
      * Assign the job ID...
      */

      job->id = atoi(dent->d_name + 1);

      if (job->id >= NextJobId)
        NextJobId = job->id + 1;

     /*
      * Load the job control file...
      */

      snprintf(filename, sizeof(filename), "%s/%s", RequestRoot, dent->d_name);
      if (ipp_read_file(filename, job->attrs) != IPP_DATA)
      {
        LogMessage(L_ERROR, "LoadAllJobs: Unable to read job control file \"%s\"!",
	           filename);
	ippDelete(job->attrs);
	free(job);
	unlink(filename);
	continue;
      }

      job->state = ippFindAttribute(job->attrs, "job-state", IPP_TAG_ENUM);

      if ((attr = ippFindAttribute(job->attrs, "job-printer-uri", IPP_TAG_URI)) == NULL)
      {
        LogMessage(L_ERROR, "LoadAllJobs: No job-printer-uri attribute in control file \"%s\"!",
	           filename);
	ippDelete(job->attrs);
	free(job);
	unlink(filename);
	continue;
      }

      httpSeparate(attr->values[0].string.text, method, username, host,
                   &port, resource);

      if ((dest = ValidateDest(host, resource, &(job->dtype))) == NULL &&
          job->state != NULL &&
	  job->state->values[0].integer <= IPP_JOB_PROCESSING)
      {
       /*
	* Job queued on remote printer or class, so add it...
	*/

	if (strncmp(resource, "/classes/", 9) == 0)
	{
	  p = AddClass(resource + 9);
	  strcpy(p->make_model, "Remote Class on unknown");
	}
	else
	{
	  p = AddPrinter(resource + 10);
	  strcpy(p->make_model, "Remote Printer on unknown");
	}

        p->state       = IPP_PRINTER_STOPPED;
	p->type        |= CUPS_PRINTER_REMOTE;
	p->browse_time = 2147483647;

	strcpy(p->location, "Location Unknown");
	strcpy(p->info, "No Information Available");
	p->hostname[0] = '\0';

	SetPrinterAttrs(p);
	dest = p->name;
      }

      if (dest == NULL)
      {
        LogMessage(L_ERROR, "LoadAllJobs: Unable to queue job for destination \"%s\"!",
	           attr->values[0].string.text);
	ippDelete(job->attrs);
	free(job);
	unlink(filename);
	continue;
      }

      strlcpy(job->dest, dest, sizeof(job->dest));

      job->sheets     = ippFindAttribute(job->attrs, "job-media-sheets-completed",
                                         IPP_TAG_INTEGER);
      job->job_sheets = ippFindAttribute(job->attrs, "job-sheets", IPP_TAG_NAME);

      attr = ippFindAttribute(job->attrs, "job-priority", IPP_TAG_INTEGER);
      job->priority = attr->values[0].integer;

      attr = ippFindAttribute(job->attrs, "job-name", IPP_TAG_NAME);
      strlcpy(job->title, attr->values[0].string.text,
              sizeof(job->title));

      attr = ippFindAttribute(job->attrs, "job-originating-user-name", IPP_TAG_NAME);
      strlcpy(job->username, attr->values[0].string.text,
              sizeof(job->username));

     /*
      * Insert the job into the array, sorting by job priority and ID...
      */

      for (current = Jobs, prev = NULL;
           current != NULL;
	   prev = current, current = current->next)
	if (job->priority > current->priority)
	  break;
	else if (job->priority == current->priority && job->id < current->id)
	  break;

      job->next = current;
      if (prev != NULL)
	prev->next = job;
      else
	Jobs = job;

      NumJobs ++;

     /*
      * Set the job hold-until time and state...
      */

      if (job->state->values[0].integer == IPP_JOB_HELD)
      {
	if ((attr = ippFindAttribute(job->attrs, "job-hold-until", IPP_TAG_KEYWORD)) == NULL)
          attr = ippFindAttribute(job->attrs, "job-hold-until", IPP_TAG_NAME);

        if (attr == NULL)
          job->state->values[0].integer = IPP_JOB_PENDING;
	else
          SetJobHoldUntil(job->id, attr->values[0].string.text);
      }
      else if (job->state->values[0].integer == IPP_JOB_PROCESSING)
        job->state->values[0].integer = IPP_JOB_PENDING;
    }

 /*
  * Read all the d##### files...
  */

  rewinddir(dir);

  while ((dent = readdir(dir)) != NULL)
    if (NAMLEN(dent) > 7 && dent->d_name[0] == 'd')
    {
     /*
      * Find the job...
      */

      jobid  = atoi(dent->d_name + 1);
      fileid = atoi(dent->d_name + 7);

      snprintf(filename, sizeof(filename), "%s/%s", RequestRoot, dent->d_name);

      if ((job = FindJob(jobid)) == NULL)
      {
        LogMessage(L_ERROR, "LoadAllJobs: Orphaned print file \"%s\"!",
	           filename);
        unlink(filename);
	continue;
      }

      if (fileid > job->num_files)
      {
        if (job->num_files == 0)
	  filetypes = (mime_type_t **)calloc(sizeof(mime_type_t *), fileid);
	else
	  filetypes = (mime_type_t **)realloc(job->filetypes,
	                                    sizeof(mime_type_t *) * fileid);

        if (filetypes == NULL)
	{
          LogMessage(L_ERROR, "LoadAllJobs: Ran out of memory for job file types!");
	  continue;
	}

        job->filetypes = filetypes;
	job->num_files = fileid;
      }

      job->filetypes[fileid - 1] = mimeFileType(MimeDatabase, filename);

      if (job->filetypes[fileid - 1] == NULL)
        job->filetypes[fileid - 1] = mimeType(MimeDatabase, "application",
	                                      "vnd.cups-raw");
    }

  closedir(dir);

 /*
  * Clean out old jobs as needed...
  */

  CleanJobs();

 /*
  * Check to see if we need to start any jobs...
  */

  CheckJobs();
}


/*
 * 'MoveJob()' - Move the specified job to a different destination.
 */

void
MoveJob(int        id,		/* I - Job ID */
        const char *dest)	/* I - Destination */
{
  job_t			*current;/* Current job */
  ipp_attribute_t	*attr;	/* job-printer-uri attribute */
  printer_t		*p;	/* Destination printer or class */


  if ((p = FindPrinter(dest)) == NULL)
    p = FindClass(dest);

  if (p == NULL)
    return;

  for (current = Jobs; current != NULL; current = current->next)
    if (current->id == id)
    {
      if (current->state->values[0].integer >= IPP_JOB_PROCESSING)
        break;

      strlcpy(current->dest, dest, sizeof(current->dest));
      current->dtype = p->type & (CUPS_PRINTER_CLASS | CUPS_PRINTER_REMOTE);

      if ((attr = ippFindAttribute(current->attrs, "job-printer-uri", IPP_TAG_URI)) != NULL)
      {
        free(attr->values[0].string.text);
	attr->values[0].string.text = strdup(p->uri);
      }

      SaveJob(current->id);

      return;
    }
}


/*
 * 'ReleaseJob()' - Release the specified job.
 */

void
ReleaseJob(int id)		/* I - Job ID */
{
  job_t	*job;			/* Job data */


  LogMessage(L_DEBUG, "ReleaseJob: id = %d", id);

  if ((job = FindJob(id)) == NULL)
    return;

  if (job->state->values[0].integer == IPP_JOB_HELD)
  {
    DEBUG_puts("ReleaseJob: setting state to pending...");

    job->state->values[0].integer = IPP_JOB_PENDING;
    SaveJob(id);
    CheckJobs();
  }
}


/*
 * 'RestartJob()' - Restart the specified job.
 */

void
RestartJob(int id)		/* I - Job ID */
{
  job_t	*job;			/* Job data */


  if ((job = FindJob(id)) == NULL)
    return;

  if (job->state->values[0].integer == IPP_JOB_STOPPED || JobFiles)
  {
    job->state->values[0].integer = IPP_JOB_PENDING;
    SaveJob(id);
    CheckJobs();
  }
}


/*
 * 'SaveJob()' - Save a job to disk.
 */

void
SaveJob(int id)			/* I - Job ID */
{
  job_t	*job;			/* Pointer to job */
  char	filename[1024];		/* Job control filename */


  if ((job = FindJob(id)) == NULL)
    return;

  snprintf(filename, sizeof(filename), "%s/c%05d", RequestRoot, id);
  ipp_write_file(filename, job->attrs);
}


/*
 * 'SetJobHoldUntil()' - Set the hold time for a job...
 */

void
SetJobHoldUntil(int        id,		/* I - Job ID */
                const char *when)	/* I - When to resume */
{
  job_t		*job;			/* Pointer to job */
  time_t	curtime;		/* Current time */
  struct tm	*curdate;		/* Current date */
  int		hour;			/* Hold hour */
  int		minute;			/* Hold minute */
  int		second;			/* Hold second */


  LogMessage(L_DEBUG, "SetJobHoldUntil(%d, \"%s\")", id, when);

  if ((job = FindJob(id)) == NULL)
    return;

  second = 0;

  if (strcmp(when, "indefinite") == 0)
  {
   /*
    * Hold indefinitely...
    */

    job->hold_until = 0;
  }
  else if (strcmp(when, "day-time") == 0)
  {
   /*
    * Hold to 6am the next morning unless local time is < 6pm.
    */

    curtime = time(NULL);
    curdate = localtime(&curtime);

    if (curdate->tm_hour < 18)
      job->hold_until = curtime;
    else
      job->hold_until = curtime +
                        ((29 - curdate->tm_hour) * 60 + 59 -
			 curdate->tm_min) * 60 + 60 - curdate->tm_sec;
  }
  else if (strcmp(when, "evening") == 0 || strcmp(when, "night") == 0)
  {
   /*
    * Hold to 6pm unless local time is > 6pm or < 6am.
    */

    curtime = time(NULL);
    curdate = localtime(&curtime);

    if (curdate->tm_hour < 6 || curdate->tm_hour >= 18)
      job->hold_until = curtime;
    else
      job->hold_until = curtime +
                        ((17 - curdate->tm_hour) * 60 + 59 -
			 curdate->tm_min) * 60 + 60 - curdate->tm_sec;
  }  
  else if (strcmp(when, "second-shift") == 0)
  {
   /*
    * Hold to 4pm unless local time is > 4pm.
    */

    curtime = time(NULL);
    curdate = localtime(&curtime);

    if (curdate->tm_hour >= 16)
      job->hold_until = curtime;
    else
      job->hold_until = curtime +
                        ((15 - curdate->tm_hour) * 60 + 59 -
			 curdate->tm_min) * 60 + 60 - curdate->tm_sec;
  }  
  else if (strcmp(when, "third-shift") == 0)
  {
   /*
    * Hold to 12am unless local time is < 8am.
    */

    curtime = time(NULL);
    curdate = localtime(&curtime);

    if (curdate->tm_hour < 8)
      job->hold_until = curtime;
    else
      job->hold_until = curtime +
                        ((23 - curdate->tm_hour) * 60 + 59 -
			 curdate->tm_min) * 60 + 60 - curdate->tm_sec;
  }  
  else if (strcmp(when, "weekend") == 0)
  {
   /*
    * Hold to weekend unless we are in the weekend.
    */

    curtime = time(NULL);
    curdate = localtime(&curtime);

    if (curdate->tm_wday == 0 || curdate->tm_wday == 6)
      job->hold_until = curtime;
    else
      job->hold_until = curtime +
                        (((5 - curdate->tm_wday) * 24 +
                          (17 - curdate->tm_hour)) * 60 + 59 -
			   curdate->tm_min) * 60 + 60 - curdate->tm_sec;
  }  
  else if (sscanf(when, "%d:%d:%d", &hour, &minute, &second) >= 2)
  {
   /*
    * Hold to specified GMT time (HH:MM or HH:MM:SS)...
    */

    curtime = time(NULL);
    curdate = gmtime(&curtime);

    job->hold_until = curtime +
                      ((hour - curdate->tm_hour) * 60 + minute -
		       curdate->tm_min) * 60 + second - curdate->tm_sec;

   /*
    * Hold until next day as needed...
    */

    if (job->hold_until < curtime)
      job->hold_until += 24 * 60 * 60 * 60;
  }

  LogMessage(L_DEBUG, "SetJobHoldUntil: hold_until = %d", (int)job->hold_until);
}


/*
 * 'SetJobPriority()' - Set the priority of a job, moving it up/down in the
 *                      list as needed.
 */

void
SetJobPriority(int id,		/* I - Job ID */
               int priority)	/* I - New priority (0 to 100) */
{
  job_t		*job,		/* Job to change */
		*current,	/* Current job */
		*prev;		/* Previous job */
  ipp_attribute_t *attr;	/* Job attribute */


 /*
  * Find the job...
  */

  for (current = Jobs, prev = NULL;
       current != NULL;
       prev = current, current = current->next)
    if (current->id == id)
      break;

  if (current == NULL)
    return;

 /*
  * Set the new priority...
  */

  job = current;
  job->priority = priority;

  if ((attr = ippFindAttribute(job->attrs, "job-priority", IPP_TAG_INTEGER)) != NULL)
    attr->values[0].integer = priority;
  else
    ippAddInteger(job->attrs, IPP_TAG_JOB, IPP_TAG_INTEGER, "job-priority",
                  priority);

  SaveJob(job->id);

 /*
  * See if we need to do any sorting...
  */

  if ((prev == NULL || job->priority < prev->priority) &&
      (job->next == NULL || job->next->priority < job->priority))
    return;

 /*
  * Remove the job from the list, and then insert it where it belongs...
  */

  if (prev == NULL)
    Jobs = job->next;
  else
    prev->next = job->next;

  for (current = Jobs, prev = NULL;
       current != NULL;
       prev = current, current = current->next)
    if (job->priority > current->priority)
      break;

  job->next = current;
  if (prev != NULL)
    prev->next = job;
  else
    Jobs = job;
}


/*
 * 'StartJob()' - Start a print job.
 */

void
StartJob(int       id,		/* I - Job ID */
         printer_t *printer)	/* I - Printer to print job */
{
  job_t		*current;	/* Current job */
  int		i;		/* Looping var */
  int		slot;		/* Pipe slot */
  int		num_filters;	/* Number of filters for job */
  mime_filter_t	*filters;	/* Filters for job */
  char		method[255],	/* Method for output */
		*optptr;	/* Pointer to options */
  ipp_attribute_t *attr;	/* Current attribute */
  int		pid;		/* Process ID of new filter process */
  int		banner_page;	/* 1 if banner page, 0 otherwise */
  int		statusfds[2],	/* Pipes used between the filters and scheduler */
		filterfds[2][2];/* Pipes used between the filters */
  char		*argv[8],	/* Filter command-line arguments */
		filename[1024],	/* Job filename */
		command[1024],	/* Full path to filter/backend command */
		jobid[255],	/* Job ID string */
		title[IPP_MAX_NAME],
				/* Job title string */
		copies[255],	/* # copies string */
		options[16384],	/* Full list of options */
		*envp[21],	/* Environment variables */
#ifdef __APPLE__
		processPath[1050],
				/* CFProcessPath environment variable */
#endif	/* __APPLE__ */
		path[1024],	/* PATH environment variable */
		language[255],	/* LANG environment variable */
		charset[255],	/* CHARSET environment variable */
		classification[1024],
				/* CLASSIFICATION environment variable */
		content_type[1024],
				/* CONTENT_TYPE environment variable */
		device_uri[1024],
				/* DEVICE_URI environment variable */
		ppd[1024],	/* PPD environment variable */
		class_name[255],
				/* CLASS environment variable */
		printer_name[255],
				/* PRINTER environment variable */
		root[1024],	/* CUPS_SERVERROOT environment variable */
		cache[255],	/* RIP_MAX_CACHE environment variable */
		tmpdir[1024],	/* TMPDIR environment variable */
		ldpath[1024],	/* LD_LIBRARY_PATH environment variable */
		nlspath[1024],	/* NLSPATH environment variable */
		datadir[1024],	/* CUPS_DATADIR environment variable */
		fontpath[1050];	/* CUPS_FONTPATH environment variable */


  LogMessage(L_DEBUG, "StartJob(%d, %p)", id, printer);

  for (current = Jobs; current != NULL; current = current->next)
    if (current->id == id)
      break;

  if (current == NULL)
    return;

  LogMessage(L_DEBUG, "StartJob() id = %d, file = %d/%d", id,
             current->current_file, current->num_files);

  if (current->num_files == 0)
  {
    LogMessage(L_ERROR, "Job ID %d has no files!  Cancelling it!", id);
    CancelJob(id, 0);
    return;
  }

 /*
  * Figure out what filters are required to convert from
  * the source to the destination type...
  */

  num_filters   = 0;
  current->cost = 0;

  if (printer->type & CUPS_PRINTER_REMOTE)
  {
   /*
    * Remote jobs go directly to the remote job...
    */

    filters = NULL;
  }
  else
  {
   /*
    * Local jobs get filtered...
    */

    filters = mimeFilter(MimeDatabase, current->filetypes[current->current_file],
                         printer->filetype, &num_filters);

    if (num_filters == 0)
    {
      LogMessage(L_ERROR, "Unable to convert file %d to printable format for job %d!",
	         current->current_file, current->id);
      current->current_file ++;

      if (current->current_file == current->num_files)
        CancelJob(current->id, 0);

      return;
    }

   /*
    * Remove NULL ("-") filters...
    */

    for (i = 0; i < num_filters;)
      if (strcmp(filters[i].filter, "-") == 0)
      {
        num_filters --;
	if (i < num_filters)
	  memcpy(filters + i, filters + i + 1,
	         (num_filters - i) * sizeof(mime_filter_t));
      }
      else
        i ++;

    if (num_filters == 0)
    {
      free(filters);
      filters = NULL;
    }
    else
    {
     /*
      * Compute filter cost...
      */

      for (i = 0; i < num_filters; i ++)
	current->cost += filters[i].cost;
    }
  }

 /*
  * See if the filter cost is too high...
  */

  if ((FilterLevel + current->cost) > FilterLimit && FilterLevel > 0 &&
      FilterLimit > 0)
  {
   /*
    * Don't print this job quite yet...
    */

    if (filters != NULL)
      free(filters);

    LogMessage(L_INFO, "Holding job %d because filter limit has been reached.",
               id);
    LogMessage(L_DEBUG, "StartJob: id = %d, file = %d, "
                        "cost = %d, level = %d, limit = %d",
               id, current->current_file, current->cost, FilterLevel,
	       FilterLimit);
    return;
  }

  FilterLevel += current->cost;

 /*
  * Update the printer and job state to "processing"...
  */

  current->state->values[0].integer = IPP_JOB_PROCESSING;
  current->status  = 0;
  current->printer = printer;
  printer->job     = current;
  SetPrinterState(printer, IPP_PRINTER_PROCESSING);

  if (current->current_file == 0)
    set_time(current, "time-at-processing");

 /*
  * Determine if we are printing a banner page or not...
  */

  if (current->job_sheets == NULL)
  {
    LogMessage(L_DEBUG, "No job-sheets attribute.");
    if ((current->job_sheets =
         ippFindAttribute(current->attrs, "job-sheets", IPP_TAG_ZERO)) != NULL)
      LogMessage(L_DEBUG, "... but someone added one without setting job_sheets!");
  }
  else if (current->job_sheets->num_values == 1)
    LogMessage(L_DEBUG, "job-sheets=%s",
               current->job_sheets->values[0].string.text);
  else
    LogMessage(L_DEBUG, "job-sheets=%s,%s",
               current->job_sheets->values[0].string.text,
               current->job_sheets->values[1].string.text);

  if (printer->type & (CUPS_PRINTER_REMOTE | CUPS_PRINTER_IMPLICIT))
    banner_page = 0;
  else if (current->job_sheets == NULL)
    banner_page = 0;
  else if (strcasecmp(current->job_sheets->values[0].string.text, "none") != 0 &&
	   current->current_file == 0)
    banner_page = 1;
  else if (current->job_sheets->num_values > 1 &&
	   strcasecmp(current->job_sheets->values[1].string.text, "none") != 0 &&
	   current->current_file == (current->num_files - 1))
    banner_page = 1;
  else
    banner_page = 0;

  LogMessage(L_DEBUG, "banner_page = %d", banner_page);

 /*
  * Building the options string is harder than it needs to be, but
  * for the moment we need to pass strings for command-line args and
  * not IPP attribute pointers... :)
  */

  optptr  = options;
  *optptr = '\0';

  snprintf(title, sizeof(title), "%s-%d", printer->name, current->id);
  strcpy(copies, "1");

  for (attr = current->attrs->attrs; attr != NULL; attr = attr->next)
  {
    if (strcmp(attr->name, "copies") == 0 &&
	attr->value_tag == IPP_TAG_INTEGER)
    {
     /*
      * Don't use the # copies attribute if we are printing the job sheets...
      */

      if (!banner_page)
        sprintf(copies, "%d", attr->values[0].integer);
    }
    else if (strcmp(attr->name, "job-name") == 0 &&
	     (attr->value_tag == IPP_TAG_NAME ||
	      attr->value_tag == IPP_TAG_NAMELANG))
      strlcpy(title, attr->values[0].string.text, sizeof(title));
    else if (attr->group_tag == IPP_TAG_JOB)
    {
     /*
      * Filter out other unwanted attributes...
      */

      if (attr->value_tag == IPP_TAG_MIMETYPE ||
	  attr->value_tag == IPP_TAG_NAMELANG ||
	  attr->value_tag == IPP_TAG_TEXTLANG ||
	  attr->value_tag == IPP_TAG_URI ||
	  attr->value_tag == IPP_TAG_URISCHEME)
	continue;

      if (strncmp(attr->name, "time-", 5) == 0)
	continue;

      if (strncmp(attr->name, "job-", 4) == 0 &&
          !(printer->type & CUPS_PRINTER_REMOTE))
	continue;

      if (strncmp(attr->name, "job-", 4) == 0 &&
          strcmp(attr->name, "job-billing") != 0 &&
          strcmp(attr->name, "job-sheets") != 0 &&
          strcmp(attr->name, "job-hold-until") != 0 &&
	  strcmp(attr->name, "job-priority") != 0)
	continue;

      if (strcmp(attr->name, "page-label") == 0 &&
	  banner_page)
        continue;

     /*
      * Otherwise add them to the list...
      */

      if (optptr > options)
	strlcat(optptr, " ", sizeof(options) - (optptr - options));

      if (attr->value_tag != IPP_TAG_BOOLEAN)
      {
	strlcat(optptr, attr->name, sizeof(options) - (optptr - options));
	strlcat(optptr, "=", sizeof(options) - (optptr - options));
      }

      for (i = 0; i < attr->num_values; i ++)
      {
	if (i)
	  strlcat(optptr, ",", sizeof(options) - (optptr - options));

	optptr += strlen(optptr);

	switch (attr->value_tag)
	{
	  case IPP_TAG_INTEGER :
	  case IPP_TAG_ENUM :
	      snprintf(optptr, sizeof(options) - (optptr - options),
	               "%d", attr->values[i].integer);
	      break;

	  case IPP_TAG_BOOLEAN :
	      if (!attr->values[i].boolean)
		strlcat(optptr, "no", sizeof(options) - (optptr - options));

	  case IPP_TAG_NOVALUE :
	      strlcat(optptr, attr->name,
	              sizeof(options) - (optptr - options));
	      break;

	  case IPP_TAG_RANGE :
	      if (attr->values[i].range.lower == attr->values[i].range.upper)
		snprintf(optptr, sizeof(options) - (optptr - options) - 1,
	        	 "%d", attr->values[i].range.lower);
              else
		snprintf(optptr, sizeof(options) - (optptr - options) - 1,
	        	 "%d-%d", attr->values[i].range.lower,
			 attr->values[i].range.upper);
	      break;

	  case IPP_TAG_RESOLUTION :
	      snprintf(optptr, sizeof(options) - (optptr - options) - 1,
	               "%dx%d%s", attr->values[i].resolution.xres,
		       attr->values[i].resolution.yres,
		       attr->values[i].resolution.units == IPP_RES_PER_INCH ?
			   "dpi" : "dpc");
	      break;

          case IPP_TAG_STRING :
	  case IPP_TAG_TEXT :
	  case IPP_TAG_NAME :
	  case IPP_TAG_KEYWORD :
	  case IPP_TAG_CHARSET :
	  case IPP_TAG_LANGUAGE :
	      if (strchr(attr->values[i].string.text, ' ') != NULL ||
		  strchr(attr->values[i].string.text, '\t') != NULL ||
		  strchr(attr->values[i].string.text, '\n') != NULL)
	      {
		strlcat(optptr, "\'", sizeof(options) - (optptr - options));
		strlcat(optptr, attr->values[i].string.text,
		        sizeof(options) - (optptr - options));
		strlcat(optptr, "\'", sizeof(options) - (optptr - options));
	      }
	      else
		strlcat(optptr, attr->values[i].string.text,
		        sizeof(options) - (optptr - options));
	      break;

          default :
	      break; /* anti-compiler-warning-code */
	}
      }

      optptr += strlen(optptr);
    }
  }

 /*
  * Build the command-line arguments for the filters.  Each filter
  * has 6 or 7 arguments:
  *
  *     argv[0] = printer
  *     argv[1] = job ID
  *     argv[2] = username
  *     argv[3] = title
  *     argv[4] = # copies
  *     argv[5] = options
  *     argv[6] = filename (optional; normally stdin)
  *
  * This allows legacy printer drivers that use the old System V
  * printing interface to be used by CUPS.
  */

  sprintf(jobid, "%d", current->id);
  snprintf(filename, sizeof(filename), "%s/d%05d-%03d", RequestRoot,
           current->id, current->current_file + 1);

  argv[0] = printer->name;
  argv[1] = jobid;
  argv[2] = current->username;
  argv[3] = title;
  argv[4] = copies;
  argv[5] = options;
  argv[6] = filename;
  argv[7] = NULL;

  LogMessage(L_DEBUG, "StartJob: argv = \"%s\",\"%s\",\"%s\",\"%s\",\"%s\",\"%s\",\"%s\"",
             argv[0], argv[1], argv[2], argv[3], argv[4], argv[5], argv[6]);

 /*
  * Create environment variable strings for the filters...
  */

  attr = ippFindAttribute(current->attrs, "attributes-natural-language",
                          IPP_TAG_LANGUAGE);

  switch (strlen(attr->values[0].string.text))
  {
    default :
       /*
        * This is an unknown or badly formatted language code; use
	* the POSIX locale...
	*/

	strcpy(language, "LANG=C");
	break;

    case 2 :
       /*
        * Just the language code (ll)...
	*/

        snprintf(language, sizeof(language), "LANG=%s",
	         attr->values[0].string.text);
        break;

    case 5 :
       /*
        * Language and country code (ll-cc)...
	*/

        snprintf(language, sizeof(language), "LANG=%c%c_%c%c",
	         attr->values[0].string.text[0],
		 attr->values[0].string.text[1],
		 toupper(attr->values[0].string.text[3]),
		 toupper(attr->values[0].string.text[4]));
        break;
  }

  attr = ippFindAttribute(current->attrs, "document-format",
                          IPP_TAG_MIMETYPE);
  if (attr != NULL &&
      (optptr = strstr(attr->values[0].string.text, "charset=")) != NULL)
    snprintf(charset, sizeof(charset), "CHARSET=%s", optptr + 8);
  else
  {
    attr = ippFindAttribute(current->attrs, "attributes-charset",
	                    IPP_TAG_CHARSET);
    snprintf(charset, sizeof(charset), "CHARSET=%s",
             attr->values[0].string.text);
  }

  snprintf(path, sizeof(path), "PATH=%s/filter:/bin:/usr/bin", ServerBin);
  snprintf(content_type, sizeof(content_type), "CONTENT_TYPE=%s/%s",
           current->filetypes[current->current_file]->super,
           current->filetypes[current->current_file]->type);
  snprintf(device_uri, sizeof(device_uri), "DEVICE_URI=%s", printer->device_uri);
  snprintf(ppd, sizeof(ppd), "PPD=%s/ppd/%s.ppd", ServerRoot, printer->name);
  snprintf(printer_name, sizeof(printer_name), "PRINTER=%s", printer->name);
  snprintf(cache, sizeof(cache), "RIP_MAX_CACHE=%s", RIPCache);
  snprintf(root, sizeof(root), "CUPS_SERVERROOT=%s", ServerRoot);
  snprintf(tmpdir, sizeof(tmpdir), "TMPDIR=%s", TempDir);
  snprintf(datadir, sizeof(datadir), "CUPS_DATADIR=%s", DataDir);
  snprintf(fontpath, sizeof(fontpath), "CUPS_FONTPATH=%s", FontPath);

  if (current->dtype & (CUPS_PRINTER_CLASS | CUPS_PRINTER_IMPLICIT))
    snprintf(class_name, sizeof(class_name), "CLASS=%s", current->dest);
  else
    class_name[0] = '\0';

  if (Classification[0] && !banner_page)
  {
    if ((attr = ippFindAttribute(current->attrs, "job-sheets",
                                 IPP_TAG_NAME)) == NULL)
      snprintf(classification, sizeof(classification), "CLASSIFICATION=%s",
               Classification);
    else if (attr->num_values > 1 &&
             strcmp(attr->values[1].string.text, "none") != 0)
      snprintf(classification, sizeof(classification), "CLASSIFICATION=%s",
               attr->values[1].string.text);
    else
      snprintf(classification, sizeof(classification), "CLASSIFICATION=%s",
               attr->values[0].string.text);
  }
  else
    classification[0] = '\0';

  if (getenv("LD_LIBRARY_PATH") != NULL)
    snprintf(ldpath, sizeof(ldpath), "LD_LIBRARY_PATH=%s",
             getenv("LD_LIBRARY_PATH"));
  else if (getenv("DYLD_LIBRARY_PATH") != NULL)
    snprintf(ldpath, sizeof(ldpath), "DYLD_LIBRARY_PATH=%s",
             getenv("DYLD_LIBRARY_PATH"));
  else
    ldpath[0] = '\0';

  if (getenv("NLSPATH") != NULL)
    snprintf(nlspath, sizeof(nlspath), "NLSPATH=%s", getenv("NLSPATH"));
  else
    nlspath[0] = '\0';

  envp[0]  = path;
  envp[1]  = "SOFTWARE=CUPS/1.1";
  envp[2]  = "USER=root";
  envp[3]  = charset;
  envp[4]  = language;
  envp[5]  = TZ;
  envp[6]  = ppd;
  envp[7]  = root;
  envp[8]  = cache;
  envp[9]  = tmpdir;
  envp[10] = content_type;
  envp[11] = device_uri;
  envp[12] = printer_name;
  envp[13] = datadir;
  envp[14] = fontpath;
  envp[15] = ldpath;
  envp[16] = nlspath;
  envp[17] = classification;
  envp[18] = class_name;
#ifdef __APPLE__
  envp[19] = processPath;
  envp[20] = NULL;
#else
  envp[19] = NULL;
#endif	/* __APPLE__ */

  LogMessage(L_DEBUG, "StartJob: envp = \"%s\",\"%s\",\"%s\",\"%s\","
                      "\"%s\",\"%s\",\"%s\",\"%s\",\"%s\",\"%s\",\"%s\","
		      "\"%s\",\"%s\",\"%s\",\"%s\",\"%s\",\"%s\",\"%s\",\"%s\"",
	     envp[0], envp[1], envp[2], envp[3], envp[4],
	     envp[5], envp[6], envp[7], envp[8], envp[9],
	     envp[10], envp[11], envp[12], envp[13], envp[14],
	     envp[15], envp[16], envp[17], envp[18]);

  current->current_file ++;

 /*
  * Make sure we have a buffer to read status info into...
  */

  if (current->buffer == NULL)
  {
    LogMessage(L_DEBUG2, "UpdateJob: Allocating status buffer...");

    if ((current->buffer = malloc(JOB_BUFFER_SIZE)) == NULL)
    {
      LogMessage(L_EMERG, "Unable to allocate memory for job status buffer - %s",
                 strerror(errno));
      CancelJob(current->id, 0);
      return;
    }

    current->bufused = 0;
  }

 /*
  * Now create processes for all of the filters...
  */

  if (pipe(statusfds))
  {
    LogMessage(L_ERROR, "Unable to create job status pipes - %s.",
	       strerror(errno));
    snprintf(printer->state_message, sizeof(printer->state_message),
             "Unable to create status pipes - %s.", strerror(errno));
    return;
  }

  LogMessage(L_DEBUG, "StartJob: statusfds = %d, %d",
             statusfds[0], statusfds[1]);

  current->pipe   = statusfds[0];
  current->status = 0;
  memset(current->procs, 0, sizeof(current->procs));

  filterfds[1][0] = open("/dev/null", O_RDONLY);
  filterfds[1][1] = -1;

  LogMessage(L_DEBUG, "StartJob: filterfds[%d] = %d, %d", 1, filterfds[1][0],
             filterfds[1][1]);

  for (i = 0, slot = 0; i < num_filters; i ++)
  {
    if (filters[i].filter[0] != '/')
      snprintf(command, sizeof(command), "%s/filter/%s", ServerBin,
               filters[i].filter);
    else
      strlcpy(command, filters[i].filter, sizeof(command));

#ifdef __APPLE__
   /*
    * Setting CFProcessPath lets OS X's Core Foundation code find
    * the bundle that may be associated with a filter or backend.
    */

    snprintf(processPath, sizeof(processPath), "CFProcessPath=%s", command);
    LogMessage(L_DEBUG, "StartJob: %s\n", processPath);
#endif	/* __APPLE__ */

    if (i < (num_filters - 1) ||
	strncmp(printer->device_uri, "file:", 5) != 0)
      pipe(filterfds[slot]);
    else
    {
      filterfds[slot][0] = -1;
      if (strncmp(printer->device_uri, "file:/dev/", 10) == 0)
	filterfds[slot][1] = open(printer->device_uri + 5,
	                          O_WRONLY | O_EXCL);
      else
	filterfds[slot][1] = open(printer->device_uri + 5,
	                          O_WRONLY | O_CREAT | O_TRUNC, 0600);
    }

    LogMessage(L_DEBUG, "StartJob: filter = \"%s\"", command);
    LogMessage(L_DEBUG, "StartJob: filterfds[%d] = %d, %d",
               slot, filterfds[slot][0], filterfds[slot][1]);

    pid = start_process(command, argv, envp, filterfds[!slot][0],
                        filterfds[slot][1], statusfds[1], 0,
			current->procs + i);

    close(filterfds[!slot][0]);
    close(filterfds[!slot][1]);

    if (pid == 0)
    {
      LogMessage(L_ERROR, "Unable to start filter \"%s\" - %s.",
                 filters[i].filter, strerror(errno));
      snprintf(printer->state_message, sizeof(printer->state_message),
               "Unable to start filter \"%s\" - %s.",
               filters[i].filter, strerror(errno));
      return;
    }

    LogMessage(L_INFO, "Started filter %s (PID %d) for job %d.",
               command, pid, current->id);

    argv[6] = NULL;
    slot    = !slot;
  }

  if (filters != NULL)
    free(filters);

 /*
  * Finally, pipe the final output into a backend process if needed...
  */

  if (strncmp(printer->device_uri, "file:", 5) != 0)
  {
    sscanf(printer->device_uri, "%254[^:]", method);
    snprintf(command, sizeof(command), "%s/backend/%s", ServerBin, method);

#ifdef __APPLE__
   /*
    * Setting CFProcessPath lets OS X's Core Foundation code find
    * the bundle that may be associated with a filter or backend.
    */

    snprintf(processPath, sizeof(processPath), "CFProcessPath=%s", command);
    LogMessage(L_DEBUG, "StartJob: %s\n", processPath);
#endif	/* __APPLE__ */

    argv[0] = printer->device_uri;

    filterfds[slot][0] = -1;
    filterfds[slot][1] = open("/dev/null", O_WRONLY);

    LogMessage(L_DEBUG, "StartJob: backend = \"%s\"", command);
    LogMessage(L_DEBUG, "StartJob: filterfds[%d] = %d, %d",
               slot, filterfds[slot][0], filterfds[slot][1]);

    pid = start_process(command, argv, envp, filterfds[!slot][0],
			filterfds[slot][1], statusfds[1], 1,
			current->procs + i);

    close(filterfds[!slot][0]);
    close(filterfds[!slot][1]);

    if (pid == 0)
    {
      LogMessage(L_ERROR, "Unable to start backend \"%s\" - %s.",
                 method, strerror(errno));
      snprintf(printer->state_message, sizeof(printer->state_message),
               "Unable to start backend \"%s\" - %s.", method, strerror(errno));
      return;
    }
    else
    {
      LogMessage(L_INFO, "Started backend %s (PID %d) for job %d.", command, pid,
                 current->id);
    }
  }
  else
  {
    filterfds[slot][0] = -1;
    filterfds[slot][1] = -1;

    close(filterfds[!slot][0]);
    close(filterfds[!slot][1]);
  }

  close(filterfds[slot][0]);
  close(filterfds[slot][1]);

  close(statusfds[1]);

  LogMessage(L_DEBUG2, "StartJob: Adding fd %d to InputSet...", current->pipe);

  FD_SET(current->pipe, &InputSet);
}


/*
 * 'StopAllJobs()' - Stop all print jobs.
 */

void
StopAllJobs(void)
{
  job_t	*current;		/* Current job */


  DEBUG_puts("StopAllJobs()");

  for (current = Jobs; current != NULL; current = current->next)
    if (current->state->values[0].integer == IPP_JOB_PROCESSING)
    {
      StopJob(current->id, 1);
      current->state->values[0].integer = IPP_JOB_PENDING;
    }
}


/*
 * 'StopJob()' - Stop a print job.
 */

void
StopJob(int id,			/* I - Job ID */
        int force)		/* I - 1 = Force all filters to stop */
{
  int	i;			/* Looping var */
  job_t	*current;		/* Current job */


  LogMessage(L_DEBUG, "StopJob: id = %d, force = %d", id, force);

  for (current = Jobs; current != NULL; current = current->next)
    if (current->id == id)
    {
      DEBUG_puts("StopJob: found job in list.");

      if (current->state->values[0].integer == IPP_JOB_PROCESSING)
      {
        DEBUG_puts("StopJob: job state is \'processing\'.");

        FilterLevel -= current->cost;

        if (current->status < 0)
	  SetPrinterState(current->printer, IPP_PRINTER_STOPPED);
	else if (current->printer->state != IPP_PRINTER_STOPPED)
	  SetPrinterState(current->printer, IPP_PRINTER_IDLE);

        LogMessage(L_DEBUG, "StopJob: printer state is %d", current->printer->state);

	current->state->values[0].integer = IPP_JOB_STOPPED;
        current->printer->job = NULL;
        current->printer      = NULL;

	current->current_file --;

        for (i = 0; current->procs[i]; i ++)
	  if (current->procs[i] > 0)
	  {
	    kill(current->procs[i], force ? SIGKILL : SIGTERM);
	    current->procs[i] = 0;
	  }

        if (current->pipe)
        {
	 /*
	  * Close the pipe and clear the input bit.
	  */

          LogMessage(L_DEBUG2, "StopJob: Removing fd %d from InputSet...",
	             current->pipe);

          close(current->pipe);
	  FD_CLR(current->pipe, &InputSet);
	  current->pipe = 0;
        }

        if (current->buffer)
	{
	 /*
	  * Free the status buffer...
	  */

          LogMessage(L_DEBUG2, "StopJob: Freeing status buffer...");

          free(current->buffer);
	  current->buffer  = NULL;
	  current->bufused = 0;
	}
      }
      return;
    }
}


/*
 * 'UpdateJob()' - Read a status update from a job's filters.
 */

void
UpdateJob(job_t *job)		/* I - Job to check */
{
  int		bytes;		/* Number of bytes read */
  int		copies;		/* Number of copies printed */
  char		*lineptr,	/* Pointer to end of line in buffer */
		*message;	/* Pointer to message text */
  int		loglevel;	/* Log level for message */


  if ((bytes = read(job->pipe, job->buffer + job->bufused,
                    JOB_BUFFER_SIZE - job->bufused - 1)) > 0)
  {
    job->bufused += bytes;
    job->buffer[job->bufused] = '\0';
    lineptr = strchr(job->buffer, '\n');
  }
  else if (bytes < 0 && errno == EINTR)
    return;
  else
  {
    lineptr  = job->buffer + job->bufused;
    *lineptr = '\0';
  }

  if (job->bufused == 0 && bytes == 0)
    lineptr = NULL;

  while (lineptr != NULL)
  {
   /*
    * Terminate each line and process it...
    */

    *lineptr++ = '\0';

   /*
    * Figure out the logging level...
    */

    if (strncmp(job->buffer, "EMERG:", 6) == 0)
    {
      loglevel = L_EMERG;
      message  = job->buffer + 6;
    }
    else if (strncmp(job->buffer, "ALERT:", 6) == 0)
    {
      loglevel = L_ALERT;
      message  = job->buffer + 6;
    }
    else if (strncmp(job->buffer, "CRIT:", 5) == 0)
    {
      loglevel = L_CRIT;
      message  = job->buffer + 5;
    }
    else if (strncmp(job->buffer, "ERROR:", 6) == 0)
    {
      loglevel = L_ERROR;
      message  = job->buffer + 6;
    }
    else if (strncmp(job->buffer, "WARNING:", 8) == 0)
    {
      loglevel = L_WARN;
      message  = job->buffer + 8;
    }
    else if (strncmp(job->buffer, "NOTICE:", 6) == 0)
    {
      loglevel = L_NOTICE;
      message  = job->buffer + 6;
    }
    else if (strncmp(job->buffer, "INFO:", 5) == 0)
    {
      loglevel = L_INFO;
      message  = job->buffer + 5;
    }
    else if (strncmp(job->buffer, "DEBUG:", 6) == 0)
    {
      loglevel = L_DEBUG;
      message  = job->buffer + 6;
    }
    else if (strncmp(job->buffer, "DEBUG2:", 7) == 0)
    {
      loglevel = L_DEBUG2;
      message  = job->buffer + 7;
    }
    else if (strncmp(job->buffer, "PAGE:", 5) == 0)
    {
      loglevel = L_PAGE;
      message  = job->buffer + 5;
    }
    else
    {
      loglevel = L_DEBUG;
      message  = job->buffer;
    }

   /*
    * Skip leading whitespace in the message...
    */

    while (isspace(*message))
      message ++;

   /*
    * Send it to the log file and printer state message as needed...
    */

    if (loglevel == L_PAGE)
    {
     /*
      * Page message; send the message to the page_log file and update the
      * job sheet count...
      */

      if (job->sheets != NULL)
      {
	if (!sscanf(message, "%*d%d", &copies))
	{
          job->sheets->values[0].integer ++;

	  if (job->printer->page_limit)
	    UpdateQuota(job->printer, job->username, 1, 0);
        }
	else
	{
          job->sheets->values[0].integer += copies;

	  if (job->printer->page_limit)
	    UpdateQuota(job->printer, job->username, copies, 0);
        }
      }

      LogPage(job, message);
    }
    else
    {
     /*
      * Other status message; send it to the error_log file...
      */

      if (loglevel != L_INFO)
	LogMessage(loglevel, "%s", message);

      if ((loglevel == L_INFO && !job->status) ||
	  loglevel < L_INFO)
        strlcpy(job->printer->state_message, message,
                sizeof(job->printer->state_message));
    }

   /*
    * Copy over the buffer data we've used up...
    */

    strcpy(job->buffer, lineptr);
    job->bufused -= lineptr - job->buffer;

    if (job->bufused < 0)
      job->bufused = 0;

    lineptr = strchr(job->buffer, '\n');
  }

  if (bytes <= 0)
  {
    LogMessage(L_DEBUG, "UpdateJob: job %d, file %d is complete.",
               job->id, job->current_file - 1);

    if (job->pipe)
    {
     /*
      * Close the pipe and clear the input bit.
      */

      LogMessage(L_DEBUG2, "UpdateJob: Removing fd %d from InputSet...",
                 job->pipe);

      close(job->pipe);
      FD_CLR(job->pipe, &InputSet);
      job->pipe = 0;
    }

    if (job->status < 0)
    {
     /*
      * Backend had errors; stop it...
      */

      StopJob(job->id, 0);
      job->state->values[0].integer = IPP_JOB_PENDING;
      SaveJob(job->id);
    }
    else if (job->status > 0)
    {
     /*
      * Filter had errors; cancel it...
      */

      if (job->current_file < job->num_files)
        StartJob(job->id, job->printer);
      else
      {
        CancelJob(job->id, 0);

        if (JobHistory)
	{
          job->state->values[0].integer = IPP_JOB_ABORTED;
	  SaveJob(job->id);
	}

        CheckJobs();
      }
    }
    else
    {
     /*
      * Job printed successfully; cancel it...
      */

      if (job->current_file < job->num_files)
      {
        FilterLevel -= job->cost;
        StartJob(job->id, job->printer);
      }
      else
      {
	CancelJob(job->id, 0);

        if (JobHistory)
	{
          job->state->values[0].integer = IPP_JOB_COMPLETED;
	  SaveJob(job->id);
	}

	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      *ipp)		/* I - Request to read into */
{
  int			fd;		/* File descriptor for file */
  int			n;		/* Length of data */
  unsigned char		buffer[8192],	/* Data buffer */
			*bufptr;	/* Pointer into buffer */
  ipp_attribute_t	*attr;		/* Current attribute */
  ipp_tag_t		tag;		/* Current tag */


 /*
  * Open the file if possible...
  */

  if (filename == NULL || ipp == 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)
  {
    default :
	break; /* anti-compiler-warning-code */

    case IPP_IDLE :
        ipp->state ++; /* Avoid common problem... */

    case IPP_HEADER :
       /*
        * Get the request header...
	*/

        if ((n = read(fd, buffer, 8)) < 8)
	{
	  DEBUG_printf(("ipp_read_file: 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(("ipp_read_file: 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("ipp_read_file: 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(("ipp_read_file: group tag = %x\n", tag));
	    continue;
	  }

          DEBUG_printf(("ipp_read_file: value tag = %x\n", tag));

         /*
	  * Get the name...
	  */

          if (read(fd, buffer, 2) < 2)
	  {
	    DEBUG_puts("ipp_read_file: unable to read name length!");
	    close(fd);
	    return (IPP_ERROR);
	  }

          n = (buffer[0] << 8) | buffer[1];

          if (n > (sizeof(buffer) - 1))
	  {
	    DEBUG_printf(("ipp_read_file: bad name length %d!\n", n));
	    return (IPP_ERROR);
	  }

          DEBUG_printf(("ipp_read_file: 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;

           /*
	    * Finally, reallocate the attribute array as needed...
	    */

	    if ((attr->num_values % IPP_MAX_VALUES) == 0)
	    {
	      ipp_attribute_t	*temp,	/* Pointer to new buffer */
				*ptr;	/* Pointer in attribute list */


             /*
	      * Reallocate memory...
	      */

              if ((temp = realloc(attr, sizeof(ipp_attribute_t) +
	                                (attr->num_values + IPP_MAX_VALUES - 1) *
					sizeof(ipp_value_t))) == NULL)
	      {
		close(fd);
        	return (IPP_ERROR);
	      }

             /*
	      * Reset pointers in the list...
	      */

	      for (ptr = ipp->attrs; ptr && ptr->next != attr; ptr = ptr->next);

              if (ptr)
	        ptr->next = temp;
	      else
	        ipp->attrs = temp;

              attr = ipp->current = ipp->last = temp;
	    }
	  }
	  else
	  {
	   /*
	    * New attribute; read the name and add it...
	    */

	    if (read(fd, buffer, n) < n)
	    {
	      DEBUG_puts("ipp_read_file: unable to read name!");
	      close(fd);
	      return (IPP_ERROR);
	    }

	    buffer[n] = '\0';
	    DEBUG_printf(("ipp_read_file: name = \'%s\'\n", buffer));

	    attr = ipp->current = _ipp_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("ipp_read_file: unable to read value length!");
	    close(fd);
	    return (IPP_ERROR);
	  }

	  n = (buffer[0] << 8) | buffer[1];
          DEBUG_printf(("ipp_read_file: 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(("ipp_read_file: 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 (n > sizeof(buffer))
		{
		  DEBUG_printf(("ipp_read_file: bad value length %d!\n", n));
		  return (IPP_ERROR);
		}

	        if (read(fd, buffer, n) < n)
		  return (IPP_ERROR);

                bufptr = buffer;

	       /*
	        * text-with-language and name-with-language are composite
		* values:
		*
		*    charset-length
		*    charset
		*    text-length
		*    text
		*/

		n = (bufptr[0] << 8) | bufptr[1];

                attr->values[attr->num_values].string.charset = calloc(n + 1, 1);

		memcpy(attr->values[attr->num_values].string.charset,
		       bufptr + 2, n);

                bufptr += 2 + n;
		n = (bufptr[0] << 8) | bufptr[1];

                attr->values[attr->num_values].string.text = calloc(n + 1, 1);

		memcpy(attr->values[attr->num_values].string.text,
		       bufptr + 2, n);

	        break;

            default : /* Other unsupported values */
                attr->values[attr->num_values].unknown.length = n;
	        if (n > 0)
		{
		  attr->values[attr->num_values].unknown.data = malloc(n);
	          if (read(fd, attr->values[attr->num_values].unknown.data, n) < n)
		    return (IPP_ERROR);
		}
		else
		  attr->values[attr->num_values].unknown.data = NULL;
	        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      *ipp)		/* 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 || ipp == NULL)
    return (IPP_ERROR);

  if ((fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC, 0600)) == -1)
    return (IPP_ERROR);

  fchmod(fd, 0600);
  fchown(fd, User, Group);

 /*
  * Write the IPP request...
  */

  ipp->state = IPP_IDLE;

  switch (ipp->state)
  {
    default :
	break; /* anti-compiler-warning-code */

    case IPP_IDLE :
        ipp->state ++; /* Avoid common problem... */

    case IPP_HEADER :
       /*
        * Send the request header...
	*/

        bufptr = buffer;

	*bufptr++ = ipp->request.any.version[0];
	*bufptr++ = ipp->request.any.version[1];
	*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("ipp_write_file: 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(("ipp_write_file: wrote group tag = %x\n", attr->group_tag));
	    *bufptr++ = attr->group_tag;
	  }

          if ((n = strlen(attr->name)) > (sizeof(buffer) - 3))
	    return (IPP_ERROR);

          DEBUG_printf(("ipp_write_file: writing value tag = %x\n", attr->value_tag));
          DEBUG_printf(("ipp_write_file: 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 ((sizeof(buffer) - (bufptr - buffer)) < 9)
		  {
                    if (write(fd, (char *)buffer, bufptr - buffer) < 0)
	            {
	              DEBUG_puts("ippWrite: Could not write IPP attribute...");
	              return (IPP_ERROR);
	            }

		    bufptr = buffer;
		  }

		  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 ((sizeof(buffer) - (bufptr - buffer)) < 6)
		  {
                    if (write(fd, (char *)buffer, bufptr - buffer) < 0)
	            {
	              DEBUG_puts("ippWrite: Could not write IPP attribute...");
	              return (IPP_ERROR);
	            }

		    bufptr = buffer;
		  }

		  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(("ipp_write_file: writing value tag = %x\n",
		                  attr->value_tag));
        	    DEBUG_printf(("ipp_write_file: writing name = 0, \'\'\n"));

                    if ((sizeof(buffer) - (bufptr - buffer)) < 3)
		    {
                      if (write(fd, (char *)buffer, bufptr - buffer) < 0)
	              {
	        	DEBUG_puts("ippWrite: Could not write IPP attribute...");
	        	return (IPP_ERROR);
	              }

		      bufptr = buffer;
		    }

                    *bufptr++ = attr->value_tag;
		    *bufptr++ = 0;
		    *bufptr++ = 0;
		  }

                  n = strlen(attr->values[i].string.text);

                  if (n > sizeof(buffer))
		    return (IPP_ERROR);

                  DEBUG_printf(("ipp_write_file: 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("ipp_write_file: 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 ((sizeof(buffer) - (bufptr - buffer)) < 16)
		  {
                    if (write(fd, (char *)buffer, bufptr - buffer) < 0)
	            {
	              DEBUG_puts("ippWrite: Could not write IPP attribute...");
	              return (IPP_ERROR);
	            }

		    bufptr = buffer;
		  }

		  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 ((sizeof(buffer) - (bufptr - buffer)) < 14)
		  {
                    if (write(fd, (char *)buffer, bufptr - buffer) < 0)
	            {
	              DEBUG_puts("ippWrite: Could not write IPP attribute...");
	              return (IPP_ERROR);
	            }

		    bufptr = buffer;
		  }

		  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 ((sizeof(buffer) - (bufptr - buffer)) < 13)
		  {
                    if (write(fd, (char *)buffer, bufptr - buffer) < 0)
	            {
	              DEBUG_puts("ippWrite: Could not write IPP attribute...");
	              return (IPP_ERROR);
	            }

		    bufptr = buffer;
		  }

		  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...
		    */

                    if ((sizeof(buffer) - (bufptr - buffer)) < 3)
		    {
                      if (write(fd, (char *)buffer, bufptr - buffer) < 0)
	              {
	        	DEBUG_puts("ippWrite: Could not write IPP attribute...");
	        	return (IPP_ERROR);
	              }

		      bufptr = buffer;
		    }

                    *bufptr++ = attr->value_tag;
		    *bufptr++ = 0;
		    *bufptr++ = 0;
		  }

                  n = strlen(attr->values[i].string.charset) +
		      strlen(attr->values[i].string.text) +
		      4;

                  if (n > sizeof(buffer))
		    return (IPP_ERROR);

                  if ((sizeof(buffer) - (bufptr - buffer)) < (n + 2))
		  {
                    if (write(fd, (char *)buffer, bufptr - buffer) < 0)
	            {
	              DEBUG_puts("ipp_write_file: Could not write IPP attribute...");
	              return (IPP_ERROR);
	            }

		    bufptr = buffer;
		  }

                 /* Length of entire value */
	          *bufptr++ = n >> 8;
		  *bufptr++ = n;

                 /* Length of charset */
                  n = strlen(attr->values[i].string.charset);
	          *bufptr++ = n >> 8;
		  *bufptr++ = n;

                 /* Charset */
		  memcpy(bufptr, attr->values[i].string.charset, n);
		  bufptr += n;

                 /* Length of text */
                  n = strlen(attr->values[i].string.text);
	          *bufptr++ = n >> 8;
		  *bufptr++ = n;

                 /* Text */
		  memcpy(bufptr, attr->values[i].string.text, n);
		  bufptr += n;
		}
		break;

            default :
	        for (i = 0; i < attr->num_values; i ++)
		{
		  if (i)
		  {
		   /*
		    * Arrays and sets are done by sending additional
		    * values with a zero-length name...
		    */

                    if ((sizeof(buffer) - (bufptr - buffer)) < 3)
		    {
                      if (write(fd, (char *)buffer, bufptr - buffer) < 0)
	              {
	        	DEBUG_puts("ippWrite: Could not write IPP attribute...");
	        	return (IPP_ERROR);
	              }

		      bufptr = buffer;
		    }

                    *bufptr++ = attr->value_tag;
		    *bufptr++ = 0;
		    *bufptr++ = 0;
		  }

                  n = attr->values[i].unknown.length;

                  if (n > sizeof(buffer))
		    return (IPP_ERROR);

                  if ((sizeof(buffer) - (bufptr - buffer)) < (n + 2))
		  {
                    if (write(fd, (char *)buffer, bufptr - buffer) < 0)
	            {
	              DEBUG_puts("ipp_write_file: Could not write IPP attribute...");
	              return (IPP_ERROR);
	            }

		    bufptr = buffer;
		  }

                 /* Length of unknown value */
	          *bufptr++ = n >> 8;
		  *bufptr++ = n;

                 /* Value */
		  if (n > 0)
		  {
		    memcpy(bufptr, attr->values[i].unknown.data, n);
		    bufptr += n;
		  }
		}
		break;
	  }

         /*
	  * Write the data out...
	  */

          if (write(fd, (char *)buffer, bufptr - buffer) < 0)
	  {
	    DEBUG_puts("ipp_write_file: Could not write IPP attribute...");
	    close(fd);
	    return (IPP_ERROR);
	  }

          DEBUG_printf(("ipp_write_file: 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("ipp_write_file: 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);
}


/*
 * 'set_time()' - Set one of the "time-at-xyz" attributes...
 */

static void
set_time(job_t      *job,	/* I - Job to update */
         const char *name)	/* I - Name of attribute */
{
  ipp_attribute_t	*attr;	/* Time attribute */


  if ((attr = ippFindAttribute(job->attrs, name, IPP_TAG_ZERO)) != NULL)
  {
    attr->value_tag         = IPP_TAG_INTEGER;
    attr->values[0].integer = time(NULL);
  }
}


/*
 * 'start_process()' - Start a background process.
 */

static int				/* O - Process ID or 0 */
start_process(const 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 */
	      int        root,		/* I - Run as root? */
              int        *pid)		/* O - Process ID */
{
  int	fd;				/* Looping var */
#if defined(HAVE_SIGACTION) && !defined(HAVE_SIGSET)
  sigset_t	oldmask,		/* POSIX signal masks */
		newmask;
  struct sigaction	action;		/* Actions for POSIX signals */
#endif /* HAVE_SIGACTION && !HAVE_SIGSET */


  LogMessage(L_DEBUG, "start_process(\"%s\", %p, %p, %d, %d, %d)",
             command, argv, envp, infd, outfd, errfd);

 /*
  * Block signals before forking...
  */

#ifdef HAVE_SIGSET
  sighold(SIGTERM);
  sighold(SIGCHLD);
#elif defined(HAVE_SIGACTION)
  sigemptyset(&newmask);
  sigaddset(&newmask, SIGTERM);
  sigaddset(&newmask, SIGCHLD);
  sigprocmask(SIG_BLOCK, &newmask, &oldmask);
#endif /* HAVE_SIGSET */

  if ((*pid = fork()) == 0)
  {
   /*
    * Child process goes here...
    *
    * Reset signal handlers
    */

#ifdef HAVE_SIGSET /* Use System V signals over POSIX to avoid bugs */
    sigset(SIGCHLD, SIG_DFL);
    sigset(SIGTERM, SIG_DFL);

    sigrelse(SIGTERM);
    sigrelse(SIGCHLD);
#elif defined(HAVE_SIGACTION)
    memset(&action, 0, sizeof(action));
    
    sigemptyset(&action.sa_mask);
    sigaddset(&action.sa_mask, SIGCHLD);
    action.sa_handler = SIG_DFL;
    sigaction(SIGCHLD, &action, NULL);
    
    sigemptyset(&action.sa_mask);
    sigaddset(&action.sa_mask, SIGTERM);
    action.sa_handler = SIG_DFL;
    sigaction(SIGTERM, &action, NULL);

    sigprocmask(SIG_SETMASK, &oldmask, NULL);
#else
    signal(SIGCHLD, SIG_DFL);
    signal(SIGTERM, SIG_DFL);
#endif /* HAVE_SIGSET */
    
    /*
    * Update stdin/stdout/stderr as needed...
    */

    close(0);
    dup(infd);
    close(1);
    dup(outfd);
    if (errfd > 2)
    {
      close(2);
      dup(errfd);
    }

   /*
    * Close extra file descriptors...
    */

    for (fd = 3; fd < MaxFDs; fd ++)
      close(fd);

   /*
    * Change user to something "safe"...
    */

    if (!root && getuid() == 0)
    {
     /*
      * Running as root, so change to non-priviledged user...
      */

      if (setgid(Group))
        exit(errno);

      if (setuid(User))
        exit(errno);
    }

   /*
    * Reset group membership to just the main one we belong to.
    */

    setgroups(0, NULL);

   /*
    * Change umask to restrict permissions on created files...
    */

    umask(077);

   /*
    * Execute the command; if for some reason this doesn't work,
    * return the error code...
    */

    execve(command, argv, envp);

    perror(command);

    exit(errno);
  }
  else if (*pid < 0)
  {
   /*
    * Error - couldn't fork a new process!
    */

    LogMessage(L_ERROR, "Unable to fork %s - %s.", command, strerror(errno));

    *pid = 0;
  }

#ifdef HAVE_SIGSET
  sigrelse(SIGTERM);
  sigrelse(SIGCHLD);
#elif defined(HAVE_SIGACTION)
  sigprocmask(SIG_SETMASK, &oldmask, NULL);
#endif /* HAVE_SIGSET */

  return (*pid);
}


/*
 * End of "$Id: job.c,v 1.5.2.3 2003/02/27 23:17:39 jlovell Exp $".
 */
