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...
70 static mime_filter_t gziptoany_filter
=
72 NULL
, /* Source type */
73 NULL
, /* Destination type */
75 "gziptoany" /* Filter program to run */
83 static int ipp_length(ipp_t
*ipp
);
84 static void set_time(job_t
*job
, const char *name
);
85 static void set_hold_until(job_t
*job
, time_t holdtime
);
89 * 'AddJob()' - Add a new job to the job queue...
92 job_t
* /* O - New job record */
93 AddJob(int priority
, /* I - Job priority */
94 const char *dest
) /* I - Job destination */
96 job_t
*job
, /* New job record */
97 *current
, /* Current job in queue */
98 *prev
; /* Previous job in queue */
101 job
= calloc(sizeof(job_t
), 1);
103 job
->id
= NextJobId
++;
104 job
->priority
= priority
;
105 job
->back_pipes
[0] = -1;
106 job
->back_pipes
[1] = -1;
107 job
->print_pipes
[0] = -1;
108 job
->print_pipes
[1] = -1;
110 SetString(&job
->dest
, dest
);
114 for (current
= Jobs
, prev
= NULL
;
116 prev
= current
, current
= current
->next
)
117 if (job
->priority
> current
->priority
)
131 * 'CancelJob()' - Cancel the specified print job.
135 CancelJob(int id
, /* I - Job to cancel */
136 int purge
) /* I - Purge jobs? */
138 int i
; /* Looping var */
139 job_t
*current
, /* Current job */
140 *prev
; /* Previous job in list */
141 char filename
[1024]; /* Job filename */
144 LogMessage(L_DEBUG
, "CancelJob: id = %d", id
);
146 for (current
= Jobs
, prev
= NULL
; current
!= NULL
; prev
= current
, current
= current
->next
)
147 if (current
->id
== id
)
150 * Stop any processes that are working on the current...
153 DEBUG_puts("CancelJob: found job in list.");
155 if (current
->state
->values
[0].integer
== IPP_JOB_PROCESSING
)
156 StopJob(current
->id
, 0);
158 current
->state
->values
[0].integer
= IPP_JOB_CANCELLED
;
160 set_time(current
, "time-at-completed");
162 cupsdExpireSubscriptions(NULL
, current
);
165 * Remove the print file for good if we aren't preserving jobs or
169 current
->current_file
= 0;
171 if (!JobHistory
|| !JobFiles
|| purge
||
172 (current
->dtype
& CUPS_PRINTER_REMOTE
))
173 for (i
= 1; i
<= current
->num_files
; i
++)
175 snprintf(filename
, sizeof(filename
), "%s/d%05d-%03d", RequestRoot
,
180 if (JobHistory
&& !purge
&& !(current
->dtype
& CUPS_PRINTER_REMOTE
))
183 * Save job state info...
186 SaveJob(current
->id
);
191 * Remove the job info file...
194 snprintf(filename
, sizeof(filename
), "%s/c%05d", RequestRoot
,
199 * Update pointers if we aren't preserving jobs...
203 Jobs
= current
->next
;
205 prev
->next
= current
->next
;
208 * Free all memory used...
211 if (current
->attrs
!= NULL
)
212 ippDelete(current
->attrs
);
214 if (current
->num_files
> 0)
216 free(current
->compressions
);
217 free(current
->filetypes
);
220 ClearString(¤t
->username
);
221 ClearString(¤t
->dest
);
234 * 'CancelJobs()' - Cancel all jobs for the given destination/user...
238 CancelJobs(const char *dest
, /* I - Destination to cancel */
239 const char *username
, /* I - Username or NULL */
240 int purge
) /* I - Purge jobs? */
242 job_t
*current
, /* Current job */
243 *next
; /* Next job */
246 for (current
= Jobs
; current
!= NULL
;)
247 if ((dest
== NULL
|| !strcmp(current
->dest
, dest
)) &&
248 (username
== NULL
|| !strcmp(current
->username
, username
)))
251 * Cancel all jobs matching this destination/user...
254 next
= current
->next
;
256 cupsdAddEvent(CUPSD_EVENT_JOB_COMPLETED
, current
->printer
, current
,
257 purge
? "Job purged." : "Job canceled.");
259 CancelJob(current
->id
, purge
);
264 current
= current
->next
;
271 * 'CheckJobs()' - Check the pending jobs and start any if the destination
278 job_t
*current
, /* Current job in queue */
279 *next
; /* Next job in queue */
280 printer_t
*printer
, /* Printer destination */
281 *pclass
; /* Printer class destination */
284 DEBUG_puts("CheckJobs()");
286 for (current
= Jobs
; current
!= NULL
; current
= next
)
289 * Save next pointer in case the job is cancelled en-route.
292 next
= current
->next
;
295 * Start held jobs if they are ready...
298 if (current
->state
->values
[0].integer
== IPP_JOB_HELD
&&
299 current
->hold_until
&&
300 current
->hold_until
< time(NULL
))
301 current
->state
->values
[0].integer
= IPP_JOB_PENDING
;
304 * Start pending jobs if the destination is available...
307 if (current
->state
->values
[0].integer
== IPP_JOB_PENDING
&& !NeedReload
)
309 if ((pclass
= FindClass(current
->dest
)) != NULL
)
312 * If the class is remote, just pass it to the remote server...
315 if (pclass
->type
& CUPS_PRINTER_REMOTE
)
317 else if (pclass
->state
!= IPP_PRINTER_STOPPED
)
318 printer
= FindAvailablePrinter(current
->dest
);
323 printer
= FindPrinter(current
->dest
);
325 if (printer
!= NULL
&& (printer
->type
& CUPS_PRINTER_IMPLICIT
))
328 * Handle implicit classes...
333 if (pclass
->state
!= IPP_PRINTER_STOPPED
)
334 printer
= FindAvailablePrinter(current
->dest
);
339 if (printer
== NULL
&& pclass
== NULL
)
342 * Whoa, the printer and/or class for this destination went away;
346 LogMessage(L_WARN
, "Printer/class %s has gone away; cancelling job %d!",
347 current
->dest
, current
->id
);
349 cupsdAddEvent(CUPSD_EVENT_JOB_COMPLETED
, current
->printer
, current
,
350 "Job cancelled because the destination printer/class has gone away.");
352 CancelJob(current
->id
, 1);
354 else if (printer
!= NULL
)
357 * See if the printer is available or remote and not printing a job;
358 * if so, start the job...
361 if (printer
->state
== IPP_PRINTER_IDLE
|| /* Printer is idle */
362 ((printer
->type
& CUPS_PRINTER_REMOTE
) && /* Printer is remote */
363 !printer
->job
)) /* and not printing a job */
364 StartJob(current
->id
, printer
);
372 * 'CleanJobs()' - Clean out old jobs.
378 job_t
*job
, /* Current job */
379 *next
; /* Next job */
385 for (job
= Jobs
; job
&& NumJobs
>= MaxJobs
; job
= next
)
389 if (job
->state
->values
[0].integer
>= IPP_JOB_CANCELLED
)
390 CancelJob(job
->id
, 1);
396 * 'FinishJob()' - Finish a job.
400 FinishJob(job_t
*job
) /* I - Job */
402 int job_history
; /* Did CancelJob() keep the job? */
403 printer_t
*printer
; /* Current printer */
406 LogMessage(L_DEBUG
, "FinishJob: job %d, file %d is complete.",
407 job
->id
, job
->current_file
- 1);
409 LogMessage(L_DEBUG2
, "FinishJob: job->status is %d", job
->status
);
411 if (job
->status_buffer
&& job
->current_file
>= job
->num_files
)
414 * Close the pipe and clear the input bit.
417 LogMessage(L_DEBUG2
, "FinishJob: Removing fd %d from InputSet...",
418 job
->status_buffer
->fd
);
420 FD_CLR(job
->status_buffer
->fd
, InputSet
);
422 LogMessage(L_DEBUG2
, "FinishJob: Closing status input pipe %d...",
423 job
->status_buffer
->fd
);
425 cupsdStatBufDelete(job
->status_buffer
);
427 job
->status_buffer
= NULL
;
433 * Backend had errors; stop it...
436 printer
= job
->printer
;
439 job
->state
->values
[0].integer
= IPP_JOB_PENDING
;
443 * If the job was queued to a class, try requeuing it... For
444 * faxes and retry-job queues, hold the current job for 5 minutes.
447 if (job
->dtype
& (CUPS_PRINTER_CLASS
| CUPS_PRINTER_IMPLICIT
))
449 else if ((printer
->type
& CUPS_PRINTER_FAX
) ||
450 !strcmp(printer
->error_policy
, "retry-job"))
453 * See how many times we've tried to send the job; if more than
454 * the limit, cancel the job.
459 if (job
->tries
>= FaxRetryLimit
)
465 LogMessage(L_ERROR
, "Canceling job %d since it could not be sent after %d tries.",
466 job
->id
, FaxRetryLimit
);
468 cupsdAddEvent(CUPSD_EVENT_JOB_COMPLETED
, job
->printer
, job
,
469 "Job cancelled since it could not be sent after %d tries.",
472 CancelJob(job
->id
, 0);
477 * Try again in N seconds...
480 set_hold_until(job
, time(NULL
) + FaxRetryInterval
);
485 else if (!strcmp(printer
->error_policy
, "abort-job"))
488 else if (job
->status
> 0)
491 * Filter had errors; cancel it...
494 if (job
->current_file
< job
->num_files
)
495 StartJob(job
->id
, job
->printer
);
498 cupsdAddEvent(CUPSD_EVENT_JOB_COMPLETED
, job
->printer
, job
,
499 "Job aborted due to filter errors; please consult the "
500 "error_log file for details.");
502 job_history
= JobHistory
&& !(job
->dtype
& CUPS_PRINTER_REMOTE
);
504 CancelJob(job
->id
, 0);
508 job
->state
->values
[0].integer
= IPP_JOB_ABORTED
;
518 * Job printed successfully; cancel it...
521 if (job
->current_file
< job
->num_files
)
523 FilterLevel
-= job
->cost
;
524 StartJob(job
->id
, job
->printer
);
528 cupsdAddEvent(CUPSD_EVENT_JOB_COMPLETED
, job
->printer
, job
,
529 "Job completed successfully.");
531 job_history
= JobHistory
&& !(job
->dtype
& CUPS_PRINTER_REMOTE
);
533 CancelJob(job
->id
, 0);
537 job
->state
->values
[0].integer
= IPP_JOB_COMPLETED
;
548 * 'FreeAllJobs()' - Free all jobs from memory.
554 job_t
*job
, /* Current job */
555 *next
; /* Next job */
562 for (job
= Jobs
; job
; job
= next
)
566 ippDelete(job
->attrs
);
568 if (job
->num_files
> 0)
570 free(job
->compressions
);
571 free(job
->filetypes
);
584 * 'FindJob()' - Find the specified job.
587 job_t
* /* O - Job data */
588 FindJob(int id
) /* I - Job ID */
590 job_t
*current
; /* Current job */
593 for (current
= Jobs
; current
!= NULL
; current
= current
->next
)
594 if (current
->id
== id
)
602 * 'GetPrinterJobCount()' - Get the number of pending, processing,
603 * or held jobs in a printer or class.
606 int /* O - Job count */
607 GetPrinterJobCount(const char *dest
) /* I - Printer or class name */
609 int count
; /* Job count */
610 job_t
*job
; /* Current job */
613 for (job
= Jobs
, count
= 0; job
!= NULL
; job
= job
->next
)
614 if (job
->state
->values
[0].integer
<= IPP_JOB_PROCESSING
&&
615 strcasecmp(job
->dest
, dest
) == 0)
623 * 'GetUserJobCount()' - Get the number of pending, processing,
624 * or held jobs for a user.
627 int /* O - Job count */
628 GetUserJobCount(const char *username
) /* I - Username */
630 int count
; /* Job count */
631 job_t
*job
; /* Current job */
634 for (job
= Jobs
, count
= 0; job
!= NULL
; job
= job
->next
)
635 if (job
->state
->values
[0].integer
<= IPP_JOB_PROCESSING
&&
636 strcmp(job
->username
, username
) == 0)
644 * 'HoldJob()' - Hold the specified job.
648 HoldJob(int id
) /* I - Job ID */
650 job_t
*job
; /* Job data */
653 LogMessage(L_DEBUG
, "HoldJob: id = %d", id
);
655 if ((job
= FindJob(id
)) == NULL
)
658 if (job
->state
->values
[0].integer
== IPP_JOB_PROCESSING
)
661 DEBUG_puts("HoldJob: setting state to held...");
663 job
->state
->values
[0].integer
= IPP_JOB_HELD
;
672 * 'LoadAllJobs()' - Load all jobs from disk.
678 cups_dir_t
*dir
; /* Directory */
679 cups_dentry_t
*dent
; /* Directory entry */
680 char filename
[1024]; /* Full filename of job file */
681 int fd
; /* File descriptor */
682 job_t
*job
, /* New job */
683 *current
, /* Current job */
684 *prev
; /* Previous job */
685 int jobid
, /* Current job ID */
686 fileid
; /* Current file ID */
687 ipp_attribute_t
*attr
; /* Job attribute */
688 char method
[HTTP_MAX_URI
], /* Method portion of URI */
689 username
[HTTP_MAX_URI
], /* Username portion of URI */
690 host
[HTTP_MAX_URI
], /* Host portion of URI */
691 resource
[HTTP_MAX_URI
]; /* Resource portion of URI */
692 int port
; /* Port portion of URI */
693 printer_t
*p
; /* Printer or class */
694 const char *dest
; /* Destination */
695 mime_type_t
**filetypes
; /* New filetypes array */
696 int *compressions
; /* New compressions array */
700 * First open the requests directory...
703 LogMessage(L_DEBUG
, "LoadAllJobs: Scanning %s...", RequestRoot
);
707 if ((dir
= cupsDirOpen(RequestRoot
)) == NULL
)
709 LogMessage(L_ERROR
, "LoadAllJobs: Unable to open spool directory %s: %s",
710 RequestRoot
, strerror(errno
));
715 * Read all the c##### files...
718 while ((dent
= cupsDirRead(dir
)) != NULL
)
719 if (strlen(dent
->filename
) >= 6 && dent
->filename
[0] == 'c')
722 * Allocate memory for the job...
725 if ((job
= calloc(sizeof(job_t
), 1)) == NULL
)
727 LogMessage(L_ERROR
, "LoadAllJobs: Ran out of memory for jobs!");
732 if ((job
->attrs
= ippNew()) == NULL
)
735 LogMessage(L_ERROR
, "LoadAllJobs: Ran out of memory for job attributes!");
741 * Assign the job ID...
744 job
->id
= atoi(dent
->filename
+ 1);
745 job
->back_pipes
[0] = -1;
746 job
->back_pipes
[1] = -1;
747 job
->print_pipes
[0] = -1;
748 job
->print_pipes
[1] = -1;
750 LogMessage(L_DEBUG
, "LoadAllJobs: Loading attributes for job %d...\n",
753 if (job
->id
>= NextJobId
)
754 NextJobId
= job
->id
+ 1;
757 * Load the job control file...
760 snprintf(filename
, sizeof(filename
), "%s/%s", RequestRoot
, dent
->filename
);
761 if ((fd
= open(filename
, O_RDONLY
)) < 0)
763 LogMessage(L_ERROR
, "LoadAllJobs: Unable to open job control file \"%s\" - %s!",
764 filename
, strerror(errno
));
765 ippDelete(job
->attrs
);
772 if (ippReadFile(fd
, job
->attrs
) != IPP_DATA
)
774 LogMessage(L_ERROR
, "LoadAllJobs: Unable to read job control file \"%s\"!",
777 ippDelete(job
->attrs
);
786 if ((job
->state
= ippFindAttribute(job
->attrs
, "job-state", IPP_TAG_ENUM
)) == NULL
)
788 LogMessage(L_ERROR
, "LoadAllJobs: Missing or bad job-state attribute in control file \"%s\"!",
790 ippDelete(job
->attrs
);
796 if ((attr
= ippFindAttribute(job
->attrs
, "job-printer-uri", IPP_TAG_URI
)) == NULL
)
798 LogMessage(L_ERROR
, "LoadAllJobs: No job-printer-uri attribute in control file \"%s\"!",
800 ippDelete(job
->attrs
);
806 httpSeparate(attr
->values
[0].string
.text
, method
, username
, host
,
809 if ((dest
= ValidateDest(host
, resource
, &(job
->dtype
), NULL
)) == NULL
&&
810 job
->state
!= NULL
&&
811 job
->state
->values
[0].integer
<= IPP_JOB_PROCESSING
)
814 * Job queued on remote printer or class, so add it...
817 if (strncmp(resource
, "/classes/", 9) == 0)
819 p
= AddClass(resource
+ 9);
820 SetString(&p
->make_model
, "Remote Class on unknown");
824 p
= AddPrinter(resource
+ 10);
825 SetString(&p
->make_model
, "Remote Printer on unknown");
828 p
->state
= IPP_PRINTER_STOPPED
;
829 p
->type
|= CUPS_PRINTER_REMOTE
;
830 p
->browse_time
= 2147483647;
832 SetString(&p
->location
, "Location Unknown");
833 SetString(&p
->info
, "No Information Available");
834 p
->hostname
[0] = '\0';
842 LogMessage(L_ERROR
, "LoadAllJobs: Unable to queue job for destination \"%s\"!",
843 attr
->values
[0].string
.text
);
844 ippDelete(job
->attrs
);
850 SetString(&job
->dest
, dest
);
852 job
->sheets
= ippFindAttribute(job
->attrs
, "job-media-sheets-completed",
854 job
->job_sheets
= ippFindAttribute(job
->attrs
, "job-sheets", IPP_TAG_NAME
);
856 if ((attr
= ippFindAttribute(job
->attrs
, "job-priority", IPP_TAG_INTEGER
)) == NULL
)
858 LogMessage(L_ERROR
, "LoadAllJobs: Missing or bad job-priority attribute in control file \"%s\"!",
860 ippDelete(job
->attrs
);
865 job
->priority
= attr
->values
[0].integer
;
867 if ((attr
= ippFindAttribute(job
->attrs
, "job-originating-user-name", IPP_TAG_NAME
)) == NULL
)
869 LogMessage(L_ERROR
, "LoadAllJobs: Missing or bad job-originating-user-name attribute in control file \"%s\"!",
871 ippDelete(job
->attrs
);
876 SetString(&job
->username
, attr
->values
[0].string
.text
);
879 * Insert the job into the array, sorting by job priority and ID...
882 for (current
= Jobs
, prev
= NULL
;
884 prev
= current
, current
= current
->next
)
885 if (job
->priority
> current
->priority
)
887 else if (job
->priority
== current
->priority
&& job
->id
< current
->id
)
899 * Set the job hold-until time and state...
902 if (job
->state
->values
[0].integer
== IPP_JOB_HELD
)
904 if ((attr
= ippFindAttribute(job
->attrs
, "job-hold-until", IPP_TAG_KEYWORD
)) == NULL
)
905 attr
= ippFindAttribute(job
->attrs
, "job-hold-until", IPP_TAG_NAME
);
908 job
->state
->values
[0].integer
= IPP_JOB_PENDING
;
910 SetJobHoldUntil(job
->id
, attr
->values
[0].string
.text
);
912 else if (job
->state
->values
[0].integer
== IPP_JOB_PROCESSING
)
913 job
->state
->values
[0].integer
= IPP_JOB_PENDING
;
917 * Read all the d##### files...
922 while ((dent
= cupsDirRead(dir
)) != NULL
)
923 if (strlen(dent
->filename
) > 7 && dent
->filename
[0] == 'd' &&
924 strchr(dent
->filename
, '-'))
930 jobid
= atoi(dent
->filename
+ 1);
931 fileid
= atoi(strchr(dent
->filename
, '-') + 1);
933 LogMessage(L_DEBUG
, "LoadAllJobs: Auto-typing document file %s...",
936 snprintf(filename
, sizeof(filename
), "%s/%s", RequestRoot
, dent
->filename
);
938 if ((job
= FindJob(jobid
)) == NULL
)
940 LogMessage(L_ERROR
, "LoadAllJobs: Orphaned print file \"%s\"!",
946 if (fileid
> job
->num_files
)
948 if (job
->num_files
== 0)
950 compressions
= (int *)calloc(fileid
, sizeof(int));
951 filetypes
= (mime_type_t
**)calloc(fileid
, sizeof(mime_type_t
*));
955 compressions
= (int *)realloc(job
->compressions
,
956 sizeof(int) * fileid
);
957 filetypes
= (mime_type_t
**)realloc(job
->filetypes
,
958 sizeof(mime_type_t
*) * fileid
);
961 if (compressions
== NULL
|| filetypes
== NULL
)
963 LogMessage(L_ERROR
, "LoadAllJobs: Ran out of memory for job file types!");
967 job
->compressions
= compressions
;
968 job
->filetypes
= filetypes
;
969 job
->num_files
= fileid
;
972 job
->filetypes
[fileid
- 1] = mimeFileType(MimeDatabase
, filename
,
973 job
->compressions
+ fileid
- 1);
975 if (job
->filetypes
[fileid
- 1] == NULL
)
976 job
->filetypes
[fileid
- 1] = mimeType(MimeDatabase
, "application",
983 * Clean out old jobs as needed...
991 * 'MoveJob()' - Move the specified job to a different destination.
995 MoveJob(int id
, /* I - Job ID */
996 const char *dest
) /* I - Destination */
998 job_t
*current
; /* Current job */
999 ipp_attribute_t
*attr
; /* job-printer-uri attribute */
1000 printer_t
*p
; /* Destination printer or class */
1003 if ((p
= FindPrinter(dest
)) == NULL
)
1004 p
= FindClass(dest
);
1009 for (current
= Jobs
; current
!= NULL
; current
= current
->next
)
1010 if (current
->id
== id
)
1012 if (current
->state
->values
[0].integer
>= IPP_JOB_PROCESSING
)
1015 SetString(¤t
->dest
, dest
);
1016 current
->dtype
= p
->type
& (CUPS_PRINTER_CLASS
| CUPS_PRINTER_REMOTE
|
1017 CUPS_PRINTER_IMPLICIT
);
1019 if ((attr
= ippFindAttribute(current
->attrs
, "job-printer-uri", IPP_TAG_URI
)) != NULL
)
1021 free(attr
->values
[0].string
.text
);
1022 attr
->values
[0].string
.text
= strdup(p
->uri
);
1025 SaveJob(current
->id
);
1033 * 'ReleaseJob()' - Release the specified job.
1037 ReleaseJob(int id
) /* I - Job ID */
1039 job_t
*job
; /* Job data */
1042 LogMessage(L_DEBUG
, "ReleaseJob: id = %d", id
);
1044 if ((job
= FindJob(id
)) == NULL
)
1047 if (job
->state
->values
[0].integer
== IPP_JOB_HELD
)
1049 DEBUG_puts("ReleaseJob: setting state to pending...");
1051 job
->state
->values
[0].integer
= IPP_JOB_PENDING
;
1059 * 'RestartJob()' - Restart the specified job.
1063 RestartJob(int id
) /* I - Job ID */
1065 job_t
*job
; /* Job data */
1068 if ((job
= FindJob(id
)) == NULL
)
1071 if (job
->state
->values
[0].integer
== IPP_JOB_STOPPED
|| JobFiles
)
1074 job
->state
->values
[0].integer
= IPP_JOB_PENDING
;
1082 * 'SaveJob()' - Save a job to disk.
1086 SaveJob(int id
) /* I - Job ID */
1088 job_t
*job
; /* Pointer to job */
1089 char filename
[1024]; /* Job control filename */
1090 int fd
; /* File descriptor */
1093 if ((job
= FindJob(id
)) == NULL
)
1096 snprintf(filename
, sizeof(filename
), "%s/c%05d", RequestRoot
, id
);
1098 if ((fd
= open(filename
, O_WRONLY
| O_CREAT
| O_TRUNC
, 0600)) < 0)
1100 LogMessage(L_ERROR
, "SaveJob: Unable to create job control file \"%s\" - %s.",
1101 filename
, strerror(errno
));
1106 fchown(fd
, RunUser
, Group
);
1108 ippWriteFile(fd
, job
->attrs
);
1110 LogMessage(L_DEBUG2
, "SaveJob: Closing file %d...", fd
);
1117 * 'SetJobHoldUntil()' - Set the hold time for a job...
1121 SetJobHoldUntil(int id
, /* I - Job ID */
1122 const char *when
) /* I - When to resume */
1124 job_t
*job
; /* Pointer to job */
1125 time_t curtime
; /* Current time */
1126 struct tm
*curdate
; /* Current date */
1127 int hour
; /* Hold hour */
1128 int minute
; /* Hold minute */
1129 int second
; /* Hold second */
1132 LogMessage(L_DEBUG
, "SetJobHoldUntil(%d, \"%s\")", id
, when
);
1134 if ((job
= FindJob(id
)) == NULL
)
1139 if (strcmp(when
, "indefinite") == 0)
1142 * Hold indefinitely...
1145 job
->hold_until
= 0;
1147 else if (strcmp(when
, "day-time") == 0)
1150 * Hold to 6am the next morning unless local time is < 6pm.
1153 curtime
= time(NULL
);
1154 curdate
= localtime(&curtime
);
1156 if (curdate
->tm_hour
< 18)
1157 job
->hold_until
= curtime
;
1159 job
->hold_until
= curtime
+
1160 ((29 - curdate
->tm_hour
) * 60 + 59 -
1161 curdate
->tm_min
) * 60 + 60 - curdate
->tm_sec
;
1163 else if (strcmp(when
, "evening") == 0 || strcmp(when
, "night") == 0)
1166 * Hold to 6pm unless local time is > 6pm or < 6am.
1169 curtime
= time(NULL
);
1170 curdate
= localtime(&curtime
);
1172 if (curdate
->tm_hour
< 6 || curdate
->tm_hour
>= 18)
1173 job
->hold_until
= curtime
;
1175 job
->hold_until
= curtime
+
1176 ((17 - curdate
->tm_hour
) * 60 + 59 -
1177 curdate
->tm_min
) * 60 + 60 - curdate
->tm_sec
;
1179 else if (strcmp(when
, "second-shift") == 0)
1182 * Hold to 4pm unless local time is > 4pm.
1185 curtime
= time(NULL
);
1186 curdate
= localtime(&curtime
);
1188 if (curdate
->tm_hour
>= 16)
1189 job
->hold_until
= curtime
;
1191 job
->hold_until
= curtime
+
1192 ((15 - curdate
->tm_hour
) * 60 + 59 -
1193 curdate
->tm_min
) * 60 + 60 - curdate
->tm_sec
;
1195 else if (strcmp(when
, "third-shift") == 0)
1198 * Hold to 12am unless local time is < 8am.
1201 curtime
= time(NULL
);
1202 curdate
= localtime(&curtime
);
1204 if (curdate
->tm_hour
< 8)
1205 job
->hold_until
= curtime
;
1207 job
->hold_until
= curtime
+
1208 ((23 - curdate
->tm_hour
) * 60 + 59 -
1209 curdate
->tm_min
) * 60 + 60 - curdate
->tm_sec
;
1211 else if (strcmp(when
, "weekend") == 0)
1214 * Hold to weekend unless we are in the weekend.
1217 curtime
= time(NULL
);
1218 curdate
= localtime(&curtime
);
1220 if (curdate
->tm_wday
== 0 || curdate
->tm_wday
== 6)
1221 job
->hold_until
= curtime
;
1223 job
->hold_until
= curtime
+
1224 (((5 - curdate
->tm_wday
) * 24 +
1225 (17 - curdate
->tm_hour
)) * 60 + 59 -
1226 curdate
->tm_min
) * 60 + 60 - curdate
->tm_sec
;
1228 else if (sscanf(when
, "%d:%d:%d", &hour
, &minute
, &second
) >= 2)
1231 * Hold to specified GMT time (HH:MM or HH:MM:SS)...
1234 curtime
= time(NULL
);
1235 curdate
= gmtime(&curtime
);
1237 job
->hold_until
= curtime
+
1238 ((hour
- curdate
->tm_hour
) * 60 + minute
-
1239 curdate
->tm_min
) * 60 + second
- curdate
->tm_sec
;
1242 * Hold until next day as needed...
1245 if (job
->hold_until
< curtime
)
1246 job
->hold_until
+= 24 * 60 * 60 * 60;
1249 LogMessage(L_DEBUG
, "SetJobHoldUntil: hold_until = %d", (int)job
->hold_until
);
1254 * 'SetJobPriority()' - Set the priority of a job, moving it up/down in the
1259 SetJobPriority(int id
, /* I - Job ID */
1260 int priority
) /* I - New priority (0 to 100) */
1262 job_t
*job
, /* Job to change */
1263 *current
, /* Current job */
1264 *prev
; /* Previous job */
1265 ipp_attribute_t
*attr
; /* Job attribute */
1272 for (current
= Jobs
, prev
= NULL
;
1274 prev
= current
, current
= current
->next
)
1275 if (current
->id
== id
)
1278 if (current
== NULL
)
1282 * Set the new priority...
1286 job
->priority
= priority
;
1288 if ((attr
= ippFindAttribute(job
->attrs
, "job-priority", IPP_TAG_INTEGER
)) != NULL
)
1289 attr
->values
[0].integer
= priority
;
1291 ippAddInteger(job
->attrs
, IPP_TAG_JOB
, IPP_TAG_INTEGER
, "job-priority",
1297 * See if we need to do any sorting...
1300 if ((prev
== NULL
|| job
->priority
< prev
->priority
) &&
1301 (job
->next
== NULL
|| job
->next
->priority
< job
->priority
))
1305 * Remove the job from the list, and then insert it where it belongs...
1311 prev
->next
= job
->next
;
1313 for (current
= Jobs
, prev
= NULL
;
1315 prev
= current
, current
= current
->next
)
1316 if (job
->priority
> current
->priority
)
1319 job
->next
= current
;
1328 * 'StartJob()' - Start a print job.
1332 StartJob(int id
, /* I - Job ID */
1333 printer_t
*printer
) /* I - Printer to print job */
1335 job_t
*current
; /* Current job */
1336 int i
; /* Looping var */
1337 int slot
; /* Pipe slot */
1338 int num_filters
; /* Number of filters for job */
1339 mime_filter_t
*filters
; /* Filters for job */
1340 char method
[255], /* Method for output */
1341 *optptr
, /* Pointer to options */
1342 *valptr
; /* Pointer in value string */
1343 ipp_attribute_t
*attr
; /* Current attribute */
1344 int pid
; /* Process ID of new filter process */
1345 int banner_page
; /* 1 if banner page, 0 otherwise */
1346 int statusfds
[2], /* Pipes used between the filters and scheduler */
1347 filterfds
[2][2]; /* Pipes used between the filters */
1348 int envc
; /* Number of environment variables */
1349 char *argv
[8], /* Filter command-line arguments */
1350 sani_uri
[1024], /* Sanitized DEVICE_URI env var */
1351 filename
[1024], /* Job filename */
1352 command
[1024], /* Full path to filter/backend command */
1353 jobid
[255], /* Job ID string */
1354 title
[IPP_MAX_NAME
], /* Job title string */
1355 copies
[255], /* # copies string */
1356 *envp
[100], /* Environment variables */
1357 charset
[255], /* CHARSET environment variable */
1358 class_name
[255], /* CLASS environment variable */
1359 classification
[1024], /* CLASSIFICATION environment variable */
1360 content_type
[1024], /* CONTENT_TYPE environment variable */
1361 device_uri
[1024], /* DEVICE_URI environment variable */
1362 lang
[255], /* LANG environment variable */
1363 ppd
[1024], /* PPD environment variable */
1364 printer_name
[255], /* PRINTER environment variable */
1365 rip_max_cache
[255]; /* RIP_MAX_CACHE environment variable */
1366 static char *options
= NULL
; /* Full list of options */
1367 static int optlength
= 0; /* Length of option buffer */
1370 LogMessage(L_DEBUG
, "StartJob(%d, %p)", id
, printer
);
1372 for (current
= Jobs
; current
!= NULL
; current
= current
->next
)
1373 if (current
->id
== id
)
1376 if (current
== NULL
)
1379 LogMessage(L_DEBUG
, "StartJob() id = %d, file = %d/%d", id
,
1380 current
->current_file
, current
->num_files
);
1382 if (current
->num_files
== 0)
1384 LogMessage(L_ERROR
, "Job ID %d has no files! Cancelling it!", id
);
1386 cupsdAddEvent(CUPSD_EVENT_JOB_COMPLETED
, current
->printer
, current
,
1387 "Job cancelled because it has no files.");
1394 * Figure out what filters are required to convert from
1395 * the source to the destination type...
1404 * Remote jobs and raw queues go directly to the printer without
1408 LogMessage(L_DEBUG
, "StartJob: Sending job to queue tagged as raw...");
1415 * Local jobs get filtered...
1418 filters
= mimeFilter(MimeDatabase
, current
->filetypes
[current
->current_file
],
1419 printer
->filetype
, &num_filters
, MAX_FILTERS
- 1);
1421 if (num_filters
== 0)
1423 LogMessage(L_ERROR
, "Unable to convert file %d to printable format for job %d!",
1424 current
->current_file
, current
->id
);
1425 LogMessage(L_INFO
, "Hint: Do you have ESP Ghostscript installed?");
1427 if (LogLevel
< L_DEBUG
)
1428 LogMessage(L_INFO
, "Hint: Try setting the LogLevel to \"debug\".");
1430 current
->current_file
++;
1432 if (current
->current_file
== current
->num_files
)
1434 cupsdAddEvent(CUPSD_EVENT_JOB_COMPLETED
, current
->printer
, current
,
1435 "Job cancelled because it has no files that can be printed.");
1437 CancelJob(current
->id
, 0);
1444 * Remove NULL ("-") filters...
1447 for (i
= 0; i
< num_filters
;)
1448 if (strcmp(filters
[i
].filter
, "-") == 0)
1451 if (i
< num_filters
)
1452 memcpy(filters
+ i
, filters
+ i
+ 1,
1453 (num_filters
- i
) * sizeof(mime_filter_t
));
1458 if (num_filters
== 0)
1466 * Compute filter cost...
1469 for (i
= 0; i
< num_filters
; i
++)
1470 current
->cost
+= filters
[i
].cost
;
1475 * See if the filter cost is too high...
1478 if ((FilterLevel
+ current
->cost
) > FilterLimit
&& FilterLevel
> 0 &&
1482 * Don't print this job quite yet...
1485 if (filters
!= NULL
)
1488 LogMessage(L_INFO
, "Holding job %d because filter limit has been reached.",
1490 LogMessage(L_DEBUG
, "StartJob: id = %d, file = %d, "
1491 "cost = %d, level = %d, limit = %d",
1492 id
, current
->current_file
, current
->cost
, FilterLevel
,
1497 FilterLevel
+= current
->cost
;
1500 * Add decompression filters, if any...
1503 if (current
->compressions
[current
->current_file
])
1506 * Add gziptoany filter to the front of the list...
1509 mime_filter_t
*temp_filters
;
1511 if (num_filters
== 0)
1512 temp_filters
= malloc(sizeof(mime_filter_t
));
1514 temp_filters
= realloc(filters
,
1515 sizeof(mime_filter_t
) * (num_filters
+ 1));
1517 if (temp_filters
== NULL
)
1519 LogMessage(L_ERROR
, "Unable to add decompression filter - %s",
1522 if (filters
!= NULL
)
1525 current
->current_file
++;
1527 if (current
->current_file
== current
->num_files
)
1529 cupsdAddEvent(CUPSD_EVENT_JOB_COMPLETED
, current
->printer
, current
,
1530 "Job cancelled because the print file could not be decompressed.");
1532 CancelJob(current
->id
, 0);
1538 filters
= temp_filters
;
1539 memmove(filters
+ 1, filters
, num_filters
* sizeof(mime_filter_t
));
1540 *filters
= gziptoany_filter
;
1545 * Add port monitor, if any...
1548 if (printer
->port_monitor
)
1551 * Add port monitor to the end of the list...
1554 mime_filter_t
*temp_filters
;
1556 if (num_filters
== 0)
1557 temp_filters
= malloc(sizeof(mime_filter_t
));
1559 temp_filters
= realloc(filters
,
1560 sizeof(mime_filter_t
) * (num_filters
+ 1));
1562 if (temp_filters
== NULL
)
1564 LogMessage(L_ERROR
, "Unable to add port monitor - %s",
1567 if (filters
!= NULL
)
1570 current
->current_file
++;
1572 if (current
->current_file
== current
->num_files
)
1574 cupsdAddEvent(CUPSD_EVENT_JOB_COMPLETED
, current
->printer
, current
,
1575 "Job cancelled because the port monitor could not be added.");
1577 CancelJob(current
->id
, 0);
1583 filters
= temp_filters
;
1584 memset(filters
+ num_filters
, 0, sizeof(mime_filter_t
));
1585 snprintf(filters
[num_filters
].filter
, sizeof(filters
[num_filters
].filter
),
1586 "%s/monitor/%s", ServerBin
, printer
->port_monitor
);
1591 * Update the printer and job state to "processing"...
1594 current
->state
->values
[0].integer
= IPP_JOB_PROCESSING
;
1595 current
->status
= 0;
1596 current
->printer
= printer
;
1597 printer
->job
= current
;
1598 SetPrinterState(printer
, IPP_PRINTER_PROCESSING
, 0);
1600 if (current
->current_file
== 0)
1602 set_time(current
, "time-at-processing");
1603 cupsdOpenPipe(current
->back_pipes
);
1607 * Determine if we are printing a banner page or not...
1610 if (current
->job_sheets
== NULL
)
1612 LogMessage(L_DEBUG
, "No job-sheets attribute.");
1613 if ((current
->job_sheets
=
1614 ippFindAttribute(current
->attrs
, "job-sheets", IPP_TAG_ZERO
)) != NULL
)
1615 LogMessage(L_DEBUG
, "... but someone added one without setting job_sheets!");
1617 else if (current
->job_sheets
->num_values
== 1)
1618 LogMessage(L_DEBUG
, "job-sheets=%s",
1619 current
->job_sheets
->values
[0].string
.text
);
1621 LogMessage(L_DEBUG
, "job-sheets=%s,%s",
1622 current
->job_sheets
->values
[0].string
.text
,
1623 current
->job_sheets
->values
[1].string
.text
);
1625 if (printer
->type
& (CUPS_PRINTER_REMOTE
| CUPS_PRINTER_IMPLICIT
))
1627 else if (current
->job_sheets
== NULL
)
1629 else if (strcasecmp(current
->job_sheets
->values
[0].string
.text
, "none") != 0 &&
1630 current
->current_file
== 0)
1632 else if (current
->job_sheets
->num_values
> 1 &&
1633 strcasecmp(current
->job_sheets
->values
[1].string
.text
, "none") != 0 &&
1634 current
->current_file
== (current
->num_files
- 1))
1639 LogMessage(L_DEBUG
, "banner_page = %d", banner_page
);
1642 * Building the options string is harder than it needs to be, but
1643 * for the moment we need to pass strings for command-line args and
1644 * not IPP attribute pointers... :)
1646 * First allocate/reallocate the option buffer as needed...
1649 i
= ipp_length(current
->attrs
);
1656 optptr
= realloc(options
, i
);
1660 LogMessage(L_CRIT
, "StartJob: Unable to allocate %d bytes for option buffer for job %d!",
1663 if (filters
!= NULL
)
1666 FilterLevel
-= current
->cost
;
1668 cupsdAddEvent(CUPSD_EVENT_JOB_COMPLETED
, current
->printer
, current
,
1669 "Job cancelled because the server ran out of memory.");
1680 * Now loop through the attributes and convert them to the textual
1681 * representation used by the filters...
1687 snprintf(title
, sizeof(title
), "%s-%d", printer
->name
, current
->id
);
1688 strcpy(copies
, "1");
1690 for (attr
= current
->attrs
->attrs
; attr
!= NULL
; attr
= attr
->next
)
1692 if (strcmp(attr
->name
, "copies") == 0 &&
1693 attr
->value_tag
== IPP_TAG_INTEGER
)
1696 * Don't use the # copies attribute if we are printing the job sheets...
1700 sprintf(copies
, "%d", attr
->values
[0].integer
);
1702 else if (strcmp(attr
->name
, "job-name") == 0 &&
1703 (attr
->value_tag
== IPP_TAG_NAME
||
1704 attr
->value_tag
== IPP_TAG_NAMELANG
))
1705 strlcpy(title
, attr
->values
[0].string
.text
, sizeof(title
));
1706 else if (attr
->group_tag
== IPP_TAG_JOB
)
1709 * Filter out other unwanted attributes...
1712 if (attr
->value_tag
== IPP_TAG_MIMETYPE
||
1713 attr
->value_tag
== IPP_TAG_NAMELANG
||
1714 attr
->value_tag
== IPP_TAG_TEXTLANG
||
1715 attr
->value_tag
== IPP_TAG_URI
||
1716 attr
->value_tag
== IPP_TAG_URISCHEME
||
1717 attr
->value_tag
== IPP_TAG_BEGIN_COLLECTION
) /* Not yet supported */
1720 if (strncmp(attr
->name
, "time-", 5) == 0)
1723 if (strncmp(attr
->name
, "job-", 4) == 0 &&
1724 !(printer
->type
& CUPS_PRINTER_REMOTE
))
1727 if (strncmp(attr
->name
, "job-", 4) == 0 &&
1728 strcmp(attr
->name
, "job-billing") != 0 &&
1729 strcmp(attr
->name
, "job-sheets") != 0 &&
1730 strcmp(attr
->name
, "job-hold-until") != 0 &&
1731 strcmp(attr
->name
, "job-priority") != 0)
1734 if ((strcmp(attr
->name
, "page-label") == 0 ||
1735 strcmp(attr
->name
, "page-border") == 0 ||
1736 strncmp(attr
->name
, "number-up", 9) == 0 ||
1737 strcmp(attr
->name
, "page-set") == 0) &&
1742 * Otherwise add them to the list...
1745 if (optptr
> options
)
1746 strlcat(optptr
, " ", optlength
- (optptr
- options
));
1748 if (attr
->value_tag
!= IPP_TAG_BOOLEAN
)
1750 strlcat(optptr
, attr
->name
, optlength
- (optptr
- options
));
1751 strlcat(optptr
, "=", optlength
- (optptr
- options
));
1754 for (i
= 0; i
< attr
->num_values
; i
++)
1757 strlcat(optptr
, ",", optlength
- (optptr
- options
));
1759 optptr
+= strlen(optptr
);
1761 switch (attr
->value_tag
)
1763 case IPP_TAG_INTEGER
:
1765 snprintf(optptr
, optlength
- (optptr
- options
),
1766 "%d", attr
->values
[i
].integer
);
1769 case IPP_TAG_BOOLEAN
:
1770 if (!attr
->values
[i
].boolean
)
1771 strlcat(optptr
, "no", optlength
- (optptr
- options
));
1773 case IPP_TAG_NOVALUE
:
1774 strlcat(optptr
, attr
->name
,
1775 optlength
- (optptr
- options
));
1778 case IPP_TAG_RANGE
:
1779 if (attr
->values
[i
].range
.lower
== attr
->values
[i
].range
.upper
)
1780 snprintf(optptr
, optlength
- (optptr
- options
) - 1,
1781 "%d", attr
->values
[i
].range
.lower
);
1783 snprintf(optptr
, optlength
- (optptr
- options
) - 1,
1784 "%d-%d", attr
->values
[i
].range
.lower
,
1785 attr
->values
[i
].range
.upper
);
1788 case IPP_TAG_RESOLUTION
:
1789 snprintf(optptr
, optlength
- (optptr
- options
) - 1,
1790 "%dx%d%s", attr
->values
[i
].resolution
.xres
,
1791 attr
->values
[i
].resolution
.yres
,
1792 attr
->values
[i
].resolution
.units
== IPP_RES_PER_INCH
?
1796 case IPP_TAG_STRING
:
1799 case IPP_TAG_KEYWORD
:
1800 case IPP_TAG_CHARSET
:
1801 case IPP_TAG_LANGUAGE
:
1802 for (valptr
= attr
->values
[i
].string
.text
; *valptr
;)
1804 if (strchr(" \t\n\\\'\"", *valptr
))
1806 *optptr
++ = *valptr
++;
1813 break; /* anti-compiler-warning-code */
1817 optptr
+= strlen(optptr
);
1822 * Build the command-line arguments for the filters. Each filter
1823 * has 6 or 7 arguments:
1827 * argv[2] = username
1829 * argv[4] = # copies
1831 * argv[6] = filename (optional; normally stdin)
1833 * This allows legacy printer drivers that use the old System V
1834 * printing interface to be used by CUPS.
1837 sprintf(jobid
, "%d", current
->id
);
1838 snprintf(filename
, sizeof(filename
), "%s/d%05d-%03d", RequestRoot
,
1839 current
->id
, current
->current_file
+ 1);
1841 argv
[0] = printer
->name
;
1843 argv
[2] = current
->username
;
1850 LogMessage(L_DEBUG
, "StartJob: argv = \"%s\",\"%s\",\"%s\",\"%s\",\"%s\",\"%s\",\"%s\"",
1851 argv
[0], argv
[1], argv
[2], argv
[3], argv
[4], argv
[5], argv
[6]);
1854 * Create environment variable strings for the filters...
1857 attr
= ippFindAttribute(current
->attrs
, "attributes-natural-language",
1860 switch (strlen(attr
->values
[0].string
.text
))
1864 * This is an unknown or badly formatted language code; use
1865 * the POSIX locale...
1868 strcpy(lang
, "LANG=C");
1873 * Just the language code (ll)...
1876 snprintf(lang
, sizeof(lang
), "LANG=%s",
1877 attr
->values
[0].string
.text
);
1882 * Language and country code (ll-cc)...
1885 snprintf(lang
, sizeof(lang
), "LANG=%c%c_%c%c",
1886 attr
->values
[0].string
.text
[0],
1887 attr
->values
[0].string
.text
[1],
1888 toupper(attr
->values
[0].string
.text
[3] & 255),
1889 toupper(attr
->values
[0].string
.text
[4] & 255));
1893 attr
= ippFindAttribute(current
->attrs
, "document-format",
1896 (optptr
= strstr(attr
->values
[0].string
.text
, "charset=")) != NULL
)
1897 snprintf(charset
, sizeof(charset
), "CHARSET=%s", optptr
+ 8);
1900 attr
= ippFindAttribute(current
->attrs
, "attributes-charset",
1902 snprintf(charset
, sizeof(charset
), "CHARSET=%s",
1903 attr
->values
[0].string
.text
);
1906 snprintf(content_type
, sizeof(content_type
), "CONTENT_TYPE=%s/%s",
1907 current
->filetypes
[current
->current_file
]->super
,
1908 current
->filetypes
[current
->current_file
]->type
);
1909 snprintf(device_uri
, sizeof(device_uri
), "DEVICE_URI=%s", printer
->device_uri
);
1910 cupsdSanitizeURI(printer
->device_uri
, sani_uri
, sizeof(sani_uri
));
1911 snprintf(ppd
, sizeof(ppd
), "PPD=%s/ppd/%s.ppd", ServerRoot
, printer
->name
);
1912 snprintf(printer_name
, sizeof(printer_name
), "PRINTER=%s", printer
->name
);
1913 snprintf(rip_max_cache
, sizeof(rip_max_cache
), "RIP_MAX_CACHE=%s", RIPCache
);
1915 envc
= cupsdLoadEnv(envp
, (int)(sizeof(envp
) / sizeof(envp
[0])));
1917 envp
[envc
++] = charset
;
1918 envp
[envc
++] = lang
;
1919 envp
[envc
++] = ppd
;
1920 envp
[envc
++] = rip_max_cache
;
1921 envp
[envc
++] = content_type
;
1922 envp
[envc
++] = device_uri
;
1923 envp
[envc
++] = printer_name
;
1925 if (Classification
&& !banner_page
)
1927 if ((attr
= ippFindAttribute(current
->attrs
, "job-sheets",
1928 IPP_TAG_NAME
)) == NULL
)
1929 snprintf(classification
, sizeof(classification
), "CLASSIFICATION=%s",
1931 else if (attr
->num_values
> 1 &&
1932 strcmp(attr
->values
[1].string
.text
, "none") != 0)
1933 snprintf(classification
, sizeof(classification
), "CLASSIFICATION=%s",
1934 attr
->values
[1].string
.text
);
1936 snprintf(classification
, sizeof(classification
), "CLASSIFICATION=%s",
1937 attr
->values
[0].string
.text
);
1939 envp
[envc
++] = classification
;
1942 if (current
->dtype
& (CUPS_PRINTER_CLASS
| CUPS_PRINTER_IMPLICIT
))
1944 snprintf(class_name
, sizeof(class_name
), "CLASS=%s", current
->dest
);
1945 envp
[envc
++] = class_name
;
1950 for (i
= 0; i
< envc
; i
++)
1951 if (strncmp(envp
[i
], "DEVICE_URI=", 11))
1952 LogMessage(L_DEBUG
, "StartJob: envp[%d]=\"%s\"", i
, envp
[i
]);
1954 LogMessage(L_DEBUG
, "StartJob: envp[%d]=\"DEVICE_URI=%s\"", i
, sani_uri
);
1956 current
->current_file
++;
1959 * Now create processes for all of the filters...
1962 if (cupsdOpenPipe(statusfds
))
1964 LogMessage(L_ERROR
, "Unable to create job status pipes - %s.",
1966 snprintf(printer
->state_message
, sizeof(printer
->state_message
),
1967 "Unable to create status pipes - %s.", strerror(errno
));
1969 AddPrinterHistory(printer
);
1971 if (filters
!= NULL
)
1974 cupsdAddEvent(CUPSD_EVENT_JOB_COMPLETED
, current
->printer
, current
,
1975 "Job cancelled because the server could not create the job status pipes.");
1977 CancelJob(current
->id
, 0);
1981 LogMessage(L_DEBUG
, "StartJob: statusfds = [ %d %d ]",
1982 statusfds
[0], statusfds
[1]);
1985 fcntl(statusfds
[0], F_SETFD
, FD_CLOEXEC
);
1986 fcntl(statusfds
[1], F_SETFD
, FD_CLOEXEC
);
1987 #endif /* FD_CLOEXEC */
1989 current
->status_buffer
= cupsdStatBufNew(statusfds
[0], "[Job %d]",
1991 current
->status
= 0;
1992 memset(current
->filters
, 0, sizeof(current
->filters
));
1994 filterfds
[1][0] = open("/dev/null", O_RDONLY
);
1995 filterfds
[1][1] = -1;
1997 if (filterfds
[1][0] < 0)
1999 LogMessage(L_ERROR
, "Unable to open \"/dev/null\" - %s.", strerror(errno
));
2000 snprintf(printer
->state_message
, sizeof(printer
->state_message
),
2001 "Unable to open \"/dev/null\" - %s.", strerror(errno
));
2003 AddPrinterHistory(printer
);
2005 if (filters
!= NULL
)
2008 cupsdClosePipe(statusfds
);
2009 CancelJob(current
->id
, 0);
2013 fcntl(filterfds
[1][0], F_SETFD
, fcntl(filterfds
[1][0], F_GETFD
) | FD_CLOEXEC
);
2015 LogMessage(L_DEBUG
, "StartJob: filterfds[%d] = [ %d %d ]", 1, filterfds
[1][0],
2018 for (i
= 0, slot
= 0; i
< num_filters
; i
++)
2020 if (filters
[i
].filter
[0] != '/')
2021 snprintf(command
, sizeof(command
), "%s/filter/%s", ServerBin
,
2024 strlcpy(command
, filters
[i
].filter
, sizeof(command
));
2028 * Setting CFProcessPath lets OS X's Core Foundation code find
2029 * the bundle that may be associated with a filter or backend.
2032 snprintf(processPath
, sizeof(processPath
), "CFProcessPath=%s", command
);
2033 LogMessage(L_DEBUG
, "StartJob: %s\n", processPath
);
2034 #endif /* __APPLE__ */
2036 if (i
< (num_filters
- 1))
2038 if (cupsdOpenPipe(filterfds
[slot
]))
2040 LogMessage(L_ERROR
, "Unable to create job filter pipes - %s.",
2042 snprintf(printer
->state_message
, sizeof(printer
->state_message
),
2043 "Unable to create filter pipes - %s.", strerror(errno
));
2044 AddPrinterHistory(printer
);
2046 if (filters
!= NULL
)
2049 cupsdClosePipe(statusfds
);
2050 cupsdClosePipe(filterfds
[!slot
]);
2052 cupsdAddEvent(CUPSD_EVENT_JOB_COMPLETED
, current
->printer
, current
,
2053 "Job cancelled because the server could not create the filter pipes.");
2055 CancelJob(current
->id
, 0);
2061 if (current
->current_file
== 1)
2063 if (strncmp(printer
->device_uri
, "file:", 5) != 0)
2065 if (cupsdOpenPipe(current
->print_pipes
))
2067 LogMessage(L_ERROR
, "Unable to create job backend pipes - %s.",
2069 snprintf(printer
->state_message
, sizeof(printer
->state_message
),
2070 "Unable to create backend pipes - %s.", strerror(errno
));
2071 AddPrinterHistory(printer
);
2073 if (filters
!= NULL
)
2076 cupsdClosePipe(statusfds
);
2077 cupsdClosePipe(filterfds
[!slot
]);
2079 cupsdAddEvent(CUPSD_EVENT_JOB_COMPLETED
, current
->printer
, current
,
2080 "Job cancelled because the server could not create the backend pipes.");
2082 CancelJob(current
->id
, 0);
2088 current
->print_pipes
[0] = -1;
2089 if (!strncmp(printer
->device_uri
, "file:/dev/", 10) &&
2090 strcmp(printer
->device_uri
, "file:/dev/null"))
2091 current
->print_pipes
[1] = open(printer
->device_uri
+ 5,
2093 else if (!strncmp(printer
->device_uri
, "file:///dev/", 12) &&
2094 strcmp(printer
->device_uri
, "file:///dev/null"))
2095 current
->print_pipes
[1] = open(printer
->device_uri
+ 7,
2098 current
->print_pipes
[1] = open(printer
->device_uri
+ 5,
2099 O_WRONLY
| O_CREAT
| O_TRUNC
, 0600);
2101 if (current
->print_pipes
[1] < 0)
2103 LogMessage(L_ERROR
, "Unable to open output file \"%s\" - %s.",
2104 printer
->device_uri
, strerror(errno
));
2105 snprintf(printer
->state_message
, sizeof(printer
->state_message
),
2106 "Unable to open output file \"%s\" - %s.",
2107 printer
->device_uri
, strerror(errno
));
2109 AddPrinterHistory(printer
);
2111 if (filters
!= NULL
)
2114 cupsdClosePipe(statusfds
);
2115 cupsdClosePipe(filterfds
[!slot
]);
2117 cupsdAddEvent(CUPSD_EVENT_JOB_COMPLETED
, current
->printer
, current
,
2118 "Job cancelled because the server could not open the output file.");
2120 CancelJob(current
->id
, 0);
2124 fcntl(current
->print_pipes
[1], F_SETFD
,
2125 fcntl(current
->print_pipes
[1], F_GETFD
) | FD_CLOEXEC
);
2128 LogMessage(L_DEBUG2
, "StartJob: print_pipes = [ %d %d ]",
2129 current
->print_pipes
[0], current
->print_pipes
[1]);
2132 filterfds
[slot
][0] = current
->print_pipes
[0];
2133 filterfds
[slot
][1] = current
->print_pipes
[1];
2136 LogMessage(L_DEBUG
, "StartJob: filter = \"%s\"", command
);
2137 LogMessage(L_DEBUG
, "StartJob: filterfds[%d] = [ %d %d ]",
2138 slot
, filterfds
[slot
][0], filterfds
[slot
][1]);
2140 pid
= cupsdStartProcess(command
, argv
, envp
, filterfds
[!slot
][0],
2141 filterfds
[slot
][1], statusfds
[1],
2142 current
->back_pipes
[0], 0, current
->filters
+ i
);
2144 LogMessage(L_DEBUG2
, "StartJob: Closing filter pipes for slot %d [ %d %d ]...",
2145 !slot
, filterfds
[!slot
][0], filterfds
[!slot
][1]);
2147 cupsdClosePipe(filterfds
[!slot
]);
2151 LogMessage(L_ERROR
, "Unable to start filter \"%s\" - %s.",
2152 filters
[i
].filter
, strerror(errno
));
2153 snprintf(printer
->state_message
, sizeof(printer
->state_message
),
2154 "Unable to start filter \"%s\" - %s.",
2155 filters
[i
].filter
, strerror(errno
));
2157 AddPrinterHistory(printer
);
2159 if (filters
!= NULL
)
2162 AddPrinterHistory(printer
);
2164 cupsdAddEvent(CUPSD_EVENT_JOB_COMPLETED
, current
->printer
, current
,
2165 "Job cancelled because the server could not execute a filter.");
2167 CancelJob(current
->id
, 0);
2171 LogMessage(L_INFO
, "Started filter %s (PID %d) for job %d.",
2172 command
, pid
, current
->id
);
2178 if (filters
!= NULL
)
2182 * Finally, pipe the final output into a backend process if needed...
2185 if (strncmp(printer
->device_uri
, "file:", 5) != 0)
2187 if (current
->current_file
== 1)
2189 sscanf(printer
->device_uri
, "%254[^:]", method
);
2190 snprintf(command
, sizeof(command
), "%s/backend/%s", ServerBin
, method
);
2194 * Setting CFProcessPath lets OS X's Core Foundation code find
2195 * the bundle that may be associated with a filter or backend.
2198 snprintf(processPath
, sizeof(processPath
), "CFProcessPath=%s", command
);
2199 LogMessage(L_DEBUG
, "StartJob: %s\n", processPath
);
2200 #endif /* __APPLE__ */
2204 filterfds
[slot
][0] = -1;
2205 filterfds
[slot
][1] = open("/dev/null", O_WRONLY
);
2207 if (filterfds
[slot
][1] < 0)
2209 LogMessage(L_ERROR
, "Unable to open \"/dev/null\" - %s.", strerror(errno
));
2210 snprintf(printer
->state_message
, sizeof(printer
->state_message
),
2211 "Unable to open \"/dev/null\" - %s.", strerror(errno
));
2213 AddPrinterHistory(printer
);
2215 if (filters
!= NULL
)
2218 cupsdClosePipe(statusfds
);
2220 cupsdAddEvent(CUPSD_EVENT_JOB_COMPLETED
, current
->printer
, current
,
2221 "Job cancelled because the server could not open a file.");
2223 CancelJob(current
->id
, 0);
2227 fcntl(filterfds
[slot
][1], F_SETFD
,
2228 fcntl(filterfds
[slot
][1], F_GETFD
) | FD_CLOEXEC
);
2230 LogMessage(L_DEBUG
, "StartJob: backend = \"%s\"", command
);
2231 LogMessage(L_DEBUG
, "StartJob: filterfds[%d] = [ %d %d ]",
2232 slot
, filterfds
[slot
][0], filterfds
[slot
][1]);
2234 pid
= cupsdStartProcess(command
, argv
, envp
, filterfds
[!slot
][0],
2235 filterfds
[slot
][1], statusfds
[1],
2236 current
->back_pipes
[1], 1,
2237 &(current
->backend
));
2241 LogMessage(L_ERROR
, "Unable to start backend \"%s\" - %s.",
2242 method
, strerror(errno
));
2243 snprintf(printer
->state_message
, sizeof(printer
->state_message
),
2244 "Unable to start backend \"%s\" - %s.", method
, strerror(errno
));
2246 LogMessage(L_DEBUG2
, "StartJob: Closing print pipes [ %d %d ]...",
2247 current
->print_pipes
[0], current
->print_pipes
[1]);
2249 cupsdClosePipe(current
->print_pipes
);
2251 LogMessage(L_DEBUG2
, "StartJob: Closing back pipes [ %d %d ]...",
2252 current
->back_pipes
[0], current
->back_pipes
[1]);
2254 cupsdClosePipe(current
->back_pipes
);
2256 cupsdAddEvent(CUPSD_EVENT_JOB_COMPLETED
, current
->printer
, current
,
2257 "Job cancelled because the server could not execute the backend.");
2259 CancelJob(current
->id
, 0);
2264 LogMessage(L_INFO
, "Started backend %s (PID %d) for job %d.",
2265 command
, pid
, current
->id
);
2269 if (current
->current_file
== current
->num_files
)
2271 LogMessage(L_DEBUG2
, "StartJob: Closing print pipes [ %d %d ]...",
2272 current
->print_pipes
[0], current
->print_pipes
[1]);
2274 cupsdClosePipe(current
->print_pipes
);
2276 LogMessage(L_DEBUG2
, "StartJob: Closing back pipes [ %d %d ]...",
2277 current
->back_pipes
[0], current
->back_pipes
[1]);
2279 cupsdClosePipe(current
->back_pipes
);
2284 filterfds
[slot
][0] = -1;
2285 filterfds
[slot
][1] = -1;
2287 if (current
->current_file
== current
->num_files
)
2289 LogMessage(L_DEBUG2
, "StartJob: Closing print pipes [ %d %d ]...",
2290 current
->print_pipes
[0], current
->print_pipes
[1]);
2292 cupsdClosePipe(current
->print_pipes
);
2296 LogMessage(L_DEBUG2
, "StartJob: Closing filter pipes for slot %d [ %d %d ]...",
2297 slot
, filterfds
[slot
][0], filterfds
[slot
][1]);
2299 cupsdClosePipe(filterfds
[slot
]);
2301 LogMessage(L_DEBUG2
, "StartJob: Closing status output pipe %d...",
2304 close(statusfds
[1]);
2306 LogMessage(L_DEBUG2
, "StartJob: Adding fd %d to InputSet...",
2307 current
->status_buffer
->fd
);
2309 FD_SET(current
->status_buffer
->fd
, InputSet
);
2314 * 'StopAllJobs()' - Stop all print jobs.
2320 job_t
*current
; /* Current job */
2323 DEBUG_puts("StopAllJobs()");
2325 for (current
= Jobs
; current
!= NULL
; current
= current
->next
)
2326 if (current
->state
->values
[0].integer
== IPP_JOB_PROCESSING
)
2328 StopJob(current
->id
, 1);
2329 current
->state
->values
[0].integer
= IPP_JOB_PENDING
;
2335 * 'StopJob()' - Stop a print job.
2339 StopJob(int id
, /* I - Job ID */
2340 int force
) /* I - 1 = Force all filters to stop */
2342 int i
; /* Looping var */
2343 job_t
*current
; /* Current job */
2346 LogMessage(L_DEBUG
, "StopJob: id = %d, force = %d", id
, force
);
2348 for (current
= Jobs
; current
!= NULL
; current
= current
->next
)
2349 if (current
->id
== id
)
2351 DEBUG_puts("StopJob: found job in list.");
2353 if (current
->state
->values
[0].integer
== IPP_JOB_PROCESSING
)
2355 DEBUG_puts("StopJob: job state is \'processing\'.");
2357 FilterLevel
-= current
->cost
;
2359 if (current
->status
< 0 &&
2360 !(current
->dtype
& (CUPS_PRINTER_CLASS
| CUPS_PRINTER_IMPLICIT
)) &&
2361 !(current
->printer
->type
& CUPS_PRINTER_FAX
) &&
2362 !strcmp(current
->printer
->error_policy
, "stop-printer"))
2363 SetPrinterState(current
->printer
, IPP_PRINTER_STOPPED
, 1);
2364 else if (current
->printer
->state
!= IPP_PRINTER_STOPPED
)
2365 SetPrinterState(current
->printer
, IPP_PRINTER_IDLE
, 0);
2367 LogMessage(L_DEBUG
, "StopJob: printer state is %d", current
->printer
->state
);
2369 current
->state
->values
[0].integer
= IPP_JOB_STOPPED
;
2370 current
->printer
->job
= NULL
;
2371 current
->printer
= NULL
;
2373 current
->current_file
--;
2375 for (i
= 0; current
->filters
[i
]; i
++)
2376 if (current
->filters
[i
] > 0)
2378 cupsdEndProcess(current
->filters
[i
], force
);
2379 current
->filters
[i
] = 0;
2382 if (current
->backend
> 0)
2384 cupsdEndProcess(current
->backend
, force
);
2385 current
->backend
= 0;
2388 LogMessage(L_DEBUG2
, "StopJob: Closing print pipes [ %d %d ]...",
2389 current
->print_pipes
[0], current
->print_pipes
[1]);
2391 cupsdClosePipe(current
->print_pipes
);
2393 LogMessage(L_DEBUG2
, "StopJob: Closing back pipes [ %d %d ]...",
2394 current
->back_pipes
[0], current
->back_pipes
[1]);
2396 cupsdClosePipe(current
->back_pipes
);
2398 if (current
->status_buffer
)
2401 * Close the pipe and clear the input bit.
2404 LogMessage(L_DEBUG2
, "StopJob: Removing fd %d from InputSet...",
2405 current
->status_buffer
->fd
);
2407 FD_CLR(current
->status_buffer
->fd
, InputSet
);
2409 LogMessage(L_DEBUG2
, "StopJob: Closing status input pipe %d...",
2410 current
->status_buffer
->fd
);
2412 cupsdStatBufDelete(current
->status_buffer
);
2414 current
->status_buffer
= NULL
;
2423 * 'UpdateJob()' - Read a status update from a job's filters.
2427 UpdateJob(job_t
*job
) /* I - Job to check */
2429 int i
; /* Looping var */
2430 int copies
; /* Number of copies printed */
2431 char message
[1024], /* Message text */
2432 *ptr
; /* Pointer update... */
2433 int loglevel
; /* Log level for message */
2436 while ((ptr
= cupsdStatBufUpdate(job
->status_buffer
, &loglevel
,
2437 message
, sizeof(message
))) != NULL
)
2440 * Process page and printer state messages as needed...
2443 if (loglevel
== L_PAGE
)
2446 * Page message; send the message to the page_log file and update the
2447 * job sheet count...
2450 if (job
->sheets
!= NULL
)
2452 if (!strncasecmp(message
, "total ", 6))
2455 * Got a total count of pages from a backend or filter...
2458 copies
= atoi(message
+ 6);
2459 copies
-= job
->sheets
->values
[0].integer
; /* Just track the delta */
2461 else if (!sscanf(message
, "%*d%d", &copies
))
2464 job
->sheets
->values
[0].integer
+= copies
;
2466 if (job
->printer
->page_limit
)
2467 UpdateQuota(job
->printer
, job
->username
, copies
, 0);
2470 LogPage(job
, message
);
2472 cupsdAddEvent(CUPSD_EVENT_JOB_PROGRESS
, job
->printer
, job
,
2473 "Printed %d page(s).", job
->sheets
->values
[0].integer
);
2475 else if (loglevel
== L_STATE
)
2476 SetPrinterReasons(job
->printer
, message
);
2478 if (!strchr(job
->status_buffer
->buffer
, '\n'))
2485 * See if all of the filters and the backend have returned their
2489 for (i
= 0; job
->filters
[i
] < 0; i
++);
2491 if (job
->filters
[i
])
2494 if (job
->current_file
>= job
->num_files
&& job
->backend
> 0)
2498 * Handle the end of job stuff...
2507 * 'ipp_length()' - Compute the size of the buffer needed to hold
2508 * the textual IPP attributes.
2511 int /* O - Size of buffer to hold IPP attributes */
2512 ipp_length(ipp_t
*ipp
) /* I - IPP request */
2514 int bytes
; /* Number of bytes */
2515 int i
; /* Looping var */
2516 ipp_attribute_t
*attr
; /* Current attribute */
2520 * Loop through all attributes...
2525 for (attr
= ipp
->attrs
; attr
!= NULL
; attr
= attr
->next
)
2528 * Skip attributes that won't be sent to filters...
2531 if (attr
->value_tag
== IPP_TAG_MIMETYPE
||
2532 attr
->value_tag
== IPP_TAG_NAMELANG
||
2533 attr
->value_tag
== IPP_TAG_TEXTLANG
||
2534 attr
->value_tag
== IPP_TAG_URI
||
2535 attr
->value_tag
== IPP_TAG_URISCHEME
)
2538 if (strncmp(attr
->name
, "time-", 5) == 0)
2542 * Add space for a leading space and commas between each value.
2543 * For the first attribute, the leading space isn't used, so the
2544 * extra byte can be used as the nul terminator...
2547 bytes
++; /* " " separator */
2548 bytes
+= attr
->num_values
; /* "," separators */
2551 * Boolean attributes appear as "foo,nofoo,foo,nofoo", while
2552 * other attributes appear as "foo=value1,value2,...,valueN".
2555 if (attr
->value_tag
!= IPP_TAG_BOOLEAN
)
2556 bytes
+= strlen(attr
->name
);
2558 bytes
+= attr
->num_values
* strlen(attr
->name
);
2561 * Now add the size required for each value in the attribute...
2564 switch (attr
->value_tag
)
2566 case IPP_TAG_INTEGER
:
2569 * Minimum value of a signed integer is -2147483647, or 11 digits.
2572 bytes
+= attr
->num_values
* 11;
2575 case IPP_TAG_BOOLEAN
:
2577 * Add two bytes for each false ("no") value...
2580 for (i
= 0; i
< attr
->num_values
; i
++)
2581 if (!attr
->values
[i
].boolean
)
2585 case IPP_TAG_RANGE
:
2587 * A range is two signed integers separated by a hyphen, or
2588 * 23 characters max.
2591 bytes
+= attr
->num_values
* 23;
2594 case IPP_TAG_RESOLUTION
:
2596 * A resolution is two signed integers separated by an "x" and
2597 * suffixed by the units, or 26 characters max.
2600 bytes
+= attr
->num_values
* 26;
2603 case IPP_TAG_STRING
:
2606 case IPP_TAG_KEYWORD
:
2607 case IPP_TAG_CHARSET
:
2608 case IPP_TAG_LANGUAGE
:
2610 * Strings can contain characters that need quoting. We need
2611 * at least 2 * len + 2 characters to cover the quotes and
2612 * any backslashes in the string.
2615 for (i
= 0; i
< attr
->num_values
; i
++)
2616 bytes
+= 2 * strlen(attr
->values
[i
].string
.text
) + 2;
2620 break; /* anti-compiler-warning-code */
2629 * 'set_time()' - Set one of the "time-at-xyz" attributes...
2633 set_time(job_t
*job
, /* I - Job to update */
2634 const char *name
) /* I - Name of attribute */
2636 ipp_attribute_t
*attr
; /* Time attribute */
2639 if ((attr
= ippFindAttribute(job
->attrs
, name
, IPP_TAG_ZERO
)) != NULL
)
2641 attr
->value_tag
= IPP_TAG_INTEGER
;
2642 attr
->values
[0].integer
= time(NULL
);
2648 * 'set_hold_until()' - Set the hold time and update job-hold-until attribute...
2652 set_hold_until(job_t
*job
, /* I - Job to update */
2653 time_t holdtime
) /* I - Hold until time */
2655 ipp_attribute_t
*attr
; /* job-hold-until attribute */
2656 struct tm
*holddate
; /* Hold date */
2657 char holdstr
[64]; /* Hold time */
2661 * Set the hold_until value and hold the job...
2664 LogMessage(L_DEBUG
, "set_hold_until: hold_until = %d", (int)holdtime
);
2666 job
->state
->values
[0].integer
= IPP_JOB_HELD
;
2667 job
->hold_until
= holdtime
;
2670 * Update the job-hold-until attribute with a string representing GMT
2671 * time (HH:MM:SS)...
2674 holddate
= gmtime(&holdtime
);
2675 snprintf(holdstr
, sizeof(holdstr
), "%d:%d:%d", holddate
->tm_hour
,
2676 holddate
->tm_min
, holddate
->tm_sec
);
2678 if ((attr
= ippFindAttribute(job
->attrs
, "job-hold-until", IPP_TAG_KEYWORD
)) == NULL
)
2679 attr
= ippFindAttribute(job
->attrs
, "job-hold-until", IPP_TAG_NAME
);
2682 * Either add the attribute or update the value of the existing one
2686 attr
= ippAddString(job
->attrs
, IPP_TAG_JOB
, IPP_TAG_KEYWORD
,
2687 "job-hold-until", NULL
, holdstr
);
2689 SetString(&attr
->values
[0].string
.text
, holdstr
);