1/* 2 * "$Id: job.c 11433 2013-11-20 18:57:44Z msweet $" 3 * 4 * Job management routines for the CUPS scheduler. 5 * 6 * Copyright 2007-2013 by Apple Inc. 7 * Copyright 1997-2007 by Easy Software Products, all rights reserved. 8 * 9 * These coded instructions, statements, and computer programs are the 10 * property of Apple Inc. and are protected by Federal copyright 11 * law. Distribution and use rights are outlined in the file "LICENSE.txt" 12 * which should have been included with this file. If this file is 13 * file is missing or damaged, see the license at "http://www.cups.org/". 14 */ 15 16/* 17 * Include necessary headers... 18 */ 19 20#include "cupsd.h" 21#include <grp.h> 22#include <cups/backend.h> 23#include <cups/dir.h> 24#ifdef __APPLE__ 25# include <IOKit/pwr_mgt/IOPMLib.h> 26# ifdef HAVE_IOKIT_PWR_MGT_IOPMLIBPRIVATE_H 27# include <IOKit/pwr_mgt/IOPMLibPrivate.h> 28# endif /* HAVE_IOKIT_PWR_MGT_IOPMLIBPRIVATE_H */ 29#endif /* __APPLE__ */ 30 31 32/* 33 * Design Notes for Job Management 34 * ------------------------------- 35 * 36 * STATE CHANGES 37 * 38 * pending Do nothing/check jobs 39 * pending-held Send SIGTERM to filters and backend 40 * processing Do nothing/start job 41 * stopped Send SIGKILL to filters and backend 42 * canceled Send SIGTERM to filters and backend 43 * aborted Finalize 44 * completed Finalize 45 * 46 * Finalize clears the printer <-> job association, deletes the status 47 * buffer, closes all of the pipes, etc. and doesn't get run until all of 48 * the print processes are finished. 49 * 50 * UNLOADING OF JOBS (cupsdUnloadCompletedJobs) 51 * 52 * We unload the job attributes when they are not needed to reduce overall 53 * memory consumption. We don't unload jobs where job->state_value < 54 * IPP_JOB_STOPPED, job->printer != NULL, or job->access_time is recent. 55 * 56 * STARTING OF JOBS (start_job) 57 * 58 * When a job is started, a status buffer, several pipes, a security 59 * profile, and a backend process are created for the life of that job. 60 * These are shared for every file in a job. For remote print jobs, the 61 * IPP backend is provided with every file in the job and no filters are 62 * run. 63 * 64 * The job->printer member tracks which printer is printing a job, which 65 * can be different than the destination in job->dest for classes. The 66 * printer object also has a job pointer to track which job is being 67 * printed. 68 * 69 * PRINTING OF JOB FILES (cupsdContinueJob) 70 * 71 * Each file in a job is filtered by 0 or more programs. After getting the 72 * list of filters needed and the total cost, the job is either passed or 73 * put back to the processing state until the current FilterLevel comes down 74 * enough to allow printing. 75 * 76 * If we can print, we build a string for the print options and run each of 77 * the filters, piping the output from one into the next. 78 * 79 * JOB STATUS UPDATES (update_job) 80 * 81 * The update_job function gets called whenever there are pending messages 82 * on the status pipe. These generally are updates to the marker-*, 83 * printer-state-message, or printer-state-reasons attributes. On EOF, 84 * finalize_job is called to clean up. 85 * 86 * FINALIZING JOBS (finalize_job) 87 * 88 * When all filters and the backend are done, we set the job state to 89 * completed (no errors), aborted (filter errors or abort-job policy), 90 * pending-held (auth required or retry-job policy), or pending 91 * (retry-current-job or stop-printer policies) as appropriate. 92 * 93 * Then we close the pipes and free the status buffers and profiles. 94 * 95 * JOB FILE COMPLETION (process_children in main.c) 96 * 97 * For multiple-file jobs, process_children (in main.c) sees that all 98 * filters have exited and calls in to print the next file if there are 99 * more files in the job, otherwise it waits for the backend to exit and 100 * update_job to do the cleanup. 101 */ 102 103 104/* 105 * Local globals... 106 */ 107 108static mime_filter_t gziptoany_filter = 109 { 110 NULL, /* Source type */ 111 NULL, /* Destination type */ 112 0, /* Cost */ 113 "gziptoany" /* Filter program to run */ 114 }; 115 116 117/* 118 * Local functions... 119 */ 120 121static int compare_active_jobs(void *first, void *second, void *data); 122static int compare_jobs(void *first, void *second, void *data); 123static void dump_job_history(cupsd_job_t *job); 124static void finalize_job(cupsd_job_t *job, int set_job_state); 125static void free_job_history(cupsd_job_t *job); 126static char *get_options(cupsd_job_t *job, int banner_page, char *copies, 127 size_t copies_size, char *title, 128 size_t title_size); 129static size_t ipp_length(ipp_t *ipp); 130static void load_job_cache(const char *filename); 131static void load_next_job_id(const char *filename); 132static void load_request_root(void); 133static void remove_job_files(cupsd_job_t *job); 134static void remove_job_history(cupsd_job_t *job); 135static void set_time(cupsd_job_t *job, const char *name); 136static void start_job(cupsd_job_t *job, cupsd_printer_t *printer); 137static void stop_job(cupsd_job_t *job, cupsd_jobaction_t action); 138static void unload_job(cupsd_job_t *job); 139static void update_job(cupsd_job_t *job); 140static void update_job_attrs(cupsd_job_t *job, int do_message); 141 142 143/* 144 * 'cupsdAddJob()' - Add a new job to the job queue. 145 */ 146 147cupsd_job_t * /* O - New job record */ 148cupsdAddJob(int priority, /* I - Job priority */ 149 const char *dest) /* I - Job destination */ 150{ 151 cupsd_job_t *job; /* New job record */ 152 153 154 if ((job = calloc(sizeof(cupsd_job_t), 1)) == NULL) 155 return (NULL); 156 157 job->id = NextJobId ++; 158 job->priority = priority; 159 job->back_pipes[0] = -1; 160 job->back_pipes[1] = -1; 161 job->print_pipes[0] = -1; 162 job->print_pipes[1] = -1; 163 job->side_pipes[0] = -1; 164 job->side_pipes[1] = -1; 165 job->status_pipes[0] = -1; 166 job->status_pipes[1] = -1; 167 168 cupsdSetString(&job->dest, dest); 169 170 /* 171 * Add the new job to the "all jobs" and "active jobs" lists... 172 */ 173 174 cupsArrayAdd(Jobs, job); 175 cupsArrayAdd(ActiveJobs, job); 176 177 return (job); 178} 179 180 181/* 182 * 'cupsdCancelJobs()' - Cancel all jobs for the given destination/user. 183 */ 184 185void 186cupsdCancelJobs(const char *dest, /* I - Destination to cancel */ 187 const char *username, /* I - Username or NULL */ 188 int purge) /* I - Purge jobs? */ 189{ 190 cupsd_job_t *job; /* Current job */ 191 192 193 for (job = (cupsd_job_t *)cupsArrayFirst(Jobs); 194 job; 195 job = (cupsd_job_t *)cupsArrayNext(Jobs)) 196 { 197 if ((!job->dest || !job->username) && !cupsdLoadJob(job)) 198 continue; 199 200 if ((!dest || !strcmp(job->dest, dest)) && 201 (!username || !strcmp(job->username, username))) 202 { 203 /* 204 * Cancel all jobs matching this destination/user... 205 */ 206 207 if (purge) 208 cupsdSetJobState(job, IPP_JOB_CANCELED, CUPSD_JOB_PURGE, 209 "Job purged by user."); 210 else if (job->state_value < IPP_JOB_CANCELED) 211 cupsdSetJobState(job, IPP_JOB_CANCELED, CUPSD_JOB_DEFAULT, 212 "Job canceled by user."); 213 } 214 } 215 216 cupsdCheckJobs(); 217} 218 219 220/* 221 * 'cupsdCheckJobs()' - Check the pending jobs and start any if the destination 222 * is available. 223 */ 224 225void 226cupsdCheckJobs(void) 227{ 228 cupsd_job_t *job; /* Current job in queue */ 229 cupsd_printer_t *printer, /* Printer destination */ 230 *pclass; /* Printer class destination */ 231 ipp_attribute_t *attr; /* Job attribute */ 232 time_t curtime; /* Current time */ 233 234 235 curtime = time(NULL); 236 237 cupsdLogMessage(CUPSD_LOG_DEBUG2, 238 "cupsdCheckJobs: %d active jobs, sleeping=%d, reload=%d, " 239 "curtime=%ld", cupsArrayCount(ActiveJobs), Sleeping, 240 NeedReload, (long)curtime); 241 242 for (job = (cupsd_job_t *)cupsArrayFirst(ActiveJobs); 243 job; 244 job = (cupsd_job_t *)cupsArrayNext(ActiveJobs)) 245 { 246 cupsdLogMessage(CUPSD_LOG_DEBUG2, 247 "cupsdCheckJobs: Job %d - dest=\"%s\", printer=%p, " 248 "state=%d, cancel_time=%ld, hold_until=%ld, kill_time=%ld, " 249 "pending_cost=%d, pending_timeout=%ld", job->id, job->dest, 250 job->printer, job->state_value, (long)job->cancel_time, 251 (long)job->hold_until, (long)job->kill_time, 252 job->pending_cost, (long)job->pending_timeout); 253 254 /* 255 * Kill jobs if they are unresponsive... 256 */ 257 258 if (job->kill_time && job->kill_time <= curtime) 259 { 260 cupsdLogJob(job, CUPSD_LOG_ERROR, "Stopping unresponsive job."); 261 262 stop_job(job, CUPSD_JOB_FORCE); 263 continue; 264 } 265 266 /* 267 * Cancel stuck jobs... 268 */ 269 270 if (job->cancel_time && job->cancel_time <= curtime) 271 { 272 cupsdSetJobState(job, IPP_JOB_CANCELED, CUPSD_JOB_DEFAULT, 273 "Canceling stuck job after %d seconds.", MaxJobTime); 274 continue; 275 } 276 277 /* 278 * Start held jobs if they are ready... 279 */ 280 281 if (job->state_value == IPP_JOB_HELD && 282 job->hold_until && 283 job->hold_until < curtime) 284 { 285 if (job->pending_timeout) 286 { 287 /* 288 * This job is pending; check that we don't have an active Send-Document 289 * operation in progress on any of the client connections, then timeout 290 * the job so we can start printing... 291 */ 292 293 cupsd_client_t *con; /* Current client connection */ 294 295 for (con = (cupsd_client_t *)cupsArrayFirst(Clients); 296 con; 297 con = (cupsd_client_t *)cupsArrayNext(Clients)) 298 if (con->request && 299 con->request->request.op.operation_id == IPP_SEND_DOCUMENT) 300 break; 301 302 if (con) 303 continue; 304 305 if (cupsdTimeoutJob(job)) 306 continue; 307 } 308 309 cupsdSetJobState(job, IPP_JOB_PENDING, CUPSD_JOB_DEFAULT, 310 "Job submission timed out."); 311 } 312 313 /* 314 * Continue jobs that are waiting on the FilterLimit... 315 */ 316 317 if (job->pending_cost > 0 && 318 ((FilterLevel + job->pending_cost) < FilterLimit || FilterLevel == 0)) 319 cupsdContinueJob(job); 320 321 /* 322 * Start pending jobs if the destination is available... 323 */ 324 325 if (job->state_value == IPP_JOB_PENDING && !NeedReload && 326#ifndef kIOPMAssertionTypeDenySystemSleep 327 !Sleeping && 328#endif /* !kIOPMAssertionTypeDenySystemSleep */ 329 !DoingShutdown && !job->printer) 330 { 331 printer = cupsdFindDest(job->dest); 332 pclass = NULL; 333 334 while (printer && (printer->type & CUPS_PRINTER_CLASS)) 335 { 336 /* 337 * If the class is remote, just pass it to the remote server... 338 */ 339 340 pclass = printer; 341 342 if (pclass->state == IPP_PRINTER_STOPPED) 343 printer = NULL; 344 else if (pclass->type & CUPS_PRINTER_REMOTE) 345 break; 346 else 347 printer = cupsdFindAvailablePrinter(printer->name); 348 } 349 350 if (!printer && !pclass) 351 { 352 /* 353 * Whoa, the printer and/or class for this destination went away; 354 * cancel the job... 355 */ 356 357 cupsdSetJobState(job, IPP_JOB_ABORTED, CUPSD_JOB_PURGE, 358 "Job aborted because the destination printer/class " 359 "has gone away."); 360 } 361 else if (printer && !printer->holding_new_jobs) 362 { 363 /* 364 * See if the printer is available or remote and not printing a job; 365 * if so, start the job... 366 */ 367 368 if (pclass) 369 { 370 /* 371 * Add/update a job-actual-printer-uri attribute for this job 372 * so that we know which printer actually printed the job... 373 */ 374 375 if ((attr = ippFindAttribute(job->attrs, "job-actual-printer-uri", 376 IPP_TAG_URI)) != NULL) 377 cupsdSetString(&attr->values[0].string.text, printer->uri); 378 else 379 ippAddString(job->attrs, IPP_TAG_JOB, IPP_TAG_URI, 380 "job-actual-printer-uri", NULL, printer->uri); 381 382 job->dirty = 1; 383 cupsdMarkDirty(CUPSD_DIRTY_JOBS); 384 } 385 386 if (!printer->job && printer->state == IPP_PRINTER_IDLE) 387 { 388 /* 389 * Start the job... 390 */ 391 392 start_job(job, printer); 393 } 394 } 395 } 396 } 397} 398 399 400/* 401 * 'cupsdCleanJobs()' - Clean out old jobs. 402 */ 403 404void 405cupsdCleanJobs(void) 406{ 407 cupsd_job_t *job; /* Current job */ 408 time_t curtime; /* Current time */ 409 410 411 cupsdLogMessage(CUPSD_LOG_DEBUG2, 412 "cupsdCleanJobs: MaxJobs=%d, JobHistory=%d, JobFiles=%d", 413 MaxJobs, JobHistory, JobFiles); 414 415 if (MaxJobs <= 0 && JobHistory == INT_MAX && JobFiles == INT_MAX) 416 return; 417 418 curtime = time(NULL); 419 JobHistoryUpdate = 0; 420 421 for (job = (cupsd_job_t *)cupsArrayFirst(Jobs); 422 job; 423 job = (cupsd_job_t *)cupsArrayNext(Jobs)) 424 { 425 if (job->state_value >= IPP_JOB_CANCELED && !job->printer) 426 { 427 /* 428 * Expire old jobs (or job files)... 429 */ 430 431 if ((MaxJobs > 0 && cupsArrayCount(Jobs) >= MaxJobs) || 432 (job->history_time && job->history_time <= curtime)) 433 { 434 cupsdLogJob(job, CUPSD_LOG_DEBUG, "Removing from history."); 435 cupsdDeleteJob(job, CUPSD_JOB_PURGE); 436 } 437 else if (job->file_time && job->file_time <= curtime) 438 { 439 cupsdLogJob(job, CUPSD_LOG_DEBUG, "Removing document files."); 440 remove_job_files(job); 441 442 if (job->history_time < JobHistoryUpdate || !JobHistoryUpdate) 443 JobHistoryUpdate = job->history_time; 444 } 445 else 446 { 447 if (job->history_time < JobHistoryUpdate || !JobHistoryUpdate) 448 JobHistoryUpdate = job->history_time; 449 450 if (job->file_time < JobHistoryUpdate || !JobHistoryUpdate) 451 JobHistoryUpdate = job->file_time; 452 } 453 } 454 } 455 456 cupsdLogMessage(CUPSD_LOG_DEBUG2, "cupsdCleanJobs: JobHistoryUpdate=%ld", 457 (long)JobHistoryUpdate); 458} 459 460 461/* 462 * 'cupsdContinueJob()' - Continue printing with the next file in a job. 463 */ 464 465void 466cupsdContinueJob(cupsd_job_t *job) /* I - Job */ 467{ 468 int i; /* Looping var */ 469 int slot; /* Pipe slot */ 470 cups_array_t *filters = NULL,/* Filters for job */ 471 *prefilters; /* Filters with prefilters */ 472 mime_filter_t *filter, /* Current filter */ 473 *prefilter, /* Prefilter */ 474 port_monitor; /* Port monitor filter */ 475 char scheme[255]; /* Device URI scheme */ 476 ipp_attribute_t *attr; /* Current attribute */ 477 const char *ptr, /* Pointer into value */ 478 *abort_message; /* Abort message */ 479 ipp_jstate_t abort_state = IPP_JOB_STOPPED; 480 /* New job state on abort */ 481 struct stat backinfo; /* Backend file information */ 482 int backroot; /* Run backend as root? */ 483 int pid; /* Process ID of new filter process */ 484 int banner_page; /* 1 if banner page, 0 otherwise */ 485 int filterfds[2][2] = { { -1, -1 }, { -1, -1 } }; 486 /* Pipes used between filters */ 487 int envc; /* Number of environment variables */ 488 struct stat fileinfo; /* Job file information */ 489 int argc; /* Number of arguments */ 490 char **argv = NULL, /* Filter command-line arguments */ 491 filename[1024], /* Job filename */ 492 command[1024], /* Full path to command */ 493 jobid[255], /* Job ID string */ 494 title[IPP_MAX_NAME], 495 /* Job title string */ 496 copies[255], /* # copies string */ 497 *options, /* Options string */ 498 *envp[MAX_ENV + 21], 499 /* Environment variables */ 500 charset[255], /* CHARSET env variable */ 501 class_name[255],/* CLASS env variable */ 502 classification[1024], 503 /* CLASSIFICATION env variable */ 504 content_type[1024], 505 /* CONTENT_TYPE env variable */ 506 device_uri[1024], 507 /* DEVICE_URI env variable */ 508 final_content_type[1024] = "", 509 /* FINAL_CONTENT_TYPE env variable */ 510 lang[255], /* LANG env variable */ 511#ifdef __APPLE__ 512 apple_language[255], 513 /* APPLE_LANGUAGE env variable */ 514#endif /* __APPLE__ */ 515 auth_info_required[255], 516 /* AUTH_INFO_REQUIRED env variable */ 517 ppd[1024], /* PPD env variable */ 518 printer_info[255], 519 /* PRINTER_INFO env variable */ 520 printer_location[255], 521 /* PRINTER_LOCATION env variable */ 522 printer_name[255], 523 /* PRINTER env variable */ 524 *printer_state_reasons = NULL, 525 /* PRINTER_STATE_REASONS env var */ 526 rip_max_cache[255]; 527 /* RIP_MAX_CACHE env variable */ 528 529 530 cupsdLogMessage(CUPSD_LOG_DEBUG2, 531 "cupsdContinueJob(job=%p(%d)): current_file=%d, num_files=%d", 532 job, job->id, job->current_file, job->num_files); 533 534 /* 535 * Figure out what filters are required to convert from 536 * the source to the destination type... 537 */ 538 539 FilterLevel -= job->cost; 540 541 job->cost = 0; 542 job->pending_cost = 0; 543 544 memset(job->filters, 0, sizeof(job->filters)); 545 546 if (job->printer->raw) 547 { 548 /* 549 * Remote jobs and raw queues go directly to the printer without 550 * filtering... 551 */ 552 553 cupsdLogJob(job, CUPSD_LOG_DEBUG, "Sending job to queue tagged as raw..."); 554 } 555 else 556 { 557 /* 558 * Local jobs get filtered... 559 */ 560 561 snprintf(filename, sizeof(filename), "%s/d%05d-%03d", RequestRoot, 562 job->id, job->current_file + 1); 563 if (stat(filename, &fileinfo)) 564 fileinfo.st_size = 0; 565 566 filters = mimeFilter2(MimeDatabase, job->filetypes[job->current_file], 567 fileinfo.st_size, job->printer->filetype, 568 &(job->cost)); 569 570 if (!filters) 571 { 572 cupsdLogJob(job, CUPSD_LOG_ERROR, 573 "Unable to convert file %d to printable format.", 574 job->current_file); 575 576 abort_message = "Aborting job because it cannot be printed."; 577 abort_state = IPP_JOB_ABORTED; 578 579 ippSetString(job->attrs, &job->reasons, 0, "document-unprintable-error"); 580 goto abort_job; 581 } 582 583 /* 584 * Figure out the final content type... 585 */ 586 587 cupsdLogJob(job, CUPSD_LOG_DEBUG, "%d filters for job:", 588 cupsArrayCount(filters)); 589 for (filter = (mime_filter_t *)cupsArrayFirst(filters); 590 filter; 591 filter = (mime_filter_t *)cupsArrayNext(filters)) 592 cupsdLogJob(job, CUPSD_LOG_DEBUG, "%s (%s/%s to %s/%s, cost %d)", 593 filter->filter, 594 filter->src ? filter->src->super : "???", 595 filter->src ? filter->src->type : "???", 596 filter->dst ? filter->dst->super : "???", 597 filter->dst ? filter->dst->type : "???", 598 filter->cost); 599 600 if (!job->printer->remote) 601 { 602 for (filter = (mime_filter_t *)cupsArrayLast(filters); 603 filter && filter->dst; 604 filter = (mime_filter_t *)cupsArrayPrev(filters)) 605 if (strcmp(filter->dst->super, "printer") || 606 strcmp(filter->dst->type, job->printer->name)) 607 break; 608 609 if (filter && filter->dst) 610 { 611 if ((ptr = strchr(filter->dst->type, '/')) != NULL) 612 snprintf(final_content_type, sizeof(final_content_type), 613 "FINAL_CONTENT_TYPE=%s", ptr + 1); 614 else 615 snprintf(final_content_type, sizeof(final_content_type), 616 "FINAL_CONTENT_TYPE=%s/%s", filter->dst->super, 617 filter->dst->type); 618 } 619 else 620 snprintf(final_content_type, sizeof(final_content_type), 621 "FINAL_CONTENT_TYPE=printer/%s", job->printer->name); 622 } 623 624 /* 625 * Remove NULL ("-") filters... 626 */ 627 628 for (filter = (mime_filter_t *)cupsArrayFirst(filters); 629 filter; 630 filter = (mime_filter_t *)cupsArrayNext(filters)) 631 if (!strcmp(filter->filter, "-")) 632 cupsArrayRemove(filters, filter); 633 634 if (cupsArrayCount(filters) == 0) 635 { 636 cupsArrayDelete(filters); 637 filters = NULL; 638 } 639 640 /* 641 * If this printer has any pre-filters, insert the required pre-filter 642 * in the filters array... 643 */ 644 645 if (job->printer->prefiltertype && filters) 646 { 647 prefilters = cupsArrayNew(NULL, NULL); 648 649 for (filter = (mime_filter_t *)cupsArrayFirst(filters); 650 filter; 651 filter = (mime_filter_t *)cupsArrayNext(filters)) 652 { 653 if ((prefilter = mimeFilterLookup(MimeDatabase, filter->src, 654 job->printer->prefiltertype))) 655 { 656 cupsArrayAdd(prefilters, prefilter); 657 job->cost += prefilter->cost; 658 } 659 660 cupsArrayAdd(prefilters, filter); 661 } 662 663 cupsArrayDelete(filters); 664 filters = prefilters; 665 } 666 } 667 668 /* 669 * Set a minimum cost of 100 for all jobs so that FilterLimit 670 * works with raw queues and other low-cost paths. 671 */ 672 673 if (job->cost < 100) 674 job->cost = 100; 675 676 /* 677 * See if the filter cost is too high... 678 */ 679 680 if ((FilterLevel + job->cost) > FilterLimit && FilterLevel > 0 && 681 FilterLimit > 0) 682 { 683 /* 684 * Don't print this job quite yet... 685 */ 686 687 cupsArrayDelete(filters); 688 689 cupsdLogJob(job, CUPSD_LOG_INFO, 690 "Holding because filter limit has been reached."); 691 cupsdLogJob(job, CUPSD_LOG_DEBUG2, 692 "cupsdContinueJob: file=%d, cost=%d, level=%d, limit=%d", 693 job->current_file, job->cost, FilterLevel, 694 FilterLimit); 695 696 job->pending_cost = job->cost; 697 job->cost = 0; 698 return; 699 } 700 701 FilterLevel += job->cost; 702 703 /* 704 * Add decompression/raw filter as needed... 705 */ 706 707 if (job->compressions[job->current_file] && 708 (!job->printer->remote || job->num_files == 1)) 709 { 710 /* 711 * Add gziptoany filter to the front of the list... 712 */ 713 714 if (!filters) 715 filters = cupsArrayNew(NULL, NULL); 716 717 if (!cupsArrayInsert(filters, &gziptoany_filter)) 718 { 719 cupsdLogJob(job, CUPSD_LOG_DEBUG, 720 "Unable to add decompression filter - %s", strerror(errno)); 721 722 cupsArrayDelete(filters); 723 724 abort_message = "Stopping job because the scheduler ran out of memory."; 725 726 goto abort_job; 727 } 728 } 729 730 /* 731 * Add port monitor, if any... 732 */ 733 734 if (job->printer->port_monitor) 735 { 736 /* 737 * Add port monitor to the end of the list... 738 */ 739 740 if (!filters) 741 filters = cupsArrayNew(NULL, NULL); 742 743 port_monitor.src = NULL; 744 port_monitor.dst = NULL; 745 port_monitor.cost = 0; 746 747 snprintf(port_monitor.filter, sizeof(port_monitor.filter), 748 "%s/monitor/%s", ServerBin, job->printer->port_monitor); 749 750 if (!cupsArrayAdd(filters, &port_monitor)) 751 { 752 cupsdLogJob(job, CUPSD_LOG_DEBUG, 753 "Unable to add port monitor - %s", strerror(errno)); 754 755 abort_message = "Stopping job because the scheduler ran out of memory."; 756 757 goto abort_job; 758 } 759 } 760 761 /* 762 * Make sure we don't go over the "MAX_FILTERS" limit... 763 */ 764 765 if (cupsArrayCount(filters) > MAX_FILTERS) 766 { 767 cupsdLogJob(job, CUPSD_LOG_DEBUG, 768 "Too many filters (%d > %d), unable to print.", 769 cupsArrayCount(filters), MAX_FILTERS); 770 771 abort_message = "Aborting job because it needs too many filters to print."; 772 abort_state = IPP_JOB_ABORTED; 773 774 ippSetString(job->attrs, &job->reasons, 0, "document-unprintable-error"); 775 776 goto abort_job; 777 } 778 779 /* 780 * Determine if we are printing a banner page or not... 781 */ 782 783 if (job->job_sheets == NULL) 784 { 785 cupsdLogJob(job, CUPSD_LOG_DEBUG, "No job-sheets attribute."); 786 if ((job->job_sheets = 787 ippFindAttribute(job->attrs, "job-sheets", IPP_TAG_ZERO)) != NULL) 788 cupsdLogJob(job, CUPSD_LOG_DEBUG, 789 "... but someone added one without setting job_sheets."); 790 } 791 else if (job->job_sheets->num_values == 1) 792 cupsdLogJob(job, CUPSD_LOG_DEBUG, "job-sheets=%s", 793 job->job_sheets->values[0].string.text); 794 else 795 cupsdLogJob(job, CUPSD_LOG_DEBUG, "job-sheets=%s,%s", 796 job->job_sheets->values[0].string.text, 797 job->job_sheets->values[1].string.text); 798 799 if (job->printer->type & CUPS_PRINTER_REMOTE) 800 banner_page = 0; 801 else if (job->job_sheets == NULL) 802 banner_page = 0; 803 else if (_cups_strcasecmp(job->job_sheets->values[0].string.text, "none") != 0 && 804 job->current_file == 0) 805 banner_page = 1; 806 else if (job->job_sheets->num_values > 1 && 807 _cups_strcasecmp(job->job_sheets->values[1].string.text, "none") != 0 && 808 job->current_file == (job->num_files - 1)) 809 banner_page = 1; 810 else 811 banner_page = 0; 812 813 if ((options = get_options(job, banner_page, copies, sizeof(copies), title, 814 sizeof(title))) == NULL) 815 { 816 abort_message = "Stopping job because the scheduler ran out of memory."; 817 818 goto abort_job; 819 } 820 821 /* 822 * Build the command-line arguments for the filters. Each filter 823 * has 6 or 7 arguments: 824 * 825 * argv[0] = printer 826 * argv[1] = job ID 827 * argv[2] = username 828 * argv[3] = title 829 * argv[4] = # copies 830 * argv[5] = options 831 * argv[6] = filename (optional; normally stdin) 832 * 833 * This allows legacy printer drivers that use the old System V 834 * printing interface to be used by CUPS. 835 * 836 * For remote jobs, we send all of the files in the argument list. 837 */ 838 839 if (job->printer->remote) 840 argc = 6 + job->num_files; 841 else 842 argc = 7; 843 844 if ((argv = calloc(argc + 1, sizeof(char *))) == NULL) 845 { 846 cupsdLogMessage(CUPSD_LOG_DEBUG, "Unable to allocate argument array - %s", 847 strerror(errno)); 848 849 abort_message = "Stopping job because the scheduler ran out of memory."; 850 851 goto abort_job; 852 } 853 854 sprintf(jobid, "%d", job->id); 855 856 argv[0] = job->printer->name; 857 argv[1] = jobid; 858 argv[2] = job->username; 859 argv[3] = title; 860 argv[4] = copies; 861 argv[5] = options; 862 863 if (job->printer->remote && job->num_files > 1) 864 { 865 for (i = 0; i < job->num_files; i ++) 866 { 867 snprintf(filename, sizeof(filename), "%s/d%05d-%03d", RequestRoot, 868 job->id, i + 1); 869 argv[6 + i] = strdup(filename); 870 } 871 } 872 else 873 { 874 snprintf(filename, sizeof(filename), "%s/d%05d-%03d", RequestRoot, 875 job->id, job->current_file + 1); 876 argv[6] = strdup(filename); 877 } 878 879 for (i = 0; argv[i]; i ++) 880 cupsdLogJob(job, CUPSD_LOG_DEBUG, "argv[%d]=\"%s\"", i, argv[i]); 881 882 /* 883 * Create environment variable strings for the filters... 884 */ 885 886 attr = ippFindAttribute(job->attrs, "attributes-natural-language", 887 IPP_TAG_LANGUAGE); 888 889#ifdef __APPLE__ 890 strlcpy(apple_language, "APPLE_LANGUAGE=", sizeof(apple_language)); 891 _cupsAppleLanguage(attr->values[0].string.text, 892 apple_language + 15, sizeof(apple_language) - 15); 893#endif /* __APPLE__ */ 894 895 switch (strlen(attr->values[0].string.text)) 896 { 897 default : 898 /* 899 * This is an unknown or badly formatted language code; use 900 * the POSIX locale... 901 */ 902 903 strlcpy(lang, "LANG=C", sizeof(lang)); 904 break; 905 906 case 2 : 907 /* 908 * Just the language code (ll)... 909 */ 910 911 snprintf(lang, sizeof(lang), "LANG=%s.UTF-8", 912 attr->values[0].string.text); 913 break; 914 915 case 5 : 916 /* 917 * Language and country code (ll-cc)... 918 */ 919 920 snprintf(lang, sizeof(lang), "LANG=%c%c_%c%c.UTF-8", 921 attr->values[0].string.text[0], 922 attr->values[0].string.text[1], 923 toupper(attr->values[0].string.text[3] & 255), 924 toupper(attr->values[0].string.text[4] & 255)); 925 break; 926 } 927 928 if ((attr = ippFindAttribute(job->attrs, "document-format", 929 IPP_TAG_MIMETYPE)) != NULL && 930 (ptr = strstr(attr->values[0].string.text, "charset=")) != NULL) 931 snprintf(charset, sizeof(charset), "CHARSET=%s", ptr + 8); 932 else 933 strlcpy(charset, "CHARSET=utf-8", sizeof(charset)); 934 935 snprintf(content_type, sizeof(content_type), "CONTENT_TYPE=%s/%s", 936 job->filetypes[job->current_file]->super, 937 job->filetypes[job->current_file]->type); 938 snprintf(device_uri, sizeof(device_uri), "DEVICE_URI=%s", 939 job->printer->device_uri); 940 snprintf(ppd, sizeof(ppd), "PPD=%s/ppd/%s.ppd", ServerRoot, 941 job->printer->name); 942 snprintf(printer_info, sizeof(printer_name), "PRINTER_INFO=%s", 943 job->printer->info ? job->printer->info : ""); 944 snprintf(printer_location, sizeof(printer_name), "PRINTER_LOCATION=%s", 945 job->printer->location ? job->printer->location : ""); 946 snprintf(printer_name, sizeof(printer_name), "PRINTER=%s", job->printer->name); 947 if (job->printer->num_reasons > 0) 948 { 949 char *psrptr; /* Pointer into PRINTER_STATE_REASONS */ 950 size_t psrlen; /* Size of PRINTER_STATE_REASONS */ 951 952 for (psrlen = 22, i = 0; i < job->printer->num_reasons; i ++) 953 psrlen += strlen(job->printer->reasons[i]) + 1; 954 955 if ((printer_state_reasons = malloc(psrlen)) != NULL) 956 { 957 /* 958 * All of these strcpy's are safe because we allocated the psr string... 959 */ 960 961 strlcpy(printer_state_reasons, "PRINTER_STATE_REASONS=", psrlen); 962 for (psrptr = printer_state_reasons + 22, i = 0; 963 i < job->printer->num_reasons; 964 i ++) 965 { 966 if (i) 967 *psrptr++ = ','; 968 strlcpy(psrptr, job->printer->reasons[i], 969 psrlen - (psrptr - printer_state_reasons)); 970 psrptr += strlen(psrptr); 971 } 972 } 973 } 974 snprintf(rip_max_cache, sizeof(rip_max_cache), "RIP_MAX_CACHE=%s", RIPCache); 975 976 if (job->printer->num_auth_info_required == 1) 977 snprintf(auth_info_required, sizeof(auth_info_required), 978 "AUTH_INFO_REQUIRED=%s", 979 job->printer->auth_info_required[0]); 980 else if (job->printer->num_auth_info_required == 2) 981 snprintf(auth_info_required, sizeof(auth_info_required), 982 "AUTH_INFO_REQUIRED=%s,%s", 983 job->printer->auth_info_required[0], 984 job->printer->auth_info_required[1]); 985 else if (job->printer->num_auth_info_required == 3) 986 snprintf(auth_info_required, sizeof(auth_info_required), 987 "AUTH_INFO_REQUIRED=%s,%s,%s", 988 job->printer->auth_info_required[0], 989 job->printer->auth_info_required[1], 990 job->printer->auth_info_required[2]); 991 else if (job->printer->num_auth_info_required == 4) 992 snprintf(auth_info_required, sizeof(auth_info_required), 993 "AUTH_INFO_REQUIRED=%s,%s,%s,%s", 994 job->printer->auth_info_required[0], 995 job->printer->auth_info_required[1], 996 job->printer->auth_info_required[2], 997 job->printer->auth_info_required[3]); 998 else 999 strlcpy(auth_info_required, "AUTH_INFO_REQUIRED=none", 1000 sizeof(auth_info_required)); 1001 1002 envc = cupsdLoadEnv(envp, (int)(sizeof(envp) / sizeof(envp[0]))); 1003 1004 envp[envc ++] = charset; 1005 envp[envc ++] = lang; 1006#ifdef __APPLE__ 1007 envp[envc ++] = apple_language; 1008#endif /* __APPLE__ */ 1009 envp[envc ++] = ppd; 1010 envp[envc ++] = rip_max_cache; 1011 envp[envc ++] = content_type; 1012 envp[envc ++] = device_uri; 1013 envp[envc ++] = printer_info; 1014 envp[envc ++] = printer_location; 1015 envp[envc ++] = printer_name; 1016 envp[envc ++] = printer_state_reasons ? printer_state_reasons : 1017 "PRINTER_STATE_REASONS=none"; 1018 envp[envc ++] = banner_page ? "CUPS_FILETYPE=job-sheet" : 1019 "CUPS_FILETYPE=document"; 1020 1021 if (final_content_type[0]) 1022 envp[envc ++] = final_content_type; 1023 1024 if (Classification && !banner_page) 1025 { 1026 if ((attr = ippFindAttribute(job->attrs, "job-sheets", 1027 IPP_TAG_NAME)) == NULL) 1028 snprintf(classification, sizeof(classification), "CLASSIFICATION=%s", 1029 Classification); 1030 else if (attr->num_values > 1 && 1031 strcmp(attr->values[1].string.text, "none") != 0) 1032 snprintf(classification, sizeof(classification), "CLASSIFICATION=%s", 1033 attr->values[1].string.text); 1034 else 1035 snprintf(classification, sizeof(classification), "CLASSIFICATION=%s", 1036 attr->values[0].string.text); 1037 1038 envp[envc ++] = classification; 1039 } 1040 1041 if (job->dtype & CUPS_PRINTER_CLASS) 1042 { 1043 snprintf(class_name, sizeof(class_name), "CLASS=%s", job->dest); 1044 envp[envc ++] = class_name; 1045 } 1046 1047 envp[envc ++] = auth_info_required; 1048 1049 for (i = 0; 1050 i < (int)(sizeof(job->auth_env) / sizeof(job->auth_env[0])); 1051 i ++) 1052 if (job->auth_env[i]) 1053 envp[envc ++] = job->auth_env[i]; 1054 else 1055 break; 1056 1057 if (job->auth_uid) 1058 envp[envc ++] = job->auth_uid; 1059 1060 envp[envc] = NULL; 1061 1062 for (i = 0; i < envc; i ++) 1063 if (!strncmp(envp[i], "AUTH_", 5)) 1064 cupsdLogJob(job, CUPSD_LOG_DEBUG, "envp[%d]=\"AUTH_%c****\"", i, 1065 envp[i][5]); 1066 else if (strncmp(envp[i], "DEVICE_URI=", 11)) 1067 cupsdLogJob(job, CUPSD_LOG_DEBUG, "envp[%d]=\"%s\"", i, envp[i]); 1068 else 1069 cupsdLogJob(job, CUPSD_LOG_DEBUG, "envp[%d]=\"DEVICE_URI=%s\"", i, 1070 job->printer->sanitized_device_uri); 1071 1072 if (job->printer->remote) 1073 job->current_file = job->num_files; 1074 else 1075 job->current_file ++; 1076 1077 /* 1078 * Now create processes for all of the filters... 1079 */ 1080 1081 for (i = 0, slot = 0, filter = (mime_filter_t *)cupsArrayFirst(filters); 1082 filter; 1083 i ++, filter = (mime_filter_t *)cupsArrayNext(filters)) 1084 { 1085 if (filter->filter[0] != '/') 1086 snprintf(command, sizeof(command), "%s/filter/%s", ServerBin, 1087 filter->filter); 1088 else 1089 strlcpy(command, filter->filter, sizeof(command)); 1090 1091 if (i < (cupsArrayCount(filters) - 1)) 1092 { 1093 if (cupsdOpenPipe(filterfds[slot])) 1094 { 1095 abort_message = "Stopping job because the scheduler could not create " 1096 "the filter pipes."; 1097 1098 goto abort_job; 1099 } 1100 } 1101 else 1102 { 1103 if (job->current_file == 1 || 1104 (job->printer->pc && job->printer->pc->single_file)) 1105 { 1106 if (strncmp(job->printer->device_uri, "file:", 5) != 0) 1107 { 1108 if (cupsdOpenPipe(job->print_pipes)) 1109 { 1110 abort_message = "Stopping job because the scheduler could not " 1111 "create the backend pipes."; 1112 1113 goto abort_job; 1114 } 1115 } 1116 else 1117 { 1118 job->print_pipes[0] = -1; 1119 if (!strcmp(job->printer->device_uri, "file:/dev/null") || 1120 !strcmp(job->printer->device_uri, "file:///dev/null")) 1121 job->print_pipes[1] = -1; 1122 else 1123 { 1124 if (!strncmp(job->printer->device_uri, "file:/dev/", 10)) 1125 job->print_pipes[1] = open(job->printer->device_uri + 5, 1126 O_WRONLY | O_EXCL); 1127 else if (!strncmp(job->printer->device_uri, "file:///dev/", 12)) 1128 job->print_pipes[1] = open(job->printer->device_uri + 7, 1129 O_WRONLY | O_EXCL); 1130 else if (!strncmp(job->printer->device_uri, "file:///", 8)) 1131 job->print_pipes[1] = open(job->printer->device_uri + 7, 1132 O_WRONLY | O_CREAT | O_TRUNC, 0600); 1133 else 1134 job->print_pipes[1] = open(job->printer->device_uri + 5, 1135 O_WRONLY | O_CREAT | O_TRUNC, 0600); 1136 1137 if (job->print_pipes[1] < 0) 1138 { 1139 abort_message = "Stopping job because the scheduler could not " 1140 "open the output file."; 1141 1142 goto abort_job; 1143 } 1144 1145 fcntl(job->print_pipes[1], F_SETFD, 1146 fcntl(job->print_pipes[1], F_GETFD) | FD_CLOEXEC); 1147 } 1148 } 1149 } 1150 1151 filterfds[slot][0] = job->print_pipes[0]; 1152 filterfds[slot][1] = job->print_pipes[1]; 1153 } 1154 1155 pid = cupsdStartProcess(command, argv, envp, filterfds[!slot][0], 1156 filterfds[slot][1], job->status_pipes[1], 1157 job->back_pipes[0], job->side_pipes[0], 0, 1158 job->profile, job, job->filters + i); 1159 1160 cupsdClosePipe(filterfds[!slot]); 1161 1162 if (pid == 0) 1163 { 1164 cupsdLogJob(job, CUPSD_LOG_ERROR, "Unable to start filter \"%s\" - %s.", 1165 filter->filter, strerror(errno)); 1166 1167 abort_message = "Stopping job because the scheduler could not execute a " 1168 "filter."; 1169 1170 goto abort_job; 1171 } 1172 1173 cupsdLogJob(job, CUPSD_LOG_INFO, "Started filter %s (PID %d)", command, 1174 pid); 1175 1176 if (argv[6]) 1177 { 1178 free(argv[6]); 1179 argv[6] = NULL; 1180 } 1181 1182 slot = !slot; 1183 } 1184 1185 cupsArrayDelete(filters); 1186 filters = NULL; 1187 1188 /* 1189 * Finally, pipe the final output into a backend process if needed... 1190 */ 1191 1192 if (strncmp(job->printer->device_uri, "file:", 5) != 0) 1193 { 1194 if (job->current_file == 1 || job->printer->remote || 1195 (job->printer->pc && job->printer->pc->single_file)) 1196 { 1197 sscanf(job->printer->device_uri, "%254[^:]", scheme); 1198 snprintf(command, sizeof(command), "%s/backend/%s", ServerBin, scheme); 1199 1200 /* 1201 * See if the backend needs to run as root... 1202 */ 1203 1204 if (RunUser) 1205 backroot = 0; 1206 else if (stat(command, &backinfo)) 1207 backroot = 0; 1208 else 1209 backroot = !(backinfo.st_mode & (S_IRWXG | S_IRWXO)); 1210 1211 argv[0] = job->printer->sanitized_device_uri; 1212 1213 filterfds[slot][0] = -1; 1214 filterfds[slot][1] = -1; 1215 1216 pid = cupsdStartProcess(command, argv, envp, filterfds[!slot][0], 1217 filterfds[slot][1], job->status_pipes[1], 1218 job->back_pipes[1], job->side_pipes[1], 1219 backroot, job->profile, job, &(job->backend)); 1220 1221 if (pid == 0) 1222 { 1223 abort_message = "Stopping job because the sheduler could not execute " 1224 "the backend."; 1225 1226 goto abort_job; 1227 } 1228 else 1229 { 1230 cupsdLogJob(job, CUPSD_LOG_INFO, "Started backend %s (PID %d)", 1231 command, pid); 1232 } 1233 } 1234 1235 if (job->current_file == job->num_files || 1236 (job->printer->pc && job->printer->pc->single_file)) 1237 cupsdClosePipe(job->print_pipes); 1238 1239 if (job->current_file == job->num_files) 1240 { 1241 cupsdClosePipe(job->back_pipes); 1242 cupsdClosePipe(job->side_pipes); 1243 1244 close(job->status_pipes[1]); 1245 job->status_pipes[1] = -1; 1246 } 1247 } 1248 else 1249 { 1250 filterfds[slot][0] = -1; 1251 filterfds[slot][1] = -1; 1252 1253 if (job->current_file == job->num_files || 1254 (job->printer->pc && job->printer->pc->single_file)) 1255 cupsdClosePipe(job->print_pipes); 1256 1257 if (job->current_file == job->num_files) 1258 { 1259 close(job->status_pipes[1]); 1260 job->status_pipes[1] = -1; 1261 } 1262 } 1263 1264 cupsdClosePipe(filterfds[slot]); 1265 1266 for (i = 6; i < argc; i ++) 1267 if (argv[i]) 1268 free(argv[i]); 1269 1270 free(argv); 1271 1272 if (printer_state_reasons) 1273 free(printer_state_reasons); 1274 1275 cupsdAddSelect(job->status_buffer->fd, (cupsd_selfunc_t)update_job, NULL, 1276 job); 1277 1278 cupsdAddEvent(CUPSD_EVENT_JOB_STATE, job->printer, job, "Job #%d started.", 1279 job->id); 1280 1281 return; 1282 1283 1284 /* 1285 * If we get here, we need to abort the current job and close out all 1286 * files and pipes... 1287 */ 1288 1289 abort_job: 1290 1291 FilterLevel -= job->cost; 1292 job->cost = 0; 1293 1294 for (slot = 0; slot < 2; slot ++) 1295 cupsdClosePipe(filterfds[slot]); 1296 1297 cupsArrayDelete(filters); 1298 1299 if (argv) 1300 { 1301 for (i = 6; i < argc; i ++) 1302 if (argv[i]) 1303 free(argv[i]); 1304 } 1305 1306 if (printer_state_reasons) 1307 free(printer_state_reasons); 1308 1309 cupsdClosePipe(job->print_pipes); 1310 cupsdClosePipe(job->back_pipes); 1311 cupsdClosePipe(job->side_pipes); 1312 1313 cupsdRemoveSelect(job->status_pipes[0]); 1314 cupsdClosePipe(job->status_pipes); 1315 cupsdStatBufDelete(job->status_buffer); 1316 job->status_buffer = NULL; 1317 1318 /* 1319 * Update the printer and job state. 1320 */ 1321 1322 cupsdSetJobState(job, abort_state, CUPSD_JOB_DEFAULT, "%s", abort_message); 1323 cupsdSetPrinterState(job->printer, IPP_PRINTER_IDLE, 0); 1324 update_job_attrs(job, 0); 1325 1326 if (job->history) 1327 free_job_history(job); 1328 1329 cupsArrayRemove(PrintingJobs, job); 1330 1331 /* 1332 * Clear the printer <-> job association... 1333 */ 1334 1335 job->printer->job = NULL; 1336 job->printer = NULL; 1337} 1338 1339 1340/* 1341 * 'cupsdDeleteJob()' - Free all memory used by a job. 1342 */ 1343 1344void 1345cupsdDeleteJob(cupsd_job_t *job, /* I - Job */ 1346 cupsd_jobaction_t action)/* I - Action */ 1347{ 1348 int i; /* Looping var */ 1349 1350 1351 if (job->printer) 1352 finalize_job(job, 1); 1353 1354 if (action == CUPSD_JOB_PURGE) 1355 remove_job_history(job); 1356 1357 cupsdClearString(&job->username); 1358 cupsdClearString(&job->dest); 1359 for (i = 0; 1360 i < (int)(sizeof(job->auth_env) / sizeof(job->auth_env[0])); 1361 i ++) 1362 cupsdClearString(job->auth_env + i); 1363 cupsdClearString(&job->auth_uid); 1364 1365 if (action == CUPSD_JOB_PURGE) 1366 remove_job_files(job); 1367 else if (job->num_files > 0) 1368 { 1369 free(job->compressions); 1370 free(job->filetypes); 1371 1372 job->num_files = 0; 1373 } 1374 1375 if (job->history) 1376 free_job_history(job); 1377 1378 unload_job(job); 1379 1380 cupsArrayRemove(Jobs, job); 1381 cupsArrayRemove(ActiveJobs, job); 1382 cupsArrayRemove(PrintingJobs, job); 1383 1384 free(job); 1385} 1386 1387 1388/* 1389 * 'cupsdFreeAllJobs()' - Free all jobs from memory. 1390 */ 1391 1392void 1393cupsdFreeAllJobs(void) 1394{ 1395 cupsd_job_t *job; /* Current job */ 1396 1397 1398 if (!Jobs) 1399 return; 1400 1401 cupsdHoldSignals(); 1402 1403 cupsdStopAllJobs(CUPSD_JOB_FORCE, 0); 1404 cupsdSaveAllJobs(); 1405 1406 for (job = (cupsd_job_t *)cupsArrayFirst(Jobs); 1407 job; 1408 job = (cupsd_job_t *)cupsArrayNext(Jobs)) 1409 cupsdDeleteJob(job, CUPSD_JOB_DEFAULT); 1410 1411 cupsdReleaseSignals(); 1412} 1413 1414 1415/* 1416 * 'cupsdFindJob()' - Find the specified job. 1417 */ 1418 1419cupsd_job_t * /* O - Job data */ 1420cupsdFindJob(int id) /* I - Job ID */ 1421{ 1422 cupsd_job_t key; /* Search key */ 1423 1424 1425 key.id = id; 1426 1427 return ((cupsd_job_t *)cupsArrayFind(Jobs, &key)); 1428} 1429 1430 1431/* 1432 * 'cupsdGetPrinterJobCount()' - Get the number of pending, processing, 1433 * or held jobs in a printer or class. 1434 */ 1435 1436int /* O - Job count */ 1437cupsdGetPrinterJobCount( 1438 const char *dest) /* I - Printer or class name */ 1439{ 1440 int count; /* Job count */ 1441 cupsd_job_t *job; /* Current job */ 1442 1443 1444 for (job = (cupsd_job_t *)cupsArrayFirst(ActiveJobs), count = 0; 1445 job; 1446 job = (cupsd_job_t *)cupsArrayNext(ActiveJobs)) 1447 if (job->dest && !_cups_strcasecmp(job->dest, dest)) 1448 count ++; 1449 1450 return (count); 1451} 1452 1453 1454/* 1455 * 'cupsdGetUserJobCount()' - Get the number of pending, processing, 1456 * or held jobs for a user. 1457 */ 1458 1459int /* O - Job count */ 1460cupsdGetUserJobCount( 1461 const char *username) /* I - Username */ 1462{ 1463 int count; /* Job count */ 1464 cupsd_job_t *job; /* Current job */ 1465 1466 1467 for (job = (cupsd_job_t *)cupsArrayFirst(ActiveJobs), count = 0; 1468 job; 1469 job = (cupsd_job_t *)cupsArrayNext(ActiveJobs)) 1470 if (!_cups_strcasecmp(job->username, username)) 1471 count ++; 1472 1473 return (count); 1474} 1475 1476 1477/* 1478 * 'cupsdLoadAllJobs()' - Load all jobs from disk. 1479 */ 1480 1481void 1482cupsdLoadAllJobs(void) 1483{ 1484 char filename[1024]; /* Full filename of job.cache file */ 1485 struct stat fileinfo, /* Information on job.cache file */ 1486 dirinfo; /* Information on RequestRoot dir */ 1487 1488 1489 1490 /* 1491 * Create the job arrays as needed... 1492 */ 1493 1494 if (!Jobs) 1495 Jobs = cupsArrayNew(compare_jobs, NULL); 1496 1497 if (!ActiveJobs) 1498 ActiveJobs = cupsArrayNew(compare_active_jobs, NULL); 1499 1500 if (!PrintingJobs) 1501 PrintingJobs = cupsArrayNew(compare_jobs, NULL); 1502 1503 /* 1504 * See whether the job.cache file is older than the RequestRoot directory... 1505 */ 1506 1507 snprintf(filename, sizeof(filename), "%s/job.cache", CacheDir); 1508 1509 if (stat(filename, &fileinfo)) 1510 { 1511 fileinfo.st_mtime = 0; 1512 1513 if (errno != ENOENT) 1514 cupsdLogMessage(CUPSD_LOG_ERROR, 1515 "Unable to get file information for \"%s\" - %s", 1516 filename, strerror(errno)); 1517 } 1518 1519 if (stat(RequestRoot, &dirinfo)) 1520 { 1521 dirinfo.st_mtime = 0; 1522 1523 if (errno != ENOENT) 1524 cupsdLogMessage(CUPSD_LOG_ERROR, 1525 "Unable to get directory information for \"%s\" - %s", 1526 RequestRoot, strerror(errno)); 1527 } 1528 1529 /* 1530 * Load the most recent source for job data... 1531 */ 1532 1533 if (dirinfo.st_mtime > fileinfo.st_mtime) 1534 { 1535 load_request_root(); 1536 1537 load_next_job_id(filename); 1538 } 1539 else 1540 load_job_cache(filename); 1541 1542 /* 1543 * Clean out old jobs as needed... 1544 */ 1545 1546 if (MaxJobs > 0 && cupsArrayCount(Jobs) >= MaxJobs) 1547 cupsdCleanJobs(); 1548} 1549 1550 1551/* 1552 * 'cupsdLoadJob()' - Load a single job. 1553 */ 1554 1555int /* O - 1 on success, 0 on failure */ 1556cupsdLoadJob(cupsd_job_t *job) /* I - Job */ 1557{ 1558 int i; /* Looping var */ 1559 char jobfile[1024]; /* Job filename */ 1560 cups_file_t *fp; /* Job file */ 1561 int fileid; /* Current file ID */ 1562 ipp_attribute_t *attr; /* Job attribute */ 1563 const char *dest; /* Destination name */ 1564 cupsd_printer_t *destptr; /* Pointer to destination */ 1565 mime_type_t **filetypes; /* New filetypes array */ 1566 int *compressions; /* New compressions array */ 1567 1568 1569 if (job->attrs) 1570 { 1571 if (job->state_value > IPP_JOB_STOPPED) 1572 job->access_time = time(NULL); 1573 1574 return (1); 1575 } 1576 1577 if ((job->attrs = ippNew()) == NULL) 1578 { 1579 cupsdLogJob(job, CUPSD_LOG_ERROR, "Ran out of memory for job attributes."); 1580 return (0); 1581 } 1582 1583 /* 1584 * Load job attributes... 1585 */ 1586 1587 cupsdLogJob(job, CUPSD_LOG_DEBUG, "Loading attributes..."); 1588 1589 snprintf(jobfile, sizeof(jobfile), "%s/c%05d", RequestRoot, job->id); 1590 if ((fp = cupsdOpenConfFile(jobfile)) == NULL) 1591 goto error; 1592 1593 if (ippReadIO(fp, (ipp_iocb_t)cupsFileRead, 1, NULL, job->attrs) != IPP_DATA) 1594 { 1595 cupsdLogJob(job, CUPSD_LOG_ERROR, 1596 "Unable to read job control file \"%s\".", jobfile); 1597 cupsFileClose(fp); 1598 goto error; 1599 } 1600 1601 cupsFileClose(fp); 1602 1603 /* 1604 * Copy attribute data to the job object... 1605 */ 1606 1607 if (!ippFindAttribute(job->attrs, "time-at-creation", IPP_TAG_INTEGER)) 1608 { 1609 cupsdLogJob(job, CUPSD_LOG_ERROR, 1610 "Missing or bad time-at-creation attribute in control file."); 1611 goto error; 1612 } 1613 1614 if ((job->state = ippFindAttribute(job->attrs, "job-state", 1615 IPP_TAG_ENUM)) == NULL) 1616 { 1617 cupsdLogJob(job, CUPSD_LOG_ERROR, 1618 "Missing or bad job-state attribute in control file."); 1619 goto error; 1620 } 1621 1622 job->state_value = (ipp_jstate_t)job->state->values[0].integer; 1623 job->file_time = 0; 1624 job->history_time = 0; 1625 1626 if (job->state_value >= IPP_JOB_CANCELED && 1627 (attr = ippFindAttribute(job->attrs, "time-at-completed", 1628 IPP_TAG_INTEGER)) != NULL) 1629 { 1630 if (JobHistory < INT_MAX) 1631 job->history_time = attr->values[0].integer + JobHistory; 1632 else 1633 job->history_time = INT_MAX; 1634 1635 if (job->history_time < time(NULL)) 1636 goto error; /* Expired, remove from history */ 1637 1638 if (job->history_time < JobHistoryUpdate || !JobHistoryUpdate) 1639 JobHistoryUpdate = job->history_time; 1640 1641 if (JobFiles < INT_MAX) 1642 job->file_time = attr->values[0].integer + JobFiles; 1643 else 1644 job->file_time = INT_MAX; 1645 1646 if (job->file_time < JobHistoryUpdate || !JobHistoryUpdate) 1647 JobHistoryUpdate = job->file_time; 1648 1649 cupsdLogMessage(CUPSD_LOG_DEBUG2, "cupsdLoadJob: JobHistoryUpdate=%ld", 1650 (long)JobHistoryUpdate); 1651 } 1652 1653 if (!job->dest) 1654 { 1655 if ((attr = ippFindAttribute(job->attrs, "job-printer-uri", 1656 IPP_TAG_URI)) == NULL) 1657 { 1658 cupsdLogJob(job, CUPSD_LOG_ERROR, 1659 "No job-printer-uri attribute in control file."); 1660 goto error; 1661 } 1662 1663 if ((dest = cupsdValidateDest(attr->values[0].string.text, &(job->dtype), 1664 &destptr)) == NULL) 1665 { 1666 cupsdLogJob(job, CUPSD_LOG_ERROR, 1667 "Unable to queue job for destination \"%s\".", 1668 attr->values[0].string.text); 1669 goto error; 1670 } 1671 1672 cupsdSetString(&job->dest, dest); 1673 } 1674 else if ((destptr = cupsdFindDest(job->dest)) == NULL) 1675 { 1676 cupsdLogJob(job, CUPSD_LOG_ERROR, 1677 "Unable to queue job for destination \"%s\".", 1678 job->dest); 1679 goto error; 1680 } 1681 1682 if ((job->reasons = ippFindAttribute(job->attrs, "job-state-reasons", 1683 IPP_TAG_KEYWORD)) == NULL) 1684 { 1685 const char *reason; /* job-state-reason keyword */ 1686 1687 cupsdLogJob(job, CUPSD_LOG_DEBUG, 1688 "Adding missing job-state-reasons attribute to control file."); 1689 1690 switch (job->state_value) 1691 { 1692 default : 1693 case IPP_JOB_PENDING : 1694 if (destptr->state == IPP_PRINTER_STOPPED) 1695 reason = "printer-stopped"; 1696 else 1697 reason = "none"; 1698 break; 1699 1700 case IPP_JOB_HELD : 1701 if ((attr = ippFindAttribute(job->attrs, "job-hold-until", 1702 IPP_TAG_ZERO)) != NULL && 1703 (attr->value_tag == IPP_TAG_NAME || 1704 attr->value_tag == IPP_TAG_NAMELANG || 1705 attr->value_tag == IPP_TAG_KEYWORD) && 1706 strcmp(attr->values[0].string.text, "no-hold")) 1707 reason = "job-hold-until-specified"; 1708 else 1709 reason = "job-incoming"; 1710 break; 1711 1712 case IPP_JOB_PROCESSING : 1713 reason = "job-printing"; 1714 break; 1715 1716 case IPP_JOB_STOPPED : 1717 reason = "job-stopped"; 1718 break; 1719 1720 case IPP_JOB_CANCELED : 1721 reason = "job-canceled-by-user"; 1722 break; 1723 1724 case IPP_JOB_ABORTED : 1725 reason = "aborted-by-system"; 1726 break; 1727 1728 case IPP_JOB_COMPLETED : 1729 reason = "job-completed-successfully"; 1730 break; 1731 } 1732 1733 job->reasons = ippAddString(job->attrs, IPP_TAG_JOB, IPP_TAG_KEYWORD, 1734 "job-state-reasons", NULL, reason); 1735 } 1736 else if (job->state_value == IPP_JOB_PENDING) 1737 { 1738 if (destptr->state == IPP_PRINTER_STOPPED) 1739 ippSetString(job->attrs, &job->reasons, 0, "printer-stopped"); 1740 else 1741 ippSetString(job->attrs, &job->reasons, 0, "none"); 1742 } 1743 1744 job->sheets = ippFindAttribute(job->attrs, "job-media-sheets-completed", 1745 IPP_TAG_INTEGER); 1746 job->job_sheets = ippFindAttribute(job->attrs, "job-sheets", IPP_TAG_NAME); 1747 1748 if (!job->priority) 1749 { 1750 if ((attr = ippFindAttribute(job->attrs, "job-priority", 1751 IPP_TAG_INTEGER)) == NULL) 1752 { 1753 cupsdLogJob(job, CUPSD_LOG_ERROR, 1754 "Missing or bad job-priority attribute in control file."); 1755 goto error; 1756 } 1757 1758 job->priority = attr->values[0].integer; 1759 } 1760 1761 if (!job->username) 1762 { 1763 if ((attr = ippFindAttribute(job->attrs, "job-originating-user-name", 1764 IPP_TAG_NAME)) == NULL) 1765 { 1766 cupsdLogJob(job, CUPSD_LOG_ERROR, 1767 "Missing or bad job-originating-user-name " 1768 "attribute in control file."); 1769 goto error; 1770 } 1771 1772 cupsdSetString(&job->username, attr->values[0].string.text); 1773 } 1774 1775 /* 1776 * Set the job hold-until time and state... 1777 */ 1778 1779 if (job->state_value == IPP_JOB_HELD) 1780 { 1781 if ((attr = ippFindAttribute(job->attrs, "job-hold-until", 1782 IPP_TAG_KEYWORD)) == NULL) 1783 attr = ippFindAttribute(job->attrs, "job-hold-until", IPP_TAG_NAME); 1784 1785 if (attr) 1786 cupsdSetJobHoldUntil(job, attr->values[0].string.text, CUPSD_JOB_DEFAULT); 1787 else 1788 { 1789 job->state->values[0].integer = IPP_JOB_PENDING; 1790 job->state_value = IPP_JOB_PENDING; 1791 } 1792 } 1793 else if (job->state_value == IPP_JOB_PROCESSING) 1794 { 1795 job->state->values[0].integer = IPP_JOB_PENDING; 1796 job->state_value = IPP_JOB_PENDING; 1797 } 1798 1799 if (!job->num_files) 1800 { 1801 /* 1802 * Find all the d##### files... 1803 */ 1804 1805 for (fileid = 1; fileid < 10000; fileid ++) 1806 { 1807 snprintf(jobfile, sizeof(jobfile), "%s/d%05d-%03d", RequestRoot, 1808 job->id, fileid); 1809 1810 if (access(jobfile, 0)) 1811 break; 1812 1813 cupsdLogJob(job, CUPSD_LOG_DEBUG, 1814 "Auto-typing document file \"%s\"...", jobfile); 1815 1816 if (fileid > job->num_files) 1817 { 1818 if (job->num_files == 0) 1819 { 1820 compressions = (int *)calloc(fileid, sizeof(int)); 1821 filetypes = (mime_type_t **)calloc(fileid, sizeof(mime_type_t *)); 1822 } 1823 else 1824 { 1825 compressions = (int *)realloc(job->compressions, 1826 sizeof(int) * fileid); 1827 filetypes = (mime_type_t **)realloc(job->filetypes, 1828 sizeof(mime_type_t *) * 1829 fileid); 1830 } 1831 1832 if (compressions) 1833 job->compressions = compressions; 1834 1835 if (filetypes) 1836 job->filetypes = filetypes; 1837 1838 if (!compressions || !filetypes) 1839 { 1840 cupsdLogJob(job, CUPSD_LOG_ERROR, 1841 "Ran out of memory for job file types."); 1842 1843 ippDelete(job->attrs); 1844 job->attrs = NULL; 1845 1846 if (job->compressions) 1847 { 1848 free(job->compressions); 1849 job->compressions = NULL; 1850 } 1851 1852 if (job->filetypes) 1853 { 1854 free(job->filetypes); 1855 job->filetypes = NULL; 1856 } 1857 1858 job->num_files = 0; 1859 return (0); 1860 } 1861 1862 job->num_files = fileid; 1863 } 1864 1865 job->filetypes[fileid - 1] = mimeFileType(MimeDatabase, jobfile, NULL, 1866 job->compressions + fileid - 1); 1867 1868 if (!job->filetypes[fileid - 1]) 1869 job->filetypes[fileid - 1] = mimeType(MimeDatabase, "application", 1870 "vnd.cups-raw"); 1871 } 1872 } 1873 1874 /* 1875 * Load authentication information as needed... 1876 */ 1877 1878 if (job->state_value < IPP_JOB_STOPPED) 1879 { 1880 snprintf(jobfile, sizeof(jobfile), "%s/a%05d", RequestRoot, job->id); 1881 1882 for (i = 0; 1883 i < (int)(sizeof(job->auth_env) / sizeof(job->auth_env[0])); 1884 i ++) 1885 cupsdClearString(job->auth_env + i); 1886 cupsdClearString(&job->auth_uid); 1887 1888 if ((fp = cupsFileOpen(jobfile, "r")) != NULL) 1889 { 1890 int bytes, /* Size of auth data */ 1891 linenum = 1; /* Current line number */ 1892 char line[65536], /* Line from file */ 1893 *value, /* Value from line */ 1894 data[65536]; /* Decoded data */ 1895 1896 1897 if (cupsFileGets(fp, line, sizeof(line)) && 1898 !strcmp(line, "CUPSD-AUTH-V3")) 1899 { 1900 i = 0; 1901 while (cupsFileGetConf(fp, line, sizeof(line), &value, &linenum)) 1902 { 1903 /* 1904 * Decode value... 1905 */ 1906 1907 if (strcmp(line, "negotiate") && strcmp(line, "uid")) 1908 { 1909 bytes = sizeof(data); 1910 httpDecode64_2(data, &bytes, value); 1911 } 1912 1913 /* 1914 * Assign environment variables... 1915 */ 1916 1917 if (!strcmp(line, "uid")) 1918 { 1919 cupsdSetStringf(&job->auth_uid, "AUTH_UID=%s", value); 1920 continue; 1921 } 1922 else if (i >= (int)(sizeof(job->auth_env) / sizeof(job->auth_env[0]))) 1923 break; 1924 1925 if (!strcmp(line, "username")) 1926 cupsdSetStringf(job->auth_env + i, "AUTH_USERNAME=%s", data); 1927 else if (!strcmp(line, "domain")) 1928 cupsdSetStringf(job->auth_env + i, "AUTH_DOMAIN=%s", data); 1929 else if (!strcmp(line, "password")) 1930 cupsdSetStringf(job->auth_env + i, "AUTH_PASSWORD=%s", data); 1931 else if (!strcmp(line, "negotiate")) 1932 cupsdSetStringf(job->auth_env + i, "AUTH_NEGOTIATE=%s", value); 1933 else 1934 continue; 1935 1936 i ++; 1937 } 1938 } 1939 1940 cupsFileClose(fp); 1941 } 1942 } 1943 1944 job->access_time = time(NULL); 1945 return (1); 1946 1947 /* 1948 * If we get here then something bad happened... 1949 */ 1950 1951 error: 1952 1953 ippDelete(job->attrs); 1954 job->attrs = NULL; 1955 1956 remove_job_history(job); 1957 remove_job_files(job); 1958 1959 return (0); 1960} 1961 1962 1963/* 1964 * 'cupsdMoveJob()' - Move the specified job to a different destination. 1965 */ 1966 1967void 1968cupsdMoveJob(cupsd_job_t *job, /* I - Job */ 1969 cupsd_printer_t *p) /* I - Destination printer or class */ 1970{ 1971 ipp_attribute_t *attr; /* job-printer-uri attribute */ 1972 const char *olddest; /* Old destination */ 1973 cupsd_printer_t *oldp; /* Old pointer */ 1974 1975 1976 /* 1977 * Don't move completed jobs... 1978 */ 1979 1980 if (job->state_value > IPP_JOB_STOPPED) 1981 return; 1982 1983 /* 1984 * Get the old destination... 1985 */ 1986 1987 olddest = job->dest; 1988 1989 if (job->printer) 1990 oldp = job->printer; 1991 else 1992 oldp = cupsdFindDest(olddest); 1993 1994 /* 1995 * Change the destination information... 1996 */ 1997 1998 if (job->state_value > IPP_JOB_HELD) 1999 cupsdSetJobState(job, IPP_JOB_PENDING, CUPSD_JOB_DEFAULT, 2000 "Stopping job prior to move."); 2001 2002 cupsdAddEvent(CUPSD_EVENT_JOB_CONFIG_CHANGED, oldp, job, 2003 "Job #%d moved from %s to %s.", job->id, olddest, 2004 p->name); 2005 2006 cupsdSetString(&job->dest, p->name); 2007 job->dtype = p->type & (CUPS_PRINTER_CLASS | CUPS_PRINTER_REMOTE); 2008 2009 if ((attr = ippFindAttribute(job->attrs, "job-printer-uri", 2010 IPP_TAG_URI)) != NULL) 2011 cupsdSetString(&(attr->values[0].string.text), p->uri); 2012 2013 cupsdAddEvent(CUPSD_EVENT_JOB_STOPPED, p, job, 2014 "Job #%d moved from %s to %s.", job->id, olddest, 2015 p->name); 2016 2017 job->dirty = 1; 2018 cupsdMarkDirty(CUPSD_DIRTY_JOBS); 2019} 2020 2021 2022/* 2023 * 'cupsdReleaseJob()' - Release the specified job. 2024 */ 2025 2026void 2027cupsdReleaseJob(cupsd_job_t *job) /* I - Job */ 2028{ 2029 cupsdLogMessage(CUPSD_LOG_DEBUG2, "cupsdReleaseJob(job=%p(%d))", job, 2030 job->id); 2031 2032 if (job->state_value == IPP_JOB_HELD) 2033 { 2034 /* 2035 * Add trailing banner as needed... 2036 */ 2037 2038 if (job->pending_timeout) 2039 cupsdTimeoutJob(job); 2040 2041 cupsdSetJobState(job, IPP_JOB_PENDING, CUPSD_JOB_DEFAULT, 2042 "Job released by user."); 2043 } 2044} 2045 2046 2047/* 2048 * 'cupsdRestartJob()' - Restart the specified job. 2049 */ 2050 2051void 2052cupsdRestartJob(cupsd_job_t *job) /* I - Job */ 2053{ 2054 cupsdLogMessage(CUPSD_LOG_DEBUG2, "cupsdRestartJob(job=%p(%d))", job, 2055 job->id); 2056 2057 if (job->state_value == IPP_JOB_STOPPED || job->num_files) 2058 cupsdSetJobState(job, IPP_JOB_PENDING, CUPSD_JOB_DEFAULT, 2059 "Job restarted by user."); 2060} 2061 2062 2063/* 2064 * 'cupsdSaveAllJobs()' - Save a summary of all jobs to disk. 2065 */ 2066 2067void 2068cupsdSaveAllJobs(void) 2069{ 2070 int i; /* Looping var */ 2071 cups_file_t *fp; /* job.cache file */ 2072 char filename[1024], /* job.cache filename */ 2073 temp[1024]; /* Temporary string */ 2074 cupsd_job_t *job; /* Current job */ 2075 time_t curtime; /* Current time */ 2076 struct tm *curdate; /* Current date */ 2077 2078 2079 snprintf(filename, sizeof(filename), "%s/job.cache", CacheDir); 2080 if ((fp = cupsdCreateConfFile(filename, ConfigFilePerm)) == NULL) 2081 return; 2082 2083 cupsdLogMessage(CUPSD_LOG_INFO, "Saving job.cache..."); 2084 2085 /* 2086 * Write a small header to the file... 2087 */ 2088 2089 curtime = time(NULL); 2090 curdate = localtime(&curtime); 2091 strftime(temp, sizeof(temp) - 1, "%Y-%m-%d %H:%M", curdate); 2092 2093 cupsFilePuts(fp, "# Job cache file for " CUPS_SVERSION "\n"); 2094 cupsFilePrintf(fp, "# Written by cupsd on %s\n", temp); 2095 cupsFilePrintf(fp, "NextJobId %d\n", NextJobId); 2096 2097 /* 2098 * Write each job known to the system... 2099 */ 2100 2101 for (job = (cupsd_job_t *)cupsArrayFirst(Jobs); 2102 job; 2103 job = (cupsd_job_t *)cupsArrayNext(Jobs)) 2104 { 2105 cupsFilePrintf(fp, "<Job %d>\n", job->id); 2106 cupsFilePrintf(fp, "State %d\n", job->state_value); 2107 cupsFilePrintf(fp, "Priority %d\n", job->priority); 2108 cupsFilePrintf(fp, "HoldUntil %d\n", (int)job->hold_until); 2109 cupsFilePrintf(fp, "Username %s\n", job->username); 2110 cupsFilePrintf(fp, "Destination %s\n", job->dest); 2111 cupsFilePrintf(fp, "DestType %d\n", job->dtype); 2112 cupsFilePrintf(fp, "NumFiles %d\n", job->num_files); 2113 for (i = 0; i < job->num_files; i ++) 2114 cupsFilePrintf(fp, "File %d %s/%s %d\n", i + 1, job->filetypes[i]->super, 2115 job->filetypes[i]->type, job->compressions[i]); 2116 cupsFilePuts(fp, "</Job>\n"); 2117 } 2118 2119 cupsdCloseCreatedConfFile(fp, filename); 2120} 2121 2122 2123/* 2124 * 'cupsdSaveJob()' - Save a job to disk. 2125 */ 2126 2127void 2128cupsdSaveJob(cupsd_job_t *job) /* I - Job */ 2129{ 2130 char filename[1024]; /* Job control filename */ 2131 cups_file_t *fp; /* Job file */ 2132 2133 2134 cupsdLogMessage(CUPSD_LOG_DEBUG2, "cupsdSaveJob(job=%p(%d)): job->attrs=%p", 2135 job, job->id, job->attrs); 2136 2137 snprintf(filename, sizeof(filename), "%s/c%05d", RequestRoot, job->id); 2138 2139 if ((fp = cupsdCreateConfFile(filename, ConfigFilePerm & 0600)) == NULL) 2140 return; 2141 2142 fchown(cupsFileNumber(fp), RunUser, Group); 2143 2144 job->attrs->state = IPP_IDLE; 2145 2146 if (ippWriteIO(fp, (ipp_iocb_t)cupsFileWrite, 1, NULL, 2147 job->attrs) != IPP_DATA) 2148 { 2149 cupsdLogJob(job, CUPSD_LOG_ERROR, "Unable to write job control file."); 2150 cupsFileClose(fp); 2151 return; 2152 } 2153 2154 if (!cupsdCloseCreatedConfFile(fp, filename)) 2155 { 2156 /* 2157 * Remove backup file and mark this job as clean... 2158 */ 2159 2160 strlcat(filename, ".O", sizeof(filename)); 2161 unlink(filename); 2162 2163 job->dirty = 0; 2164 } 2165} 2166 2167 2168/* 2169 * 'cupsdSetJobHoldUntil()' - Set the hold time for a job. 2170 */ 2171 2172void 2173cupsdSetJobHoldUntil(cupsd_job_t *job, /* I - Job */ 2174 const char *when, /* I - When to resume */ 2175 int update)/* I - Update job-hold-until attr? */ 2176{ 2177 time_t curtime; /* Current time */ 2178 struct tm *curdate; /* Current date */ 2179 int hour; /* Hold hour */ 2180 int minute; /* Hold minute */ 2181 int second = 0; /* Hold second */ 2182 2183 2184 cupsdLogMessage(CUPSD_LOG_DEBUG2, 2185 "cupsdSetJobHoldUntil(job=%p(%d), when=\"%s\", update=%d)", 2186 job, job->id, when, update); 2187 2188 if (update) 2189 { 2190 /* 2191 * Update the job-hold-until attribute... 2192 */ 2193 2194 ipp_attribute_t *attr; /* job-hold-until attribute */ 2195 2196 if ((attr = ippFindAttribute(job->attrs, "job-hold-until", 2197 IPP_TAG_KEYWORD)) == NULL) 2198 attr = ippFindAttribute(job->attrs, "job-hold-until", IPP_TAG_NAME); 2199 2200 if (attr) 2201 cupsdSetString(&(attr->values[0].string.text), when); 2202 else 2203 attr = ippAddString(job->attrs, IPP_TAG_JOB, IPP_TAG_KEYWORD, 2204 "job-hold-until", NULL, when); 2205 2206 if (attr) 2207 { 2208 if (isdigit(when[0] & 255)) 2209 attr->value_tag = IPP_TAG_NAME; 2210 else 2211 attr->value_tag = IPP_TAG_KEYWORD; 2212 2213 job->dirty = 1; 2214 cupsdMarkDirty(CUPSD_DIRTY_JOBS); 2215 } 2216 2217 ippSetString(job->attrs, &job->reasons, 0, "job-hold-until-specified"); 2218 } 2219 2220 /* 2221 * Update the hold time... 2222 */ 2223 2224 job->cancel_time = 0; 2225 2226 if (!strcmp(when, "indefinite") || !strcmp(when, "auth-info-required")) 2227 { 2228 /* 2229 * Hold indefinitely... 2230 */ 2231 2232 job->hold_until = 0; 2233 2234 if (MaxHoldTime > 0) 2235 job->cancel_time = time(NULL) + MaxHoldTime; 2236 } 2237 else if (!strcmp(when, "day-time")) 2238 { 2239 /* 2240 * Hold to 6am the next morning unless local time is < 6pm. 2241 */ 2242 2243 curtime = time(NULL); 2244 curdate = localtime(&curtime); 2245 2246 if (curdate->tm_hour < 18) 2247 job->hold_until = curtime; 2248 else 2249 job->hold_until = curtime + 2250 ((29 - curdate->tm_hour) * 60 + 59 - 2251 curdate->tm_min) * 60 + 60 - curdate->tm_sec; 2252 } 2253 else if (!strcmp(when, "evening") || !strcmp(when, "night")) 2254 { 2255 /* 2256 * Hold to 6pm unless local time is > 6pm or < 6am. 2257 */ 2258 2259 curtime = time(NULL); 2260 curdate = localtime(&curtime); 2261 2262 if (curdate->tm_hour < 6 || curdate->tm_hour >= 18) 2263 job->hold_until = curtime; 2264 else 2265 job->hold_until = curtime + 2266 ((17 - curdate->tm_hour) * 60 + 59 - 2267 curdate->tm_min) * 60 + 60 - curdate->tm_sec; 2268 } 2269 else if (!strcmp(when, "second-shift")) 2270 { 2271 /* 2272 * Hold to 4pm unless local time is > 4pm. 2273 */ 2274 2275 curtime = time(NULL); 2276 curdate = localtime(&curtime); 2277 2278 if (curdate->tm_hour >= 16) 2279 job->hold_until = curtime; 2280 else 2281 job->hold_until = curtime + 2282 ((15 - curdate->tm_hour) * 60 + 59 - 2283 curdate->tm_min) * 60 + 60 - curdate->tm_sec; 2284 } 2285 else if (!strcmp(when, "third-shift")) 2286 { 2287 /* 2288 * Hold to 12am unless local time is < 8am. 2289 */ 2290 2291 curtime = time(NULL); 2292 curdate = localtime(&curtime); 2293 2294 if (curdate->tm_hour < 8) 2295 job->hold_until = curtime; 2296 else 2297 job->hold_until = curtime + 2298 ((23 - curdate->tm_hour) * 60 + 59 - 2299 curdate->tm_min) * 60 + 60 - curdate->tm_sec; 2300 } 2301 else if (!strcmp(when, "weekend")) 2302 { 2303 /* 2304 * Hold to weekend unless we are in the weekend. 2305 */ 2306 2307 curtime = time(NULL); 2308 curdate = localtime(&curtime); 2309 2310 if (curdate->tm_wday == 0 || curdate->tm_wday == 6) 2311 job->hold_until = curtime; 2312 else 2313 job->hold_until = curtime + 2314 (((5 - curdate->tm_wday) * 24 + 2315 (17 - curdate->tm_hour)) * 60 + 59 - 2316 curdate->tm_min) * 60 + 60 - curdate->tm_sec; 2317 } 2318 else if (sscanf(when, "%d:%d:%d", &hour, &minute, &second) >= 2) 2319 { 2320 /* 2321 * Hold to specified GMT time (HH:MM or HH:MM:SS)... 2322 */ 2323 2324 curtime = time(NULL); 2325 curdate = gmtime(&curtime); 2326 2327 job->hold_until = curtime + 2328 ((hour - curdate->tm_hour) * 60 + minute - 2329 curdate->tm_min) * 60 + second - curdate->tm_sec; 2330 2331 /* 2332 * Hold until next day as needed... 2333 */ 2334 2335 if (job->hold_until < curtime) 2336 job->hold_until += 24 * 60 * 60; 2337 } 2338 2339 cupsdLogMessage(CUPSD_LOG_DEBUG2, "cupsdSetJobHoldUntil: hold_until=%d", 2340 (int)job->hold_until); 2341} 2342 2343 2344/* 2345 * 'cupsdSetJobPriority()' - Set the priority of a job, moving it up/down in 2346 * the list as needed. 2347 */ 2348 2349void 2350cupsdSetJobPriority( 2351 cupsd_job_t *job, /* I - Job ID */ 2352 int priority) /* I - New priority (0 to 100) */ 2353{ 2354 ipp_attribute_t *attr; /* Job attribute */ 2355 2356 2357 /* 2358 * Don't change completed jobs... 2359 */ 2360 2361 if (job->state_value >= IPP_JOB_PROCESSING) 2362 return; 2363 2364 /* 2365 * Set the new priority and re-add the job into the active list... 2366 */ 2367 2368 cupsArrayRemove(ActiveJobs, job); 2369 2370 job->priority = priority; 2371 2372 if ((attr = ippFindAttribute(job->attrs, "job-priority", 2373 IPP_TAG_INTEGER)) != NULL) 2374 attr->values[0].integer = priority; 2375 else 2376 ippAddInteger(job->attrs, IPP_TAG_JOB, IPP_TAG_INTEGER, "job-priority", 2377 priority); 2378 2379 cupsArrayAdd(ActiveJobs, job); 2380 2381 job->dirty = 1; 2382 cupsdMarkDirty(CUPSD_DIRTY_JOBS); 2383} 2384 2385 2386/* 2387 * 'cupsdSetJobState()' - Set the state of the specified print job. 2388 */ 2389 2390void 2391cupsdSetJobState( 2392 cupsd_job_t *job, /* I - Job to cancel */ 2393 ipp_jstate_t newstate, /* I - New job state */ 2394 cupsd_jobaction_t action, /* I - Action to take */ 2395 const char *message, /* I - Message to log */ 2396 ...) /* I - Additional arguments as needed */ 2397{ 2398 int i; /* Looping var */ 2399 ipp_jstate_t oldstate; /* Old state */ 2400 char filename[1024]; /* Job filename */ 2401 ipp_attribute_t *attr; /* Job attribute */ 2402 2403 2404 cupsdLogMessage(CUPSD_LOG_DEBUG2, 2405 "cupsdSetJobState(job=%p(%d), state=%d, newstate=%d, " 2406 "action=%d, message=\"%s\")", job, job->id, job->state_value, 2407 newstate, action, message ? message : "(null)"); 2408 2409 2410 /* 2411 * Make sure we have the job attributes... 2412 */ 2413 2414 if (!cupsdLoadJob(job)) 2415 return; 2416 2417 /* 2418 * Don't do anything if the state is unchanged and we aren't purging the 2419 * job... 2420 */ 2421 2422 oldstate = job->state_value; 2423 if (newstate == oldstate && action != CUPSD_JOB_PURGE) 2424 return; 2425 2426 /* 2427 * Stop any processes that are working on the current job... 2428 */ 2429 2430 if (oldstate == IPP_JOB_PROCESSING) 2431 stop_job(job, action); 2432 2433 /* 2434 * Set the new job state... 2435 */ 2436 2437 job->state_value = newstate; 2438 2439 if (job->state) 2440 job->state->values[0].integer = newstate; 2441 2442 switch (newstate) 2443 { 2444 case IPP_JOB_PENDING : 2445 /* 2446 * Update job-hold-until as needed... 2447 */ 2448 2449 if ((attr = ippFindAttribute(job->attrs, "job-hold-until", 2450 IPP_TAG_KEYWORD)) == NULL) 2451 attr = ippFindAttribute(job->attrs, "job-hold-until", IPP_TAG_NAME); 2452 2453 if (attr) 2454 { 2455 attr->value_tag = IPP_TAG_KEYWORD; 2456 cupsdSetString(&(attr->values[0].string.text), "no-hold"); 2457 } 2458 2459 default : 2460 break; 2461 2462 case IPP_JOB_ABORTED : 2463 case IPP_JOB_CANCELED : 2464 case IPP_JOB_COMPLETED : 2465 set_time(job, "time-at-completed"); 2466 ippSetString(job->attrs, &job->reasons, 0, "processing-to-stop-point"); 2467 break; 2468 } 2469 2470 /* 2471 * Log message as needed... 2472 */ 2473 2474 if (message) 2475 { 2476 char buffer[2048]; /* Message buffer */ 2477 va_list ap; /* Pointer to additional arguments */ 2478 2479 va_start(ap, message); 2480 vsnprintf(buffer, sizeof(buffer), message, ap); 2481 va_end(ap); 2482 2483 if (newstate > IPP_JOB_STOPPED) 2484 cupsdAddEvent(CUPSD_EVENT_JOB_COMPLETED, job->printer, job, "%s", buffer); 2485 else 2486 cupsdAddEvent(CUPSD_EVENT_JOB_STATE, job->printer, job, "%s", buffer); 2487 2488 if (newstate == IPP_JOB_STOPPED || newstate == IPP_JOB_ABORTED) 2489 cupsdLogJob(job, CUPSD_LOG_ERROR, "%s", buffer); 2490 else 2491 cupsdLogJob(job, CUPSD_LOG_INFO, "%s", buffer); 2492 } 2493 2494 /* 2495 * Handle post-state-change actions... 2496 */ 2497 2498 switch (newstate) 2499 { 2500 case IPP_JOB_PROCESSING : 2501 /* 2502 * Add the job to the "printing" list... 2503 */ 2504 2505 if (!cupsArrayFind(PrintingJobs, job)) 2506 cupsArrayAdd(PrintingJobs, job); 2507 2508 /* 2509 * Set the processing time... 2510 */ 2511 2512 set_time(job, "time-at-processing"); 2513 2514 case IPP_JOB_PENDING : 2515 case IPP_JOB_HELD : 2516 case IPP_JOB_STOPPED : 2517 /* 2518 * Make sure the job is in the active list... 2519 */ 2520 2521 if (!cupsArrayFind(ActiveJobs, job)) 2522 cupsArrayAdd(ActiveJobs, job); 2523 2524 /* 2525 * Save the job state to disk... 2526 */ 2527 2528 job->dirty = 1; 2529 cupsdMarkDirty(CUPSD_DIRTY_JOBS); 2530 break; 2531 2532 case IPP_JOB_ABORTED : 2533 case IPP_JOB_CANCELED : 2534 case IPP_JOB_COMPLETED : 2535 if (newstate == IPP_JOB_CANCELED) 2536 { 2537 /* 2538 * Remove the job from the active list if there are no processes still 2539 * running for it... 2540 */ 2541 2542 for (i = 0; job->filters[i] < 0; i++); 2543 2544 if (!job->filters[i] && job->backend <= 0) 2545 cupsArrayRemove(ActiveJobs, job); 2546 } 2547 else 2548 { 2549 /* 2550 * Otherwise just remove the job from the active list immediately... 2551 */ 2552 2553 cupsArrayRemove(ActiveJobs, job); 2554 } 2555 2556 /* 2557 * Expire job subscriptions since the job is now "completed"... 2558 */ 2559 2560 cupsdExpireSubscriptions(NULL, job); 2561 2562#ifdef __APPLE__ 2563 /* 2564 * If we are going to sleep and the PrintingJobs count is now 0, allow the 2565 * sleep to happen immediately... 2566 */ 2567 2568 if (Sleeping && cupsArrayCount(PrintingJobs) == 0) 2569 cupsdAllowSleep(); 2570#endif /* __APPLE__ */ 2571 2572 /* 2573 * Remove any authentication data... 2574 */ 2575 2576 snprintf(filename, sizeof(filename), "%s/a%05d", RequestRoot, job->id); 2577 if (cupsdRemoveFile(filename) && errno != ENOENT) 2578 cupsdLogMessage(CUPSD_LOG_ERROR, 2579 "Unable to remove authentication cache: %s", 2580 strerror(errno)); 2581 2582 for (i = 0; 2583 i < (int)(sizeof(job->auth_env) / sizeof(job->auth_env[0])); 2584 i ++) 2585 cupsdClearString(job->auth_env + i); 2586 2587 cupsdClearString(&job->auth_uid); 2588 2589 /* 2590 * Remove the print file for good if we aren't preserving jobs or 2591 * files... 2592 */ 2593 2594 if (!JobHistory || !JobFiles || action == CUPSD_JOB_PURGE) 2595 remove_job_files(job); 2596 2597 if (JobHistory && action != CUPSD_JOB_PURGE) 2598 { 2599 /* 2600 * Save job state info... 2601 */ 2602 2603 job->dirty = 1; 2604 cupsdMarkDirty(CUPSD_DIRTY_JOBS); 2605 } 2606 else if (!job->printer) 2607 { 2608 /* 2609 * Delete the job immediately if not actively printing... 2610 */ 2611 2612 cupsdDeleteJob(job, CUPSD_JOB_PURGE); 2613 job = NULL; 2614 } 2615 break; 2616 } 2617 2618 /* 2619 * Finalize the job immediately if we forced things... 2620 */ 2621 2622 if (action >= CUPSD_JOB_FORCE && job && job->printer) 2623 finalize_job(job, 0); 2624 2625 /* 2626 * Update the server "busy" state... 2627 */ 2628 2629 cupsdSetBusyState(); 2630} 2631 2632 2633/* 2634 * 'cupsdStopAllJobs()' - Stop all print jobs. 2635 */ 2636 2637void 2638cupsdStopAllJobs( 2639 cupsd_jobaction_t action, /* I - Action */ 2640 int kill_delay) /* I - Number of seconds before we kill */ 2641{ 2642 cupsd_job_t *job; /* Current job */ 2643 2644 2645 DEBUG_puts("cupsdStopAllJobs()"); 2646 2647 for (job = (cupsd_job_t *)cupsArrayFirst(PrintingJobs); 2648 job; 2649 job = (cupsd_job_t *)cupsArrayNext(PrintingJobs)) 2650 { 2651 if (kill_delay) 2652 job->kill_time = time(NULL) + kill_delay; 2653 2654 cupsdSetJobState(job, IPP_JOB_PENDING, action, NULL); 2655 } 2656} 2657 2658 2659/* 2660 * 'cupsdUnloadCompletedJobs()' - Flush completed job history from memory. 2661 */ 2662 2663void 2664cupsdUnloadCompletedJobs(void) 2665{ 2666 cupsd_job_t *job; /* Current job */ 2667 time_t expire; /* Expiration time */ 2668 2669 2670 expire = time(NULL) - 60; 2671 2672 for (job = (cupsd_job_t *)cupsArrayFirst(Jobs); 2673 job; 2674 job = (cupsd_job_t *)cupsArrayNext(Jobs)) 2675 if (job->attrs && job->state_value >= IPP_JOB_STOPPED && !job->printer && 2676 job->access_time < expire) 2677 { 2678 if (job->dirty) 2679 cupsdSaveJob(job); 2680 2681 unload_job(job); 2682 } 2683} 2684 2685 2686/* 2687 * 'cupsdUpdateJobs()' - Update the history/file files for all jobs. 2688 */ 2689 2690void 2691cupsdUpdateJobs(void) 2692{ 2693 cupsd_job_t *job; /* Current job */ 2694 time_t curtime; /* Current time */ 2695 ipp_attribute_t *attr; /* time-at-completed attribute */ 2696 2697 2698 curtime = time(NULL); 2699 JobHistoryUpdate = 0; 2700 2701 for (job = (cupsd_job_t *)cupsArrayFirst(Jobs); 2702 job; 2703 job = (cupsd_job_t *)cupsArrayNext(Jobs)) 2704 { 2705 if (job->state_value >= IPP_JOB_CANCELED && 2706 (attr = ippFindAttribute(job->attrs, "time-at-completed", 2707 IPP_TAG_INTEGER)) != NULL) 2708 { 2709 /* 2710 * Update history/file expiration times... 2711 */ 2712 2713 if (JobHistory < INT_MAX) 2714 job->history_time = attr->values[0].integer + JobHistory; 2715 else 2716 job->history_time = INT_MAX; 2717 2718 if (job->history_time < curtime) 2719 { 2720 cupsdDeleteJob(job, CUPSD_JOB_PURGE); 2721 continue; 2722 } 2723 2724 if (job->history_time < JobHistoryUpdate || !JobHistoryUpdate) 2725 JobHistoryUpdate = job->history_time; 2726 2727 if (JobFiles < INT_MAX) 2728 job->file_time = attr->values[0].integer + JobFiles; 2729 else 2730 job->file_time = INT_MAX; 2731 2732 if (job->file_time < JobHistoryUpdate || !JobHistoryUpdate) 2733 JobHistoryUpdate = job->file_time; 2734 } 2735 } 2736 2737 cupsdLogMessage(CUPSD_LOG_DEBUG2, "cupsdUpdateAllJobs: JobHistoryUpdate=%ld", 2738 (long)JobHistoryUpdate); 2739} 2740 2741 2742/* 2743 * 'compare_active_jobs()' - Compare the job IDs and priorities of two jobs. 2744 */ 2745 2746static int /* O - Difference */ 2747compare_active_jobs(void *first, /* I - First job */ 2748 void *second, /* I - Second job */ 2749 void *data) /* I - App data (not used) */ 2750{ 2751 int diff; /* Difference */ 2752 2753 2754 (void)data; 2755 2756 if ((diff = ((cupsd_job_t *)second)->priority - 2757 ((cupsd_job_t *)first)->priority) != 0) 2758 return (diff); 2759 else 2760 return (((cupsd_job_t *)first)->id - ((cupsd_job_t *)second)->id); 2761} 2762 2763 2764/* 2765 * 'compare_jobs()' - Compare the job IDs of two jobs. 2766 */ 2767 2768static int /* O - Difference */ 2769compare_jobs(void *first, /* I - First job */ 2770 void *second, /* I - Second job */ 2771 void *data) /* I - App data (not used) */ 2772{ 2773 (void)data; 2774 2775 return (((cupsd_job_t *)first)->id - ((cupsd_job_t *)second)->id); 2776} 2777 2778 2779/* 2780 * 'dump_job_history()' - Dump any debug messages for a job. 2781 */ 2782 2783static void 2784dump_job_history(cupsd_job_t *job) /* I - Job */ 2785{ 2786 int i, /* Looping var */ 2787 oldsize; /* Current MaxLogSize */ 2788 struct tm *date; /* Date/time value */ 2789 cupsd_joblog_t *message; /* Current message */ 2790 char temp[2048], /* Log message */ 2791 *ptr, /* Pointer into log message */ 2792 start[256], /* Start time */ 2793 end[256]; /* End time */ 2794 cupsd_printer_t *printer; /* Printer for job */ 2795 2796 2797 /* 2798 * See if we have anything to dump... 2799 */ 2800 2801 if (!job->history) 2802 return; 2803 2804 /* 2805 * Disable log rotation temporarily... 2806 */ 2807 2808 oldsize = MaxLogSize; 2809 MaxLogSize = 0; 2810 2811 /* 2812 * Copy the debug messages to the log... 2813 */ 2814 2815 message = (cupsd_joblog_t *)cupsArrayFirst(job->history); 2816 date = localtime(&(message->time)); 2817 strftime(start, sizeof(start), "%X", date); 2818 2819 message = (cupsd_joblog_t *)cupsArrayLast(job->history); 2820 date = localtime(&(message->time)); 2821 strftime(end, sizeof(end), "%X", date); 2822 2823 snprintf(temp, sizeof(temp), 2824 "[Job %d] The following messages were recorded from %s to %s", 2825 job->id, start, end); 2826 cupsdWriteErrorLog(CUPSD_LOG_DEBUG, temp); 2827 2828 for (message = (cupsd_joblog_t *)cupsArrayFirst(job->history); 2829 message; 2830 message = (cupsd_joblog_t *)cupsArrayNext(job->history)) 2831 cupsdWriteErrorLog(CUPSD_LOG_DEBUG, message->message); 2832 2833 snprintf(temp, sizeof(temp), "[Job %d] End of messages", job->id); 2834 cupsdWriteErrorLog(CUPSD_LOG_DEBUG, temp); 2835 2836 /* 2837 * Log the printer state values... 2838 */ 2839 2840 if ((printer = job->printer) == NULL) 2841 printer = cupsdFindDest(job->dest); 2842 2843 if (printer) 2844 { 2845 snprintf(temp, sizeof(temp), "[Job %d] printer-state=%d(%s)", job->id, 2846 printer->state, 2847 printer->state == IPP_PRINTER_IDLE ? "idle" : 2848 printer->state == IPP_PRINTER_PROCESSING ? "processing" : 2849 "stopped"); 2850 cupsdWriteErrorLog(CUPSD_LOG_DEBUG, temp); 2851 2852 snprintf(temp, sizeof(temp), "[Job %d] printer-state-message=\"%s\"", 2853 job->id, printer->state_message); 2854 cupsdWriteErrorLog(CUPSD_LOG_DEBUG, temp); 2855 2856 snprintf(temp, sizeof(temp), "[Job %d] printer-state-reasons=", job->id); 2857 ptr = temp + strlen(temp); 2858 if (printer->num_reasons == 0) 2859 strlcpy(ptr, "none", sizeof(temp) - (ptr - temp)); 2860 else 2861 { 2862 for (i = 0; 2863 i < printer->num_reasons && ptr < (temp + sizeof(temp) - 2); 2864 i ++) 2865 { 2866 if (i) 2867 *ptr++ = ','; 2868 2869 strlcpy(ptr, printer->reasons[i], sizeof(temp) - (ptr - temp)); 2870 ptr += strlen(ptr); 2871 } 2872 } 2873 cupsdWriteErrorLog(CUPSD_LOG_DEBUG, temp); 2874 } 2875 2876 /* 2877 * Restore log file rotation... 2878 */ 2879 2880 MaxLogSize = oldsize; 2881 2882 /* 2883 * Free all messages... 2884 */ 2885 2886 free_job_history(job); 2887} 2888 2889 2890/* 2891 * 'free_job_history()' - Free any log history. 2892 */ 2893 2894static void 2895free_job_history(cupsd_job_t *job) /* I - Job */ 2896{ 2897 char *message; /* Current message */ 2898 2899 2900 if (!job->history) 2901 return; 2902 2903 for (message = (char *)cupsArrayFirst(job->history); 2904 message; 2905 message = (char *)cupsArrayNext(job->history)) 2906 free(message); 2907 2908 cupsArrayDelete(job->history); 2909 job->history = NULL; 2910} 2911 2912 2913/* 2914 * 'finalize_job()' - Cleanup after job filter processes and support data. 2915 */ 2916 2917static void 2918finalize_job(cupsd_job_t *job, /* I - Job */ 2919 int set_job_state) /* I - 1 = set the job state */ 2920{ 2921 ipp_pstate_t printer_state; /* New printer state value */ 2922 ipp_jstate_t job_state; /* New job state value */ 2923 const char *message; /* Message for job state */ 2924 char buffer[1024]; /* Buffer for formatted messages */ 2925 2926 2927 cupsdLogMessage(CUPSD_LOG_DEBUG2, "finalize_job(job=%p(%d))", job, job->id); 2928 2929 /* 2930 * Clear the "connecting-to-device" reason, which is only valid when a printer 2931 * is processing, along with any remote printing job state... 2932 */ 2933 2934 cupsdSetPrinterReasons(job->printer, "-connecting-to-device," 2935 "cups-remote-pending," 2936 "cups-remote-pending-held," 2937 "cups-remote-processing," 2938 "cups-remote-stopped," 2939 "cups-remote-canceled," 2940 "cups-remote-aborted," 2941 "cups-remote-completed"); 2942 2943 /* 2944 * Similarly, clear the "offline-report" reason for non-USB devices since we 2945 * rarely have current information for network devices... 2946 */ 2947 2948 if (strncmp(job->printer->device_uri, "usb:", 4) && 2949 strncmp(job->printer->device_uri, "ippusb:", 7)) 2950 cupsdSetPrinterReasons(job->printer, "-offline-report"); 2951 2952 /* 2953 * Free the security profile... 2954 */ 2955 2956 cupsdDestroyProfile(job->profile); 2957 job->profile = NULL; 2958 2959 /* 2960 * Clear the unresponsive job watchdog timers... 2961 */ 2962 2963 job->cancel_time = 0; 2964 job->kill_time = 0; 2965 2966 /* 2967 * Close pipes and status buffer... 2968 */ 2969 2970 cupsdClosePipe(job->print_pipes); 2971 cupsdClosePipe(job->back_pipes); 2972 cupsdClosePipe(job->side_pipes); 2973 2974 cupsdRemoveSelect(job->status_pipes[0]); 2975 cupsdClosePipe(job->status_pipes); 2976 cupsdStatBufDelete(job->status_buffer); 2977 job->status_buffer = NULL; 2978 2979 /* 2980 * Process the exit status... 2981 */ 2982 2983 if (job->printer->state == IPP_PRINTER_PROCESSING) 2984 printer_state = IPP_PRINTER_IDLE; 2985 else 2986 printer_state = job->printer->state; 2987 2988 switch (job_state = job->state_value) 2989 { 2990 case IPP_JOB_PENDING : 2991 message = "Job paused."; 2992 break; 2993 2994 case IPP_JOB_HELD : 2995 message = "Job held."; 2996 break; 2997 2998 default : 2999 case IPP_JOB_PROCESSING : 3000 case IPP_JOB_COMPLETED : 3001 job_state = IPP_JOB_COMPLETED; 3002 message = "Job completed."; 3003 3004 if (!job->status) 3005 ippSetString(job->attrs, &job->reasons, 0, 3006 "job-completed-successfully"); 3007 break; 3008 3009 case IPP_JOB_STOPPED : 3010 message = "Job stopped."; 3011 3012 ippSetString(job->attrs, &job->reasons, 0, "job-stopped"); 3013 break; 3014 3015 case IPP_JOB_CANCELED : 3016 message = "Job canceled."; 3017 3018 ippSetString(job->attrs, &job->reasons, 0, "job-canceled-by-user"); 3019 break; 3020 3021 case IPP_JOB_ABORTED : 3022 message = "Job aborted."; 3023 break; 3024 } 3025 3026 if (job->status < 0) 3027 { 3028 /* 3029 * Backend had errors... 3030 */ 3031 3032 int exit_code; /* Exit code from backend */ 3033 3034 /* 3035 * Convert the status to an exit code. Due to the way the W* macros are 3036 * implemented on MacOS X (bug?), we have to store the exit status in a 3037 * variable first and then convert... 3038 */ 3039 3040 exit_code = -job->status; 3041 if (WIFEXITED(exit_code)) 3042 exit_code = WEXITSTATUS(exit_code); 3043 else 3044 { 3045 ippSetString(job->attrs, &job->reasons, 0, "cups-backend-crashed"); 3046 exit_code = job->status; 3047 } 3048 3049 cupsdLogJob(job, CUPSD_LOG_INFO, "Backend returned status %d (%s)", 3050 exit_code, 3051 exit_code == CUPS_BACKEND_FAILED ? "failed" : 3052 exit_code == CUPS_BACKEND_AUTH_REQUIRED ? 3053 "authentication required" : 3054 exit_code == CUPS_BACKEND_HOLD ? "hold job" : 3055 exit_code == CUPS_BACKEND_STOP ? "stop printer" : 3056 exit_code == CUPS_BACKEND_CANCEL ? "cancel job" : 3057 exit_code == CUPS_BACKEND_RETRY ? "retry job later" : 3058 exit_code == CUPS_BACKEND_RETRY_CURRENT ? "retry job immediately" : 3059 exit_code < 0 ? "crashed" : "unknown"); 3060 3061 /* 3062 * Do what needs to be done... 3063 */ 3064 3065 switch (exit_code) 3066 { 3067 default : 3068 case CUPS_BACKEND_FAILED : 3069 /* 3070 * Backend failure, use the error-policy to determine how to 3071 * act... 3072 */ 3073 3074 if (job->dtype & CUPS_PRINTER_CLASS) 3075 { 3076 /* 3077 * Queued on a class - mark the job as pending and we'll retry on 3078 * another printer... 3079 */ 3080 3081 if (job_state == IPP_JOB_COMPLETED) 3082 { 3083 job_state = IPP_JOB_PENDING; 3084 message = "Retrying job on another printer."; 3085 3086 ippSetString(job->attrs, &job->reasons, 0, 3087 "resources-are-not-ready"); 3088 } 3089 } 3090 else if (!strcmp(job->printer->error_policy, "retry-current-job")) 3091 { 3092 /* 3093 * The error policy is "retry-current-job" - mark the job as pending 3094 * and we'll retry on the same printer... 3095 */ 3096 3097 if (job_state == IPP_JOB_COMPLETED) 3098 { 3099 job_state = IPP_JOB_PENDING; 3100 message = "Retrying job on same printer."; 3101 3102 ippSetString(job->attrs, &job->reasons, 0, "none"); 3103 } 3104 } 3105 else if ((job->printer->type & CUPS_PRINTER_FAX) || 3106 !strcmp(job->printer->error_policy, "retry-job")) 3107 { 3108 if (job_state == IPP_JOB_COMPLETED) 3109 { 3110 /* 3111 * The job was queued on a fax or the error policy is "retry-job" - 3112 * hold the job if the number of retries is less than the 3113 * JobRetryLimit, otherwise abort the job. 3114 */ 3115 3116 job->tries ++; 3117 3118 if (job->tries > JobRetryLimit && JobRetryLimit > 0) 3119 { 3120 /* 3121 * Too many tries... 3122 */ 3123 3124 snprintf(buffer, sizeof(buffer), 3125 "Job aborted after %d unsuccessful attempts.", 3126 JobRetryLimit); 3127 job_state = IPP_JOB_ABORTED; 3128 message = buffer; 3129 3130 ippSetString(job->attrs, &job->reasons, 0, "aborted-by-system"); 3131 } 3132 else 3133 { 3134 /* 3135 * Try again in N seconds... 3136 */ 3137 3138 snprintf(buffer, sizeof(buffer), 3139 "Job held for %d seconds since it could not be sent.", 3140 JobRetryInterval); 3141 3142 job->hold_until = time(NULL) + JobRetryInterval; 3143 job_state = IPP_JOB_HELD; 3144 message = buffer; 3145 3146 ippSetString(job->attrs, &job->reasons, 0, 3147 "resources-are-not-ready"); 3148 } 3149 } 3150 } 3151 else if (!strcmp(job->printer->error_policy, "abort-job") && 3152 job_state == IPP_JOB_COMPLETED) 3153 { 3154 job_state = IPP_JOB_ABORTED; 3155 message = "Job aborted due to backend errors; please consult " 3156 "the error_log file for details."; 3157 3158 ippSetString(job->attrs, &job->reasons, 0, "aborted-by-system"); 3159 } 3160 else if (job->state_value == IPP_JOB_PROCESSING) 3161 { 3162 job_state = IPP_JOB_PENDING; 3163 printer_state = IPP_PRINTER_STOPPED; 3164 message = "Printer stopped due to backend errors; please " 3165 "consult the error_log file for details."; 3166 3167 ippSetString(job->attrs, &job->reasons, 0, "none"); 3168 } 3169 break; 3170 3171 case CUPS_BACKEND_CANCEL : 3172 /* 3173 * Cancel the job... 3174 */ 3175 3176 if (job_state == IPP_JOB_COMPLETED) 3177 { 3178 job_state = IPP_JOB_CANCELED; 3179 message = "Job canceled at printer."; 3180 3181 ippSetString(job->attrs, &job->reasons, 0, "canceled-at-device"); 3182 } 3183 break; 3184 3185 case CUPS_BACKEND_HOLD : 3186 if (job_state == IPP_JOB_COMPLETED) 3187 { 3188 /* 3189 * Hold the job... 3190 */ 3191 3192 const char *reason = ippGetString(job->reasons, 0, NULL); 3193 3194 cupsdLogJob(job, CUPSD_LOG_DEBUG, "job-state-reasons=\"%s\"", 3195 reason); 3196 3197 if (!reason || strncmp(reason, "account-", 8)) 3198 { 3199 cupsdSetJobHoldUntil(job, "indefinite", 1); 3200 3201 ippSetString(job->attrs, &job->reasons, 0, 3202 "job-hold-until-specified"); 3203 message = "Job held indefinitely due to backend errors; please " 3204 "consult the error_log file for details."; 3205 } 3206 else if (!strcmp(reason, "account-info-needed")) 3207 { 3208 cupsdSetJobHoldUntil(job, "indefinite", 0); 3209 3210 message = "Job held indefinitely - account information is " 3211 "required."; 3212 } 3213 else if (!strcmp(reason, "account-closed")) 3214 { 3215 cupsdSetJobHoldUntil(job, "indefinite", 0); 3216 3217 message = "Job held indefinitely - account has been closed."; 3218 } 3219 else if (!strcmp(reason, "account-limit-reached")) 3220 { 3221 cupsdSetJobHoldUntil(job, "indefinite", 0); 3222 3223 message = "Job held indefinitely - account limit has been " 3224 "reached."; 3225 } 3226 else 3227 { 3228 cupsdSetJobHoldUntil(job, "indefinite", 0); 3229 3230 message = "Job held indefinitely - account authorization failed."; 3231 } 3232 3233 job_state = IPP_JOB_HELD; 3234 } 3235 break; 3236 3237 case CUPS_BACKEND_STOP : 3238 /* 3239 * Stop the printer... 3240 */ 3241 3242 printer_state = IPP_PRINTER_STOPPED; 3243 message = "Printer stopped due to backend errors; please " 3244 "consult the error_log file for details."; 3245 3246 if (job_state == IPP_JOB_COMPLETED) 3247 { 3248 job_state = IPP_JOB_PENDING; 3249 3250 ippSetString(job->attrs, &job->reasons, 0, 3251 "resources-are-not-ready"); 3252 } 3253 break; 3254 3255 case CUPS_BACKEND_AUTH_REQUIRED : 3256 /* 3257 * Hold the job for authentication... 3258 */ 3259 3260 if (job_state == IPP_JOB_COMPLETED) 3261 { 3262 cupsdSetJobHoldUntil(job, "auth-info-required", 1); 3263 3264 job_state = IPP_JOB_HELD; 3265 message = "Job held for authentication."; 3266 3267 if (strncmp(job->reasons->values[0].string.text, "account-", 8)) 3268 ippSetString(job->attrs, &job->reasons, 0, 3269 "cups-held-for-authentication"); 3270 } 3271 break; 3272 3273 case CUPS_BACKEND_RETRY : 3274 if (job_state == IPP_JOB_COMPLETED) 3275 { 3276 /* 3277 * Hold the job if the number of retries is less than the 3278 * JobRetryLimit, otherwise abort the job. 3279 */ 3280 3281 job->tries ++; 3282 3283 if (job->tries > JobRetryLimit && JobRetryLimit > 0) 3284 { 3285 /* 3286 * Too many tries... 3287 */ 3288 3289 snprintf(buffer, sizeof(buffer), 3290 "Job aborted after %d unsuccessful attempts.", 3291 JobRetryLimit); 3292 job_state = IPP_JOB_ABORTED; 3293 message = buffer; 3294 3295 ippSetString(job->attrs, &job->reasons, 0, "aborted-by-system"); 3296 } 3297 else 3298 { 3299 /* 3300 * Try again in N seconds... 3301 */ 3302 3303 snprintf(buffer, sizeof(buffer), 3304 "Job held for %d seconds since it could not be sent.", 3305 JobRetryInterval); 3306 3307 job->hold_until = time(NULL) + JobRetryInterval; 3308 job_state = IPP_JOB_HELD; 3309 message = buffer; 3310 3311 ippSetString(job->attrs, &job->reasons, 0, 3312 "resources-are-not-ready"); 3313 } 3314 } 3315 break; 3316 3317 case CUPS_BACKEND_RETRY_CURRENT : 3318 /* 3319 * Mark the job as pending and retry on the same printer... 3320 */ 3321 3322 if (job_state == IPP_JOB_COMPLETED) 3323 { 3324 job_state = IPP_JOB_PENDING; 3325 message = "Retrying job on same printer."; 3326 3327 ippSetString(job->attrs, &job->reasons, 0, "none"); 3328 } 3329 break; 3330 } 3331 } 3332 else if (job->status > 0) 3333 { 3334 /* 3335 * Filter had errors; stop job... 3336 */ 3337 3338 if (job_state == IPP_JOB_COMPLETED) 3339 { 3340 job_state = IPP_JOB_STOPPED; 3341 message = "Job stopped due to filter errors; please consult the " 3342 "error_log file for details."; 3343 3344 if (WIFSIGNALED(job->status)) 3345 ippSetString(job->attrs, &job->reasons, 0, "cups-filter-crashed"); 3346 else 3347 ippSetString(job->attrs, &job->reasons, 0, "job-completed-with-errors"); 3348 } 3349 } 3350 3351 /* 3352 * Update the printer and job state. 3353 */ 3354 3355 if (set_job_state && job_state != job->state_value) 3356 cupsdSetJobState(job, job_state, CUPSD_JOB_DEFAULT, "%s", message); 3357 3358 cupsdSetPrinterState(job->printer, printer_state, 3359 printer_state == IPP_PRINTER_STOPPED); 3360 update_job_attrs(job, 0); 3361 3362 if (job->history) 3363 { 3364 if (job->status && 3365 (job->state_value == IPP_JOB_ABORTED || 3366 job->state_value == IPP_JOB_STOPPED)) 3367 dump_job_history(job); 3368 else 3369 free_job_history(job); 3370 } 3371 3372 cupsArrayRemove(PrintingJobs, job); 3373 3374 /* 3375 * Apply any PPD updates... 3376 */ 3377 3378 if (job->num_keywords) 3379 { 3380 if (cupsdUpdatePrinterPPD(job->printer, job->num_keywords, job->keywords)) 3381 cupsdSetPrinterAttrs(job->printer); 3382 3383 cupsFreeOptions(job->num_keywords, job->keywords); 3384 3385 job->num_keywords = 0; 3386 job->keywords = NULL; 3387 } 3388 3389 /* 3390 * Clear the printer <-> job association... 3391 */ 3392 3393 job->printer->job = NULL; 3394 job->printer = NULL; 3395 3396 /* 3397 * Try printing another job... 3398 */ 3399 3400 if (printer_state != IPP_PRINTER_STOPPED) 3401 cupsdCheckJobs(); 3402} 3403 3404 3405/* 3406 * 'get_options()' - Get a string containing the job options. 3407 */ 3408 3409static char * /* O - Options string */ 3410get_options(cupsd_job_t *job, /* I - Job */ 3411 int banner_page, /* I - Printing a banner page? */ 3412 char *copies, /* I - Copies buffer */ 3413 size_t copies_size, /* I - Size of copies buffer */ 3414 char *title, /* I - Title buffer */ 3415 size_t title_size) /* I - Size of title buffer */ 3416{ 3417 int i; /* Looping var */ 3418 size_t newlength; /* New option buffer length */ 3419 char *optptr, /* Pointer to options */ 3420 *valptr; /* Pointer in value string */ 3421 ipp_attribute_t *attr; /* Current attribute */ 3422 _ppd_cache_t *pc; /* PPD cache and mapping data */ 3423 int num_pwgppds; /* Number of PWG->PPD options */ 3424 cups_option_t *pwgppds, /* PWG->PPD options */ 3425 *pwgppd, /* Current PWG->PPD option */ 3426 *preset; /* Current preset option */ 3427 int print_color_mode, 3428 /* Output mode (if any) */ 3429 print_quality; /* Print quality (if any) */ 3430 const char *ppd; /* PPD option choice */ 3431 int exact; /* Did we get an exact match? */ 3432 static char *options = NULL;/* Full list of options */ 3433 static size_t optlength = 0; /* Length of option buffer */ 3434 3435 3436 /* 3437 * Building the options string is harder than it needs to be, but for the 3438 * moment we need to pass strings for command-line args and not IPP attribute 3439 * pointers... :) 3440 * 3441 * First build an options array for any PWG->PPD mapped option/choice pairs. 3442 */ 3443 3444 pc = job->printer->pc; 3445 num_pwgppds = 0; 3446 pwgppds = NULL; 3447 3448 if (pc && 3449 !ippFindAttribute(job->attrs, 3450 "com.apple.print.DocumentTicket.PMSpoolFormat", 3451 IPP_TAG_ZERO) && 3452 !ippFindAttribute(job->attrs, "APPrinterPreset", IPP_TAG_ZERO) && 3453 (ippFindAttribute(job->attrs, "print-color-mode", IPP_TAG_ZERO) || 3454 ippFindAttribute(job->attrs, "print-quality", IPP_TAG_ZERO))) 3455 { 3456 /* 3457 * Map print-color-mode and print-quality to a preset... 3458 */ 3459 3460 if ((attr = ippFindAttribute(job->attrs, "print-color-mode", 3461 IPP_TAG_KEYWORD)) != NULL && 3462 !strcmp(attr->values[0].string.text, "monochrome")) 3463 print_color_mode = _PWG_PRINT_COLOR_MODE_MONOCHROME; 3464 else 3465 print_color_mode = _PWG_PRINT_COLOR_MODE_COLOR; 3466 3467 if ((attr = ippFindAttribute(job->attrs, "print-quality", 3468 IPP_TAG_ENUM)) != NULL && 3469 attr->values[0].integer >= IPP_QUALITY_DRAFT && 3470 attr->values[0].integer <= IPP_QUALITY_HIGH) 3471 print_quality = attr->values[0].integer - IPP_QUALITY_DRAFT; 3472 else 3473 print_quality = _PWG_PRINT_QUALITY_NORMAL; 3474 3475 if (pc->num_presets[print_color_mode][print_quality] == 0) 3476 { 3477 /* 3478 * Try to find a preset that works so that we maximize the chances of us 3479 * getting a good print using IPP attributes. 3480 */ 3481 3482 if (pc->num_presets[print_color_mode][_PWG_PRINT_QUALITY_NORMAL] > 0) 3483 print_quality = _PWG_PRINT_QUALITY_NORMAL; 3484 else if (pc->num_presets[_PWG_PRINT_COLOR_MODE_COLOR][print_quality] > 0) 3485 print_color_mode = _PWG_PRINT_COLOR_MODE_COLOR; 3486 else 3487 { 3488 print_quality = _PWG_PRINT_QUALITY_NORMAL; 3489 print_color_mode = _PWG_PRINT_COLOR_MODE_COLOR; 3490 } 3491 } 3492 3493 if (pc->num_presets[print_color_mode][print_quality] > 0) 3494 { 3495 /* 3496 * Copy the preset options as long as the corresponding names are not 3497 * already defined in the IPP request... 3498 */ 3499 3500 for (i = pc->num_presets[print_color_mode][print_quality], 3501 preset = pc->presets[print_color_mode][print_quality]; 3502 i > 0; 3503 i --, preset ++) 3504 { 3505 if (!ippFindAttribute(job->attrs, preset->name, IPP_TAG_ZERO)) 3506 num_pwgppds = cupsAddOption(preset->name, preset->value, num_pwgppds, 3507 &pwgppds); 3508 } 3509 } 3510 } 3511 3512 if (pc) 3513 { 3514 if (!ippFindAttribute(job->attrs, "InputSlot", IPP_TAG_ZERO) && 3515 !ippFindAttribute(job->attrs, "HPPaperSource", IPP_TAG_ZERO)) 3516 { 3517 if ((ppd = _ppdCacheGetInputSlot(pc, job->attrs, NULL)) != NULL) 3518 num_pwgppds = cupsAddOption(pc->source_option, ppd, num_pwgppds, 3519 &pwgppds); 3520 } 3521 if (!ippFindAttribute(job->attrs, "MediaType", IPP_TAG_ZERO) && 3522 (ppd = _ppdCacheGetMediaType(pc, job->attrs, NULL)) != NULL) 3523 num_pwgppds = cupsAddOption("MediaType", ppd, num_pwgppds, &pwgppds); 3524 3525 if (!ippFindAttribute(job->attrs, "PageRegion", IPP_TAG_ZERO) && 3526 !ippFindAttribute(job->attrs, "PageSize", IPP_TAG_ZERO) && 3527 (ppd = _ppdCacheGetPageSize(pc, job->attrs, NULL, &exact)) != NULL) 3528 { 3529 num_pwgppds = cupsAddOption("PageSize", ppd, num_pwgppds, &pwgppds); 3530 3531 if (!ippFindAttribute(job->attrs, "media", IPP_TAG_ZERO)) 3532 num_pwgppds = cupsAddOption("media", ppd, num_pwgppds, &pwgppds); 3533 } 3534 3535 if (!ippFindAttribute(job->attrs, "OutputBin", IPP_TAG_ZERO) && 3536 (attr = ippFindAttribute(job->attrs, "output-bin", 3537 IPP_TAG_ZERO)) != NULL && 3538 (attr->value_tag == IPP_TAG_KEYWORD || 3539 attr->value_tag == IPP_TAG_NAME) && 3540 (ppd = _ppdCacheGetOutputBin(pc, attr->values[0].string.text)) != NULL) 3541 { 3542 /* 3543 * Map output-bin to OutputBin option... 3544 */ 3545 3546 num_pwgppds = cupsAddOption("OutputBin", ppd, num_pwgppds, &pwgppds); 3547 } 3548 3549 if (pc->sides_option && 3550 !ippFindAttribute(job->attrs, pc->sides_option, IPP_TAG_ZERO) && 3551 (attr = ippFindAttribute(job->attrs, "sides", IPP_TAG_KEYWORD)) != NULL) 3552 { 3553 /* 3554 * Map sides to duplex option... 3555 */ 3556 3557 if (!strcmp(attr->values[0].string.text, "one-sided")) 3558 num_pwgppds = cupsAddOption(pc->sides_option, pc->sides_1sided, 3559 num_pwgppds, &pwgppds); 3560 else if (!strcmp(attr->values[0].string.text, "two-sided-long-edge")) 3561 num_pwgppds = cupsAddOption(pc->sides_option, pc->sides_2sided_long, 3562 num_pwgppds, &pwgppds); 3563 else if (!strcmp(attr->values[0].string.text, "two-sided-short-edge")) 3564 num_pwgppds = cupsAddOption(pc->sides_option, pc->sides_2sided_short, 3565 num_pwgppds, &pwgppds); 3566 } 3567 3568 /* 3569 * Map finishings values... 3570 */ 3571 3572 num_pwgppds = _ppdCacheGetFinishingOptions(pc, job->attrs, 3573 IPP_FINISHINGS_NONE, num_pwgppds, 3574 &pwgppds); 3575 } 3576 3577 /* 3578 * Figure out how much room we need... 3579 */ 3580 3581 newlength = ipp_length(job->attrs); 3582 3583 for (i = num_pwgppds, pwgppd = pwgppds; i > 0; i --, pwgppd ++) 3584 newlength += 1 + strlen(pwgppd->name) + 1 + strlen(pwgppd->value); 3585 3586 /* 3587 * Then allocate/reallocate the option buffer as needed... 3588 */ 3589 3590 if (newlength == 0) /* This can never happen, but Clang */ 3591 newlength = 1; /* thinks it can... */ 3592 3593 if (newlength > optlength || !options) 3594 { 3595 if (!options) 3596 optptr = malloc(newlength); 3597 else 3598 optptr = realloc(options, newlength); 3599 3600 if (!optptr) 3601 { 3602 cupsdLogJob(job, CUPSD_LOG_CRIT, 3603 "Unable to allocate " CUPS_LLFMT " bytes for option buffer.", 3604 CUPS_LLCAST newlength); 3605 return (NULL); 3606 } 3607 3608 options = optptr; 3609 optlength = newlength; 3610 } 3611 3612 /* 3613 * Now loop through the attributes and convert them to the textual 3614 * representation used by the filters... 3615 */ 3616 3617 optptr = options; 3618 *optptr = '\0'; 3619 3620 snprintf(title, title_size, "%s-%d", job->printer->name, job->id); 3621 strlcpy(copies, "1", copies_size); 3622 3623 for (attr = job->attrs->attrs; attr != NULL; attr = attr->next) 3624 { 3625 if (!strcmp(attr->name, "copies") && 3626 attr->value_tag == IPP_TAG_INTEGER) 3627 { 3628 /* 3629 * Don't use the # copies attribute if we are printing the job sheets... 3630 */ 3631 3632 if (!banner_page) 3633 snprintf(copies, copies_size, "%d", attr->values[0].integer); 3634 } 3635 else if (!strcmp(attr->name, "job-name") && 3636 (attr->value_tag == IPP_TAG_NAME || 3637 attr->value_tag == IPP_TAG_NAMELANG)) 3638 strlcpy(title, attr->values[0].string.text, title_size); 3639 else if (attr->group_tag == IPP_TAG_JOB) 3640 { 3641 /* 3642 * Filter out other unwanted attributes... 3643 */ 3644 3645 if (attr->value_tag == IPP_TAG_NOVALUE || 3646 attr->value_tag == IPP_TAG_MIMETYPE || 3647 attr->value_tag == IPP_TAG_NAMELANG || 3648 attr->value_tag == IPP_TAG_TEXTLANG || 3649 (attr->value_tag == IPP_TAG_URI && strcmp(attr->name, "job-uuid") && 3650 strcmp(attr->name, "job-authorization-uri")) || 3651 attr->value_tag == IPP_TAG_URISCHEME || 3652 attr->value_tag == IPP_TAG_BEGIN_COLLECTION) /* Not yet supported */ 3653 continue; 3654 3655 if (!strcmp(attr->name, "job-hold-until") || 3656 !strcmp(attr->name, "job-id") || 3657 !strcmp(attr->name, "job-k-octets") || 3658 !strcmp(attr->name, "job-media-sheets") || 3659 !strcmp(attr->name, "job-media-sheets-completed") || 3660 !strcmp(attr->name, "job-state") || 3661 !strcmp(attr->name, "job-state-reasons")) 3662 continue; 3663 3664 if (!strncmp(attr->name, "job-", 4) && 3665 strcmp(attr->name, "job-account-id") && 3666 strcmp(attr->name, "job-accounting-user-id") && 3667 strcmp(attr->name, "job-authorization-uri") && 3668 strcmp(attr->name, "job-billing") && 3669 strcmp(attr->name, "job-impressions") && 3670 strcmp(attr->name, "job-originating-host-name") && 3671 strcmp(attr->name, "job-password") && 3672 strcmp(attr->name, "job-password-encryption") && 3673 strcmp(attr->name, "job-uuid") && 3674 !(job->printer->type & CUPS_PRINTER_REMOTE)) 3675 continue; 3676 3677 if ((!strcmp(attr->name, "job-impressions") || 3678 !strcmp(attr->name, "page-label") || 3679 !strcmp(attr->name, "page-border") || 3680 !strncmp(attr->name, "number-up", 9) || 3681 !strcmp(attr->name, "page-ranges") || 3682 !strcmp(attr->name, "page-set") || 3683 !_cups_strcasecmp(attr->name, "AP_FIRSTPAGE_InputSlot") || 3684 !_cups_strcasecmp(attr->name, "AP_FIRSTPAGE_ManualFeed") || 3685 !_cups_strcasecmp(attr->name, "com.apple.print.PrintSettings." 3686 "PMTotalSidesImaged..n.") || 3687 !_cups_strcasecmp(attr->name, "com.apple.print.PrintSettings." 3688 "PMTotalBeginPages..n.")) && 3689 banner_page) 3690 continue; 3691 3692 /* 3693 * Otherwise add them to the list... 3694 */ 3695 3696 if (optptr > options) 3697 strlcat(optptr, " ", optlength - (optptr - options)); 3698 3699 if (attr->value_tag != IPP_TAG_BOOLEAN) 3700 { 3701 strlcat(optptr, attr->name, optlength - (optptr - options)); 3702 strlcat(optptr, "=", optlength - (optptr - options)); 3703 } 3704 3705 for (i = 0; i < attr->num_values; i ++) 3706 { 3707 if (i) 3708 strlcat(optptr, ",", optlength - (optptr - options)); 3709 3710 optptr += strlen(optptr); 3711 3712 switch (attr->value_tag) 3713 { 3714 case IPP_TAG_INTEGER : 3715 case IPP_TAG_ENUM : 3716 snprintf(optptr, optlength - (optptr - options), 3717 "%d", attr->values[i].integer); 3718 break; 3719 3720 case IPP_TAG_BOOLEAN : 3721 if (!attr->values[i].boolean) 3722 strlcat(optptr, "no", optlength - (optptr - options)); 3723 3724 strlcat(optptr, attr->name, 3725 optlength - (optptr - options)); 3726 break; 3727 3728 case IPP_TAG_RANGE : 3729 if (attr->values[i].range.lower == attr->values[i].range.upper) 3730 snprintf(optptr, optlength - (optptr - options) - 1, 3731 "%d", attr->values[i].range.lower); 3732 else 3733 snprintf(optptr, optlength - (optptr - options) - 1, 3734 "%d-%d", attr->values[i].range.lower, 3735 attr->values[i].range.upper); 3736 break; 3737 3738 case IPP_TAG_RESOLUTION : 3739 snprintf(optptr, optlength - (optptr - options) - 1, 3740 "%dx%d%s", attr->values[i].resolution.xres, 3741 attr->values[i].resolution.yres, 3742 attr->values[i].resolution.units == IPP_RES_PER_INCH ? 3743 "dpi" : "dpcm"); 3744 break; 3745 3746 case IPP_TAG_STRING : 3747 case IPP_TAG_TEXT : 3748 case IPP_TAG_NAME : 3749 case IPP_TAG_KEYWORD : 3750 case IPP_TAG_CHARSET : 3751 case IPP_TAG_LANGUAGE : 3752 case IPP_TAG_URI : 3753 for (valptr = attr->values[i].string.text; *valptr;) 3754 { 3755 if (strchr(" \t\n\\\'\"", *valptr)) 3756 *optptr++ = '\\'; 3757 *optptr++ = *valptr++; 3758 } 3759 3760 *optptr = '\0'; 3761 break; 3762 3763 default : 3764 break; /* anti-compiler-warning-code */ 3765 } 3766 } 3767 3768 optptr += strlen(optptr); 3769 } 3770 } 3771 3772 /* 3773 * Finally loop through the PWG->PPD mapped options and add them... 3774 */ 3775 3776 for (i = num_pwgppds, pwgppd = pwgppds; i > 0; i --, pwgppd ++) 3777 { 3778 *optptr++ = ' '; 3779 strlcpy(optptr, pwgppd->name, optlength - (optptr - options)); 3780 optptr += strlen(optptr); 3781 *optptr++ = '='; 3782 strlcpy(optptr, pwgppd->value, optlength - (optptr - options)); 3783 optptr += strlen(optptr); 3784 } 3785 3786 cupsFreeOptions(num_pwgppds, pwgppds); 3787 3788 /* 3789 * Return the options string... 3790 */ 3791 3792 return (options); 3793} 3794 3795 3796/* 3797 * 'ipp_length()' - Compute the size of the buffer needed to hold 3798 * the textual IPP attributes. 3799 */ 3800 3801static size_t /* O - Size of attribute buffer */ 3802ipp_length(ipp_t *ipp) /* I - IPP request */ 3803{ 3804 size_t bytes; /* Number of bytes */ 3805 int i; /* Looping var */ 3806 ipp_attribute_t *attr; /* Current attribute */ 3807 3808 3809 /* 3810 * Loop through all attributes... 3811 */ 3812 3813 bytes = 0; 3814 3815 for (attr = ipp->attrs; attr != NULL; attr = attr->next) 3816 { 3817 /* 3818 * Skip attributes that won't be sent to filters... 3819 */ 3820 3821 if (attr->value_tag == IPP_TAG_NOVALUE || 3822 attr->value_tag == IPP_TAG_MIMETYPE || 3823 attr->value_tag == IPP_TAG_NAMELANG || 3824 attr->value_tag == IPP_TAG_TEXTLANG || 3825 attr->value_tag == IPP_TAG_URI || 3826 attr->value_tag == IPP_TAG_URISCHEME) 3827 continue; 3828 3829 /* 3830 * Add space for a leading space and commas between each value. 3831 * For the first attribute, the leading space isn't used, so the 3832 * extra byte can be used as the nul terminator... 3833 */ 3834 3835 bytes ++; /* " " separator */ 3836 bytes += attr->num_values; /* "," separators */ 3837 3838 /* 3839 * Boolean attributes appear as "foo,nofoo,foo,nofoo", while 3840 * other attributes appear as "foo=value1,value2,...,valueN". 3841 */ 3842 3843 if (attr->value_tag != IPP_TAG_BOOLEAN) 3844 bytes += strlen(attr->name); 3845 else 3846 bytes += attr->num_values * strlen(attr->name); 3847 3848 /* 3849 * Now add the size required for each value in the attribute... 3850 */ 3851 3852 switch (attr->value_tag) 3853 { 3854 case IPP_TAG_INTEGER : 3855 case IPP_TAG_ENUM : 3856 /* 3857 * Minimum value of a signed integer is -2147483647, or 11 digits. 3858 */ 3859 3860 bytes += attr->num_values * 11; 3861 break; 3862 3863 case IPP_TAG_BOOLEAN : 3864 /* 3865 * Add two bytes for each false ("no") value... 3866 */ 3867 3868 for (i = 0; i < attr->num_values; i ++) 3869 if (!attr->values[i].boolean) 3870 bytes += 2; 3871 break; 3872 3873 case IPP_TAG_RANGE : 3874 /* 3875 * A range is two signed integers separated by a hyphen, or 3876 * 23 characters max. 3877 */ 3878 3879 bytes += attr->num_values * 23; 3880 break; 3881 3882 case IPP_TAG_RESOLUTION : 3883 /* 3884 * A resolution is two signed integers separated by an "x" and 3885 * suffixed by the units, or 26 characters max. 3886 */ 3887 3888 bytes += attr->num_values * 26; 3889 break; 3890 3891 case IPP_TAG_STRING : 3892 case IPP_TAG_TEXT : 3893 case IPP_TAG_NAME : 3894 case IPP_TAG_KEYWORD : 3895 case IPP_TAG_CHARSET : 3896 case IPP_TAG_LANGUAGE : 3897 case IPP_TAG_URI : 3898 /* 3899 * Strings can contain characters that need quoting. We need 3900 * at least 2 * len + 2 characters to cover the quotes and 3901 * any backslashes in the string. 3902 */ 3903 3904 for (i = 0; i < attr->num_values; i ++) 3905 bytes += 2 * strlen(attr->values[i].string.text) + 2; 3906 break; 3907 3908 default : 3909 break; /* anti-compiler-warning-code */ 3910 } 3911 } 3912 3913 return (bytes); 3914} 3915 3916 3917/* 3918 * 'load_job_cache()' - Load jobs from the job.cache file. 3919 */ 3920 3921static void 3922load_job_cache(const char *filename) /* I - job.cache filename */ 3923{ 3924 cups_file_t *fp; /* job.cache file */ 3925 char line[1024], /* Line buffer */ 3926 *value; /* Value on line */ 3927 int linenum; /* Line number in file */ 3928 cupsd_job_t *job; /* Current job */ 3929 int jobid; /* Job ID */ 3930 char jobfile[1024]; /* Job filename */ 3931 3932 3933 /* 3934 * Open the job.cache file... 3935 */ 3936 3937 if ((fp = cupsdOpenConfFile(filename)) == NULL) 3938 { 3939 load_request_root(); 3940 return; 3941 } 3942 3943 /* 3944 * Read entries from the job cache file and create jobs as needed. 3945 */ 3946 3947 cupsdLogMessage(CUPSD_LOG_INFO, "Loading job cache file \"%s\"...", 3948 filename); 3949 3950 linenum = 0; 3951 job = NULL; 3952 3953 while (cupsFileGetConf(fp, line, sizeof(line), &value, &linenum)) 3954 { 3955 if (!_cups_strcasecmp(line, "NextJobId")) 3956 { 3957 if (value) 3958 NextJobId = atoi(value); 3959 } 3960 else if (!_cups_strcasecmp(line, "<Job")) 3961 { 3962 if (job) 3963 { 3964 cupsdLogMessage(CUPSD_LOG_ERROR, "Missing </Job> directive on line %d.", 3965 linenum); 3966 continue; 3967 } 3968 3969 if (!value) 3970 { 3971 cupsdLogMessage(CUPSD_LOG_ERROR, "Missing job ID on line %d.", linenum); 3972 continue; 3973 } 3974 3975 jobid = atoi(value); 3976 3977 if (jobid < 1) 3978 { 3979 cupsdLogMessage(CUPSD_LOG_ERROR, "Bad job ID %d on line %d.", jobid, 3980 linenum); 3981 continue; 3982 } 3983 3984 snprintf(jobfile, sizeof(jobfile), "%s/c%05d", RequestRoot, jobid); 3985 if (access(jobfile, 0)) 3986 { 3987 snprintf(jobfile, sizeof(jobfile), "%s/c%05d.N", RequestRoot, jobid); 3988 if (access(jobfile, 0)) 3989 { 3990 cupsdLogMessage(CUPSD_LOG_ERROR, "[Job %d] Files have gone away.", 3991 jobid); 3992 continue; 3993 } 3994 } 3995 3996 job = calloc(1, sizeof(cupsd_job_t)); 3997 if (!job) 3998 { 3999 cupsdLogMessage(CUPSD_LOG_EMERG, 4000 "[Job %d] Unable to allocate memory for job.", jobid); 4001 break; 4002 } 4003 4004 job->id = jobid; 4005 job->back_pipes[0] = -1; 4006 job->back_pipes[1] = -1; 4007 job->print_pipes[0] = -1; 4008 job->print_pipes[1] = -1; 4009 job->side_pipes[0] = -1; 4010 job->side_pipes[1] = -1; 4011 job->status_pipes[0] = -1; 4012 job->status_pipes[1] = -1; 4013 4014 cupsdLogJob(job, CUPSD_LOG_DEBUG, "Loading from cache..."); 4015 } 4016 else if (!job) 4017 { 4018 cupsdLogMessage(CUPSD_LOG_ERROR, 4019 "Missing <Job #> directive on line %d.", linenum); 4020 continue; 4021 } 4022 else if (!_cups_strcasecmp(line, "</Job>")) 4023 { 4024 cupsArrayAdd(Jobs, job); 4025 4026 if (job->state_value <= IPP_JOB_STOPPED && cupsdLoadJob(job)) 4027 cupsArrayAdd(ActiveJobs, job); 4028 4029 job = NULL; 4030 } 4031 else if (!value) 4032 { 4033 cupsdLogMessage(CUPSD_LOG_ERROR, "Missing value on line %d.", linenum); 4034 continue; 4035 } 4036 else if (!_cups_strcasecmp(line, "State")) 4037 { 4038 job->state_value = (ipp_jstate_t)atoi(value); 4039 4040 if (job->state_value < IPP_JOB_PENDING) 4041 job->state_value = IPP_JOB_PENDING; 4042 else if (job->state_value > IPP_JOB_COMPLETED) 4043 job->state_value = IPP_JOB_COMPLETED; 4044 } 4045 else if (!_cups_strcasecmp(line, "HoldUntil")) 4046 { 4047 job->hold_until = atoi(value); 4048 } 4049 else if (!_cups_strcasecmp(line, "Priority")) 4050 { 4051 job->priority = atoi(value); 4052 } 4053 else if (!_cups_strcasecmp(line, "Username")) 4054 { 4055 cupsdSetString(&job->username, value); 4056 } 4057 else if (!_cups_strcasecmp(line, "Destination")) 4058 { 4059 cupsdSetString(&job->dest, value); 4060 } 4061 else if (!_cups_strcasecmp(line, "DestType")) 4062 { 4063 job->dtype = (cups_ptype_t)atoi(value); 4064 } 4065 else if (!_cups_strcasecmp(line, "NumFiles")) 4066 { 4067 job->num_files = atoi(value); 4068 4069 if (job->num_files < 0) 4070 { 4071 cupsdLogMessage(CUPSD_LOG_ERROR, "Bad NumFiles value %d on line %d.", 4072 job->num_files, linenum); 4073 job->num_files = 0; 4074 continue; 4075 } 4076 4077 if (job->num_files > 0) 4078 { 4079 snprintf(jobfile, sizeof(jobfile), "%s/d%05d-001", RequestRoot, 4080 job->id); 4081 if (access(jobfile, 0)) 4082 { 4083 cupsdLogJob(job, CUPSD_LOG_INFO, "Data files have gone away."); 4084 job->num_files = 0; 4085 continue; 4086 } 4087 4088 job->filetypes = calloc(job->num_files, sizeof(mime_type_t *)); 4089 job->compressions = calloc(job->num_files, sizeof(int)); 4090 4091 if (!job->filetypes || !job->compressions) 4092 { 4093 cupsdLogJob(job, CUPSD_LOG_EMERG, 4094 "Unable to allocate memory for %d files.", 4095 job->num_files); 4096 break; 4097 } 4098 } 4099 } 4100 else if (!_cups_strcasecmp(line, "File")) 4101 { 4102 int number, /* File number */ 4103 compression; /* Compression value */ 4104 char super[MIME_MAX_SUPER], /* MIME super type */ 4105 type[MIME_MAX_TYPE]; /* MIME type */ 4106 4107 4108 if (sscanf(value, "%d%*[ \t]%15[^/]/%255s%d", &number, super, type, 4109 &compression) != 4) 4110 { 4111 cupsdLogMessage(CUPSD_LOG_ERROR, "Bad File on line %d.", linenum); 4112 continue; 4113 } 4114 4115 if (number < 1 || number > job->num_files) 4116 { 4117 cupsdLogMessage(CUPSD_LOG_ERROR, "Bad File number %d on line %d.", 4118 number, linenum); 4119 continue; 4120 } 4121 4122 number --; 4123 4124 job->compressions[number] = compression; 4125 job->filetypes[number] = mimeType(MimeDatabase, super, type); 4126 4127 if (!job->filetypes[number]) 4128 { 4129 /* 4130 * If the original MIME type is unknown, auto-type it! 4131 */ 4132 4133 cupsdLogJob(job, CUPSD_LOG_ERROR, 4134 "Unknown MIME type %s/%s for file %d.", 4135 super, type, number + 1); 4136 4137 snprintf(jobfile, sizeof(jobfile), "%s/d%05d-%03d", RequestRoot, 4138 job->id, number + 1); 4139 job->filetypes[number] = mimeFileType(MimeDatabase, jobfile, NULL, 4140 job->compressions + number); 4141 4142 /* 4143 * If that didn't work, assume it is raw... 4144 */ 4145 4146 if (!job->filetypes[number]) 4147 job->filetypes[number] = mimeType(MimeDatabase, "application", 4148 "vnd.cups-raw"); 4149 } 4150 } 4151 else 4152 cupsdLogMessage(CUPSD_LOG_ERROR, "Unknown %s directive on line %d.", 4153 line, linenum); 4154 } 4155 4156 if (job) 4157 { 4158 cupsdLogMessage(CUPSD_LOG_ERROR, 4159 "Missing </Job> directive on line %d.", linenum); 4160 cupsdDeleteJob(job, CUPSD_JOB_PURGE); 4161 } 4162 4163 cupsFileClose(fp); 4164} 4165 4166 4167/* 4168 * 'load_next_job_id()' - Load the NextJobId value from the job.cache file. 4169 */ 4170 4171static void 4172load_next_job_id(const char *filename) /* I - job.cache filename */ 4173{ 4174 cups_file_t *fp; /* job.cache file */ 4175 char line[1024], /* Line buffer */ 4176 *value; /* Value on line */ 4177 int linenum; /* Line number in file */ 4178 int next_job_id; /* NextJobId value from line */ 4179 4180 4181 /* 4182 * Read the NextJobId directive from the job.cache file and use 4183 * the value (if any). 4184 */ 4185 4186 if ((fp = cupsFileOpen(filename, "r")) == NULL) 4187 { 4188 if (errno != ENOENT) 4189 cupsdLogMessage(CUPSD_LOG_ERROR, 4190 "Unable to open job cache file \"%s\": %s", 4191 filename, strerror(errno)); 4192 4193 return; 4194 } 4195 4196 cupsdLogMessage(CUPSD_LOG_INFO, 4197 "Loading NextJobId from job cache file \"%s\"...", filename); 4198 4199 linenum = 0; 4200 4201 while (cupsFileGetConf(fp, line, sizeof(line), &value, &linenum)) 4202 { 4203 if (!_cups_strcasecmp(line, "NextJobId")) 4204 { 4205 if (value) 4206 { 4207 next_job_id = atoi(value); 4208 4209 if (next_job_id > NextJobId) 4210 NextJobId = next_job_id; 4211 } 4212 break; 4213 } 4214 } 4215 4216 cupsFileClose(fp); 4217} 4218 4219 4220/* 4221 * 'load_request_root()' - Load jobs from the RequestRoot directory. 4222 */ 4223 4224static void 4225load_request_root(void) 4226{ 4227 cups_dir_t *dir; /* Directory */ 4228 cups_dentry_t *dent; /* Directory entry */ 4229 cupsd_job_t *job; /* New job */ 4230 4231 4232 /* 4233 * Open the requests directory... 4234 */ 4235 4236 cupsdLogMessage(CUPSD_LOG_DEBUG, "Scanning %s for jobs...", RequestRoot); 4237 4238 if ((dir = cupsDirOpen(RequestRoot)) == NULL) 4239 { 4240 cupsdLogMessage(CUPSD_LOG_ERROR, 4241 "Unable to open spool directory \"%s\": %s", 4242 RequestRoot, strerror(errno)); 4243 return; 4244 } 4245 4246 /* 4247 * Read all the c##### files... 4248 */ 4249 4250 while ((dent = cupsDirRead(dir)) != NULL) 4251 if (strlen(dent->filename) >= 6 && dent->filename[0] == 'c') 4252 { 4253 /* 4254 * Allocate memory for the job... 4255 */ 4256 4257 if ((job = calloc(sizeof(cupsd_job_t), 1)) == NULL) 4258 { 4259 cupsdLogMessage(CUPSD_LOG_ERROR, "Ran out of memory for jobs."); 4260 cupsDirClose(dir); 4261 return; 4262 } 4263 4264 /* 4265 * Assign the job ID... 4266 */ 4267 4268 job->id = atoi(dent->filename + 1); 4269 job->back_pipes[0] = -1; 4270 job->back_pipes[1] = -1; 4271 job->print_pipes[0] = -1; 4272 job->print_pipes[1] = -1; 4273 job->side_pipes[0] = -1; 4274 job->side_pipes[1] = -1; 4275 job->status_pipes[0] = -1; 4276 job->status_pipes[1] = -1; 4277 4278 if (job->id >= NextJobId) 4279 NextJobId = job->id + 1; 4280 4281 /* 4282 * Load the job... 4283 */ 4284 4285 if (cupsdLoadJob(job)) 4286 { 4287 /* 4288 * Insert the job into the array, sorting by job priority and ID... 4289 */ 4290 4291 cupsArrayAdd(Jobs, job); 4292 4293 if (job->state_value <= IPP_JOB_STOPPED) 4294 cupsArrayAdd(ActiveJobs, job); 4295 else 4296 unload_job(job); 4297 } 4298 else 4299 free(job); 4300 } 4301 4302 cupsDirClose(dir); 4303} 4304 4305 4306/* 4307 * 'remove_job_files()' - Remove the document files for a job. 4308 */ 4309 4310static void 4311remove_job_files(cupsd_job_t *job) /* I - Job */ 4312{ 4313 int i; /* Looping var */ 4314 char filename[1024]; /* Document filename */ 4315 4316 4317 if (job->num_files <= 0) 4318 return; 4319 4320 for (i = 1; i <= job->num_files; i ++) 4321 { 4322 snprintf(filename, sizeof(filename), "%s/d%05d-%03d", RequestRoot, 4323 job->id, i); 4324 cupsdUnlinkOrRemoveFile(filename); 4325 } 4326 4327 free(job->filetypes); 4328 free(job->compressions); 4329 4330 job->file_time = 0; 4331 job->num_files = 0; 4332 job->filetypes = NULL; 4333 job->compressions = NULL; 4334 4335 LastEvent |= CUPSD_EVENT_PRINTER_STATE_CHANGED; 4336} 4337 4338 4339/* 4340 * 'remove_job_history()' - Remove the control file for a job. 4341 */ 4342 4343static void 4344remove_job_history(cupsd_job_t *job) /* I - Job */ 4345{ 4346 char filename[1024]; /* Control filename */ 4347 4348 4349 /* 4350 * Remove the job info file... 4351 */ 4352 4353 snprintf(filename, sizeof(filename), "%s/c%05d", RequestRoot, 4354 job->id); 4355 cupsdUnlinkOrRemoveFile(filename); 4356 4357 LastEvent |= CUPSD_EVENT_PRINTER_STATE_CHANGED; 4358} 4359 4360 4361/* 4362 * 'set_time()' - Set one of the "time-at-xyz" attributes. 4363 */ 4364 4365static void 4366set_time(cupsd_job_t *job, /* I - Job to update */ 4367 const char *name) /* I - Name of attribute */ 4368{ 4369 ipp_attribute_t *attr; /* Time attribute */ 4370 time_t curtime; /* Current time */ 4371 4372 4373 curtime = time(NULL); 4374 4375 cupsdLogJob(job, CUPSD_LOG_DEBUG, "%s=%ld", name, (long)curtime); 4376 4377 if ((attr = ippFindAttribute(job->attrs, name, IPP_TAG_ZERO)) != NULL) 4378 { 4379 attr->value_tag = IPP_TAG_INTEGER; 4380 attr->values[0].integer = curtime; 4381 } 4382 4383 if (!strcmp(name, "time-at-completed")) 4384 { 4385 if (JobHistory < INT_MAX && attr) 4386 job->history_time = attr->values[0].integer + JobHistory; 4387 else 4388 job->history_time = INT_MAX; 4389 4390 if (job->history_time < JobHistoryUpdate || !JobHistoryUpdate) 4391 JobHistoryUpdate = job->history_time; 4392 4393 if (JobFiles < INT_MAX && attr) 4394 job->file_time = attr->values[0].integer + JobFiles; 4395 else 4396 job->file_time = INT_MAX; 4397 4398 if (job->file_time < JobHistoryUpdate || !JobHistoryUpdate) 4399 JobHistoryUpdate = job->file_time; 4400 4401 cupsdLogMessage(CUPSD_LOG_DEBUG2, "set_time: JobHistoryUpdate=%ld", 4402 (long)JobHistoryUpdate); 4403 } 4404} 4405 4406 4407/* 4408 * 'start_job()' - Start a print job. 4409 */ 4410 4411static void 4412start_job(cupsd_job_t *job, /* I - Job ID */ 4413 cupsd_printer_t *printer) /* I - Printer to print job */ 4414{ 4415 const char *filename; /* Support filename */ 4416 4417 4418 cupsdLogMessage(CUPSD_LOG_DEBUG2, "start_job(job=%p(%d), printer=%p(%s))", 4419 job, job->id, printer, printer->name); 4420 4421 /* 4422 * Make sure we have some files around before we try to print... 4423 */ 4424 4425 if (job->num_files == 0) 4426 { 4427 ippSetString(job->attrs, &job->reasons, 0, "aborted-by-system"); 4428 cupsdSetJobState(job, IPP_JOB_ABORTED, CUPSD_JOB_DEFAULT, 4429 "Aborting job because it has no files."); 4430 return; 4431 } 4432 4433 /* 4434 * Update the printer and job state to "processing"... 4435 */ 4436 4437 if (!cupsdLoadJob(job)) 4438 return; 4439 4440 if (!job->printer_message) 4441 job->printer_message = ippFindAttribute(job->attrs, 4442 "job-printer-state-message", 4443 IPP_TAG_TEXT); 4444 if (job->printer_message) 4445 cupsdSetString(&(job->printer_message->values[0].string.text), ""); 4446 4447 ippSetString(job->attrs, &job->reasons, 0, "job-printing"); 4448 cupsdSetJobState(job, IPP_JOB_PROCESSING, CUPSD_JOB_DEFAULT, NULL); 4449 cupsdSetPrinterState(printer, IPP_PRINTER_PROCESSING, 0); 4450 cupsdSetPrinterReasons(printer, "-cups-remote-pending," 4451 "cups-remote-pending-held," 4452 "cups-remote-processing," 4453 "cups-remote-stopped," 4454 "cups-remote-canceled," 4455 "cups-remote-aborted," 4456 "cups-remote-completed"); 4457 4458 job->cost = 0; 4459 job->current_file = 0; 4460 job->file_time = 0; 4461 job->history_time = 0; 4462 job->progress = 0; 4463 job->printer = printer; 4464 printer->job = job; 4465 4466 if (MaxJobTime > 0) 4467 job->cancel_time = time(NULL) + MaxJobTime; 4468 else 4469 job->cancel_time = 0; 4470 4471 /* 4472 * Check for support files... 4473 */ 4474 4475 cupsdSetPrinterReasons(job->printer, "-cups-missing-filter-warning," 4476 "cups-insecure-filter-warning"); 4477 4478 if (printer->pc) 4479 { 4480 for (filename = (const char *)cupsArrayFirst(printer->pc->support_files); 4481 filename; 4482 filename = (const char *)cupsArrayNext(printer->pc->support_files)) 4483 { 4484 if (_cupsFileCheck(filename, _CUPS_FILE_CHECK_FILE, !RunUser, 4485 cupsdLogFCMessage, printer)) 4486 break; 4487 } 4488 } 4489 4490 /* 4491 * Setup the last exit status and security profiles... 4492 */ 4493 4494 job->status = 0; 4495 job->profile = cupsdCreateProfile(job->id); 4496 4497 /* 4498 * Create the status pipes and buffer... 4499 */ 4500 4501 if (cupsdOpenPipe(job->status_pipes)) 4502 { 4503 cupsdLogJob(job, CUPSD_LOG_DEBUG, 4504 "Unable to create job status pipes - %s.", strerror(errno)); 4505 4506 cupsdSetJobState(job, IPP_JOB_STOPPED, CUPSD_JOB_DEFAULT, 4507 "Job stopped because the scheduler could not create the " 4508 "job status pipes."); 4509 4510 cupsdDestroyProfile(job->profile); 4511 job->profile = NULL; 4512 return; 4513 } 4514 4515 job->status_buffer = cupsdStatBufNew(job->status_pipes[0], NULL); 4516 job->status_level = CUPSD_LOG_INFO; 4517 4518 /* 4519 * Create the backchannel pipes and make them non-blocking... 4520 */ 4521 4522 if (cupsdOpenPipe(job->back_pipes)) 4523 { 4524 cupsdLogJob(job, CUPSD_LOG_DEBUG, 4525 "Unable to create back-channel pipes - %s.", strerror(errno)); 4526 4527 cupsdSetJobState(job, IPP_JOB_STOPPED, CUPSD_JOB_DEFAULT, 4528 "Job stopped because the scheduler could not create the " 4529 "back-channel pipes."); 4530 4531 cupsdClosePipe(job->status_pipes); 4532 cupsdStatBufDelete(job->status_buffer); 4533 job->status_buffer = NULL; 4534 4535 cupsdDestroyProfile(job->profile); 4536 job->profile = NULL; 4537 return; 4538 } 4539 4540 fcntl(job->back_pipes[0], F_SETFL, 4541 fcntl(job->back_pipes[0], F_GETFL) | O_NONBLOCK); 4542 fcntl(job->back_pipes[1], F_SETFL, 4543 fcntl(job->back_pipes[1], F_GETFL) | O_NONBLOCK); 4544 4545 /* 4546 * Create the side-channel pipes and make them non-blocking... 4547 */ 4548 4549 if (socketpair(AF_LOCAL, SOCK_STREAM, 0, job->side_pipes)) 4550 { 4551 cupsdLogJob(job, CUPSD_LOG_DEBUG, 4552 "Unable to create side-channel pipes - %s.", strerror(errno)); 4553 4554 cupsdSetJobState(job, IPP_JOB_STOPPED, CUPSD_JOB_DEFAULT, 4555 "Job stopped because the scheduler could not create the " 4556 "side-channel pipes."); 4557 4558 cupsdClosePipe(job->back_pipes); 4559 4560 cupsdClosePipe(job->status_pipes); 4561 cupsdStatBufDelete(job->status_buffer); 4562 job->status_buffer = NULL; 4563 4564 cupsdDestroyProfile(job->profile); 4565 job->profile = NULL; 4566 return; 4567 } 4568 4569 fcntl(job->side_pipes[0], F_SETFL, 4570 fcntl(job->side_pipes[0], F_GETFL) | O_NONBLOCK); 4571 fcntl(job->side_pipes[1], F_SETFL, 4572 fcntl(job->side_pipes[1], F_GETFL) | O_NONBLOCK); 4573 4574 fcntl(job->side_pipes[0], F_SETFD, 4575 fcntl(job->side_pipes[0], F_GETFD) | FD_CLOEXEC); 4576 fcntl(job->side_pipes[1], F_SETFD, 4577 fcntl(job->side_pipes[1], F_GETFD) | FD_CLOEXEC); 4578 4579 /* 4580 * Now start the first file in the job... 4581 */ 4582 4583 cupsdContinueJob(job); 4584} 4585 4586 4587/* 4588 * 'stop_job()' - Stop a print job. 4589 */ 4590 4591static void 4592stop_job(cupsd_job_t *job, /* I - Job */ 4593 cupsd_jobaction_t action) /* I - Action */ 4594{ 4595 int i; /* Looping var */ 4596 4597 4598 cupsdLogMessage(CUPSD_LOG_DEBUG2, "stop_job(job=%p(%d), action=%d)", job, 4599 job->id, action); 4600 4601 FilterLevel -= job->cost; 4602 job->cost = 0; 4603 4604 if (action == CUPSD_JOB_DEFAULT && !job->kill_time) 4605 job->kill_time = time(NULL) + JobKillDelay; 4606 else if (action >= CUPSD_JOB_FORCE) 4607 job->kill_time = 0; 4608 4609 for (i = 0; job->filters[i]; i ++) 4610 if (job->filters[i] > 0) 4611 { 4612 cupsdEndProcess(job->filters[i], action >= CUPSD_JOB_FORCE); 4613 4614 if (action >= CUPSD_JOB_FORCE) 4615 job->filters[i] = -job->filters[i]; 4616 } 4617 4618 if (job->backend > 0) 4619 { 4620 cupsdEndProcess(job->backend, action >= CUPSD_JOB_FORCE); 4621 4622 if (action >= CUPSD_JOB_FORCE) 4623 job->backend = -job->backend; 4624 } 4625 4626 if (action >= CUPSD_JOB_FORCE) 4627 { 4628 /* 4629 * Clear job status... 4630 */ 4631 4632 job->status = 0; 4633 } 4634} 4635 4636 4637/* 4638 * 'unload_job()' - Unload a job from memory. 4639 */ 4640 4641static void 4642unload_job(cupsd_job_t *job) /* I - Job */ 4643{ 4644 if (!job->attrs) 4645 return; 4646 4647 cupsdLogJob(job, CUPSD_LOG_DEBUG, "Unloading..."); 4648 4649 ippDelete(job->attrs); 4650 4651 job->attrs = NULL; 4652 job->state = NULL; 4653 job->reasons = NULL; 4654 job->sheets = NULL; 4655 job->job_sheets = NULL; 4656 job->printer_message = NULL; 4657 job->printer_reasons = NULL; 4658} 4659 4660 4661/* 4662 * 'update_job()' - Read a status update from a job's filters. 4663 */ 4664 4665void 4666update_job(cupsd_job_t *job) /* I - Job to check */ 4667{ 4668 int i; /* Looping var */ 4669 int copies; /* Number of copies printed */ 4670 char message[CUPSD_SB_BUFFER_SIZE], 4671 /* Message text */ 4672 *ptr; /* Pointer update... */ 4673 int loglevel, /* Log level for message */ 4674 event = 0; /* Events? */ 4675 static const char * const levels[] = /* Log levels */ 4676 { 4677 "NONE", 4678 "EMERG", 4679 "ALERT", 4680 "CRIT", 4681 "ERROR", 4682 "WARN", 4683 "NOTICE", 4684 "INFO", 4685 "DEBUG", 4686 "DEBUG2" 4687 }; 4688 4689 4690 /* 4691 * Get the printer associated with this job; if the printer is stopped for 4692 * any reason then job->printer will be reset to NULL, so make sure we have 4693 * a valid pointer... 4694 */ 4695 4696 while ((ptr = cupsdStatBufUpdate(job->status_buffer, &loglevel, 4697 message, sizeof(message))) != NULL) 4698 { 4699 /* 4700 * Process page and printer state messages as needed... 4701 */ 4702 4703 if (loglevel == CUPSD_LOG_PAGE) 4704 { 4705 /* 4706 * Page message; send the message to the page_log file and update the 4707 * job sheet count... 4708 */ 4709 4710 cupsdLogJob(job, CUPSD_LOG_DEBUG, "PAGE: %s", message); 4711 4712 if (job->sheets) 4713 { 4714 if (!_cups_strncasecmp(message, "total ", 6)) 4715 { 4716 /* 4717 * Got a total count of pages from a backend or filter... 4718 */ 4719 4720 copies = atoi(message + 6); 4721 copies -= job->sheets->values[0].integer; /* Just track the delta */ 4722 } 4723 else if (!sscanf(message, "%*d%d", &copies)) 4724 copies = 1; 4725 4726 job->sheets->values[0].integer += copies; 4727 4728 if (job->printer->page_limit) 4729 cupsdUpdateQuota(job->printer, job->username, copies, 0); 4730 } 4731 4732 cupsdLogPage(job, message); 4733 4734 if (job->sheets) 4735 cupsdAddEvent(CUPSD_EVENT_JOB_PROGRESS, job->printer, job, 4736 "Printed %d page(s).", job->sheets->values[0].integer); 4737 } 4738 else if (loglevel == CUPSD_LOG_JOBSTATE) 4739 { 4740 /* 4741 * Support "keyword" to set job-state-reasons to the specified keyword. 4742 * This is sufficient for the current paid printing stuff. 4743 */ 4744 4745 cupsdLogJob(job, CUPSD_LOG_DEBUG, "JOBSTATE: %s", message); 4746 4747 ippSetString(job->attrs, &job->reasons, 0, message); 4748 } 4749 else if (loglevel == CUPSD_LOG_STATE) 4750 { 4751 cupsdLogJob(job, CUPSD_LOG_DEBUG, "STATE: %s", message); 4752 4753 if (!strcmp(message, "paused")) 4754 { 4755 cupsdStopPrinter(job->printer, 1); 4756 return; 4757 } 4758 else if (message[0] && cupsdSetPrinterReasons(job->printer, message)) 4759 { 4760 event |= CUPSD_EVENT_PRINTER_STATE; 4761 4762 if (MaxJobTime > 0 && strstr(message, "connecting-to-device") != NULL) 4763 { 4764 /* 4765 * Reset cancel time after connecting to the device... 4766 */ 4767 4768 for (i = 0; i < job->printer->num_reasons; i ++) 4769 if (!strcmp(job->printer->reasons[i], "connecting-to-device")) 4770 break; 4771 4772 if (i >= job->printer->num_reasons) 4773 job->cancel_time = time(NULL) + MaxJobTime; 4774 } 4775 } 4776 4777 update_job_attrs(job, 0); 4778 } 4779 else if (loglevel == CUPSD_LOG_ATTR) 4780 { 4781 /* 4782 * Set attribute(s)... 4783 */ 4784 4785 int num_attrs; /* Number of attributes */ 4786 cups_option_t *attrs; /* Attributes */ 4787 const char *attr; /* Attribute */ 4788 4789 cupsdLogJob(job, CUPSD_LOG_DEBUG, "ATTR: %s", message); 4790 4791 num_attrs = cupsParseOptions(message, 0, &attrs); 4792 4793 if ((attr = cupsGetOption("auth-info-default", num_attrs, 4794 attrs)) != NULL) 4795 { 4796 job->printer->num_options = cupsAddOption("auth-info", attr, 4797 job->printer->num_options, 4798 &(job->printer->options)); 4799 cupsdSetPrinterAttrs(job->printer); 4800 4801 cupsdMarkDirty(CUPSD_DIRTY_PRINTERS); 4802 } 4803 4804 if ((attr = cupsGetOption("auth-info-required", num_attrs, 4805 attrs)) != NULL) 4806 { 4807 cupsdSetAuthInfoRequired(job->printer, attr, NULL); 4808 cupsdSetPrinterAttrs(job->printer); 4809 4810 cupsdMarkDirty(CUPSD_DIRTY_PRINTERS); 4811 } 4812 4813 if ((attr = cupsGetOption("job-media-progress", num_attrs, 4814 attrs)) != NULL) 4815 { 4816 int progress = atoi(attr); 4817 4818 4819 if (progress >= 0 && progress <= 100) 4820 { 4821 job->progress = progress; 4822 4823 if (job->sheets) 4824 cupsdAddEvent(CUPSD_EVENT_JOB_PROGRESS, job->printer, job, 4825 "Printing page %d, %d%%", 4826 job->sheets->values[0].integer, job->progress); 4827 } 4828 } 4829 4830 if ((attr = cupsGetOption("printer-alert", num_attrs, attrs)) != NULL) 4831 { 4832 cupsdSetString(&job->printer->alert, attr); 4833 event |= CUPSD_EVENT_PRINTER_STATE; 4834 } 4835 4836 if ((attr = cupsGetOption("printer-alert-description", num_attrs, 4837 attrs)) != NULL) 4838 { 4839 cupsdSetString(&job->printer->alert_description, attr); 4840 event |= CUPSD_EVENT_PRINTER_STATE; 4841 } 4842 4843 if ((attr = cupsGetOption("marker-colors", num_attrs, attrs)) != NULL) 4844 { 4845 cupsdSetPrinterAttr(job->printer, "marker-colors", (char *)attr); 4846 job->printer->marker_time = time(NULL); 4847 event |= CUPSD_EVENT_PRINTER_STATE; 4848 cupsdMarkDirty(CUPSD_DIRTY_PRINTERS); 4849 } 4850 4851 if ((attr = cupsGetOption("marker-levels", num_attrs, attrs)) != NULL) 4852 { 4853 cupsdSetPrinterAttr(job->printer, "marker-levels", (char *)attr); 4854 job->printer->marker_time = time(NULL); 4855 event |= CUPSD_EVENT_PRINTER_STATE; 4856 cupsdMarkDirty(CUPSD_DIRTY_PRINTERS); 4857 } 4858 4859 if ((attr = cupsGetOption("marker-low-levels", num_attrs, attrs)) != NULL) 4860 { 4861 cupsdSetPrinterAttr(job->printer, "marker-low-levels", (char *)attr); 4862 job->printer->marker_time = time(NULL); 4863 event |= CUPSD_EVENT_PRINTER_STATE; 4864 cupsdMarkDirty(CUPSD_DIRTY_PRINTERS); 4865 } 4866 4867 if ((attr = cupsGetOption("marker-high-levels", num_attrs, attrs)) != NULL) 4868 { 4869 cupsdSetPrinterAttr(job->printer, "marker-high-levels", (char *)attr); 4870 job->printer->marker_time = time(NULL); 4871 event |= CUPSD_EVENT_PRINTER_STATE; 4872 cupsdMarkDirty(CUPSD_DIRTY_PRINTERS); 4873 } 4874 4875 if ((attr = cupsGetOption("marker-message", num_attrs, attrs)) != NULL) 4876 { 4877 cupsdSetPrinterAttr(job->printer, "marker-message", (char *)attr); 4878 job->printer->marker_time = time(NULL); 4879 event |= CUPSD_EVENT_PRINTER_STATE; 4880 cupsdMarkDirty(CUPSD_DIRTY_PRINTERS); 4881 } 4882 4883 if ((attr = cupsGetOption("marker-names", num_attrs, attrs)) != NULL) 4884 { 4885 cupsdSetPrinterAttr(job->printer, "marker-names", (char *)attr); 4886 job->printer->marker_time = time(NULL); 4887 event |= CUPSD_EVENT_PRINTER_STATE; 4888 cupsdMarkDirty(CUPSD_DIRTY_PRINTERS); 4889 } 4890 4891 if ((attr = cupsGetOption("marker-types", num_attrs, attrs)) != NULL) 4892 { 4893 cupsdSetPrinterAttr(job->printer, "marker-types", (char *)attr); 4894 job->printer->marker_time = time(NULL); 4895 event |= CUPSD_EVENT_PRINTER_STATE; 4896 cupsdMarkDirty(CUPSD_DIRTY_PRINTERS); 4897 } 4898 4899 cupsFreeOptions(num_attrs, attrs); 4900 } 4901 else if (loglevel == CUPSD_LOG_PPD) 4902 { 4903 /* 4904 * Set attribute(s)... 4905 */ 4906 4907 cupsdLogJob(job, CUPSD_LOG_DEBUG, "PPD: %s", message); 4908 4909 job->num_keywords = cupsParseOptions(message, job->num_keywords, 4910 &job->keywords); 4911 } 4912 else 4913 { 4914 /* 4915 * Strip legacy message prefix... 4916 */ 4917 4918 if (!strncmp(message, "recoverable:", 12)) 4919 { 4920 ptr = message + 12; 4921 while (isspace(*ptr & 255)) 4922 ptr ++; 4923 } 4924 else if (!strncmp(message, "recovered:", 10)) 4925 { 4926 ptr = message + 10; 4927 while (isspace(*ptr & 255)) 4928 ptr ++; 4929 } 4930 else 4931 ptr = message; 4932 4933 if (*ptr) 4934 cupsdLogJob(job, loglevel, "%s", ptr); 4935 4936 if (loglevel < CUPSD_LOG_DEBUG && 4937 strcmp(job->printer->state_message, ptr)) 4938 { 4939 strlcpy(job->printer->state_message, ptr, 4940 sizeof(job->printer->state_message)); 4941 4942 event |= CUPSD_EVENT_PRINTER_STATE | CUPSD_EVENT_JOB_PROGRESS; 4943 4944 if (loglevel <= job->status_level && job->status_level > CUPSD_LOG_ERROR) 4945 { 4946 /* 4947 * Some messages show in the job-printer-state-message attribute... 4948 */ 4949 4950 if (loglevel != CUPSD_LOG_NOTICE) 4951 job->status_level = loglevel; 4952 4953 update_job_attrs(job, 1); 4954 4955 cupsdLogJob(job, CUPSD_LOG_DEBUG, 4956 "Set job-printer-state-message to \"%s\", " 4957 "current level=%s", 4958 job->printer_message->values[0].string.text, 4959 levels[job->status_level]); 4960 } 4961 } 4962 } 4963 4964 if (!strchr(job->status_buffer->buffer, '\n')) 4965 break; 4966 } 4967 4968 if (event & CUPSD_EVENT_JOB_PROGRESS) 4969 cupsdAddEvent(CUPSD_EVENT_JOB_PROGRESS, job->printer, job, 4970 "%s", job->printer->state_message); 4971 if (event & CUPSD_EVENT_PRINTER_STATE) 4972 cupsdAddEvent(CUPSD_EVENT_PRINTER_STATE, job->printer, NULL, 4973 (job->printer->type & CUPS_PRINTER_CLASS) ? 4974 "Class \"%s\" state changed." : 4975 "Printer \"%s\" state changed.", 4976 job->printer->name); 4977 4978 4979 if (ptr == NULL && !job->status_buffer->bufused) 4980 { 4981 /* 4982 * See if all of the filters and the backend have returned their 4983 * exit statuses. 4984 */ 4985 4986 for (i = 0; job->filters[i] < 0; i ++); 4987 4988 if (job->filters[i]) 4989 { 4990 /* 4991 * EOF but we haven't collected the exit status of all filters... 4992 */ 4993 4994 cupsdCheckProcess(); 4995 return; 4996 } 4997 4998 if (job->current_file >= job->num_files && job->backend > 0) 4999 { 5000 /* 5001 * EOF but we haven't collected the exit status of the backend... 5002 */ 5003 5004 cupsdCheckProcess(); 5005 return; 5006 } 5007 5008 /* 5009 * Handle the end of job stuff... 5010 */ 5011 5012 finalize_job(job, 1); 5013 5014 /* 5015 * Check for new jobs... 5016 */ 5017 5018 cupsdCheckJobs(); 5019 } 5020} 5021 5022 5023/* 5024 * 'update_job_attrs()' - Update the job-printer-* attributes. 5025 */ 5026 5027void 5028update_job_attrs(cupsd_job_t *job, /* I - Job to update */ 5029 int do_message)/* I - 1 = copy job-printer-state message */ 5030{ 5031 int i; /* Looping var */ 5032 int num_reasons; /* Actual number of reasons */ 5033 const char * const *reasons; /* Reasons */ 5034 static const char *none = "none"; /* "none" reason */ 5035 5036 5037 /* 5038 * Get/create the job-printer-state-* attributes... 5039 */ 5040 5041 if (!job->printer_message) 5042 { 5043 if ((job->printer_message = ippFindAttribute(job->attrs, 5044 "job-printer-state-message", 5045 IPP_TAG_TEXT)) == NULL) 5046 job->printer_message = ippAddString(job->attrs, IPP_TAG_JOB, IPP_TAG_TEXT, 5047 "job-printer-state-message", 5048 NULL, ""); 5049 } 5050 5051 if (!job->printer_reasons) 5052 job->printer_reasons = ippFindAttribute(job->attrs, 5053 "job-printer-state-reasons", 5054 IPP_TAG_KEYWORD); 5055 5056 /* 5057 * Copy or clear the printer-state-message value as needed... 5058 */ 5059 5060 if (job->state_value != IPP_JOB_PROCESSING && 5061 job->status_level == CUPSD_LOG_INFO) 5062 { 5063 cupsdSetString(&(job->printer_message->values[0].string.text), ""); 5064 5065 job->dirty = 1; 5066 cupsdMarkDirty(CUPSD_DIRTY_JOBS); 5067 } 5068 else if (job->printer->state_message[0] && do_message) 5069 { 5070 cupsdSetString(&(job->printer_message->values[0].string.text), 5071 job->printer->state_message); 5072 5073 job->dirty = 1; 5074 cupsdMarkDirty(CUPSD_DIRTY_JOBS); 5075 } 5076 5077 /* 5078 * ... and the printer-state-reasons value... 5079 */ 5080 5081 if (job->printer->num_reasons == 0) 5082 { 5083 num_reasons = 1; 5084 reasons = &none; 5085 } 5086 else 5087 { 5088 num_reasons = job->printer->num_reasons; 5089 reasons = (const char * const *)job->printer->reasons; 5090 } 5091 5092 if (!job->printer_reasons || job->printer_reasons->num_values != num_reasons) 5093 { 5094 /* 5095 * Replace/create a job-printer-state-reasons attribute... 5096 */ 5097 5098 ippDeleteAttribute(job->attrs, job->printer_reasons); 5099 5100 job->printer_reasons = ippAddStrings(job->attrs, 5101 IPP_TAG_JOB, IPP_TAG_KEYWORD, 5102 "job-printer-state-reasons", 5103 num_reasons, NULL, NULL); 5104 } 5105 else 5106 { 5107 /* 5108 * Don't bother clearing the reason strings if they are the same... 5109 */ 5110 5111 for (i = 0; i < num_reasons; i ++) 5112 if (strcmp(job->printer_reasons->values[i].string.text, reasons[i])) 5113 break; 5114 5115 if (i >= num_reasons) 5116 return; 5117 5118 /* 5119 * Not the same, so free the current strings... 5120 */ 5121 5122 for (i = 0; i < num_reasons; i ++) 5123 _cupsStrFree(job->printer_reasons->values[i].string.text); 5124 } 5125 5126 /* 5127 * Copy the reasons... 5128 */ 5129 5130 for (i = 0; i < num_reasons; i ++) 5131 job->printer_reasons->values[i].string.text = _cupsStrAlloc(reasons[i]); 5132 5133 job->dirty = 1; 5134 cupsdMarkDirty(CUPSD_DIRTY_JOBS); 5135} 5136 5137 5138/* 5139 * End of "$Id: job.c 11433 2013-11-20 18:57:44Z msweet $". 5140 */ 5141