4 * Job management routines for the Common UNIX Printing System (CUPS).
6 * Copyright 1997-2005 by Easy Software Products, all rights reserved.
8 * These coded instructions, statements, and computer programs are the
9 * property of Easy Software Products and are protected by Federal
10 * copyright law. Distribution and use rights are outlined in the file
11 * "LICENSE.txt" which should have been included with this file. If this
12 * file is missing or damaged please contact Easy Software Products
15 * Attn: CUPS Licensing Information
16 * Easy Software Products
17 * 44141 Airport View Drive, Suite 204
18 * Hollywood, Maryland 20636 USA
20 * Voice: (301) 373-9600
21 * EMail: cups-info@cups.org
22 * WWW: http://www.cups.org
26 * AddJob() - Add a new job to the job queue...
27 * CancelJob() - Cancel the specified print job.
28 * CancelJobs() - Cancel all jobs for the given destination/user...
29 * CheckJobs() - Check the pending jobs and start any if the
30 * destination is available.
31 * CleanJobs() - Clean out old jobs.
32 * FreeAllJobs() - Free all jobs from memory.
33 * FindJob() - Find the specified job.
34 * GetPrinterJobCount() - Get the number of pending, processing,
35 * or held jobs in a printer or class.
36 * GetUserJobCount() - Get the number of pending, processing,
37 * or held jobs for a user.
38 * HoldJob() - Hold the specified job.
39 * LoadAllJobs() - Load all jobs from disk.
40 * MoveJob() - Move the specified job to a different
42 * ReleaseJob() - Release the specified job.
43 * RestartJob() - Restart the specified job.
44 * SaveJob() - Save a job to disk.
45 * SetJobHoldUntil() - Set the hold time for a job...
46 * SetJobPriority() - Set the priority of a job, moving it up/down
47 * in the list as needed.
48 * StartJob() - Start a print job.
49 * StopAllJobs() - Stop all print jobs.
50 * StopJob() - Stop a print job.
51 * UpdateJob() - Read a status update from a job's filters.
52 * ipp_length() - Compute the size of the buffer needed to hold
53 * the textual IPP attributes.
54 * set_hold_until() - Set the hold time and update job-hold-until attribute.
58 * Include necessary headers...
63 #include <cups/backend.h>
71 static mime_filter_t gziptoany_filter
=
73 NULL
, /* Source type */
74 NULL
, /* Destination type */
76 "gziptoany" /* Filter program to run */
84 static int ipp_length(ipp_t
*ipp
);
85 static void set_time(job_t
*job
, const char *name
);
86 static void set_hold_until(job_t
*job
, time_t holdtime
);
90 * 'AddJob()' - Add a new job to the job queue...
93 job_t
* /* O - New job record */
94 AddJob(int priority
, /* I - Job priority */
95 const char *dest
) /* I - Job destination */
97 job_t
*job
, /* New job record */
98 *current
, /* Current job in queue */
99 *prev
; /* Previous job in queue */
102 job
= calloc(sizeof(job_t
), 1);
104 job
->id
= NextJobId
++;
105 job
->priority
= priority
;
106 job
->back_pipes
[0] = -1;
107 job
->back_pipes
[1] = -1;
108 job
->print_pipes
[0] = -1;
109 job
->print_pipes
[1] = -1;
111 SetString(&job
->dest
, dest
);
115 for (current
= Jobs
, prev
= NULL
;
117 prev
= current
, current
= current
->next
)
118 if (job
->priority
> current
->priority
)
132 * 'CancelJob()' - Cancel the specified print job.
136 CancelJob(int id
, /* I - Job to cancel */
137 int purge
) /* I - Purge jobs? */
139 int i
; /* Looping var */
140 job_t
*current
, /* Current job */
141 *prev
; /* Previous job in list */
142 char filename
[1024]; /* Job filename */
145 LogMessage(L_DEBUG
, "CancelJob: id = %d", id
);
147 for (current
= Jobs
, prev
= NULL
; current
!= NULL
; prev
= current
, current
= current
->next
)
148 if (current
->id
== id
)
151 * Stop any processes that are working on the current...
154 DEBUG_puts("CancelJob: found job in list.");
156 if (current
->state
->values
[0].integer
== IPP_JOB_PROCESSING
)
157 StopJob(current
->id
, 0);
159 current
->state
->values
[0].integer
= IPP_JOB_CANCELLED
;
161 set_time(current
, "time-at-completed");
163 cupsdExpireSubscriptions(NULL
, current
);
166 * Remove the print file for good if we aren't preserving jobs or
170 current
->current_file
= 0;
172 if (!JobHistory
|| !JobFiles
|| purge
||
173 (current
->dtype
& CUPS_PRINTER_REMOTE
))
174 for (i
= 1; i
<= current
->num_files
; i
++)
176 snprintf(filename
, sizeof(filename
), "%s/d%05d-%03d", RequestRoot
,
181 if (JobHistory
&& !purge
&& !(current
->dtype
& CUPS_PRINTER_REMOTE
))
184 * Save job state info...
187 SaveJob(current
->id
);
192 * Remove the job info file...
195 snprintf(filename
, sizeof(filename
), "%s/c%05d", RequestRoot
,
200 * Update pointers if we aren't preserving jobs...
204 Jobs
= current
->next
;
206 prev
->next
= current
->next
;
209 * Free all memory used...
212 if (current
->attrs
!= NULL
)
213 ippDelete(current
->attrs
);
215 if (current
->num_files
> 0)
217 free(current
->compressions
);
218 free(current
->filetypes
);
221 ClearString(¤t
->username
);
222 ClearString(¤t
->dest
);
235 * 'CancelJobs()' - Cancel all jobs for the given destination/user...
239 CancelJobs(const char *dest
, /* I - Destination to cancel */
240 const char *username
, /* I - Username or NULL */
241 int purge
) /* I - Purge jobs? */
243 job_t
*current
, /* Current job */
244 *next
; /* Next job */
247 for (current
= Jobs
; current
!= NULL
;)
248 if ((dest
== NULL
|| !strcmp(current
->dest
, dest
)) &&
249 (username
== NULL
|| !strcmp(current
->username
, username
)))
252 * Cancel all jobs matching this destination/user...
255 next
= current
->next
;
257 cupsdAddEvent(CUPSD_EVENT_JOB_COMPLETED
, current
->printer
, current
,
258 purge
? "Job purged." : "Job canceled.");
260 CancelJob(current
->id
, purge
);
265 current
= current
->next
;
272 * 'CheckJobs()' - Check the pending jobs and start any if the destination
279 job_t
*current
, /* Current job in queue */
280 *next
; /* Next job in queue */
281 printer_t
*printer
, /* Printer destination */
282 *pclass
; /* Printer class destination */
285 DEBUG_puts("CheckJobs()");
287 for (current
= Jobs
; current
!= NULL
; current
= next
)
290 * Save next pointer in case the job is cancelled en-route.
293 next
= current
->next
;
296 * Start held jobs if they are ready...
299 if (current
->state
->values
[0].integer
== IPP_JOB_HELD
&&
300 current
->hold_until
&&
301 current
->hold_until
< time(NULL
))
302 current
->state
->values
[0].integer
= IPP_JOB_PENDING
;
305 * Start pending jobs if the destination is available...
308 if (current
->state
->values
[0].integer
== IPP_JOB_PENDING
&& !NeedReload
)
310 if ((pclass
= FindClass(current
->dest
)) != NULL
)
313 * If the class is remote, just pass it to the remote server...
316 if (pclass
->type
& CUPS_PRINTER_REMOTE
)
318 else if (pclass
->state
!= IPP_PRINTER_STOPPED
)
319 printer
= FindAvailablePrinter(current
->dest
);
324 printer
= FindPrinter(current
->dest
);
326 if (printer
!= NULL
&& (printer
->type
& CUPS_PRINTER_IMPLICIT
))
329 * Handle implicit classes...
334 if (pclass
->state
!= IPP_PRINTER_STOPPED
)
335 printer
= FindAvailablePrinter(current
->dest
);
340 if (printer
== NULL
&& pclass
== NULL
)
343 * Whoa, the printer and/or class for this destination went away;
347 LogMessage(L_WARN
, "Printer/class %s has gone away; cancelling job %d!",
348 current
->dest
, current
->id
);
350 cupsdAddEvent(CUPSD_EVENT_JOB_COMPLETED
, current
->printer
, current
,
351 "Job cancelled because the destination printer/class has gone away.");
353 CancelJob(current
->id
, 1);
355 else if (printer
!= NULL
)
358 * See if the printer is available or remote and not printing a job;
359 * if so, start the job...
362 if (printer
->state
== IPP_PRINTER_IDLE
|| /* Printer is idle */
363 ((printer
->type
& CUPS_PRINTER_REMOTE
) && /* Printer is remote */
364 !printer
->job
)) /* and not printing a job */
365 StartJob(current
->id
, printer
);
373 * 'CleanJobs()' - Clean out old jobs.
379 job_t
*job
, /* Current job */
380 *next
; /* Next job */
386 for (job
= Jobs
; job
&& NumJobs
>= MaxJobs
; job
= next
)
390 if (job
->state
->values
[0].integer
>= IPP_JOB_CANCELLED
)
391 CancelJob(job
->id
, 1);
397 * 'FinishJob()' - Finish a job.
401 FinishJob(job_t
*job
) /* I - Job */
403 int job_history
; /* Did CancelJob() keep the job? */
404 printer_t
*printer
; /* Current printer */
407 LogMessage(L_DEBUG
, "FinishJob: job %d, file %d is complete.",
408 job
->id
, job
->current_file
- 1);
410 LogMessage(L_DEBUG2
, "FinishJob: job->status is %d", job
->status
);
412 if (job
->status_buffer
&& job
->current_file
>= job
->num_files
)
415 * Close the pipe and clear the input bit.
418 LogMessage(L_DEBUG2
, "FinishJob: Removing fd %d from InputSet...",
419 job
->status_buffer
->fd
);
421 FD_CLR(job
->status_buffer
->fd
, InputSet
);
423 LogMessage(L_DEBUG2
, "FinishJob: Closing status input pipe %d...",
424 job
->status_buffer
->fd
);
426 cupsdStatBufDelete(job
->status_buffer
);
428 job
->status_buffer
= NULL
;
434 * Backend had errors; stop it...
437 printer
= job
->printer
;
439 switch (-job
->status
)
442 case CUPS_BACKEND_FAILED
:
444 * Backend failure, use the error-policy to determine how to
449 job
->state
->values
[0].integer
= IPP_JOB_PENDING
;
453 * If the job was queued to a class, try requeuing it... For
454 * faxes and retry-job queues, hold the current job for 5 minutes.
457 if (job
->dtype
& (CUPS_PRINTER_CLASS
| CUPS_PRINTER_IMPLICIT
))
459 else if ((printer
->type
& CUPS_PRINTER_FAX
) ||
460 !strcmp(printer
->error_policy
, "retry-job"))
463 * See how many times we've tried to send the job; if more than
464 * the limit, cancel the job.
469 if (job
->tries
>= FaxRetryLimit
)
475 LogMessage(L_ERROR
, "Canceling job %d since it could not be sent after %d tries.",
476 job
->id
, FaxRetryLimit
);
478 cupsdAddEvent(CUPSD_EVENT_JOB_COMPLETED
, job
->printer
, job
,
479 "Job cancelled since it could not be sent after %d tries.",
482 CancelJob(job
->id
, 0);
487 * Try again in N seconds...
490 set_hold_until(job
, time(NULL
) + FaxRetryInterval
);
493 else if (!strcmp(printer
->error_policy
, "abort-job"))
494 CancelJob(job
->id
, 0);
497 case CUPS_BACKEND_CANCEL
:
502 CancelJob(job
->id
, 0);
505 case CUPS_BACKEND_HOLD
:
511 SetJobHoldUntil(job
->id
, "indefinite");
515 case CUPS_BACKEND_STOP
:
517 * Stop the printer...
522 SetPrinterState(printer
, IPP_PRINTER_STOPPED
, 1);
525 case CUPS_BACKEND_AUTH_REQUIRED
:
527 SetJobHoldUntil(job
->id
, "indefinite");
529 /* cupsdSetJobStateReasons(job->id, "authentication-required"); */
535 * Try printing another job...
540 else if (job
->status
> 0)
543 * Filter had errors; cancel it...
546 if (job
->current_file
< job
->num_files
)
547 StartJob(job
->id
, job
->printer
);
550 cupsdAddEvent(CUPSD_EVENT_JOB_COMPLETED
, job
->printer
, job
,
551 "Job aborted due to filter errors; please consult the "
552 "error_log file for details.");
554 job_history
= JobHistory
&& !(job
->dtype
& CUPS_PRINTER_REMOTE
);
556 CancelJob(job
->id
, 0);
560 job
->state
->values
[0].integer
= IPP_JOB_ABORTED
;
570 * Job printed successfully; cancel it...
573 if (job
->current_file
< job
->num_files
)
575 FilterLevel
-= job
->cost
;
576 StartJob(job
->id
, job
->printer
);
580 cupsdAddEvent(CUPSD_EVENT_JOB_COMPLETED
, job
->printer
, job
,
581 "Job completed successfully.");
583 job_history
= JobHistory
&& !(job
->dtype
& CUPS_PRINTER_REMOTE
);
585 CancelJob(job
->id
, 0);
589 job
->state
->values
[0].integer
= IPP_JOB_COMPLETED
;
600 * 'FreeAllJobs()' - Free all jobs from memory.
606 job_t
*job
, /* Current job */
607 *next
; /* Next job */
614 for (job
= Jobs
; job
; job
= next
)
618 ippDelete(job
->attrs
);
620 if (job
->num_files
> 0)
622 free(job
->compressions
);
623 free(job
->filetypes
);
636 * 'FindJob()' - Find the specified job.
639 job_t
* /* O - Job data */
640 FindJob(int id
) /* I - Job ID */
642 job_t
*current
; /* Current job */
645 for (current
= Jobs
; current
!= NULL
; current
= current
->next
)
646 if (current
->id
== id
)
654 * 'GetPrinterJobCount()' - Get the number of pending, processing,
655 * or held jobs in a printer or class.
658 int /* O - Job count */
659 GetPrinterJobCount(const char *dest
) /* I - Printer or class name */
661 int count
; /* Job count */
662 job_t
*job
; /* Current job */
665 for (job
= Jobs
, count
= 0; job
!= NULL
; job
= job
->next
)
666 if (job
->state
->values
[0].integer
<= IPP_JOB_PROCESSING
&&
667 strcasecmp(job
->dest
, dest
) == 0)
675 * 'GetUserJobCount()' - Get the number of pending, processing,
676 * or held jobs for a user.
679 int /* O - Job count */
680 GetUserJobCount(const char *username
) /* I - Username */
682 int count
; /* Job count */
683 job_t
*job
; /* Current job */
686 for (job
= Jobs
, count
= 0; job
!= NULL
; job
= job
->next
)
687 if (job
->state
->values
[0].integer
<= IPP_JOB_PROCESSING
&&
688 strcmp(job
->username
, username
) == 0)
696 * 'HoldJob()' - Hold the specified job.
700 HoldJob(int id
) /* I - Job ID */
702 job_t
*job
; /* Job data */
705 LogMessage(L_DEBUG
, "HoldJob: id = %d", id
);
707 if ((job
= FindJob(id
)) == NULL
)
710 if (job
->state
->values
[0].integer
== IPP_JOB_PROCESSING
)
713 DEBUG_puts("HoldJob: setting state to held...");
715 job
->state
->values
[0].integer
= IPP_JOB_HELD
;
724 * 'LoadAllJobs()' - Load all jobs from disk.
730 cups_dir_t
*dir
; /* Directory */
731 cups_dentry_t
*dent
; /* Directory entry */
732 char filename
[1024]; /* Full filename of job file */
733 int fd
; /* File descriptor */
734 job_t
*job
, /* New job */
735 *current
, /* Current job */
736 *prev
; /* Previous job */
737 int jobid
, /* Current job ID */
738 fileid
; /* Current file ID */
739 ipp_attribute_t
*attr
; /* Job attribute */
740 char method
[HTTP_MAX_URI
], /* Method portion of URI */
741 username
[HTTP_MAX_URI
], /* Username portion of URI */
742 host
[HTTP_MAX_URI
], /* Host portion of URI */
743 resource
[HTTP_MAX_URI
]; /* Resource portion of URI */
744 int port
; /* Port portion of URI */
745 printer_t
*p
; /* Printer or class */
746 const char *dest
; /* Destination */
747 mime_type_t
**filetypes
; /* New filetypes array */
748 int *compressions
; /* New compressions array */
752 * First open the requests directory...
755 LogMessage(L_DEBUG
, "LoadAllJobs: Scanning %s...", RequestRoot
);
759 if ((dir
= cupsDirOpen(RequestRoot
)) == NULL
)
761 LogMessage(L_ERROR
, "LoadAllJobs: Unable to open spool directory %s: %s",
762 RequestRoot
, strerror(errno
));
767 * Read all the c##### files...
770 while ((dent
= cupsDirRead(dir
)) != NULL
)
771 if (strlen(dent
->filename
) >= 6 && dent
->filename
[0] == 'c')
774 * Allocate memory for the job...
777 if ((job
= calloc(sizeof(job_t
), 1)) == NULL
)
779 LogMessage(L_ERROR
, "LoadAllJobs: Ran out of memory for jobs!");
784 if ((job
->attrs
= ippNew()) == NULL
)
787 LogMessage(L_ERROR
, "LoadAllJobs: Ran out of memory for job attributes!");
793 * Assign the job ID...
796 job
->id
= atoi(dent
->filename
+ 1);
797 job
->back_pipes
[0] = -1;
798 job
->back_pipes
[1] = -1;
799 job
->print_pipes
[0] = -1;
800 job
->print_pipes
[1] = -1;
802 LogMessage(L_DEBUG
, "LoadAllJobs: Loading attributes for job %d...\n",
805 if (job
->id
>= NextJobId
)
806 NextJobId
= job
->id
+ 1;
809 * Load the job control file...
812 snprintf(filename
, sizeof(filename
), "%s/%s", RequestRoot
, dent
->filename
);
813 if ((fd
= open(filename
, O_RDONLY
)) < 0)
815 LogMessage(L_ERROR
, "LoadAllJobs: Unable to open job control file \"%s\" - %s!",
816 filename
, strerror(errno
));
817 ippDelete(job
->attrs
);
824 if (ippReadFile(fd
, job
->attrs
) != IPP_DATA
)
826 LogMessage(L_ERROR
, "LoadAllJobs: Unable to read job control file \"%s\"!",
829 ippDelete(job
->attrs
);
838 if ((job
->state
= ippFindAttribute(job
->attrs
, "job-state", IPP_TAG_ENUM
)) == NULL
)
840 LogMessage(L_ERROR
, "LoadAllJobs: Missing or bad job-state attribute in control file \"%s\"!",
842 ippDelete(job
->attrs
);
848 if ((attr
= ippFindAttribute(job
->attrs
, "job-printer-uri", IPP_TAG_URI
)) == NULL
)
850 LogMessage(L_ERROR
, "LoadAllJobs: No job-printer-uri attribute in control file \"%s\"!",
852 ippDelete(job
->attrs
);
858 httpSeparate(attr
->values
[0].string
.text
, method
, username
, host
,
861 if ((dest
= ValidateDest(host
, resource
, &(job
->dtype
), NULL
)) == NULL
&&
862 job
->state
!= NULL
&&
863 job
->state
->values
[0].integer
<= IPP_JOB_PROCESSING
)
866 * Job queued on remote printer or class, so add it...
869 if (strncmp(resource
, "/classes/", 9) == 0)
871 p
= AddClass(resource
+ 9);
872 SetString(&p
->make_model
, "Remote Class on unknown");
876 p
= AddPrinter(resource
+ 10);
877 SetString(&p
->make_model
, "Remote Printer on unknown");
880 p
->state
= IPP_PRINTER_STOPPED
;
881 p
->type
|= CUPS_PRINTER_REMOTE
;
882 p
->browse_time
= 2147483647;
884 SetString(&p
->location
, "Location Unknown");
885 SetString(&p
->info
, "No Information Available");
886 p
->hostname
[0] = '\0';
894 LogMessage(L_ERROR
, "LoadAllJobs: Unable to queue job for destination \"%s\"!",
895 attr
->values
[0].string
.text
);
896 ippDelete(job
->attrs
);
902 SetString(&job
->dest
, dest
);
904 job
->sheets
= ippFindAttribute(job
->attrs
, "job-media-sheets-completed",
906 job
->job_sheets
= ippFindAttribute(job
->attrs
, "job-sheets", IPP_TAG_NAME
);
908 if ((attr
= ippFindAttribute(job
->attrs
, "job-priority", IPP_TAG_INTEGER
)) == NULL
)
910 LogMessage(L_ERROR
, "LoadAllJobs: Missing or bad job-priority attribute in control file \"%s\"!",
912 ippDelete(job
->attrs
);
917 job
->priority
= attr
->values
[0].integer
;
919 if ((attr
= ippFindAttribute(job
->attrs
, "job-originating-user-name", IPP_TAG_NAME
)) == NULL
)
921 LogMessage(L_ERROR
, "LoadAllJobs: Missing or bad job-originating-user-name attribute in control file \"%s\"!",
923 ippDelete(job
->attrs
);
928 SetString(&job
->username
, attr
->values
[0].string
.text
);
931 * Insert the job into the array, sorting by job priority and ID...
934 for (current
= Jobs
, prev
= NULL
;
936 prev
= current
, current
= current
->next
)
937 if (job
->priority
> current
->priority
)
939 else if (job
->priority
== current
->priority
&& job
->id
< current
->id
)
951 * Set the job hold-until time and state...
954 if (job
->state
->values
[0].integer
== IPP_JOB_HELD
)
956 if ((attr
= ippFindAttribute(job
->attrs
, "job-hold-until", IPP_TAG_KEYWORD
)) == NULL
)
957 attr
= ippFindAttribute(job
->attrs
, "job-hold-until", IPP_TAG_NAME
);
960 job
->state
->values
[0].integer
= IPP_JOB_PENDING
;
962 SetJobHoldUntil(job
->id
, attr
->values
[0].string
.text
);
964 else if (job
->state
->values
[0].integer
== IPP_JOB_PROCESSING
)
965 job
->state
->values
[0].integer
= IPP_JOB_PENDING
;
969 * Read all the d##### files...
974 while ((dent
= cupsDirRead(dir
)) != NULL
)
975 if (strlen(dent
->filename
) > 7 && dent
->filename
[0] == 'd' &&
976 strchr(dent
->filename
, '-'))
982 jobid
= atoi(dent
->filename
+ 1);
983 fileid
= atoi(strchr(dent
->filename
, '-') + 1);
985 LogMessage(L_DEBUG
, "LoadAllJobs: Auto-typing document file %s...",
988 snprintf(filename
, sizeof(filename
), "%s/%s", RequestRoot
, dent
->filename
);
990 if ((job
= FindJob(jobid
)) == NULL
)
992 LogMessage(L_ERROR
, "LoadAllJobs: Orphaned print file \"%s\"!",
998 if (fileid
> job
->num_files
)
1000 if (job
->num_files
== 0)
1002 compressions
= (int *)calloc(fileid
, sizeof(int));
1003 filetypes
= (mime_type_t
**)calloc(fileid
, sizeof(mime_type_t
*));
1007 compressions
= (int *)realloc(job
->compressions
,
1008 sizeof(int) * fileid
);
1009 filetypes
= (mime_type_t
**)realloc(job
->filetypes
,
1010 sizeof(mime_type_t
*) * fileid
);
1013 if (compressions
== NULL
|| filetypes
== NULL
)
1015 LogMessage(L_ERROR
, "LoadAllJobs: Ran out of memory for job file types!");
1019 job
->compressions
= compressions
;
1020 job
->filetypes
= filetypes
;
1021 job
->num_files
= fileid
;
1024 job
->filetypes
[fileid
- 1] = mimeFileType(MimeDatabase
, filename
,
1025 job
->compressions
+ fileid
- 1);
1027 if (job
->filetypes
[fileid
- 1] == NULL
)
1028 job
->filetypes
[fileid
- 1] = mimeType(MimeDatabase
, "application",
1035 * Clean out old jobs as needed...
1043 * 'MoveJob()' - Move the specified job to a different destination.
1047 MoveJob(int id
, /* I - Job ID */
1048 const char *dest
) /* I - Destination */
1050 job_t
*current
; /* Current job */
1051 ipp_attribute_t
*attr
; /* job-printer-uri attribute */
1052 printer_t
*p
; /* Destination printer or class */
1055 if ((p
= FindPrinter(dest
)) == NULL
)
1056 p
= FindClass(dest
);
1061 for (current
= Jobs
; current
!= NULL
; current
= current
->next
)
1062 if (current
->id
== id
)
1064 if (current
->state
->values
[0].integer
>= IPP_JOB_PROCESSING
)
1067 SetString(¤t
->dest
, dest
);
1068 current
->dtype
= p
->type
& (CUPS_PRINTER_CLASS
| CUPS_PRINTER_REMOTE
|
1069 CUPS_PRINTER_IMPLICIT
);
1071 if ((attr
= ippFindAttribute(current
->attrs
, "job-printer-uri", IPP_TAG_URI
)) != NULL
)
1073 free(attr
->values
[0].string
.text
);
1074 attr
->values
[0].string
.text
= strdup(p
->uri
);
1077 SaveJob(current
->id
);
1085 * 'ReleaseJob()' - Release the specified job.
1089 ReleaseJob(int id
) /* I - Job ID */
1091 job_t
*job
; /* Job data */
1094 LogMessage(L_DEBUG
, "ReleaseJob: id = %d", id
);
1096 if ((job
= FindJob(id
)) == NULL
)
1099 if (job
->state
->values
[0].integer
== IPP_JOB_HELD
)
1101 DEBUG_puts("ReleaseJob: setting state to pending...");
1103 job
->state
->values
[0].integer
= IPP_JOB_PENDING
;
1111 * 'RestartJob()' - Restart the specified job.
1115 RestartJob(int id
) /* I - Job ID */
1117 job_t
*job
; /* Job data */
1120 if ((job
= FindJob(id
)) == NULL
)
1123 if (job
->state
->values
[0].integer
== IPP_JOB_STOPPED
|| JobFiles
)
1126 job
->state
->values
[0].integer
= IPP_JOB_PENDING
;
1134 * 'SaveJob()' - Save a job to disk.
1138 SaveJob(int id
) /* I - Job ID */
1140 job_t
*job
; /* Pointer to job */
1141 char filename
[1024]; /* Job control filename */
1142 int fd
; /* File descriptor */
1145 if ((job
= FindJob(id
)) == NULL
)
1148 snprintf(filename
, sizeof(filename
), "%s/c%05d", RequestRoot
, id
);
1150 if ((fd
= open(filename
, O_WRONLY
| O_CREAT
| O_TRUNC
, 0600)) < 0)
1152 LogMessage(L_ERROR
, "SaveJob: Unable to create job control file \"%s\" - %s.",
1153 filename
, strerror(errno
));
1158 fchown(fd
, RunUser
, Group
);
1160 ippWriteFile(fd
, job
->attrs
);
1162 LogMessage(L_DEBUG2
, "SaveJob: Closing file %d...", fd
);
1169 * 'SetJobHoldUntil()' - Set the hold time for a job...
1173 SetJobHoldUntil(int id
, /* I - Job ID */
1174 const char *when
) /* I - When to resume */
1176 job_t
*job
; /* Pointer to job */
1177 time_t curtime
; /* Current time */
1178 struct tm
*curdate
; /* Current date */
1179 int hour
; /* Hold hour */
1180 int minute
; /* Hold minute */
1181 int second
; /* Hold second */
1184 LogMessage(L_DEBUG
, "SetJobHoldUntil(%d, \"%s\")", id
, when
);
1186 if ((job
= FindJob(id
)) == NULL
)
1191 if (strcmp(when
, "indefinite") == 0)
1194 * Hold indefinitely...
1197 job
->hold_until
= 0;
1199 else if (strcmp(when
, "day-time") == 0)
1202 * Hold to 6am the next morning unless local time is < 6pm.
1205 curtime
= time(NULL
);
1206 curdate
= localtime(&curtime
);
1208 if (curdate
->tm_hour
< 18)
1209 job
->hold_until
= curtime
;
1211 job
->hold_until
= curtime
+
1212 ((29 - curdate
->tm_hour
) * 60 + 59 -
1213 curdate
->tm_min
) * 60 + 60 - curdate
->tm_sec
;
1215 else if (strcmp(when
, "evening") == 0 || strcmp(when
, "night") == 0)
1218 * Hold to 6pm unless local time is > 6pm or < 6am.
1221 curtime
= time(NULL
);
1222 curdate
= localtime(&curtime
);
1224 if (curdate
->tm_hour
< 6 || curdate
->tm_hour
>= 18)
1225 job
->hold_until
= curtime
;
1227 job
->hold_until
= curtime
+
1228 ((17 - curdate
->tm_hour
) * 60 + 59 -
1229 curdate
->tm_min
) * 60 + 60 - curdate
->tm_sec
;
1231 else if (strcmp(when
, "second-shift") == 0)
1234 * Hold to 4pm unless local time is > 4pm.
1237 curtime
= time(NULL
);
1238 curdate
= localtime(&curtime
);
1240 if (curdate
->tm_hour
>= 16)
1241 job
->hold_until
= curtime
;
1243 job
->hold_until
= curtime
+
1244 ((15 - curdate
->tm_hour
) * 60 + 59 -
1245 curdate
->tm_min
) * 60 + 60 - curdate
->tm_sec
;
1247 else if (strcmp(when
, "third-shift") == 0)
1250 * Hold to 12am unless local time is < 8am.
1253 curtime
= time(NULL
);
1254 curdate
= localtime(&curtime
);
1256 if (curdate
->tm_hour
< 8)
1257 job
->hold_until
= curtime
;
1259 job
->hold_until
= curtime
+
1260 ((23 - curdate
->tm_hour
) * 60 + 59 -
1261 curdate
->tm_min
) * 60 + 60 - curdate
->tm_sec
;
1263 else if (strcmp(when
, "weekend") == 0)
1266 * Hold to weekend unless we are in the weekend.
1269 curtime
= time(NULL
);
1270 curdate
= localtime(&curtime
);
1272 if (curdate
->tm_wday
== 0 || curdate
->tm_wday
== 6)
1273 job
->hold_until
= curtime
;
1275 job
->hold_until
= curtime
+
1276 (((5 - curdate
->tm_wday
) * 24 +
1277 (17 - curdate
->tm_hour
)) * 60 + 59 -
1278 curdate
->tm_min
) * 60 + 60 - curdate
->tm_sec
;
1280 else if (sscanf(when
, "%d:%d:%d", &hour
, &minute
, &second
) >= 2)
1283 * Hold to specified GMT time (HH:MM or HH:MM:SS)...
1286 curtime
= time(NULL
);
1287 curdate
= gmtime(&curtime
);
1289 job
->hold_until
= curtime
+
1290 ((hour
- curdate
->tm_hour
) * 60 + minute
-
1291 curdate
->tm_min
) * 60 + second
- curdate
->tm_sec
;
1294 * Hold until next day as needed...
1297 if (job
->hold_until
< curtime
)
1298 job
->hold_until
+= 24 * 60 * 60 * 60;
1301 LogMessage(L_DEBUG
, "SetJobHoldUntil: hold_until = %d", (int)job
->hold_until
);
1306 * 'SetJobPriority()' - Set the priority of a job, moving it up/down in the
1311 SetJobPriority(int id
, /* I - Job ID */
1312 int priority
) /* I - New priority (0 to 100) */
1314 job_t
*job
, /* Job to change */
1315 *current
, /* Current job */
1316 *prev
; /* Previous job */
1317 ipp_attribute_t
*attr
; /* Job attribute */
1324 for (current
= Jobs
, prev
= NULL
;
1326 prev
= current
, current
= current
->next
)
1327 if (current
->id
== id
)
1330 if (current
== NULL
)
1334 * Set the new priority...
1338 job
->priority
= priority
;
1340 if ((attr
= ippFindAttribute(job
->attrs
, "job-priority", IPP_TAG_INTEGER
)) != NULL
)
1341 attr
->values
[0].integer
= priority
;
1343 ippAddInteger(job
->attrs
, IPP_TAG_JOB
, IPP_TAG_INTEGER
, "job-priority",
1349 * See if we need to do any sorting...
1352 if ((prev
== NULL
|| job
->priority
< prev
->priority
) &&
1353 (job
->next
== NULL
|| job
->next
->priority
< job
->priority
))
1357 * Remove the job from the list, and then insert it where it belongs...
1363 prev
->next
= job
->next
;
1365 for (current
= Jobs
, prev
= NULL
;
1367 prev
= current
, current
= current
->next
)
1368 if (job
->priority
> current
->priority
)
1371 job
->next
= current
;
1380 * 'StartJob()' - Start a print job.
1384 StartJob(int id
, /* I - Job ID */
1385 printer_t
*printer
) /* I - Printer to print job */
1387 job_t
*current
; /* Current job */
1388 int i
; /* Looping var */
1389 int slot
; /* Pipe slot */
1390 int num_filters
; /* Number of filters for job */
1391 mime_filter_t
*filters
; /* Filters for job */
1392 char method
[255], /* Method for output */
1393 *optptr
, /* Pointer to options */
1394 *valptr
; /* Pointer in value string */
1395 ipp_attribute_t
*attr
; /* Current attribute */
1396 int pid
; /* Process ID of new filter process */
1397 int banner_page
; /* 1 if banner page, 0 otherwise */
1398 int statusfds
[2], /* Pipes used between the filters and scheduler */
1399 filterfds
[2][2]; /* Pipes used between the filters */
1400 int envc
; /* Number of environment variables */
1401 char *argv
[8], /* Filter command-line arguments */
1402 sani_uri
[1024], /* Sanitized DEVICE_URI env var */
1403 filename
[1024], /* Job filename */
1404 command
[1024], /* Full path to filter/backend command */
1405 jobid
[255], /* Job ID string */
1406 title
[IPP_MAX_NAME
], /* Job title string */
1407 copies
[255], /* # copies string */
1408 *envp
[100], /* Environment variables */
1409 charset
[255], /* CHARSET environment variable */
1410 class_name
[255], /* CLASS environment variable */
1411 classification
[1024], /* CLASSIFICATION environment variable */
1412 content_type
[1024], /* CONTENT_TYPE environment variable */
1413 device_uri
[1024], /* DEVICE_URI environment variable */
1414 lang
[255], /* LANG environment variable */
1415 ppd
[1024], /* PPD environment variable */
1416 printer_name
[255], /* PRINTER environment variable */
1417 rip_max_cache
[255]; /* RIP_MAX_CACHE environment variable */
1418 static char *options
= NULL
; /* Full list of options */
1419 static int optlength
= 0; /* Length of option buffer */
1422 LogMessage(L_DEBUG
, "StartJob(%d, %p)", id
, printer
);
1424 for (current
= Jobs
; current
!= NULL
; current
= current
->next
)
1425 if (current
->id
== id
)
1428 if (current
== NULL
)
1431 LogMessage(L_DEBUG
, "StartJob() id = %d, file = %d/%d", id
,
1432 current
->current_file
, current
->num_files
);
1434 if (current
->num_files
== 0)
1436 LogMessage(L_ERROR
, "Job ID %d has no files! Cancelling it!", id
);
1438 cupsdAddEvent(CUPSD_EVENT_JOB_COMPLETED
, current
->printer
, current
,
1439 "Job cancelled because it has no files.");
1446 * Figure out what filters are required to convert from
1447 * the source to the destination type...
1456 * Remote jobs and raw queues go directly to the printer without
1460 LogMessage(L_DEBUG
, "StartJob: Sending job to queue tagged as raw...");
1467 * Local jobs get filtered...
1470 filters
= mimeFilter(MimeDatabase
, current
->filetypes
[current
->current_file
],
1471 printer
->filetype
, &num_filters
, MAX_FILTERS
- 1);
1473 if (num_filters
== 0)
1475 LogMessage(L_ERROR
, "Unable to convert file %d to printable format for job %d!",
1476 current
->current_file
, current
->id
);
1477 LogMessage(L_INFO
, "Hint: Do you have ESP Ghostscript installed?");
1479 if (LogLevel
< L_DEBUG
)
1480 LogMessage(L_INFO
, "Hint: Try setting the LogLevel to \"debug\".");
1482 current
->current_file
++;
1484 if (current
->current_file
== current
->num_files
)
1486 cupsdAddEvent(CUPSD_EVENT_JOB_COMPLETED
, current
->printer
, current
,
1487 "Job cancelled because it has no files that can be printed.");
1489 CancelJob(current
->id
, 0);
1496 * Remove NULL ("-") filters...
1499 for (i
= 0; i
< num_filters
;)
1500 if (strcmp(filters
[i
].filter
, "-") == 0)
1503 if (i
< num_filters
)
1504 memcpy(filters
+ i
, filters
+ i
+ 1,
1505 (num_filters
- i
) * sizeof(mime_filter_t
));
1510 if (num_filters
== 0)
1518 * Compute filter cost...
1521 for (i
= 0; i
< num_filters
; i
++)
1522 current
->cost
+= filters
[i
].cost
;
1527 * See if the filter cost is too high...
1530 if ((FilterLevel
+ current
->cost
) > FilterLimit
&& FilterLevel
> 0 &&
1534 * Don't print this job quite yet...
1537 if (filters
!= NULL
)
1540 LogMessage(L_INFO
, "Holding job %d because filter limit has been reached.",
1542 LogMessage(L_DEBUG
, "StartJob: id = %d, file = %d, "
1543 "cost = %d, level = %d, limit = %d",
1544 id
, current
->current_file
, current
->cost
, FilterLevel
,
1549 FilterLevel
+= current
->cost
;
1552 * Add decompression filters, if any...
1555 if (current
->compressions
[current
->current_file
])
1558 * Add gziptoany filter to the front of the list...
1561 mime_filter_t
*temp_filters
;
1563 if (num_filters
== 0)
1564 temp_filters
= malloc(sizeof(mime_filter_t
));
1566 temp_filters
= realloc(filters
,
1567 sizeof(mime_filter_t
) * (num_filters
+ 1));
1569 if (temp_filters
== NULL
)
1571 LogMessage(L_ERROR
, "Unable to add decompression filter - %s",
1574 if (filters
!= NULL
)
1577 current
->current_file
++;
1579 if (current
->current_file
== current
->num_files
)
1581 cupsdAddEvent(CUPSD_EVENT_JOB_COMPLETED
, current
->printer
, current
,
1582 "Job cancelled because the print file could not be decompressed.");
1584 CancelJob(current
->id
, 0);
1590 filters
= temp_filters
;
1591 memmove(filters
+ 1, filters
, num_filters
* sizeof(mime_filter_t
));
1592 *filters
= gziptoany_filter
;
1597 * Add port monitor, if any...
1600 if (printer
->port_monitor
)
1603 * Add port monitor to the end of the list...
1606 mime_filter_t
*temp_filters
;
1608 if (num_filters
== 0)
1609 temp_filters
= malloc(sizeof(mime_filter_t
));
1611 temp_filters
= realloc(filters
,
1612 sizeof(mime_filter_t
) * (num_filters
+ 1));
1614 if (temp_filters
== NULL
)
1616 LogMessage(L_ERROR
, "Unable to add port monitor - %s",
1619 if (filters
!= NULL
)
1622 current
->current_file
++;
1624 if (current
->current_file
== current
->num_files
)
1626 cupsdAddEvent(CUPSD_EVENT_JOB_COMPLETED
, current
->printer
, current
,
1627 "Job cancelled because the port monitor could not be added.");
1629 CancelJob(current
->id
, 0);
1635 filters
= temp_filters
;
1636 memset(filters
+ num_filters
, 0, sizeof(mime_filter_t
));
1637 snprintf(filters
[num_filters
].filter
, sizeof(filters
[num_filters
].filter
),
1638 "%s/monitor/%s", ServerBin
, printer
->port_monitor
);
1643 * Update the printer and job state to "processing"...
1646 current
->state
->values
[0].integer
= IPP_JOB_PROCESSING
;
1647 current
->status
= 0;
1648 current
->printer
= printer
;
1649 printer
->job
= current
;
1650 SetPrinterState(printer
, IPP_PRINTER_PROCESSING
, 0);
1652 if (current
->current_file
== 0)
1654 set_time(current
, "time-at-processing");
1655 cupsdOpenPipe(current
->back_pipes
);
1659 * Determine if we are printing a banner page or not...
1662 if (current
->job_sheets
== NULL
)
1664 LogMessage(L_DEBUG
, "No job-sheets attribute.");
1665 if ((current
->job_sheets
=
1666 ippFindAttribute(current
->attrs
, "job-sheets", IPP_TAG_ZERO
)) != NULL
)
1667 LogMessage(L_DEBUG
, "... but someone added one without setting job_sheets!");
1669 else if (current
->job_sheets
->num_values
== 1)
1670 LogMessage(L_DEBUG
, "job-sheets=%s",
1671 current
->job_sheets
->values
[0].string
.text
);
1673 LogMessage(L_DEBUG
, "job-sheets=%s,%s",
1674 current
->job_sheets
->values
[0].string
.text
,
1675 current
->job_sheets
->values
[1].string
.text
);
1677 if (printer
->type
& (CUPS_PRINTER_REMOTE
| CUPS_PRINTER_IMPLICIT
))
1679 else if (current
->job_sheets
== NULL
)
1681 else if (strcasecmp(current
->job_sheets
->values
[0].string
.text
, "none") != 0 &&
1682 current
->current_file
== 0)
1684 else if (current
->job_sheets
->num_values
> 1 &&
1685 strcasecmp(current
->job_sheets
->values
[1].string
.text
, "none") != 0 &&
1686 current
->current_file
== (current
->num_files
- 1))
1691 LogMessage(L_DEBUG
, "banner_page = %d", banner_page
);
1694 * Building the options string is harder than it needs to be, but
1695 * for the moment we need to pass strings for command-line args and
1696 * not IPP attribute pointers... :)
1698 * First allocate/reallocate the option buffer as needed...
1701 i
= ipp_length(current
->attrs
);
1708 optptr
= realloc(options
, i
);
1712 LogMessage(L_CRIT
, "StartJob: Unable to allocate %d bytes for option buffer for job %d!",
1715 if (filters
!= NULL
)
1718 FilterLevel
-= current
->cost
;
1720 cupsdAddEvent(CUPSD_EVENT_JOB_COMPLETED
, current
->printer
, current
,
1721 "Job cancelled because the server ran out of memory.");
1732 * Now loop through the attributes and convert them to the textual
1733 * representation used by the filters...
1739 snprintf(title
, sizeof(title
), "%s-%d", printer
->name
, current
->id
);
1740 strcpy(copies
, "1");
1742 for (attr
= current
->attrs
->attrs
; attr
!= NULL
; attr
= attr
->next
)
1744 if (strcmp(attr
->name
, "copies") == 0 &&
1745 attr
->value_tag
== IPP_TAG_INTEGER
)
1748 * Don't use the # copies attribute if we are printing the job sheets...
1752 sprintf(copies
, "%d", attr
->values
[0].integer
);
1754 else if (strcmp(attr
->name
, "job-name") == 0 &&
1755 (attr
->value_tag
== IPP_TAG_NAME
||
1756 attr
->value_tag
== IPP_TAG_NAMELANG
))
1757 strlcpy(title
, attr
->values
[0].string
.text
, sizeof(title
));
1758 else if (attr
->group_tag
== IPP_TAG_JOB
)
1761 * Filter out other unwanted attributes...
1764 if (attr
->value_tag
== IPP_TAG_MIMETYPE
||
1765 attr
->value_tag
== IPP_TAG_NAMELANG
||
1766 attr
->value_tag
== IPP_TAG_TEXTLANG
||
1767 attr
->value_tag
== IPP_TAG_URI
||
1768 attr
->value_tag
== IPP_TAG_URISCHEME
||
1769 attr
->value_tag
== IPP_TAG_BEGIN_COLLECTION
) /* Not yet supported */
1772 if (strncmp(attr
->name
, "time-", 5) == 0)
1775 if (strncmp(attr
->name
, "job-", 4) == 0 &&
1776 !(printer
->type
& CUPS_PRINTER_REMOTE
))
1779 if (strncmp(attr
->name
, "job-", 4) == 0 &&
1780 strcmp(attr
->name
, "job-billing") != 0 &&
1781 strcmp(attr
->name
, "job-sheets") != 0 &&
1782 strcmp(attr
->name
, "job-hold-until") != 0 &&
1783 strcmp(attr
->name
, "job-priority") != 0)
1786 if ((strcmp(attr
->name
, "page-label") == 0 ||
1787 strcmp(attr
->name
, "page-border") == 0 ||
1788 strncmp(attr
->name
, "number-up", 9) == 0 ||
1789 strcmp(attr
->name
, "page-set") == 0) &&
1794 * Otherwise add them to the list...
1797 if (optptr
> options
)
1798 strlcat(optptr
, " ", optlength
- (optptr
- options
));
1800 if (attr
->value_tag
!= IPP_TAG_BOOLEAN
)
1802 strlcat(optptr
, attr
->name
, optlength
- (optptr
- options
));
1803 strlcat(optptr
, "=", optlength
- (optptr
- options
));
1806 for (i
= 0; i
< attr
->num_values
; i
++)
1809 strlcat(optptr
, ",", optlength
- (optptr
- options
));
1811 optptr
+= strlen(optptr
);
1813 switch (attr
->value_tag
)
1815 case IPP_TAG_INTEGER
:
1817 snprintf(optptr
, optlength
- (optptr
- options
),
1818 "%d", attr
->values
[i
].integer
);
1821 case IPP_TAG_BOOLEAN
:
1822 if (!attr
->values
[i
].boolean
)
1823 strlcat(optptr
, "no", optlength
- (optptr
- options
));
1825 case IPP_TAG_NOVALUE
:
1826 strlcat(optptr
, attr
->name
,
1827 optlength
- (optptr
- options
));
1830 case IPP_TAG_RANGE
:
1831 if (attr
->values
[i
].range
.lower
== attr
->values
[i
].range
.upper
)
1832 snprintf(optptr
, optlength
- (optptr
- options
) - 1,
1833 "%d", attr
->values
[i
].range
.lower
);
1835 snprintf(optptr
, optlength
- (optptr
- options
) - 1,
1836 "%d-%d", attr
->values
[i
].range
.lower
,
1837 attr
->values
[i
].range
.upper
);
1840 case IPP_TAG_RESOLUTION
:
1841 snprintf(optptr
, optlength
- (optptr
- options
) - 1,
1842 "%dx%d%s", attr
->values
[i
].resolution
.xres
,
1843 attr
->values
[i
].resolution
.yres
,
1844 attr
->values
[i
].resolution
.units
== IPP_RES_PER_INCH
?
1848 case IPP_TAG_STRING
:
1851 case IPP_TAG_KEYWORD
:
1852 case IPP_TAG_CHARSET
:
1853 case IPP_TAG_LANGUAGE
:
1854 for (valptr
= attr
->values
[i
].string
.text
; *valptr
;)
1856 if (strchr(" \t\n\\\'\"", *valptr
))
1858 *optptr
++ = *valptr
++;
1865 break; /* anti-compiler-warning-code */
1869 optptr
+= strlen(optptr
);
1874 * Build the command-line arguments for the filters. Each filter
1875 * has 6 or 7 arguments:
1879 * argv[2] = username
1881 * argv[4] = # copies
1883 * argv[6] = filename (optional; normally stdin)
1885 * This allows legacy printer drivers that use the old System V
1886 * printing interface to be used by CUPS.
1889 sprintf(jobid
, "%d", current
->id
);
1890 snprintf(filename
, sizeof(filename
), "%s/d%05d-%03d", RequestRoot
,
1891 current
->id
, current
->current_file
+ 1);
1893 argv
[0] = printer
->name
;
1895 argv
[2] = current
->username
;
1902 LogMessage(L_DEBUG
, "StartJob: argv = \"%s\",\"%s\",\"%s\",\"%s\",\"%s\",\"%s\",\"%s\"",
1903 argv
[0], argv
[1], argv
[2], argv
[3], argv
[4], argv
[5], argv
[6]);
1906 * Create environment variable strings for the filters...
1909 attr
= ippFindAttribute(current
->attrs
, "attributes-natural-language",
1912 switch (strlen(attr
->values
[0].string
.text
))
1916 * This is an unknown or badly formatted language code; use
1917 * the POSIX locale...
1920 strcpy(lang
, "LANG=C");
1925 * Just the language code (ll)...
1928 snprintf(lang
, sizeof(lang
), "LANG=%s",
1929 attr
->values
[0].string
.text
);
1934 * Language and country code (ll-cc)...
1937 snprintf(lang
, sizeof(lang
), "LANG=%c%c_%c%c",
1938 attr
->values
[0].string
.text
[0],
1939 attr
->values
[0].string
.text
[1],
1940 toupper(attr
->values
[0].string
.text
[3] & 255),
1941 toupper(attr
->values
[0].string
.text
[4] & 255));
1945 attr
= ippFindAttribute(current
->attrs
, "document-format",
1948 (optptr
= strstr(attr
->values
[0].string
.text
, "charset=")) != NULL
)
1949 snprintf(charset
, sizeof(charset
), "CHARSET=%s", optptr
+ 8);
1952 attr
= ippFindAttribute(current
->attrs
, "attributes-charset",
1954 snprintf(charset
, sizeof(charset
), "CHARSET=%s",
1955 attr
->values
[0].string
.text
);
1958 snprintf(content_type
, sizeof(content_type
), "CONTENT_TYPE=%s/%s",
1959 current
->filetypes
[current
->current_file
]->super
,
1960 current
->filetypes
[current
->current_file
]->type
);
1961 snprintf(device_uri
, sizeof(device_uri
), "DEVICE_URI=%s", printer
->device_uri
);
1962 cupsdSanitizeURI(printer
->device_uri
, sani_uri
, sizeof(sani_uri
));
1963 snprintf(ppd
, sizeof(ppd
), "PPD=%s/ppd/%s.ppd", ServerRoot
, printer
->name
);
1964 snprintf(printer_name
, sizeof(printer_name
), "PRINTER=%s", printer
->name
);
1965 snprintf(rip_max_cache
, sizeof(rip_max_cache
), "RIP_MAX_CACHE=%s", RIPCache
);
1967 envc
= cupsdLoadEnv(envp
, (int)(sizeof(envp
) / sizeof(envp
[0])));
1969 envp
[envc
++] = charset
;
1970 envp
[envc
++] = lang
;
1971 envp
[envc
++] = ppd
;
1972 envp
[envc
++] = rip_max_cache
;
1973 envp
[envc
++] = content_type
;
1974 envp
[envc
++] = device_uri
;
1975 envp
[envc
++] = printer_name
;
1977 if (Classification
&& !banner_page
)
1979 if ((attr
= ippFindAttribute(current
->attrs
, "job-sheets",
1980 IPP_TAG_NAME
)) == NULL
)
1981 snprintf(classification
, sizeof(classification
), "CLASSIFICATION=%s",
1983 else if (attr
->num_values
> 1 &&
1984 strcmp(attr
->values
[1].string
.text
, "none") != 0)
1985 snprintf(classification
, sizeof(classification
), "CLASSIFICATION=%s",
1986 attr
->values
[1].string
.text
);
1988 snprintf(classification
, sizeof(classification
), "CLASSIFICATION=%s",
1989 attr
->values
[0].string
.text
);
1991 envp
[envc
++] = classification
;
1994 if (current
->dtype
& (CUPS_PRINTER_CLASS
| CUPS_PRINTER_IMPLICIT
))
1996 snprintf(class_name
, sizeof(class_name
), "CLASS=%s", current
->dest
);
1997 envp
[envc
++] = class_name
;
2002 for (i
= 0; i
< envc
; i
++)
2003 if (strncmp(envp
[i
], "DEVICE_URI=", 11))
2004 LogMessage(L_DEBUG
, "StartJob: envp[%d]=\"%s\"", i
, envp
[i
]);
2006 LogMessage(L_DEBUG
, "StartJob: envp[%d]=\"DEVICE_URI=%s\"", i
, sani_uri
);
2008 current
->current_file
++;
2011 * Now create processes for all of the filters...
2014 if (cupsdOpenPipe(statusfds
))
2016 LogMessage(L_ERROR
, "Unable to create job status pipes - %s.",
2018 snprintf(printer
->state_message
, sizeof(printer
->state_message
),
2019 "Unable to create status pipes - %s.", strerror(errno
));
2021 AddPrinterHistory(printer
);
2023 if (filters
!= NULL
)
2026 cupsdAddEvent(CUPSD_EVENT_JOB_COMPLETED
, current
->printer
, current
,
2027 "Job cancelled because the server could not create the job status pipes.");
2029 CancelJob(current
->id
, 0);
2033 LogMessage(L_DEBUG
, "StartJob: statusfds = [ %d %d ]",
2034 statusfds
[0], statusfds
[1]);
2037 fcntl(statusfds
[0], F_SETFD
, FD_CLOEXEC
);
2038 fcntl(statusfds
[1], F_SETFD
, FD_CLOEXEC
);
2039 #endif /* FD_CLOEXEC */
2041 current
->status_buffer
= cupsdStatBufNew(statusfds
[0], "[Job %d]",
2043 current
->status
= 0;
2044 memset(current
->filters
, 0, sizeof(current
->filters
));
2046 filterfds
[1][0] = open("/dev/null", O_RDONLY
);
2047 filterfds
[1][1] = -1;
2049 if (filterfds
[1][0] < 0)
2051 LogMessage(L_ERROR
, "Unable to open \"/dev/null\" - %s.", strerror(errno
));
2052 snprintf(printer
->state_message
, sizeof(printer
->state_message
),
2053 "Unable to open \"/dev/null\" - %s.", strerror(errno
));
2055 AddPrinterHistory(printer
);
2057 if (filters
!= NULL
)
2060 cupsdClosePipe(statusfds
);
2061 CancelJob(current
->id
, 0);
2065 fcntl(filterfds
[1][0], F_SETFD
, fcntl(filterfds
[1][0], F_GETFD
) | FD_CLOEXEC
);
2067 LogMessage(L_DEBUG
, "StartJob: filterfds[%d] = [ %d %d ]", 1, filterfds
[1][0],
2070 for (i
= 0, slot
= 0; i
< num_filters
; i
++)
2072 if (filters
[i
].filter
[0] != '/')
2073 snprintf(command
, sizeof(command
), "%s/filter/%s", ServerBin
,
2076 strlcpy(command
, filters
[i
].filter
, sizeof(command
));
2078 if (i
< (num_filters
- 1))
2080 if (cupsdOpenPipe(filterfds
[slot
]))
2082 LogMessage(L_ERROR
, "Unable to create job filter pipes - %s.",
2084 snprintf(printer
->state_message
, sizeof(printer
->state_message
),
2085 "Unable to create filter pipes - %s.", strerror(errno
));
2086 AddPrinterHistory(printer
);
2088 if (filters
!= NULL
)
2091 cupsdClosePipe(statusfds
);
2092 cupsdClosePipe(filterfds
[!slot
]);
2094 cupsdAddEvent(CUPSD_EVENT_JOB_COMPLETED
, current
->printer
, current
,
2095 "Job cancelled because the server could not create the filter pipes.");
2097 CancelJob(current
->id
, 0);
2103 if (current
->current_file
== 1)
2105 if (strncmp(printer
->device_uri
, "file:", 5) != 0)
2107 if (cupsdOpenPipe(current
->print_pipes
))
2109 LogMessage(L_ERROR
, "Unable to create job backend pipes - %s.",
2111 snprintf(printer
->state_message
, sizeof(printer
->state_message
),
2112 "Unable to create backend pipes - %s.", strerror(errno
));
2113 AddPrinterHistory(printer
);
2115 if (filters
!= NULL
)
2118 cupsdClosePipe(statusfds
);
2119 cupsdClosePipe(filterfds
[!slot
]);
2121 cupsdAddEvent(CUPSD_EVENT_JOB_COMPLETED
, current
->printer
, current
,
2122 "Job cancelled because the server could not create the backend pipes.");
2124 CancelJob(current
->id
, 0);
2130 current
->print_pipes
[0] = -1;
2131 if (!strncmp(printer
->device_uri
, "file:/dev/", 10) &&
2132 strcmp(printer
->device_uri
, "file:/dev/null"))
2133 current
->print_pipes
[1] = open(printer
->device_uri
+ 5,
2135 else if (!strncmp(printer
->device_uri
, "file:///dev/", 12) &&
2136 strcmp(printer
->device_uri
, "file:///dev/null"))
2137 current
->print_pipes
[1] = open(printer
->device_uri
+ 7,
2140 current
->print_pipes
[1] = open(printer
->device_uri
+ 5,
2141 O_WRONLY
| O_CREAT
| O_TRUNC
, 0600);
2143 if (current
->print_pipes
[1] < 0)
2145 LogMessage(L_ERROR
, "Unable to open output file \"%s\" - %s.",
2146 printer
->device_uri
, strerror(errno
));
2147 snprintf(printer
->state_message
, sizeof(printer
->state_message
),
2148 "Unable to open output file \"%s\" - %s.",
2149 printer
->device_uri
, strerror(errno
));
2151 AddPrinterHistory(printer
);
2153 if (filters
!= NULL
)
2156 cupsdClosePipe(statusfds
);
2157 cupsdClosePipe(filterfds
[!slot
]);
2159 cupsdAddEvent(CUPSD_EVENT_JOB_COMPLETED
, current
->printer
, current
,
2160 "Job cancelled because the server could not open the output file.");
2162 CancelJob(current
->id
, 0);
2166 fcntl(current
->print_pipes
[1], F_SETFD
,
2167 fcntl(current
->print_pipes
[1], F_GETFD
) | FD_CLOEXEC
);
2170 LogMessage(L_DEBUG2
, "StartJob: print_pipes = [ %d %d ]",
2171 current
->print_pipes
[0], current
->print_pipes
[1]);
2174 filterfds
[slot
][0] = current
->print_pipes
[0];
2175 filterfds
[slot
][1] = current
->print_pipes
[1];
2178 LogMessage(L_DEBUG
, "StartJob: filter = \"%s\"", command
);
2179 LogMessage(L_DEBUG
, "StartJob: filterfds[%d] = [ %d %d ]",
2180 slot
, filterfds
[slot
][0], filterfds
[slot
][1]);
2182 pid
= cupsdStartProcess(command
, argv
, envp
, filterfds
[!slot
][0],
2183 filterfds
[slot
][1], statusfds
[1],
2184 current
->back_pipes
[0], 0, current
->filters
+ i
);
2186 LogMessage(L_DEBUG2
, "StartJob: Closing filter pipes for slot %d [ %d %d ]...",
2187 !slot
, filterfds
[!slot
][0], filterfds
[!slot
][1]);
2189 cupsdClosePipe(filterfds
[!slot
]);
2193 LogMessage(L_ERROR
, "Unable to start filter \"%s\" - %s.",
2194 filters
[i
].filter
, strerror(errno
));
2195 snprintf(printer
->state_message
, sizeof(printer
->state_message
),
2196 "Unable to start filter \"%s\" - %s.",
2197 filters
[i
].filter
, strerror(errno
));
2199 AddPrinterHistory(printer
);
2201 if (filters
!= NULL
)
2204 AddPrinterHistory(printer
);
2206 cupsdAddEvent(CUPSD_EVENT_JOB_COMPLETED
, current
->printer
, current
,
2207 "Job cancelled because the server could not execute a filter.");
2209 CancelJob(current
->id
, 0);
2213 LogMessage(L_INFO
, "Started filter %s (PID %d) for job %d.",
2214 command
, pid
, current
->id
);
2220 if (filters
!= NULL
)
2224 * Finally, pipe the final output into a backend process if needed...
2227 if (strncmp(printer
->device_uri
, "file:", 5) != 0)
2229 if (current
->current_file
== 1)
2231 sscanf(printer
->device_uri
, "%254[^:]", method
);
2232 snprintf(command
, sizeof(command
), "%s/backend/%s", ServerBin
, method
);
2236 filterfds
[slot
][0] = -1;
2237 filterfds
[slot
][1] = open("/dev/null", O_WRONLY
);
2239 if (filterfds
[slot
][1] < 0)
2241 LogMessage(L_ERROR
, "Unable to open \"/dev/null\" - %s.", strerror(errno
));
2242 snprintf(printer
->state_message
, sizeof(printer
->state_message
),
2243 "Unable to open \"/dev/null\" - %s.", strerror(errno
));
2245 AddPrinterHistory(printer
);
2247 if (filters
!= NULL
)
2250 cupsdClosePipe(statusfds
);
2252 cupsdAddEvent(CUPSD_EVENT_JOB_COMPLETED
, current
->printer
, current
,
2253 "Job cancelled because the server could not open a file.");
2255 CancelJob(current
->id
, 0);
2259 fcntl(filterfds
[slot
][1], F_SETFD
,
2260 fcntl(filterfds
[slot
][1], F_GETFD
) | FD_CLOEXEC
);
2262 LogMessage(L_DEBUG
, "StartJob: backend = \"%s\"", command
);
2263 LogMessage(L_DEBUG
, "StartJob: filterfds[%d] = [ %d %d ]",
2264 slot
, filterfds
[slot
][0], filterfds
[slot
][1]);
2266 pid
= cupsdStartProcess(command
, argv
, envp
, filterfds
[!slot
][0],
2267 filterfds
[slot
][1], statusfds
[1],
2268 current
->back_pipes
[1], 1,
2269 &(current
->backend
));
2273 LogMessage(L_ERROR
, "Unable to start backend \"%s\" - %s.",
2274 method
, strerror(errno
));
2275 snprintf(printer
->state_message
, sizeof(printer
->state_message
),
2276 "Unable to start backend \"%s\" - %s.", method
, strerror(errno
));
2278 LogMessage(L_DEBUG2
, "StartJob: Closing print pipes [ %d %d ]...",
2279 current
->print_pipes
[0], current
->print_pipes
[1]);
2281 cupsdClosePipe(current
->print_pipes
);
2283 LogMessage(L_DEBUG2
, "StartJob: Closing back pipes [ %d %d ]...",
2284 current
->back_pipes
[0], current
->back_pipes
[1]);
2286 cupsdClosePipe(current
->back_pipes
);
2288 cupsdAddEvent(CUPSD_EVENT_JOB_COMPLETED
, current
->printer
, current
,
2289 "Job cancelled because the server could not execute the backend.");
2291 CancelJob(current
->id
, 0);
2296 LogMessage(L_INFO
, "Started backend %s (PID %d) for job %d.",
2297 command
, pid
, current
->id
);
2301 if (current
->current_file
== current
->num_files
)
2303 LogMessage(L_DEBUG2
, "StartJob: Closing print pipes [ %d %d ]...",
2304 current
->print_pipes
[0], current
->print_pipes
[1]);
2306 cupsdClosePipe(current
->print_pipes
);
2308 LogMessage(L_DEBUG2
, "StartJob: Closing back pipes [ %d %d ]...",
2309 current
->back_pipes
[0], current
->back_pipes
[1]);
2311 cupsdClosePipe(current
->back_pipes
);
2316 filterfds
[slot
][0] = -1;
2317 filterfds
[slot
][1] = -1;
2319 if (current
->current_file
== current
->num_files
)
2321 LogMessage(L_DEBUG2
, "StartJob: Closing print pipes [ %d %d ]...",
2322 current
->print_pipes
[0], current
->print_pipes
[1]);
2324 cupsdClosePipe(current
->print_pipes
);
2328 LogMessage(L_DEBUG2
, "StartJob: Closing filter pipes for slot %d [ %d %d ]...",
2329 slot
, filterfds
[slot
][0], filterfds
[slot
][1]);
2331 cupsdClosePipe(filterfds
[slot
]);
2333 LogMessage(L_DEBUG2
, "StartJob: Closing status output pipe %d...",
2336 close(statusfds
[1]);
2338 LogMessage(L_DEBUG2
, "StartJob: Adding fd %d to InputSet...",
2339 current
->status_buffer
->fd
);
2341 FD_SET(current
->status_buffer
->fd
, InputSet
);
2346 * 'StopAllJobs()' - Stop all print jobs.
2352 job_t
*current
; /* Current job */
2355 DEBUG_puts("StopAllJobs()");
2357 for (current
= Jobs
; current
!= NULL
; current
= current
->next
)
2358 if (current
->state
->values
[0].integer
== IPP_JOB_PROCESSING
)
2360 StopJob(current
->id
, 1);
2361 current
->state
->values
[0].integer
= IPP_JOB_PENDING
;
2367 * 'StopJob()' - Stop a print job.
2371 StopJob(int id
, /* I - Job ID */
2372 int force
) /* I - 1 = Force all filters to stop */
2374 int i
; /* Looping var */
2375 job_t
*current
; /* Current job */
2378 LogMessage(L_DEBUG
, "StopJob: id = %d, force = %d", id
, force
);
2380 for (current
= Jobs
; current
!= NULL
; current
= current
->next
)
2381 if (current
->id
== id
)
2383 DEBUG_puts("StopJob: found job in list.");
2385 if (current
->state
->values
[0].integer
== IPP_JOB_PROCESSING
)
2387 DEBUG_puts("StopJob: job state is \'processing\'.");
2389 FilterLevel
-= current
->cost
;
2391 if (current
->status
< 0 &&
2392 !(current
->dtype
& (CUPS_PRINTER_CLASS
| CUPS_PRINTER_IMPLICIT
)) &&
2393 !(current
->printer
->type
& CUPS_PRINTER_FAX
) &&
2394 !strcmp(current
->printer
->error_policy
, "stop-printer"))
2395 SetPrinterState(current
->printer
, IPP_PRINTER_STOPPED
, 1);
2396 else if (current
->printer
->state
!= IPP_PRINTER_STOPPED
)
2397 SetPrinterState(current
->printer
, IPP_PRINTER_IDLE
, 0);
2399 LogMessage(L_DEBUG
, "StopJob: printer state is %d", current
->printer
->state
);
2401 current
->state
->values
[0].integer
= IPP_JOB_STOPPED
;
2402 current
->printer
->job
= NULL
;
2403 current
->printer
= NULL
;
2405 current
->current_file
--;
2407 for (i
= 0; current
->filters
[i
]; i
++)
2408 if (current
->filters
[i
] > 0)
2410 cupsdEndProcess(current
->filters
[i
], force
);
2411 current
->filters
[i
] = 0;
2414 if (current
->backend
> 0)
2416 cupsdEndProcess(current
->backend
, force
);
2417 current
->backend
= 0;
2420 LogMessage(L_DEBUG2
, "StopJob: Closing print pipes [ %d %d ]...",
2421 current
->print_pipes
[0], current
->print_pipes
[1]);
2423 cupsdClosePipe(current
->print_pipes
);
2425 LogMessage(L_DEBUG2
, "StopJob: Closing back pipes [ %d %d ]...",
2426 current
->back_pipes
[0], current
->back_pipes
[1]);
2428 cupsdClosePipe(current
->back_pipes
);
2430 if (current
->status_buffer
)
2433 * Close the pipe and clear the input bit.
2436 LogMessage(L_DEBUG2
, "StopJob: Removing fd %d from InputSet...",
2437 current
->status_buffer
->fd
);
2439 FD_CLR(current
->status_buffer
->fd
, InputSet
);
2441 LogMessage(L_DEBUG2
, "StopJob: Closing status input pipe %d...",
2442 current
->status_buffer
->fd
);
2444 cupsdStatBufDelete(current
->status_buffer
);
2446 current
->status_buffer
= NULL
;
2455 * 'UpdateJob()' - Read a status update from a job's filters.
2459 UpdateJob(job_t
*job
) /* I - Job to check */
2461 int i
; /* Looping var */
2462 int copies
; /* Number of copies printed */
2463 char message
[1024], /* Message text */
2464 *ptr
; /* Pointer update... */
2465 int loglevel
; /* Log level for message */
2468 while ((ptr
= cupsdStatBufUpdate(job
->status_buffer
, &loglevel
,
2469 message
, sizeof(message
))) != NULL
)
2472 * Process page and printer state messages as needed...
2475 if (loglevel
== L_PAGE
)
2478 * Page message; send the message to the page_log file and update the
2479 * job sheet count...
2482 if (job
->sheets
!= NULL
)
2484 if (!strncasecmp(message
, "total ", 6))
2487 * Got a total count of pages from a backend or filter...
2490 copies
= atoi(message
+ 6);
2491 copies
-= job
->sheets
->values
[0].integer
; /* Just track the delta */
2493 else if (!sscanf(message
, "%*d%d", &copies
))
2496 job
->sheets
->values
[0].integer
+= copies
;
2498 if (job
->printer
->page_limit
)
2499 UpdateQuota(job
->printer
, job
->username
, copies
, 0);
2502 LogPage(job
, message
);
2504 cupsdAddEvent(CUPSD_EVENT_JOB_PROGRESS
, job
->printer
, job
,
2505 "Printed %d page(s).", job
->sheets
->values
[0].integer
);
2507 else if (loglevel
== L_STATE
)
2508 SetPrinterReasons(job
->printer
, message
);
2509 else if (loglevel
== L_ATTR
)
2512 * Set attribute(s)...
2518 if (!strchr(job
->status_buffer
->buffer
, '\n'))
2525 * See if all of the filters and the backend have returned their
2529 for (i
= 0; job
->filters
[i
] < 0; i
++);
2531 if (job
->filters
[i
])
2534 if (job
->current_file
>= job
->num_files
&& job
->backend
> 0)
2538 * Handle the end of job stuff...
2547 * 'ipp_length()' - Compute the size of the buffer needed to hold
2548 * the textual IPP attributes.
2551 int /* O - Size of buffer to hold IPP attributes */
2552 ipp_length(ipp_t
*ipp
) /* I - IPP request */
2554 int bytes
; /* Number of bytes */
2555 int i
; /* Looping var */
2556 ipp_attribute_t
*attr
; /* Current attribute */
2560 * Loop through all attributes...
2565 for (attr
= ipp
->attrs
; attr
!= NULL
; attr
= attr
->next
)
2568 * Skip attributes that won't be sent to filters...
2571 if (attr
->value_tag
== IPP_TAG_MIMETYPE
||
2572 attr
->value_tag
== IPP_TAG_NAMELANG
||
2573 attr
->value_tag
== IPP_TAG_TEXTLANG
||
2574 attr
->value_tag
== IPP_TAG_URI
||
2575 attr
->value_tag
== IPP_TAG_URISCHEME
)
2578 if (strncmp(attr
->name
, "time-", 5) == 0)
2582 * Add space for a leading space and commas between each value.
2583 * For the first attribute, the leading space isn't used, so the
2584 * extra byte can be used as the nul terminator...
2587 bytes
++; /* " " separator */
2588 bytes
+= attr
->num_values
; /* "," separators */
2591 * Boolean attributes appear as "foo,nofoo,foo,nofoo", while
2592 * other attributes appear as "foo=value1,value2,...,valueN".
2595 if (attr
->value_tag
!= IPP_TAG_BOOLEAN
)
2596 bytes
+= strlen(attr
->name
);
2598 bytes
+= attr
->num_values
* strlen(attr
->name
);
2601 * Now add the size required for each value in the attribute...
2604 switch (attr
->value_tag
)
2606 case IPP_TAG_INTEGER
:
2609 * Minimum value of a signed integer is -2147483647, or 11 digits.
2612 bytes
+= attr
->num_values
* 11;
2615 case IPP_TAG_BOOLEAN
:
2617 * Add two bytes for each false ("no") value...
2620 for (i
= 0; i
< attr
->num_values
; i
++)
2621 if (!attr
->values
[i
].boolean
)
2625 case IPP_TAG_RANGE
:
2627 * A range is two signed integers separated by a hyphen, or
2628 * 23 characters max.
2631 bytes
+= attr
->num_values
* 23;
2634 case IPP_TAG_RESOLUTION
:
2636 * A resolution is two signed integers separated by an "x" and
2637 * suffixed by the units, or 26 characters max.
2640 bytes
+= attr
->num_values
* 26;
2643 case IPP_TAG_STRING
:
2646 case IPP_TAG_KEYWORD
:
2647 case IPP_TAG_CHARSET
:
2648 case IPP_TAG_LANGUAGE
:
2650 * Strings can contain characters that need quoting. We need
2651 * at least 2 * len + 2 characters to cover the quotes and
2652 * any backslashes in the string.
2655 for (i
= 0; i
< attr
->num_values
; i
++)
2656 bytes
+= 2 * strlen(attr
->values
[i
].string
.text
) + 2;
2660 break; /* anti-compiler-warning-code */
2669 * 'set_time()' - Set one of the "time-at-xyz" attributes...
2673 set_time(job_t
*job
, /* I - Job to update */
2674 const char *name
) /* I - Name of attribute */
2676 ipp_attribute_t
*attr
; /* Time attribute */
2679 if ((attr
= ippFindAttribute(job
->attrs
, name
, IPP_TAG_ZERO
)) != NULL
)
2681 attr
->value_tag
= IPP_TAG_INTEGER
;
2682 attr
->values
[0].integer
= time(NULL
);
2688 * 'set_hold_until()' - Set the hold time and update job-hold-until attribute...
2692 set_hold_until(job_t
*job
, /* I - Job to update */
2693 time_t holdtime
) /* I - Hold until time */
2695 ipp_attribute_t
*attr
; /* job-hold-until attribute */
2696 struct tm
*holddate
; /* Hold date */
2697 char holdstr
[64]; /* Hold time */
2701 * Set the hold_until value and hold the job...
2704 LogMessage(L_DEBUG
, "set_hold_until: hold_until = %d", (int)holdtime
);
2706 job
->state
->values
[0].integer
= IPP_JOB_HELD
;
2707 job
->hold_until
= holdtime
;
2710 * Update the job-hold-until attribute with a string representing GMT
2711 * time (HH:MM:SS)...
2714 holddate
= gmtime(&holdtime
);
2715 snprintf(holdstr
, sizeof(holdstr
), "%d:%d:%d", holddate
->tm_hour
,
2716 holddate
->tm_min
, holddate
->tm_sec
);
2718 if ((attr
= ippFindAttribute(job
->attrs
, "job-hold-until", IPP_TAG_KEYWORD
)) == NULL
)
2719 attr
= ippFindAttribute(job
->attrs
, "job-hold-until", IPP_TAG_NAME
);
2722 * Either add the attribute or update the value of the existing one
2726 attr
= ippAddString(job
->attrs
, IPP_TAG_JOB
, IPP_TAG_KEYWORD
,
2727 "job-hold-until", NULL
, holdstr
);
2729 SetString(&attr
->values
[0].string
.text
, holdstr
);