1/* 2 * "$Id: classes.c 11791 2014-04-02 16:56:54Z msweet $" 3 * 4 * Printer class routines for the CUPS scheduler. 5 * 6 * Copyright 2007-2014 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 22 23/* 24 * 'cupsdAddClass()' - Add a class to the system. 25 */ 26 27cupsd_printer_t * /* O - New class */ 28cupsdAddClass(const char *name) /* I - Name of class */ 29{ 30 cupsd_printer_t *c; /* New class */ 31 char uri[1024]; /* Class URI */ 32 33 34 /* 35 * Add the printer and set the type to "class"... 36 */ 37 38 if ((c = cupsdAddPrinter(name)) != NULL) 39 { 40 /* 41 * Change from a printer to a class... 42 */ 43 44 c->type = CUPS_PRINTER_CLASS; 45 46 httpAssembleURIf(HTTP_URI_CODING_ALL, uri, sizeof(uri), "ipp", NULL, 47 ServerName, RemotePort, "/classes/%s", name); 48 cupsdSetString(&c->uri, uri); 49 50 cupsdSetString(&c->error_policy, "retry-current-job"); 51 } 52 53 return (c); 54} 55 56 57/* 58 * 'cupsdAddPrinterToClass()' - Add a printer to a class... 59 */ 60 61void 62cupsdAddPrinterToClass( 63 cupsd_printer_t *c, /* I - Class to add to */ 64 cupsd_printer_t *p) /* I - Printer to add */ 65{ 66 int i; /* Looping var */ 67 cupsd_printer_t **temp; /* Pointer to printer array */ 68 69 70 /* 71 * See if this printer is already a member of the class... 72 */ 73 74 for (i = 0; i < c->num_printers; i ++) 75 if (c->printers[i] == p) 76 return; 77 78 /* 79 * Allocate memory as needed... 80 */ 81 82 if (c->num_printers == 0) 83 temp = malloc(sizeof(cupsd_printer_t *)); 84 else 85 temp = realloc(c->printers, sizeof(cupsd_printer_t *) * (size_t)(c->num_printers + 1)); 86 87 if (temp == NULL) 88 { 89 cupsdLogMessage(CUPSD_LOG_ERROR, "Unable to add printer %s to class %s!", 90 p->name, c->name); 91 return; 92 } 93 94 /* 95 * Add the printer to the end of the array and update the number of printers. 96 */ 97 98 c->printers = temp; 99 temp += c->num_printers; 100 c->num_printers ++; 101 102 *temp = p; 103} 104 105 106/* 107 * 'cupsdDeletePrinterFromClass()' - Delete a printer from a class. 108 */ 109 110int /* O - 1 if class changed, 0 otherwise */ 111cupsdDeletePrinterFromClass( 112 cupsd_printer_t *c, /* I - Class to delete from */ 113 cupsd_printer_t *p) /* I - Printer to delete */ 114{ 115 int i; /* Looping var */ 116 117 118 /* 119 * See if the printer is in the class... 120 */ 121 122 for (i = 0; i < c->num_printers; i ++) 123 if (p == c->printers[i]) 124 break; 125 126 /* 127 * If it is, remove it from the list... 128 */ 129 130 if (i < c->num_printers) 131 { 132 /* 133 * Yes, remove the printer... 134 */ 135 136 c->num_printers --; 137 if (i < c->num_printers) 138 memmove(c->printers + i, c->printers + i + 1, 139 (size_t)(c->num_printers - i) * sizeof(cupsd_printer_t *)); 140 } 141 else 142 return (0); 143 144 /* 145 * Update the IPP attributes (have to do this for member-names)... 146 */ 147 148 cupsdSetPrinterAttrs(c); 149 150 return (1); 151} 152 153 154/* 155 * 'cupsdDeletePrinterFromClasses()' - Delete a printer from all classes. 156 */ 157 158int /* O - 1 if class changed, 0 otherwise */ 159cupsdDeletePrinterFromClasses( 160 cupsd_printer_t *p) /* I - Printer to delete */ 161{ 162 int changed = 0; /* Any class changed? */ 163 cupsd_printer_t *c; /* Pointer to current class */ 164 165 166 /* 167 * Loop through the printer/class list and remove the printer 168 * from each class listed... 169 */ 170 171 for (c = (cupsd_printer_t *)cupsArrayFirst(Printers); 172 c; 173 c = (cupsd_printer_t *)cupsArrayNext(Printers)) 174 if (c->type & CUPS_PRINTER_CLASS) 175 changed |= cupsdDeletePrinterFromClass(c, p); 176 177 return (changed); 178} 179 180 181/* 182 * 'cupsdFindAvailablePrinter()' - Find an available printer in a class. 183 */ 184 185cupsd_printer_t * /* O - Available printer or NULL */ 186cupsdFindAvailablePrinter( 187 const char *name) /* I - Class to check */ 188{ 189 int i; /* Looping var */ 190 cupsd_printer_t *c; /* Printer class */ 191 192 193 /* 194 * Find the class... 195 */ 196 197 if ((c = cupsdFindClass(name)) == NULL) 198 { 199 cupsdLogMessage(CUPSD_LOG_ERROR, "Unable to find class \"%s\"!", name); 200 return (NULL); 201 } 202 203 if (c->num_printers == 0) 204 return (NULL); 205 206 /* 207 * Make sure that the last printer is also a valid index into the printer 208 * array. If not, reset the last printer to 0... 209 */ 210 211 if (c->last_printer >= c->num_printers) 212 c->last_printer = 0; 213 214 /* 215 * Loop through the printers in the class and return the first idle 216 * printer... We keep track of the last printer that we used so that 217 * a "round robin" type of scheduling is realized (otherwise the first 218 * server might be saturated with print jobs...) 219 * 220 * Thanks to Joel Fredrikson for helping us get this right! 221 */ 222 223 for (i = c->last_printer + 1; ; i ++) 224 { 225 if (i >= c->num_printers) 226 i = 0; 227 228 if (c->printers[i]->accepting && 229 (c->printers[i]->state == IPP_PRINTER_IDLE || 230 ((c->printers[i]->type & CUPS_PRINTER_REMOTE) && !c->printers[i]->job))) 231 { 232 c->last_printer = i; 233 return (c->printers[i]); 234 } 235 236 if (i == c->last_printer) 237 break; 238 } 239 240 return (NULL); 241} 242 243 244/* 245 * 'cupsdFindClass()' - Find the named class. 246 */ 247 248cupsd_printer_t * /* O - Matching class or NULL */ 249cupsdFindClass(const char *name) /* I - Name of class */ 250{ 251 cupsd_printer_t *c; /* Current class/printer */ 252 253 254 if ((c = cupsdFindDest(name)) != NULL && (c->type & CUPS_PRINTER_CLASS)) 255 return (c); 256 else 257 return (NULL); 258} 259 260 261/* 262 * 'cupsdLoadAllClasses()' - Load classes from the classes.conf file. 263 */ 264 265void 266cupsdLoadAllClasses(void) 267{ 268 int i; /* Looping var */ 269 cups_file_t *fp; /* classes.conf file */ 270 int linenum; /* Current line number */ 271 char line[4096], /* Line from file */ 272 *value, /* Pointer to value */ 273 *valueptr; /* Pointer into value */ 274 cupsd_printer_t *p, /* Current printer class */ 275 *temp; /* Temporary pointer to printer */ 276 277 278 /* 279 * Open the classes.conf file... 280 */ 281 282 snprintf(line, sizeof(line), "%s/classes.conf", ServerRoot); 283 if ((fp = cupsdOpenConfFile(line)) == NULL) 284 return; 285 286 /* 287 * Read class configurations until we hit EOF... 288 */ 289 290 linenum = 0; 291 p = NULL; 292 293 while (cupsFileGetConf(fp, line, sizeof(line), &value, &linenum)) 294 { 295 /* 296 * Decode the directive... 297 */ 298 299 if (!_cups_strcasecmp(line, "<Class") || 300 !_cups_strcasecmp(line, "<DefaultClass")) 301 { 302 /* 303 * <Class name> or <DefaultClass name> 304 */ 305 306 if (p == NULL && value) 307 { 308 cupsdLogMessage(CUPSD_LOG_DEBUG, "Loading class %s...", value); 309 310 /* 311 * Since prior classes may have implicitly defined this class, 312 * see if it already exists... 313 */ 314 315 if ((p = cupsdFindDest(value)) != NULL) 316 { 317 p->type = CUPS_PRINTER_CLASS; 318 cupsdSetStringf(&p->uri, "ipp://%s:%d/classes/%s", ServerName, 319 LocalPort, value); 320 cupsdSetString(&p->error_policy, "retry-job"); 321 } 322 else 323 p = cupsdAddClass(value); 324 325 p->accepting = 1; 326 p->state = IPP_PRINTER_IDLE; 327 328 if (!_cups_strcasecmp(line, "<DefaultClass")) 329 DefaultPrinter = p; 330 } 331 else 332 cupsdLogMessage(CUPSD_LOG_ERROR, 333 "Syntax error on line %d of classes.conf.", linenum); 334 } 335 else if (!_cups_strcasecmp(line, "</Class>") || !_cups_strcasecmp(line, "</DefaultClass>")) 336 { 337 if (p != NULL) 338 { 339 cupsdSetPrinterAttrs(p); 340 p = NULL; 341 } 342 else 343 cupsdLogMessage(CUPSD_LOG_ERROR, 344 "Syntax error on line %d of classes.conf.", linenum); 345 } 346 else if (!p) 347 { 348 cupsdLogMessage(CUPSD_LOG_ERROR, 349 "Syntax error on line %d of classes.conf.", linenum); 350 } 351 else if (!_cups_strcasecmp(line, "UUID")) 352 { 353 if (value && !strncmp(value, "urn:uuid:", 9)) 354 cupsdSetString(&(p->uuid), value); 355 else 356 cupsdLogMessage(CUPSD_LOG_ERROR, 357 "Bad UUID on line %d of classes.conf.", linenum); 358 } 359 else if (!_cups_strcasecmp(line, "AuthInfoRequired")) 360 { 361 if (!cupsdSetAuthInfoRequired(p, value, NULL)) 362 cupsdLogMessage(CUPSD_LOG_ERROR, 363 "Bad AuthInfoRequired on line %d of classes.conf.", 364 linenum); 365 } 366 else if (!_cups_strcasecmp(line, "Info")) 367 { 368 if (value) 369 cupsdSetString(&p->info, value); 370 } 371 else if (!_cups_strcasecmp(line, "Location")) 372 { 373 if (value) 374 cupsdSetString(&p->location, value); 375 } 376 else if (!_cups_strcasecmp(line, "Option") && value) 377 { 378 /* 379 * Option name value 380 */ 381 382 for (valueptr = value; *valueptr && !isspace(*valueptr & 255); valueptr ++); 383 384 if (!*valueptr) 385 cupsdLogMessage(CUPSD_LOG_ERROR, 386 "Syntax error on line %d of classes.conf.", linenum); 387 else 388 { 389 for (; *valueptr && isspace(*valueptr & 255); *valueptr++ = '\0'); 390 391 p->num_options = cupsAddOption(value, valueptr, p->num_options, 392 &(p->options)); 393 } 394 } 395 else if (!_cups_strcasecmp(line, "Printer")) 396 { 397 if (!value) 398 { 399 cupsdLogMessage(CUPSD_LOG_ERROR, 400 "Syntax error on line %d of classes.conf.", linenum); 401 continue; 402 } 403 else if ((temp = cupsdFindPrinter(value)) == NULL) 404 { 405 cupsdLogMessage(CUPSD_LOG_WARN, 406 "Unknown printer %s on line %d of classes.conf.", 407 value, linenum); 408 409 /* 410 * Add the missing remote printer... 411 */ 412 413 if ((temp = cupsdAddPrinter(value)) != NULL) 414 { 415 cupsdSetString(&temp->make_model, "Remote Printer on unknown"); 416 417 temp->state = IPP_PRINTER_STOPPED; 418 temp->type |= CUPS_PRINTER_REMOTE; 419 420 cupsdSetString(&temp->location, "Location Unknown"); 421 cupsdSetString(&temp->info, "No Information Available"); 422 temp->hostname[0] = '\0'; 423 424 cupsdSetPrinterAttrs(temp); 425 } 426 } 427 428 if (temp) 429 cupsdAddPrinterToClass(p, temp); 430 } 431 else if (!_cups_strcasecmp(line, "State")) 432 { 433 /* 434 * Set the initial queue state... 435 */ 436 437 if (!_cups_strcasecmp(value, "idle")) 438 p->state = IPP_PRINTER_IDLE; 439 else if (!_cups_strcasecmp(value, "stopped")) 440 { 441 p->state = IPP_PRINTER_STOPPED; 442 443 for (i = 0 ; i < p->num_reasons; i ++) 444 if (!strcmp("paused", p->reasons[i])) 445 break; 446 447 if (i >= p->num_reasons && 448 p->num_reasons < (int)(sizeof(p->reasons) / sizeof(p->reasons[0]))) 449 { 450 p->reasons[p->num_reasons] = _cupsStrAlloc("paused"); 451 p->num_reasons ++; 452 } 453 } 454 else 455 cupsdLogMessage(CUPSD_LOG_ERROR, 456 "Syntax error on line %d of classes.conf.", 457 linenum); 458 } 459 else if (!_cups_strcasecmp(line, "StateMessage")) 460 { 461 /* 462 * Set the initial queue state message... 463 */ 464 465 if (value) 466 strlcpy(p->state_message, value, sizeof(p->state_message)); 467 } 468 else if (!_cups_strcasecmp(line, "StateTime")) 469 { 470 /* 471 * Set the state time... 472 */ 473 474 if (value) 475 p->state_time = atoi(value); 476 } 477 else if (!_cups_strcasecmp(line, "Accepting")) 478 { 479 /* 480 * Set the initial accepting state... 481 */ 482 483 if (value && 484 (!_cups_strcasecmp(value, "yes") || 485 !_cups_strcasecmp(value, "on") || 486 !_cups_strcasecmp(value, "true"))) 487 p->accepting = 1; 488 else if (value && 489 (!_cups_strcasecmp(value, "no") || 490 !_cups_strcasecmp(value, "off") || 491 !_cups_strcasecmp(value, "false"))) 492 p->accepting = 0; 493 else 494 cupsdLogMessage(CUPSD_LOG_ERROR, 495 "Syntax error on line %d of classes.conf.", 496 linenum); 497 } 498 else if (!_cups_strcasecmp(line, "Shared")) 499 { 500 /* 501 * Set the initial shared state... 502 */ 503 504 if (value && 505 (!_cups_strcasecmp(value, "yes") || 506 !_cups_strcasecmp(value, "on") || 507 !_cups_strcasecmp(value, "true"))) 508 p->shared = 1; 509 else if (value && 510 (!_cups_strcasecmp(value, "no") || 511 !_cups_strcasecmp(value, "off") || 512 !_cups_strcasecmp(value, "false"))) 513 p->shared = 0; 514 else 515 cupsdLogMessage(CUPSD_LOG_ERROR, 516 "Syntax error on line %d of classes.conf.", 517 linenum); 518 } 519 else if (!_cups_strcasecmp(line, "JobSheets")) 520 { 521 /* 522 * Set the initial job sheets... 523 */ 524 525 if (value) 526 { 527 for (valueptr = value; 528 *valueptr && !isspace(*valueptr & 255); 529 valueptr ++); 530 531 if (*valueptr) 532 *valueptr++ = '\0'; 533 534 cupsdSetString(&p->job_sheets[0], value); 535 536 while (isspace(*valueptr & 255)) 537 valueptr ++; 538 539 if (*valueptr) 540 { 541 for (value = valueptr; 542 *valueptr && !isspace(*valueptr & 255); 543 valueptr ++); 544 545 if (*valueptr) 546 *valueptr = '\0'; 547 548 cupsdSetString(&p->job_sheets[1], value); 549 } 550 } 551 else 552 cupsdLogMessage(CUPSD_LOG_ERROR, 553 "Syntax error on line %d of classes.conf.", linenum); 554 } 555 else if (!_cups_strcasecmp(line, "AllowUser")) 556 { 557 if (value) 558 { 559 p->deny_users = 0; 560 cupsdAddString(&(p->users), value); 561 } 562 else 563 cupsdLogMessage(CUPSD_LOG_ERROR, 564 "Syntax error on line %d of classes.conf.", linenum); 565 } 566 else if (!_cups_strcasecmp(line, "DenyUser")) 567 { 568 if (value) 569 { 570 p->deny_users = 1; 571 cupsdAddString(&(p->users), value); 572 } 573 else 574 cupsdLogMessage(CUPSD_LOG_ERROR, 575 "Syntax error on line %d of classes.conf.", linenum); 576 } 577 else if (!_cups_strcasecmp(line, "QuotaPeriod")) 578 { 579 if (value) 580 p->quota_period = atoi(value); 581 else 582 cupsdLogMessage(CUPSD_LOG_ERROR, 583 "Syntax error on line %d of classes.conf.", linenum); 584 } 585 else if (!_cups_strcasecmp(line, "PageLimit")) 586 { 587 if (value) 588 p->page_limit = atoi(value); 589 else 590 cupsdLogMessage(CUPSD_LOG_ERROR, 591 "Syntax error on line %d of classes.conf.", linenum); 592 } 593 else if (!_cups_strcasecmp(line, "KLimit")) 594 { 595 if (value) 596 p->k_limit = atoi(value); 597 else 598 cupsdLogMessage(CUPSD_LOG_ERROR, 599 "Syntax error on line %d of classes.conf.", linenum); 600 } 601 else if (!_cups_strcasecmp(line, "OpPolicy")) 602 { 603 if (value) 604 { 605 cupsd_policy_t *pol; /* Policy */ 606 607 608 if ((pol = cupsdFindPolicy(value)) != NULL) 609 { 610 cupsdSetString(&p->op_policy, value); 611 p->op_policy_ptr = pol; 612 } 613 else 614 cupsdLogMessage(CUPSD_LOG_ERROR, 615 "Bad policy \"%s\" on line %d of classes.conf", 616 value, linenum); 617 } 618 else 619 cupsdLogMessage(CUPSD_LOG_ERROR, 620 "Syntax error on line %d of classes.conf.", linenum); 621 } 622 else if (!_cups_strcasecmp(line, "ErrorPolicy")) 623 { 624 if (value) 625 { 626 if (strcmp(value, "retry-current-job") && strcmp(value, "retry-job")) 627 cupsdLogMessage(CUPSD_LOG_WARN, 628 "ErrorPolicy %s ignored on line %d of classes.conf", 629 value, linenum); 630 } 631 else 632 cupsdLogMessage(CUPSD_LOG_ERROR, 633 "Syntax error on line %d of classes.conf.", linenum); 634 } 635 else 636 { 637 /* 638 * Something else we don't understand... 639 */ 640 641 cupsdLogMessage(CUPSD_LOG_ERROR, 642 "Unknown configuration directive %s on line %d of classes.conf.", 643 line, linenum); 644 } 645 } 646 647 cupsFileClose(fp); 648} 649 650 651/* 652 * 'cupsdSaveAllClasses()' - Save classes to the classes.conf file. 653 */ 654 655void 656cupsdSaveAllClasses(void) 657{ 658 cups_file_t *fp; /* classes.conf file */ 659 char filename[1024], /* classes.conf filename */ 660 temp[1024], /* Temporary string */ 661 value[2048], /* Value string */ 662 *name; /* Current user name */ 663 cupsd_printer_t *pclass; /* Current printer class */ 664 int i; /* Looping var */ 665 time_t curtime; /* Current time */ 666 struct tm *curdate; /* Current date */ 667 cups_option_t *option; /* Current option */ 668 669 670 /* 671 * Create the classes.conf file... 672 */ 673 674 snprintf(filename, sizeof(filename), "%s/classes.conf", ServerRoot); 675 676 if ((fp = cupsdCreateConfFile(filename, ConfigFilePerm)) == NULL) 677 return; 678 679 cupsdLogMessage(CUPSD_LOG_INFO, "Saving classes.conf..."); 680 681 /* 682 * Write a small header to the file... 683 */ 684 685 curtime = time(NULL); 686 curdate = localtime(&curtime); 687 strftime(temp, sizeof(temp) - 1, "%Y-%m-%d %H:%M", curdate); 688 689 cupsFilePuts(fp, "# Class configuration file for " CUPS_SVERSION "\n"); 690 cupsFilePrintf(fp, "# Written by cupsd on %s\n", temp); 691 cupsFilePuts(fp, "# DO NOT EDIT THIS FILE WHEN CUPSD IS RUNNING\n"); 692 693 /* 694 * Write each local class known to the system... 695 */ 696 697 for (pclass = (cupsd_printer_t *)cupsArrayFirst(Printers); 698 pclass; 699 pclass = (cupsd_printer_t *)cupsArrayNext(Printers)) 700 { 701 /* 702 * Skip remote destinations and regular printers... 703 */ 704 705 if ((pclass->type & CUPS_PRINTER_REMOTE) || 706 !(pclass->type & CUPS_PRINTER_CLASS)) 707 continue; 708 709 /* 710 * Write printers as needed... 711 */ 712 713 if (pclass == DefaultPrinter) 714 cupsFilePrintf(fp, "<DefaultClass %s>\n", pclass->name); 715 else 716 cupsFilePrintf(fp, "<Class %s>\n", pclass->name); 717 718 cupsFilePrintf(fp, "UUID %s\n", pclass->uuid); 719 720 if (pclass->num_auth_info_required > 0) 721 { 722 switch (pclass->num_auth_info_required) 723 { 724 case 1 : 725 strlcpy(value, pclass->auth_info_required[0], sizeof(value)); 726 break; 727 728 case 2 : 729 snprintf(value, sizeof(value), "%s,%s", 730 pclass->auth_info_required[0], 731 pclass->auth_info_required[1]); 732 break; 733 734 case 3 : 735 default : 736 snprintf(value, sizeof(value), "%s,%s,%s", 737 pclass->auth_info_required[0], 738 pclass->auth_info_required[1], 739 pclass->auth_info_required[2]); 740 break; 741 } 742 743 cupsFilePutConf(fp, "AuthInfoRequired", value); 744 } 745 746 if (pclass->info) 747 cupsFilePutConf(fp, "Info", pclass->info); 748 749 if (pclass->location) 750 cupsFilePutConf(fp, "Location", pclass->location); 751 752 if (pclass->state == IPP_PRINTER_STOPPED) 753 cupsFilePuts(fp, "State Stopped\n"); 754 else 755 cupsFilePuts(fp, "State Idle\n"); 756 757 cupsFilePrintf(fp, "StateTime %d\n", (int)pclass->state_time); 758 759 if (pclass->accepting) 760 cupsFilePuts(fp, "Accepting Yes\n"); 761 else 762 cupsFilePuts(fp, "Accepting No\n"); 763 764 if (pclass->shared) 765 cupsFilePuts(fp, "Shared Yes\n"); 766 else 767 cupsFilePuts(fp, "Shared No\n"); 768 769 snprintf(value, sizeof(value), "%s %s", pclass->job_sheets[0], 770 pclass->job_sheets[1]); 771 cupsFilePutConf(fp, "JobSheets", value); 772 773 for (i = 0; i < pclass->num_printers; i ++) 774 cupsFilePrintf(fp, "Printer %s\n", pclass->printers[i]->name); 775 776 cupsFilePrintf(fp, "QuotaPeriod %d\n", pclass->quota_period); 777 cupsFilePrintf(fp, "PageLimit %d\n", pclass->page_limit); 778 cupsFilePrintf(fp, "KLimit %d\n", pclass->k_limit); 779 780 for (name = (char *)cupsArrayFirst(pclass->users); 781 name; 782 name = (char *)cupsArrayNext(pclass->users)) 783 cupsFilePutConf(fp, pclass->deny_users ? "DenyUser" : "AllowUser", name); 784 785 if (pclass->op_policy) 786 cupsFilePutConf(fp, "OpPolicy", pclass->op_policy); 787 if (pclass->error_policy) 788 cupsFilePutConf(fp, "ErrorPolicy", pclass->error_policy); 789 790 for (i = pclass->num_options, option = pclass->options; 791 i > 0; 792 i --, option ++) 793 { 794 snprintf(value, sizeof(value), "%s %s", option->name, option->value); 795 cupsFilePutConf(fp, "Option", value); 796 } 797 798 if (pclass == DefaultPrinter) 799 cupsFilePuts(fp, "</DefaultClass>\n"); 800 else 801 cupsFilePuts(fp, "</Class>\n"); 802 } 803 804 cupsdCloseCreatedConfFile(fp, filename); 805} 806 807 808/* 809 * End of "$Id: classes.c 11791 2014-04-02 16:56:54Z msweet $". 810 */ 811