]>
git.ipfire.org Git - thirdparty/cups.git/blob - scheduler/cups-driverd.c
4 * PPD/driver support for the Common UNIX Printing System (CUPS).
6 * Copyright 1997-2005 by Easy Software Products.
8 * These coded instructions, statements, and computer programs are the
9 * property of Easy Software Products and are protected by Federal
10 * copyright law. Distribution and use rights are outlined in the file
11 * "LICENSE.txt" which should have been included with this file. If this
12 * file is missing or damaged please contact Easy Software Products
15 * Attn: CUPS Licensing Information
16 * Easy Software Products
17 * 44141 Airport View Drive, Suite 204
18 * Hollywood, Maryland 20636 USA
20 * Voice: (301) 373-9600
21 * EMail: cups-info@cups.org
22 * WWW: http://www.cups.org
29 * Include necessary headers...
37 * PPD information structures...
40 typedef struct /**** PPD record ****/
42 time_t mtime
; /* Modification time */
43 size_t size
; /* Size in bytes */
44 char name
[512 - sizeof(time_t) - sizeof(size_t)],
46 natural_language
[128], /* Natural language(s) */
47 make
[128], /* Manufacturer */
48 make_and_model
[256]; /* Make and model */
51 typedef struct /**** In-memory record ****/
53 int found
; /* 1 if PPD is found */
54 ppd_rec_t record
; /* PPDs.dat record */
62 int NumPPDs
, /* Number of PPD files */
63 SortedPPDs
, /* Number of sorted PPD files */
64 AllocPPDs
; /* Number of allocated entries */
65 ppd_info_t
*PPDs
; /* PPD file info */
66 int ChangedPPD
; /* Did we change the PPD database? */
73 ppd_info_t
*add_ppd(const char *name
, const char *natural_language
,
74 const char *make
, const char *make_and_model
,
75 time_t mtime
, size_t size
);
76 int cat_ppd(const char *name
);
77 int compare_names(const ppd_info_t
*p0
, const ppd_info_t
*p1
);
78 int compare_ppds(const ppd_info_t
*p0
, const ppd_info_t
*p1
);
79 int list_ppds(int request_id
, int limit
, const char *optarg
);
80 int load_drivers(void);
81 int load_ppds(const char *d
, const char *p
);
85 * 'main()' - Scan for drivers and return an IPP response.
89 * cups-driverd request_id limit options
92 int /* O - Exit code */
93 main(int argc
, /* I - Number of command-line args */
94 char *argv
[]) /* I - Command-line arguments */
97 * Check the command-line...
101 (!strcmp(argv
[1], "cat") && argc
!= 3) ||
102 (!strcmp(argv
[1], "list") && argc
!= 5))
104 fputs("Usage: cups-driverd cat ppd-name\n", stderr
);
105 fputs("Usage: cups-driverd list request_id limit options\n", stderr
);
110 * Install or list PPDs...
113 if (!strcmp(argv
[1], "install"))
114 return (install_ppd(argv
[2]));
116 return (list_ppds(atoi(argv
[2]), atoi(argv
[3]), argv
[4]));
121 * 'add_ppd()' - Add a PPD file.
124 ppd_info_t
* /* O - PPD */
125 add_ppd(const char *name
, /* I - PPD name */
126 const char *natural_language
, /* I - Language(s) */
127 const char *make
, /* I - Manufacturer */
128 const char *make_and_model
, /* I - NickName */
129 time_t mtime
, /* I - Modification time */
130 size_t size
) /* I - File size */
132 ppd_info_t
*ppd
; /* PPD */
136 * Add a new PPD file...
139 if (NumPPDs
>= AllocPPDs
)
142 * Allocate (more) memory for the PPD files...
148 ppd
= malloc(sizeof(ppd_info_t
) * AllocPPDs
);
150 ppd
= realloc(PPDs
, sizeof(ppd_info_t
) * AllocPPDs
);
154 fprintf(stderr
, "ERROR: cups-driverd: Ran out of memory for %d PPD files!\n",
162 ppd
= PPDs
+ NumPPDs
;
166 * Zero-out the PPD data and copy the values over...
169 memset(ppd
, 0, sizeof(ppd_info_t
));
172 ppd
->record
.mtime
= mtime
;
173 ppd
->record
.size
= size
;
175 strlcpy(ppd
->record
.name
, name
, sizeof(ppd
->record
.name
));
176 strlcpy(ppd
->record
.make
, make
, sizeof(ppd
->record
.make
));
177 strlcpy(ppd
->record
.make_and_model
, make_and_model
,
178 sizeof(ppd
->record
.make_and_model
));
179 strlcpy(ppd
->record
.natural_language
, natural_language
,
180 sizeof(ppd
->record
.natural_language
));
183 * Return the new PPD pointer...
191 * 'cat_ppd()' - Copy a PPD file to stdout.
194 int /* O - Exit code */
195 cat_ppd(const char *name
) /* I - PPD name */
202 * 'compare_names()' - Compare PPD filenames for sorting.
205 int /* O - Result of comparison */
206 compare_names(const ppd_info_t
*p0
, /* I - First PPD file */
207 const ppd_info_t
*p1
) /* I - Second PPD file */
209 return (strcasecmp(p0
->record
.name
, p1
->record
.name
));
214 * 'compare_ppds()' - Compare PPD file make and model names for sorting.
217 int /* O - Result of comparison */
218 compare_ppds(const ppd_info_t
*p0
, /* I - First PPD file */
219 const ppd_info_t
*p1
) /* I - Second PPD file */
221 int diff
; /* Difference between strings */
224 * First compare manufacturers...
227 if ((diff
= strcasecmp(p0
->record
.make
, p1
->record
.make
)) != 0)
229 else if ((diff
= cupsdCompareNames(p0
->record
.make_and_model
,
230 p1
->record
.make_and_model
)) != 0)
233 return (strcasecmp(p0
->record
.natural_language
,
234 p1
->record
.natural_language
));
239 * 'list_ppds()' - List PPD files.
242 int /* O - Exit code */
243 list_ppds(int request_id
, /* I - Request ID */
244 int limit
, /* I - Limit */
245 const char *optarg
) /* I - Option argument */
247 const char *server_bin
; /* CUPS_SERVERBIN environment variable */
248 char driver
[1024]; /* Location of driver programs */
249 FILE *fp
; /* Pipe to driver program */
250 cups_dir_t
*dir
; /* Directory pointer */
251 cups_dentry_t
*dent
; /* Directory entry */
252 char filename
[1024], /* Name of backend */
253 line
[2048], /* Line from backend */
254 dclass
[64], /* Device class */
255 uri
[1024], /* Device URI */
256 info
[128], /* Device info */
257 make_model
[256]; /* Make and model */
258 int num_options
; /* Number of options */
259 cups_option_t
*options
; /* Options */
260 const char *requested
; /* requested-attributes option */
261 int send_natural_language
, /* Send ppd-natural-language attribute? */
262 send_make
, /* Send ppd-make attribute? */
263 send_make_and_model
, /* Send ppd-make-and-model attribute? */
264 send_name
; /* Send ppd-name attribute? */
265 dev_info_t
*dev
; /* Current device */
266 #if defined(HAVE_SIGACTION) && !defined(HAVE_SIGSET)
267 struct sigaction action
; /* Actions for POSIX signals */
268 #endif /* HAVE_SIGACTION && !HAVE_SIGSET */
269 int i
; /* Looping var */
270 ppd_info_t
*ppd
; /* Current PPD file */
271 cups_file_t
*fp
; /* PPDs.dat file */
272 struct stat fileinfo
; /* PPDs.dat information */
273 char filename
[1024]; /* PPDs.dat filename */
277 * See if we a PPD database file...
282 PPDs
= (ppd_info_t
*)0;
285 snprintf(filename
, sizeof(filename
), "%s/PPDs.dat", ServerRoot
);
286 if (!stat(filename
, &fileinfo
) &&
287 (NumPPDs
= fileinfo
.st_size
/ sizeof(ppd_rec_t
)) > 0)
290 * We have a PPDs.dat file, so read it!
295 if ((PPDs
= malloc(sizeof(ppd_info_t
) * NumPPDs
)) == NULL
)
297 fprintf(stderr
, "ERROR: LoadPPDs: Unable to allocate memory for %d PPD files!",
302 else if ((fp
= cupsFileOpen(filename
, "rb")) != NULL
)
304 for (i
= NumPPDs
, ppd
= PPDs
; i
> 0; i
--, ppd
++)
306 cupsFileRead(fp
, (char *)&(ppd
->record
), sizeof(ppd_rec_t
));
312 LogMessage(L_INFO
, "LoadPPDs: Read \"%s\", %d PPDs...", filename
,
316 * Sort the PPDs by name...
321 qsort(PPDs
, NumPPDs
, sizeof(ppd_info_t
),
322 (int (*)(const void *, const void *))compare_names
);
327 fprintf(stderr
, "ERROR: LoadPPDs: Unable to read \"%s\" - %s", filename
,
334 * Load all PPDs in the specified directory and below...
337 SortedPPDs
= NumPPDs
;
342 * Cull PPD files that are no longer present...
345 for (i
= NumPPDs
, ppd
= PPDs
; i
> 0; i
--, ppd
++)
349 * Remove this PPD file from the list...
353 memmove(ppd
, ppd
+ 1, (i
- 1) * sizeof(ppd_info_t
));
360 * Sort the PPDs by make and model...
364 qsort(PPDs
, NumPPDs
, sizeof(ppd_info_t
),
365 (int (*)(const void *, const void *))compare_PPDs
);
368 * Write the new PPDs.dat file...
373 if ((fp
= cupsFileOpen(filename
, "wb")) != NULL
)
375 for (i
= NumPPDs
, ppd
= PPDs
; i
> 0; i
--, ppd
++)
376 cupsFileWrite(fp
, (char *)&(ppd
->record
), sizeof(ppd_rec_t
));
380 LogMessage(L_INFO
, "LoadPPDs: Wrote \"%s\", %d PPDs...", filename
,
384 fprintf(stderr
, "ERROR: LoadPPDs: Unable to write \"%s\" - %s", filename
,
388 LogMessage(L_INFO
, "LoadPPDs: No new or changed PPDs...");
391 * Create the list of PPDs...
397 * First the raw driver...
400 ippAddString(PPDs
, IPP_TAG_PRINTER
, IPP_TAG_NAME
,
401 "ppd-name", NULL
, "raw");
402 ippAddString(PPDs
, IPP_TAG_PRINTER
, IPP_TAG_TEXT
,
403 "ppd-make", NULL
, "Raw");
404 ippAddString(PPDs
, IPP_TAG_PRINTER
, IPP_TAG_TEXT
,
405 "ppd-make-and-model", NULL
, "Raw Queue");
406 ippAddString(PPDs
, IPP_TAG_PRINTER
, IPP_TAG_LANGUAGE
,
407 "ppd-natural-language", NULL
, "en");
410 * Then the PPD files...
413 for (i
= NumPPDs
, ppd
= PPDs
; i
> 0; i
--, ppd
++)
415 ippAddSeparator(PPDs
);
417 ippAddString(PPDs
, IPP_TAG_PRINTER
, IPP_TAG_NAME
,
418 "ppd-name", NULL
, ppd
->record
.name
);
419 ippAddString(PPDs
, IPP_TAG_PRINTER
, IPP_TAG_TEXT
,
420 "ppd-make", NULL
, ppd
->record
.make
);
421 ippAddString(PPDs
, IPP_TAG_PRINTER
, IPP_TAG_TEXT
,
422 "ppd-make-and-model", NULL
, ppd
->record
.make_and_model
);
423 ippAddString(PPDs
, IPP_TAG_PRINTER
, IPP_TAG_LANGUAGE
,
424 "ppd-natural-language", NULL
, ppd
->record
.natural_language
);
428 * Free the memory used...
438 num_options
= cupsParseOptions(argv
[3], 0, &options
);
439 requested
= cupsGetOption("requested-attributes", num_options
, options
);
441 if (!requested
|| strstr(requested
, "all"))
445 send_make_and_model
= 1;
450 send_class
= strstr(requested
, "device-class") != NULL
;
451 send_info
= strstr(requested
, "device-info") != NULL
;
452 send_make_and_model
= strstr(requested
, "device-make-and-model") != NULL
;
453 send_uri
= strstr(requested
, "device-uri") != NULL
;
457 * Try opening the backend directory...
460 if ((server_bin
= getenv("CUPS_SERVERBIN")) == NULL
)
461 server_bin
= CUPS_SERVERBIN
;
463 snprintf(backends
, sizeof(backends
), "%s/backend", server_bin
);
465 if ((dir
= cupsDirOpen(backends
)) == NULL
)
467 fprintf(stderr
, "ERROR: [cups-deviced] Unable to open backend directory \"%s\": %s",
468 backends
, strerror(errno
));
473 * Setup the devices array...
478 devs
= (dev_info_t
*)0;
481 * Loop through all of the device backends...
484 while ((dent
= cupsDirRead(dir
)) != NULL
)
487 * Run the backend with no arguments and collect the output...
490 snprintf(filename
, sizeof(filename
), "%s/%s", backends
, dent
->filename
);
491 if ((fp
= popen(filename
, "r")) != NULL
)
494 * Set an alarm for the first read from the backend; this avoids
495 * problems when a backend is hung getting device information.
498 #ifdef HAVE_SIGSET /* Use System V signals over POSIX to avoid bugs */
499 sigset(SIGALRM
, sigalrm_handler
);
500 #elif defined(HAVE_SIGACTION)
501 memset(&action
, 0, sizeof(action
));
503 sigemptyset(&action
.sa_mask
);
504 sigaddset(&action
.sa_mask
, SIGALRM
);
505 action
.sa_handler
= sigalrm_handler
;
506 sigaction(SIGALRM
, &action
, NULL
);
508 signal(SIGALRM
, sigalrm_handler
);
509 #endif /* HAVE_SIGSET */
513 compat
= !strcmp(dent
->filename
, "smb");
517 while (fgets(line
, sizeof(line
), fp
) != NULL
)
520 * Reset the alarm clock...
526 * Each line is of the form:
528 * class URI "make model" "name"
531 if (!strncasecmp(line
, "Usage", 5))
533 else if (sscanf(line
, "%63s%1023s%*[ \t]\"%255[^\"]\"%*[ \t]\"%127[^\"]",
534 dclass
, uri
, make_model
, info
) != 4)
537 * Bad format; strip trailing newline and write an error message.
540 if (line
[strlen(line
) - 1] == '\n')
541 line
[strlen(line
) - 1] = '\0';
543 fprintf(stderr
, "ERROR: [cups-deviced] Bad line from \"%s\": %s\n",
544 dent
->filename
, line
);
551 * Add the device to the array of available devices...
554 dev
= add_dev(dclass
, make_model
, info
, uri
);
561 fprintf(stderr
, "DEBUG: [cups-deviced] Added device \"%s\"...\n", uri
);
567 * Turn the alarm clock off and close the pipe to the command...
573 fprintf(stderr
, "WARNING: [cups-deviced] Backend \"%s\" did not respond within 30 seconds!\n",
579 * Hack for backends that don't support the CUPS 1.1 calling convention:
580 * add a network device with the method == backend name.
583 if (count
== 0 && compat
)
585 snprintf(line
, sizeof(line
), "Unknown Network Device (%s)",
588 dev
= add_dev("network", line
, "Unknown", dent
->filename
);
595 fprintf(stderr
, "DEBUG: [cups-deviced] Compatibility device \"%s\"...\n",
600 fprintf(stderr
, "WARNING: [cups-deviced] Unable to execute \"%s\" backend: %s\n",
601 dent
->filename
, strerror(errno
));
607 * Sort the available devices...
611 qsort(devs
, num_devs
, sizeof(dev_info_t
),
612 (int (*)(const void *, const void *))compare_devs
);
615 * Output the list of devices...
618 puts("Content-Type: application/ipp\n");
620 cupsdSendIPPHeader(IPP_OK
, atoi(argv
[1]));
621 cupsdSendIPPGroup(IPP_TAG_OPERATION
);
622 cupsdSendIPPString(IPP_TAG_CHARSET
, "attributes-charset", "utf-8");
623 cupsdSendIPPString(IPP_TAG_LANGUAGE
, "attributes-natural-language", "en-US");
625 if ((count
= atoi(argv
[2])) <= 0)
628 if (count
> num_devs
)
631 for (dev
= devs
; count
> 0; count
--, dev
++)
634 * Add strings to attributes...
637 cupsdSendIPPGroup(IPP_TAG_PRINTER
);
639 cupsdSendIPPString(IPP_TAG_KEYWORD
, "device-class", dev
->device_class
);
641 cupsdSendIPPString(IPP_TAG_TEXT
, "device-info", dev
->device_info
);
642 if (send_make_and_model
)
643 cupsdSendIPPString(IPP_TAG_TEXT
, "device-make-and-model",
644 dev
->device_make_and_model
);
646 cupsdSendIPPString(IPP_TAG_URI
, "device-uri", dev
->device_uri
);
649 cupsdSendIPPTrailer();
652 * Free the devices array and return...
663 * 'load_ppds()' - Load PPD files recursively.
666 int /* O - 1 on success, 0 on failure */
667 load_ppds(const char *d
, /* I - Actual directory */
668 const char *p
) /* I - Virtual path in name */
670 int i
; /* Looping var */
671 cups_file_t
*fp
; /* Pointer to file */
672 cups_dir_t
*dir
; /* Directory pointer */
673 cups_dentry_t
*dent
; /* Directory entry */
674 char filename
[1024], /* Name of PPD or directory */
675 line
[256], /* Line from backend */
676 *ptr
, /* Pointer into name */
677 name
[128], /* Name of PPD file */
678 language
[64], /* PPD language version */
679 country
[64], /* Country code */
680 manufacturer
[256], /* Manufacturer */
681 make_model
[256], /* Make and Model */
682 model_name
[256], /* ModelName */
683 nick_name
[256]; /* NickName */
684 ppd_info_t
*ppd
, /* New PPD file */
685 key
; /* Search key */
686 int new_ppd
; /* Is this a new PPD? */
687 struct /* LanguageVersion translation table */
689 const char *version
, /* LanguageVersion string */
690 *language
; /* Language code */
702 { "japanese", "jp" },
703 { "norwegian", "no" },
705 { "portuguese", "pt" },
714 if ((dir
= cupsDirOpen(d
)) == NULL
)
716 fprintf(stderr
, "ERROR: cups-driverd: Unable to open PPD directory \"%s\": %s\n",
721 while ((dent
= cupsDirRead(dir
)) != NULL
)
724 * See if this is a file...
727 snprintf(filename
, sizeof(filename
), "%s/%s", d
, dent
->filename
);
730 snprintf(name
, sizeof(name
), "%s/%s", p
, dent
->filename
);
732 strlcpy(name
, dent
->filename
, sizeof(name
));
734 if (S_ISDIR(dent
->fileinfo
.st_mode
))
740 if (!load_ppds(filename
, name
))
750 * See if this file has been scanned before...
755 strcpy(key
.record
.name
, name
);
757 ppd
= bsearch(&key
, PPDs
, SortedPPDs
, sizeof(ppd_info_t
),
758 (int (*)(const void *, const void *))compare_names
);
761 ppd
->record
.size
== fileinfo
.st_size
&&
762 ppd
->record
.mtime
== fileinfo
.st_mtime
)
772 * No, file is new/changed, so re-scan it...
775 if ((fp
= cupsFileOpen(filename
, "rb")) == NULL
)
779 * Now see if this is a PPD file...
783 cupsFileGets(fp
, line
, sizeof(line
));
785 if (strncmp(line
, "*PPD-Adobe:", 11))
788 * Nope, close the file and continue...
797 * Now read until we get the NickName field...
800 model_name
[0] = '\0';
802 manufacturer
[0] = '\0';
803 strcpy(language
, "en");
805 while (cupsFileGets(fp
, line
, sizeof(line
)) != NULL
)
807 if (!strncmp(line
, "*Manufacturer:", 14))
808 sscanf(line
, "%*[^\"]\"%255[^\"]", manufacturer
);
809 else if (!strncmp(line
, "*ModelName:", 11))
810 sscanf(line
, "%*[^\"]\"%127[^\"]", model_name
);
811 else if (!strncmp(line
, "*LanguageVersion:", 17))
812 sscanf(line
, "%*[^:]:%63s", language
);
813 else if (!strncmp(line
, "*NickName:", 10))
814 sscanf(line
, "%*[^\"]\"%255[^\"]", nick_name
);
815 else if (!strncmp(line
, "*OpenUI", 7))
818 * Stop early if we have a NickName or ModelName attributes
819 * before the first OpenUI...
822 if (model_name
[0] || nick_name
[0])
827 * Stop early if we have both the Manufacturer and NickName
831 if (manufacturer
[0] && nick_name
[0])
842 * See if we got all of the required info...
846 strcpy(make_model
, nick_name
);
848 strcpy(make_model
, model_name
);
850 while (isspace(make_model
[0] & 255))
851 cups_strcpy(make_model
, make_model
+ 1);
854 continue; /* Nope... */
857 * See if we got a manufacturer...
860 while (isspace(manufacturer
[0] & 255))
861 cups_strcpy(manufacturer
, manufacturer
+ 1);
863 if (!manufacturer
[0] || !strcmp(manufacturer
, "ESP"))
866 * Nope, copy the first part of the make and model then...
869 strlcpy(manufacturer
, make_model
, sizeof(manufacturer
));
872 * Truncate at the first space, dash, or slash, or make the
873 * manufacturer "Other"...
876 for (ptr
= manufacturer
; *ptr
; ptr
++)
877 if (*ptr
== ' ' || *ptr
== '-' || *ptr
== '/')
880 if (*ptr
&& ptr
> manufacturer
)
882 else if (!strncasecmp(manufacturer
, "agfa", 4))
883 strcpy(manufacturer
, "AGFA");
884 else if (!strncasecmp(manufacturer
, "herk", 4) ||
885 !strncasecmp(manufacturer
, "linotype", 8))
886 strcpy(manufacturer
, "LHAG");
888 strcpy(manufacturer
, "Other");
891 * Hack for various vendors...
894 if (!strcasecmp(manufacturer
, "XPrint"))
895 strcpy(manufacturer
, "Xerox");
896 else if (!strcasecmp(manufacturer
, "Eastman"))
897 strcpy(manufacturer
, "Kodak");
898 else if (!strcasecmp(manufacturer
, "laserwriter"))
899 strcpy(manufacturer
, "Apple");
900 else if (!strcasecmp(manufacturer
, "colorpoint"))
901 strcpy(manufacturer
, "Seiko");
902 else if (!strcasecmp(manufacturer
, "fiery"))
903 strcpy(manufacturer
, "EFI");
904 else if (!strcasecmp(manufacturer
, "ps") ||
905 !strcasecmp(manufacturer
, "colorpass"))
906 strcpy(manufacturer
, "Canon");
907 else if (!strncasecmp(manufacturer
, "primera", 7))
908 strcpy(manufacturer
, "Fargo");
909 else if (!strcasecmp(manufacturer
, "designjet"))
910 strcpy(manufacturer
, "HP");
912 else if (!strncasecmp(manufacturer
, "LHAG", 4) ||
913 !strncasecmp(manufacturer
, "linotype", 8))
914 strcpy(manufacturer
, "LHAG");
917 * Fix the language as needed...
920 if ((ptr
= strchr(language
, '-')) != NULL
)
922 else if ((ptr
= strchr(language
, '_')) != NULL
)
928 * Setup the country suffix...
932 cups_strcpy(country
+ 1, ptr
);
937 * No country suffix...
943 for (i
= 0; i
< (int)(sizeof(languages
) / sizeof(languages
[0])); i
++)
944 if (!strcasecmp(languages
[i
].version
, language
))
947 if (i
< (int)(sizeof(languages
) / sizeof(languages
[0])))
950 * Found a known language...
953 snprintf(language
, sizeof(language
), "%s%s", languages
[i
].language
,
959 * Unknown language; use "xx"...
962 strcpy(language
, "xx");
966 * Add the PPD file...
974 * Add new PPD file...
977 fprintf(stderr
, "DEBUG: cups-driverd: Adding ppd \"%s\"...\n", name
);
979 if (!add_ppd(name
, manufacturer
, make_model
, language
,
980 dent
->fileinfo
.st_mtime
, dent
->fileinfo
.st_size
))
989 * Update existing record...
992 fprintf(stderr
, "DEBUG: cups-driverd: Updating ppd \"%s\"...\n", name
);
994 memset(ppd
, 0, sizeof(ppd_info_t
));
997 ppd
->record
.mtime
= dent
->fileinfo
.st_mtime
;
998 ppd
->record
.size
= dent
->fileinfo
.st_size
;
1000 strlcpy(ppd
->record
.name
, name
, sizeof(ppd
->record
.name
));
1001 strlcpy(ppd
->record
.make
, manufacturer
, sizeof(ppd
->record
.make
));
1002 strlcpy(ppd
->record
.make_and_model
, make_model
,
1003 sizeof(ppd
->record
.make_and_model
));
1004 strlcpy(ppd
->record
.natural_language
, language
,
1005 sizeof(ppd
->record
.natural_language
));
1018 * 'load_drivers()' - Load driver-generated PPD files.
1021 int /* O - 1 on success, 0 on failure */