• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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