1 //
2 // PPD file compiler main entry for the CUPS PPD Compiler.
3 //
4 // Copyright © 2020-2024 by OpenPrinting.
5 // Copyright 2007-2014 by Apple Inc.
6 // Copyright 2002-2007 by Easy Software Products.
7 //
8 // Licensed under Apache License v2.0. See the file "LICENSE" for more information.
9 //
10
11 //
12 // Include necessary headers...
13 //
14
15 #include "ppdc-private.h"
16 #include <unistd.h>
17 #include <sys/stat.h>
18 #include <sys/types.h>
19
20
21 //
22 // Local functions...
23 //
24
25 static void usage(void) _CUPS_NORETURN;
26
27
28 //
29 // 'main()' - Main entry for the PPD compiler.
30 //
31
32 int // O - Exit status
main(int argc,char * argv[])33 main(int argc, // I - Number of command-line arguments
34 char *argv[]) // I - Command-line arguments
35 {
36 int i, j; // Looping vars
37 ppdcCatalog *catalog; // Message catalog
38 const char *outdir; // Output directory
39 ppdcSource *src; // PPD source file data
40 ppdcDriver *d; // Current driver
41 cups_file_t *fp; // PPD file
42 char *opt, // Current option
43 *value, // Value in option
44 *outname, // Output filename
45 make_model[1024],
46 // Make and model
47 pcfilename[1024],
48 // Lowercase pcfilename
49 filename[1024]; // PPD filename
50 int comp, // Compress
51 do_test, // Test PPD files
52 single_language,// Generate single-language files
53 use_model_name, // Use ModelName for filename
54 verbose; // Verbosity
55 ppdcLineEnding le; // Line ending to use
56 ppdcArray *locales; // List of locales
57 cups_array_t *filenames; // List of generated filenames
58
59
60 _cupsSetLocale(argv);
61
62 // Scan the command-line...
63 catalog = NULL;
64 comp = 0;
65 do_test = 0;
66 le = PPDC_LFONLY;
67 locales = NULL;
68 outdir = "ppd";
69 single_language = 0;
70 src = new ppdcSource();
71 use_model_name = 0;
72 verbose = 0;
73 filenames = cupsArrayNew((cups_array_func_t)_cups_strcasecmp, NULL);
74
75 for (i = 1; i < argc; i ++)
76 if (argv[i][0] == '-')
77 {
78 for (opt = argv[i] + 1; *opt; opt ++)
79 switch (*opt)
80 {
81 case 'D' : // Define variable
82 i ++;
83 if (i >= argc)
84 usage();
85
86 if ((value = strchr(argv[i], '=')) != NULL)
87 {
88 *value++ = '\0';
89
90 src->set_variable(argv[i], value);
91 }
92 else
93 src->set_variable(argv[i], "1");
94 break;
95
96 case 'I' : // Include directory...
97 i ++;
98 if (i >= argc)
99 usage();
100
101 if (verbose > 1)
102 _cupsLangPrintf(stdout,
103 _("ppdc: Adding include directory \"%s\"."),
104 argv[i]);
105
106 ppdcSource::add_include(argv[i]);
107 break;
108
109 case 'c' : // Message catalog...
110 i ++;
111 if (i >= argc)
112 usage();
113
114 if (verbose > 1)
115 _cupsLangPrintf(stdout,
116 _("ppdc: Loading messages from \"%s\"."),
117 argv[i]);
118
119 if (!catalog)
120 catalog = new ppdcCatalog("en");
121
122 if (catalog->load_messages(argv[i]))
123 {
124 _cupsLangPrintf(stderr,
125 _("ppdc: Unable to load localization file "
126 "\"%s\" - %s"), argv[i], strerror(errno));
127 return (1);
128 }
129 break;
130
131 case 'd' : // Output directory...
132 i ++;
133 if (i >= argc)
134 usage();
135
136 if (verbose > 1)
137 _cupsLangPrintf(stdout,
138 _("ppdc: Writing PPD files to directory "
139 "\"%s\"."), argv[i]);
140
141 outdir = argv[i];
142 break;
143
144 case 'l' : // Language(s)...
145 i ++;
146 if (i >= argc)
147 usage();
148
149 if (strchr(argv[i], ','))
150 {
151 // Comma-delimited list of languages...
152 char temp[1024], // Copy of language list
153 *start, // Start of current locale name
154 *end; // End of current locale name
155
156
157 locales = new ppdcArray();
158
159 strlcpy(temp, argv[i], sizeof(temp));
160 for (start = temp; *start; start = end)
161 {
162 if ((end = strchr(start, ',')) != NULL)
163 *end++ = '\0';
164 else
165 end = start + strlen(start);
166
167 if (end > start)
168 locales->add(new ppdcString(start));
169 }
170 }
171 else
172 {
173 single_language = 1;
174
175 if (verbose > 1)
176 _cupsLangPrintf(stdout,
177 _("ppdc: Loading messages for locale "
178 "\"%s\"."), argv[i]);
179
180 if (catalog)
181 catalog->release();
182
183 catalog = new ppdcCatalog(argv[i]);
184
185 if (catalog->messages->count == 0 && strcmp(argv[i], "en"))
186 {
187 _cupsLangPrintf(stderr,
188 _("ppdc: Unable to find localization for "
189 "\"%s\" - %s"), argv[i], strerror(errno));
190 return (1);
191 }
192 }
193 break;
194
195 case 'm' : // Use ModelName for filename
196 use_model_name = 1;
197 break;
198
199 case 't' : // Test PPDs instead of generating them
200 do_test = 1;
201 break;
202
203 case 'v' : // Be verbose...
204 verbose ++;
205 break;
206
207 case 'z' : // Compress files...
208 comp = 1;
209 break;
210
211 case '-' : // --option
212 if (!strcmp(opt, "-lf"))
213 {
214 le = PPDC_LFONLY;
215 opt += strlen(opt) - 1;
216 break;
217 }
218 else if (!strcmp(opt, "-cr"))
219 {
220 le = PPDC_CRONLY;
221 opt += strlen(opt) - 1;
222 break;
223 }
224 else if (!strcmp(opt, "-crlf"))
225 {
226 le = PPDC_CRLF;
227 opt += strlen(opt) - 1;
228 break;
229 }
230
231 default : // Unknown
232 usage();
233 }
234 }
235 else
236 {
237 // Open and load the driver info file...
238 if (verbose > 1)
239 _cupsLangPrintf(stdout,
240 _("ppdc: Loading driver information file \"%s\"."),
241 argv[i]);
242
243 src->read_file(argv[i]);
244 }
245
246
247 if (src->drivers->count > 0)
248 {
249 // Create the output directory...
250 if (mkdir(outdir, 0777))
251 {
252 if (errno != EEXIST)
253 {
254 _cupsLangPrintf(stderr,
255 _("ppdc: Unable to create output directory %s: %s"),
256 outdir, strerror(errno));
257 return (1);
258 }
259 }
260
261 // Write PPD files...
262 for (d = (ppdcDriver *)src->drivers->first();
263 d;
264 d = (ppdcDriver *)src->drivers->next())
265 {
266 if (do_test)
267 {
268 // Test the PPD file for this driver...
269 int pid, // Process ID
270 fds[2]; // Pipe file descriptors
271
272
273 if (pipe(fds))
274 {
275 _cupsLangPrintf(stderr,
276 _("ppdc: Unable to create output pipes: %s"),
277 strerror(errno));
278 return (1);
279 }
280
281 if ((pid = fork()) == 0)
282 {
283 // Child process comes here...
284 dup2(fds[0], 0);
285
286 close(fds[0]);
287 close(fds[1]);
288
289 execlp("cupstestppd", "cupstestppd", "-", (char *)0);
290
291 _cupsLangPrintf(stderr,
292 _("ppdc: Unable to execute cupstestppd: %s"),
293 strerror(errno));
294 return (errno);
295 }
296 else if (pid < 0)
297 {
298 _cupsLangPrintf(stderr, _("ppdc: Unable to execute cupstestppd: %s"),
299 strerror(errno));
300 return (errno);
301 }
302
303 close(fds[0]);
304 fp = cupsFileOpenFd(fds[1], "w");
305 }
306 else
307 {
308 // Write the PPD file for this driver...
309 if (use_model_name)
310 {
311 if (!_cups_strncasecmp(d->model_name->value, d->manufacturer->value,
312 strlen(d->manufacturer->value)))
313 {
314 // Model name already starts with the manufacturer...
315 outname = d->model_name->value;
316 }
317 else
318 {
319 // Add manufacturer to the front of the model name...
320 snprintf(make_model, sizeof(make_model), "%s %s",
321 d->manufacturer->value, d->model_name->value);
322 outname = make_model;
323 }
324 }
325 else if (d->file_name)
326 outname = d->file_name->value;
327 else
328 outname = d->pc_file_name->value;
329
330 if (strstr(outname, ".PPD"))
331 {
332 // Convert PCFileName to lowercase...
333 for (j = 0;
334 outname[j] && j < (int)(sizeof(pcfilename) - 1);
335 j ++)
336 pcfilename[j] = (char)tolower(outname[j] & 255);
337
338 pcfilename[j] = '\0';
339 }
340 else
341 {
342 // Leave PCFileName as-is...
343 strlcpy(pcfilename, outname, sizeof(pcfilename));
344 }
345
346 // Open the PPD file for writing...
347 if (comp)
348 snprintf(filename, sizeof(filename), "%s/%s.gz", outdir, pcfilename);
349 else
350 snprintf(filename, sizeof(filename), "%s/%s", outdir, pcfilename);
351
352 if (cupsArrayFind(filenames, filename))
353 _cupsLangPrintf(stderr,
354 _("ppdc: Warning - overlapping filename \"%s\"."),
355 filename);
356 else
357 cupsArrayAdd(filenames, strdup(filename));
358
359 fp = cupsFileOpen(filename, comp ? "w9" : "w");
360 if (!fp)
361 {
362 _cupsLangPrintf(stderr,
363 _("ppdc: Unable to create PPD file \"%s\" - %s."),
364 filename, strerror(errno));
365 return (1);
366 }
367
368 if (verbose)
369 _cupsLangPrintf(stdout, _("ppdc: Writing %s."), filename);
370 }
371
372 /*
373 * Write the PPD file...
374 */
375
376 ppdcArray *templocales = locales;
377
378 if (!templocales && !single_language)
379 {
380 templocales = new ppdcArray();
381 for (ppdcCatalog *tempcatalog = (ppdcCatalog *)src->po_files->first();
382 tempcatalog;
383 tempcatalog = (ppdcCatalog *)src->po_files->next())
384 {
385 tempcatalog->locale->retain();
386 templocales->add(tempcatalog->locale);
387 }
388 }
389
390 if (d->write_ppd_file(fp, catalog, templocales, src, le))
391 {
392 cupsFileClose(fp);
393 return (1);
394 }
395
396 if (templocales && templocales != locales)
397 templocales->release();
398
399 cupsFileClose(fp);
400 }
401 }
402 else
403 usage();
404
405 // Delete the printer driver information...
406 src->release();
407
408 // Message catalog...
409 if (catalog)
410 catalog->release();
411
412 // Return with no errors.
413 return (0);
414 }
415
416
417 //
418 // 'usage()' - Show usage and exit.
419 //
420
421 static void
usage(void)422 usage(void)
423 {
424 _cupsLangPuts(stdout, _("Usage: ppdc [options] filename.drv [ ... "
425 "filenameN.drv ]"));
426 _cupsLangPuts(stdout, _("Options:"));
427 _cupsLangPuts(stdout, _(" -D name=value Set named variable to "
428 "value."));
429 _cupsLangPuts(stdout, _(" -I include-dir Add include directory to "
430 "search path."));
431 _cupsLangPuts(stdout, _(" -c catalog.po Load the specified "
432 "message catalog."));
433 _cupsLangPuts(stdout, _(" -d output-dir Specify the output "
434 "directory."));
435 _cupsLangPuts(stdout, _(" -l lang[,lang,...] Specify the output "
436 "language(s) (locale)."));
437 _cupsLangPuts(stdout, _(" -m Use the ModelName value "
438 "as the filename."));
439 _cupsLangPuts(stdout, _(" -t Test PPDs instead of "
440 "generating them."));
441 _cupsLangPuts(stdout, _(" -v Be verbose."));
442 _cupsLangPuts(stdout, _(" -z Compress PPD files using "
443 "GNU zip."));
444 _cupsLangPuts(stdout, _(" --cr End lines with CR (Mac "
445 "OS 9)."));
446 _cupsLangPuts(stdout, _(" --crlf End lines with CR + LF "
447 "(Windows)."));
448 _cupsLangPuts(stdout, _(" --lf End lines with LF "
449 "(UNIX/Linux/macOS)."));
450
451 exit(1);
452 }
453