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