/*
 * PPD/driver support for CUPS.
 *
 * This program handles listing and installing static PPD files, PPD files
 * created from driver information files, and dynamically generated PPD files
 * using driver helper programs.
 *
 * Copyright © 2021-2022 by OpenPrinting.
 * Copyright © 2007-2019 by Apple Inc.
 * Copyright © 1997-2007 by Easy Software Products.
 *
 * Licensed under Apache License v2.0.  See the file "LICENSE" for more
 * information.
 */

/*
 * Include necessary headers...
 */

#include "util.h"
#include <cups/dir.h>
#include <cups/transcode.h>
#include <cups/ppd-private.h>
#include <ppdc/ppdc.h>
#include <regex.h>


/*
 * Constants...
 */

#define PPD_SYNC	0x50504441	/* Sync word for ppds.dat (PPDA) */
#define PPD_MAX_LANG	32		/* Maximum languages */
#define PPD_MAX_PROD	32		/* Maximum products */
#define PPD_MAX_VERS	32		/* Maximum versions */

#define PPD_TYPE_POSTSCRIPT	0	/* PostScript PPD */
#define PPD_TYPE_PDF		1	/* PDF PPD */
#define PPD_TYPE_RASTER		2	/* CUPS raster PPD */
#define PPD_TYPE_FAX		3	/* Facsimile/MFD PPD */
#define PPD_TYPE_UNKNOWN	4	/* Other/hybrid PPD */
#define PPD_TYPE_DRV		5	/* Driver info file */
#define PPD_TYPE_ARCHIVE	6	/* Archive file */

#define TAR_BLOCK	512		/* Number of bytes in a block */
#define TAR_BLOCKS	10		/* Blocking factor */

#define TAR_MAGIC	"ustar"		/* 5 chars and a null */
#define TAR_VERSION	"00"		/* POSIX tar version */

#define TAR_OLDNORMAL	'\0'		/* Normal disk file, Unix compat */
#define TAR_NORMAL	'0'		/* Normal disk file */
#define TAR_LINK	'1'		/* Link to previously dumped file */
#define TAR_SYMLINK	'2'		/* Symbolic link */
#define TAR_CHR		'3'		/* Character special file */
#define TAR_BLK		'4'		/* Block special file */
#define TAR_DIR		'5'		/* Directory */
#define TAR_FIFO	'6'		/* FIFO special file */
#define TAR_CONTIG	'7'		/* Contiguous file */


/*
 * PPD information structures...
 */

typedef struct				/**** PPD record ****/
{
  time_t	mtime;			/* Modification time */
  off_t		size;			/* Size in bytes */
  int		model_number;		/* cupsModelNumber */
  int		type;			/* ppd-type */
  char		filename[512],		/* Filename */
		name[256],		/* PPD name */
		languages[PPD_MAX_LANG][6],
					/* LanguageVersion/cupsLanguages */
		products[PPD_MAX_PROD][128],
					/* Product strings */
		psversions[PPD_MAX_VERS][32],
					/* PSVersion strings */
		make[128],		/* Manufacturer */
		make_and_model[128],	/* NickName/ModelName */
		device_id[256],		/* IEEE 1284 Device ID */
		scheme[128];		/* PPD scheme */
} ppd_rec_t;

typedef struct				/**** In-memory record ****/
{
  int		found;			/* 1 if PPD is found */
  int		matches;		/* Match count */
  ppd_rec_t	record;			/* PPDs.dat record */
} ppd_info_t;

typedef union				/**** TAR record format ****/
{
  unsigned char	all[TAR_BLOCK];		/* Raw data block */
  struct
  {
    char	pathname[100],		/* Destination path */
		mode[8],		/* Octal file permissions */
		uid[8],			/* Octal user ID */
		gid[8],			/* Octal group ID */
		size[12],		/* Octal size in bytes */
		mtime[12],		/* Octal modification time */
		chksum[8],		/* Octal checksum value */
		linkflag,		/* File type */
		linkname[100],		/* Source path for link */
		magic[6],		/* Magic string */
		version[2],		/* Format version */
		uname[32],		/* User name */
		gname[32],		/* Group name */
		devmajor[8],		/* Octal device major number */
		devminor[8],		/* Octal device minor number */
		prefix[155];		/* Prefix for long filenames */
  }	header;
} tar_rec_t;


/*
 * Globals...
 */

static cups_array_t	*Inodes = NULL,	/* Inodes of directories we've visited */
			*PPDsByName = NULL,
					/* PPD files sorted by filename and name */
			*PPDsByMakeModel = NULL;
					/* PPD files sorted by make and model */
static int		ChangedPPD;	/* Did we change the PPD database? */
static const char * const PPDTypes[] =	/* ppd-type values */
			{
			  "postscript",
			  "pdf",
			  "raster",
			  "fax",
			  "object",
			  "object-direct",
			  "object-storage",
			  "unknown",
			  "drv",
			  "archive"
			};


/*
 * Local functions...
 */

static ppd_info_t	*add_ppd(const char *filename, const char *name,
			         const char *language, const char *make,
				 const char *make_and_model,
				 const char *device_id, const char *product,
				 const char *psversion, time_t mtime,
				 size_t size, int model_number, int type,
				 const char *scheme);
static int		cat_drv(const char *name, int request_id);
static void		cat_ppd(const char *name, int request_id);
static int		cat_static(const char *name, int request_id);
static int		cat_tar(const char *name, int request_id);
static int		compare_inodes(struct stat *a, struct stat *b);
static int		compare_matches(const ppd_info_t *p0,
			                const ppd_info_t *p1);
static int		compare_names(const ppd_info_t *p0,
			              const ppd_info_t *p1);
static int		compare_ppds(const ppd_info_t *p0,
			             const ppd_info_t *p1);
static void		dump_ppds_dat(const char *filename);
static void		free_array(cups_array_t *a);
static cups_file_t	*get_file(const char *name, int request_id,
			          const char *subdir, char *buffer,
			          size_t bufsize, char **subfile);
static void		list_ppds(int request_id, int limit, const char *opt);
static int		load_drivers(cups_array_t *include,
			             cups_array_t *exclude);
static int		load_drv(const char *filename, const char *name,
			         cups_file_t *fp, time_t mtime, off_t size);
static void		load_ppd(const char *filename, const char *name,
			         const char *scheme, struct stat *fileinfo,
			         ppd_info_t *ppd, cups_file_t *fp, off_t end);
static int		load_ppds(const char *d, const char *p, int descend);
static void		load_ppds_dat(char *filename, size_t filesize,
			              int verbose);
static int		load_tar(const char *filename, const char *name,
			         cups_file_t *fp, time_t mtime, off_t size);
static int		read_tar(cups_file_t *fp, char *name, size_t namesize,
			         struct stat *info);
static regex_t		*regex_device_id(const char *device_id);
static regex_t		*regex_string(const char *s);


/*
 * 'main()' - Scan for drivers and return an IPP response.
 *
 * Usage:
 *
 *    cups-driverd request_id limit options
 */

int					/* O - Exit code */
main(int  argc,				/* I - Number of command-line args */
     char *argv[])			/* I - Command-line arguments */
{
 /*
  * Install or list PPDs...
  */

  if (argc == 3 && !strcmp(argv[1], "cat"))
    cat_ppd(argv[2], 0);
  else if ((argc == 2 || argc == 3) && !strcmp(argv[1], "dump"))
    dump_ppds_dat(argv[2]);
  else if (argc == 4 && !strcmp(argv[1], "get"))
    cat_ppd(argv[3], atoi(argv[2]));
  else if (argc == 5 && !strcmp(argv[1], "list"))
    list_ppds(atoi(argv[2]), atoi(argv[3]), argv[4]);
  else
  {
    fputs("Usage: cups-driverd cat ppd-name\n", stderr);
    fputs("Usage: cups-driverd dump\n", stderr);
    fputs("Usage: cups-driverd get request_id ppd-name\n", stderr);
    fputs("Usage: cups-driverd list request_id limit options\n", stderr);
    return (1);
  }
}


/*
 * 'add_ppd()' - Add a PPD file.
 */

static ppd_info_t *			/* O - PPD */
add_ppd(const char *filename,		/* I - PPD filename */
        const char *name,		/* I - PPD name */
        const char *language,		/* I - LanguageVersion */
        const char *make,		/* I - Manufacturer */
	const char *make_and_model,	/* I - NickName/ModelName */
	const char *device_id,		/* I - 1284DeviceID */
	const char *product,		/* I - Product */
	const char *psversion,		/* I - PSVersion */
        time_t     mtime,		/* I - Modification time */
	size_t     size,		/* I - File size */
	int        model_number,	/* I - Model number */
	int        type,		/* I - Driver type */
	const char *scheme)		/* I - PPD scheme */
{
  ppd_info_t	*ppd;			/* PPD */
  char		*recommended;		/* Foomatic driver string */


 /*
  * Add a new PPD file...
  */

  if ((ppd = (ppd_info_t *)calloc(1, sizeof(ppd_info_t))) == NULL)
  {
    fprintf(stderr,
	    "ERROR: [cups-driverd] Ran out of memory for %d PPD files!\n",
	    cupsArrayCount(PPDsByName));
    return (NULL);
  }

 /*
  * Zero-out the PPD data and copy the values over...
  */

  ppd->found               = 1;
  ppd->record.mtime        = mtime;
  ppd->record.size         = (off_t)size;
  ppd->record.model_number = model_number;
  ppd->record.type         = type;

  strlcpy(ppd->record.filename, filename, sizeof(ppd->record.filename));
  strlcpy(ppd->record.name, name, sizeof(ppd->record.name));
  strlcpy(ppd->record.languages[0], language,
          sizeof(ppd->record.languages[0]));
  strlcpy(ppd->record.products[0], product, sizeof(ppd->record.products[0]));
  strlcpy(ppd->record.psversions[0], psversion,
          sizeof(ppd->record.psversions[0]));
  strlcpy(ppd->record.make, make, sizeof(ppd->record.make));
  strlcpy(ppd->record.make_and_model, make_and_model,
          sizeof(ppd->record.make_and_model));
  strlcpy(ppd->record.device_id, device_id, sizeof(ppd->record.device_id));
  strlcpy(ppd->record.scheme, scheme, sizeof(ppd->record.scheme));

 /*
  * Strip confusing (and often wrong) "recommended" suffix added by
  * Foomatic drivers...
  */

  if ((recommended = strstr(ppd->record.make_and_model,
                            " (recommended)")) != NULL)
    *recommended = '\0';

 /*
  * Add the PPD to the PPD arrays...
  */

  cupsArrayAdd(PPDsByName, ppd);
  cupsArrayAdd(PPDsByMakeModel, ppd);

 /*
  * Return the new PPD pointer...
  */

  return (ppd);
}


/*
 * 'cat_drv()' - Generate a PPD from a driver info file.
 */

static int				/* O - Exit code */
cat_drv(const char *name,		/* I - PPD name */
        int        request_id)		/* I - Request ID for response? */
{
  cups_file_t	*fp;			// File pointer
  ppdcSource	*src;			// PPD source file data
  ppdcDriver	*d;			// Current driver
  cups_file_t	*out;			// Stdout via CUPS file API
  char		message[2048],		// status-message
		filename[1024],		// Full path to .drv file(s)
		scheme[32],		// URI scheme ("drv")
		userpass[256],		// User/password info (unused)
		host[2],		// Hostname (unused)
		resource[1024],		// Resource path (/dir/to/filename.drv)
		*pc_file_name;		// Filename portion of URI
  int		port;			// Port number (unused)


  // Pull out the path to the .drv file...
  if (httpSeparateURI(HTTP_URI_CODING_ALL, name, scheme, sizeof(scheme),
                      userpass, sizeof(userpass), host, sizeof(host), &port,
		      resource, sizeof(resource)) < HTTP_URI_OK)
  {
    fprintf(stderr, "ERROR: Bad PPD name \"%s\".\n", name);

    if (request_id)
    {
      snprintf(message, sizeof(message), "Bad PPD name \"%s\".", name);

      cupsdSendIPPHeader(IPP_NOT_FOUND, request_id);
      cupsdSendIPPGroup(IPP_TAG_OPERATION);
      cupsdSendIPPString(IPP_TAG_CHARSET, "attributes-charset", "utf-8");
      cupsdSendIPPString(IPP_TAG_LANGUAGE, "attributes-natural-language",
			 "en-US");
      cupsdSendIPPString(IPP_TAG_TEXT, "status-message", message);
      cupsdSendIPPTrailer();
    }

    return (1);
  }

  if ((fp = get_file(resource, request_id, "drv", filename, sizeof(filename), &pc_file_name)) == NULL || !pc_file_name)
    return (1);

  src = new ppdcSource(filename, fp);

  for (d = (ppdcDriver *)src->drivers->first();
       d;
       d = (ppdcDriver *)src->drivers->next())
    if (!strcmp(pc_file_name, d->pc_file_name->value) ||
        (d->file_name && !strcmp(pc_file_name, d->file_name->value)))
      break;

  if (d)
  {
    ppdcArray	*locales;		// Locale names
    ppdcCatalog	*catalog;		// Message catalog in .drv file


    fprintf(stderr, "DEBUG2: [cups-driverd] %u locales defined in \"%s\"...\n", (unsigned)src->po_files->count, filename);

    locales = new ppdcArray();
    for (catalog = (ppdcCatalog *)src->po_files->first();
         catalog;
	 catalog = (ppdcCatalog *)src->po_files->next())
    {
      fprintf(stderr, "DEBUG2: [cups-driverd] Adding locale \"%s\"...\n",
              catalog->locale->value);
      catalog->locale->retain();
      locales->add(catalog->locale);
    }

    if (request_id)
    {
      cupsdSendIPPHeader(IPP_OK, request_id);
      cupsdSendIPPGroup(IPP_TAG_OPERATION);
      cupsdSendIPPString(IPP_TAG_CHARSET, "attributes-charset", "utf-8");
      cupsdSendIPPString(IPP_TAG_LANGUAGE, "attributes-natural-language",
			 "en-US");
      cupsdSendIPPTrailer();
      fflush(stdout);
    }

    out = cupsFileStdout();
    d->write_ppd_file(out, NULL, locales, src, PPDC_LFONLY);
    cupsFileClose(out);

    locales->release();
  }
  else
  {
    fprintf(stderr, "ERROR: PPD \"%s\" not found.\n", name);

    if (request_id)
    {
      snprintf(message, sizeof(message), "PPD \"%s\" not found.", name);

      cupsdSendIPPHeader(IPP_NOT_FOUND, request_id);
      cupsdSendIPPGroup(IPP_TAG_OPERATION);
      cupsdSendIPPString(IPP_TAG_CHARSET, "attributes-charset", "utf-8");
      cupsdSendIPPString(IPP_TAG_LANGUAGE, "attributes-natural-language",
			 "en-US");
      cupsdSendIPPString(IPP_TAG_TEXT, "status-message", message);
      cupsdSendIPPTrailer();
    }
  }

  src->release();
  cupsFileClose(fp);

  return (!d);
}


/*
 * 'cat_ppd()' - Copy a PPD file to stdout.
 */

static void
cat_ppd(const char *name,		/* I - PPD name */
        int        request_id)		/* I - Request ID for response? */
{
  char		scheme[256],		/* Scheme from PPD name */
		*sptr,			/* Pointer into scheme */
		line[1024],		/* Line/filename */
		message[2048];		/* status-message */


 /*
  * Figure out if this is a static or dynamic PPD file...
  */

  if (strstr(name, "../"))
  {
    fputs("ERROR: Invalid PPD name.\n", stderr);
    exit(1);
  }

  strlcpy(scheme, name, sizeof(scheme));
  if ((sptr = strchr(scheme, ':')) != NULL)
  {
    *sptr = '\0';

    if (!strcmp(scheme, "file"))
    {
     /*
      * "file:name" == "name"...
      */

      name += 5;

      while (*name == '/')
        name ++;

      if (!strstr(name, ".tar/") && !strstr(name, ".tar.gz/"))
	scheme[0] = '\0';
    }
  }
  else
    scheme[0] = '\0';

  if (request_id > 0)
    puts("Content-Type: application/ipp\n");

  if (!scheme[0])
    exit(cat_static(name, request_id));
  else if (!strcmp(scheme, "drv"))
    exit(cat_drv(name, request_id));
  else if (!strcmp(scheme, "file"))
    exit(cat_tar(name, request_id));
  else
  {
   /*
    * Dynamic PPD, see if we have a driver program to support it...
    */

    const char	*serverbin;		/* CUPS_SERVERBIN env var */
    char	*argv[4];		/* Arguments for program */


    if ((serverbin = getenv("CUPS_SERVERBIN")) == NULL)
      serverbin = CUPS_SERVERBIN;

    snprintf(line, sizeof(line), "%s/driver/%s", serverbin, scheme);
    if (access(line, X_OK))
    {
     /*
      * File does not exist or is not executable...
      */

      fprintf(stderr, "ERROR: [cups-driverd] Unable to access \"%s\" - %s\n",
              line, strerror(errno));

      if (request_id > 0)
      {
        snprintf(message, sizeof(message), "Unable to access \"%s\" - %s",
		 line, strerror(errno));

	cupsdSendIPPHeader(IPP_NOT_FOUND, request_id);
	cupsdSendIPPGroup(IPP_TAG_OPERATION);
	cupsdSendIPPString(IPP_TAG_CHARSET, "attributes-charset", "utf-8");
	cupsdSendIPPString(IPP_TAG_LANGUAGE, "attributes-natural-language",
	                   "en-US");
        cupsdSendIPPString(IPP_TAG_TEXT, "status-message", message);
        cupsdSendIPPTrailer();
      }

      exit(1);
    }

   /*
    * Yes, let it cat the PPD file...
    */

    if (request_id)
    {
      cupsdSendIPPHeader(IPP_OK, request_id);
      cupsdSendIPPGroup(IPP_TAG_OPERATION);
      cupsdSendIPPString(IPP_TAG_CHARSET, "attributes-charset", "utf-8");
      cupsdSendIPPString(IPP_TAG_LANGUAGE, "attributes-natural-language",
			 "en-US");
      cupsdSendIPPTrailer();
    }

    argv[0] = scheme;
    argv[1] = (char *)"cat";
    argv[2] = (char *)name;
    argv[3] = NULL;

    if (cupsdExec(line, argv))
    {
     /*
      * Unable to execute driver...
      */

      fprintf(stderr, "ERROR: [cups-driverd] Unable to execute \"%s\" - %s\n",
              line, strerror(errno));
      exit(1);
    }
  }

 /*
  * Exit with no errors...
  */

  exit(0);
}


/*
 * 'copy_static()' - Copy a static PPD file to stdout.
 */

static int				/* O - Exit code */
cat_static(const char *name,		/* I - PPD name */
           int        request_id)	/* I - Request ID for response? */
{
  cups_file_t	*fp;			/* PPD file */
  char		filename[1024],		/* PPD filename */
		line[1024];		/* Line buffer */


  if ((fp = get_file(name, request_id, "model", filename, sizeof(filename),
                     NULL)) == NULL)
    return (1);

  if (request_id)
  {
    cupsdSendIPPHeader(IPP_OK, request_id);
    cupsdSendIPPGroup(IPP_TAG_OPERATION);
    cupsdSendIPPString(IPP_TAG_CHARSET, "attributes-charset", "utf-8");
    cupsdSendIPPString(IPP_TAG_LANGUAGE, "attributes-natural-language",
		       "en-US");
    cupsdSendIPPTrailer();
  }

 /*
  * Now copy the file to stdout...
  */

  while (cupsFileGets(fp, line, sizeof(line)))
    puts(line);

  cupsFileClose(fp);

  return (0);
}


/*
 * 'cat_tar()' - Copy an archived PPD file to stdout.
 */

static int				/* O - Exit code */
cat_tar(const char *name,		/* I - PPD name */
        int        request_id)		/* I - Request ID */
{
  cups_file_t	*fp;			/* Archive file pointer */
  char		filename[1024],		/* Archive filename */
		*ppdname,		/* PPD filename in archive */
		curname[256],		/* Current name in archive */
		buffer[8192];		/* Copy buffer */
  struct stat	curinfo;		/* Current file info in archive */
  off_t		total,			/* Total bytes copied */
		next;			/* Offset for next record in archive */
  ssize_t	bytes;			/* Bytes read */


 /*
  * Open the archive file...
  */

  if ((fp = get_file(name, request_id, "model", filename, sizeof(filename),
                     &ppdname)) == NULL || !ppdname)
    return (1);

 /*
  * Scan the archive for the PPD...
  */

  while (read_tar(fp, curname, sizeof(curname), &curinfo))
  {
    next = cupsFileTell(fp) + ((curinfo.st_size + TAR_BLOCK - 1) &
                               ~(TAR_BLOCK - 1));

    if (!strcmp(ppdname, curname))
    {
      if (request_id)
      {
	cupsdSendIPPHeader(IPP_OK, request_id);
	cupsdSendIPPGroup(IPP_TAG_OPERATION);
	cupsdSendIPPString(IPP_TAG_CHARSET, "attributes-charset", "utf-8");
	cupsdSendIPPString(IPP_TAG_LANGUAGE, "attributes-natural-language",
			   "en-US");
	cupsdSendIPPTrailer();
      }

      for (total = 0; total < curinfo.st_size; total += bytes)
      {
        if ((size_t)(bytes = (curinfo.st_size - total)) > sizeof(buffer))
          bytes = sizeof(buffer);

        if ((bytes = cupsFileRead(fp, buffer, (size_t)bytes)) < 0)
        {
          if (errno == EINTR || errno == EAGAIN)
          {
            bytes = 0;
          }
          else
          {
            perror("ERROR: [cups-driverd] Read error");
            break;
          }
        }
        else if (bytes > 0 && fwrite(buffer, (size_t)bytes, 1, stdout) != 1)
          break;
      }

      cupsFileClose(fp);
      return (0);
    }

    if (cupsFileTell(fp) != next)
    {
      if (cupsFileSeek(fp, next) != next)
        break;
    }
  }

  cupsFileClose(fp);

  fprintf(stderr, "ERROR: PPD \"%s\" not found.\n", name);

  if (request_id)
  {
    snprintf(buffer, sizeof(buffer), "PPD \"%s\" not found.", name);

    cupsdSendIPPHeader(IPP_NOT_FOUND, request_id);
    cupsdSendIPPGroup(IPP_TAG_OPERATION);
    cupsdSendIPPString(IPP_TAG_CHARSET, "attributes-charset", "utf-8");
    cupsdSendIPPString(IPP_TAG_LANGUAGE, "attributes-natural-language",
		       "en-US");
    cupsdSendIPPString(IPP_TAG_TEXT, "status-message", buffer);
    cupsdSendIPPTrailer();
  }

  return (1);
}


/*
 * 'compare_inodes()' - Compare two inodes.
 */

static int				/* O - Result of comparison */
compare_inodes(struct stat *a,		/* I - First inode */
               struct stat *b)		/* I - Second inode */
{
  if (a->st_dev != b->st_dev)
    return (a->st_dev - b->st_dev);
  else
    return (a->st_ino - b->st_ino);
}


/*
 * 'compare_matches()' - Compare PPD match scores for sorting.
 */

static int
compare_matches(const ppd_info_t *p0,	/* I - First PPD */
                const ppd_info_t *p1)	/* I - Second PPD */
{
  if (p1->matches != p0->matches)
    return (p1->matches - p0->matches);
  else
    return (cupsdCompareNames(p0->record.make_and_model,
			      p1->record.make_and_model));
}


/*
 * 'compare_names()' - Compare PPD filenames for sorting.
 */

static int				/* O - Result of comparison */
compare_names(const ppd_info_t *p0,	/* I - First PPD file */
              const ppd_info_t *p1)	/* I - Second PPD file */
{
  int	diff;				/* Difference between strings */


  if ((diff = strcmp(p0->record.filename, p1->record.filename)) != 0)
    return (diff);
  else
    return (strcmp(p0->record.name, p1->record.name));
}


/*
 * 'compare_ppds()' - Compare PPD file make and model names for sorting.
 */

static int				/* O - Result of comparison */
compare_ppds(const ppd_info_t *p0,	/* I - First PPD file */
             const ppd_info_t *p1)	/* I - Second PPD file */
{
  int	diff;				/* Difference between strings */


 /*
  * First compare manufacturers...
  */

  if ((diff = _cups_strcasecmp(p0->record.make, p1->record.make)) != 0)
    return (diff);
  else if ((diff = cupsdCompareNames(p0->record.make_and_model,
                                     p1->record.make_and_model)) != 0)
    return (diff);
  else if ((diff = strcmp(p0->record.languages[0],
                          p1->record.languages[0])) != 0)
    return (diff);
  else
    return (compare_names(p0, p1));
}


/*
 * 'dump_ppds_dat()' - Dump the contents of the ppds.dat file.
 */

static void
dump_ppds_dat(const char *filename)	/* I - Filename */
{
  char		temp[1024];		/* ppds.dat filename */
  ppd_info_t	*ppd;			/* Current PPD */


 /*
  * See if we a PPD database file...
  */

  if (filename)
    strlcpy(temp, filename, sizeof(temp));
  else
    temp[0] = '\0';

  load_ppds_dat(temp, sizeof(temp), 0);

  puts("mtime,size,model_number,type,filename,name,languages0,products0,"
       "psversions0,make,make_and_model,device_id,scheme");
  for (ppd = (ppd_info_t *)cupsArrayFirst(PPDsByName);
       ppd;
       ppd = (ppd_info_t *)cupsArrayNext(PPDsByName))
    printf("%d,%ld,%d,%d,\"%s\",\"%s\",\"%s\",\"%s\",\"%s\",\"%s\",\"%s\","
           "\"%s\",\"%s\"\n",
           (int)ppd->record.mtime, (long)ppd->record.size,
	   ppd->record.model_number, ppd->record.type, ppd->record.filename,
	   ppd->record.name, ppd->record.languages[0], ppd->record.products[0],
	   ppd->record.psversions[0], ppd->record.make,
	   ppd->record.make_and_model, ppd->record.device_id,
	   ppd->record.scheme);

  exit(0);
}


/*
 * 'free_array()' - Free an array of strings.
 */

static void
free_array(cups_array_t *a)		/* I - Array to free */
{
  char	*ptr;				/* Pointer to string */


  for (ptr = (char *)cupsArrayFirst(a);
       ptr;
       ptr = (char *)cupsArrayNext(a))
    free(ptr);

  cupsArrayDelete(a);
}


/*
 * 'get_file()' - Get the filename associated with a request.
 */

static cups_file_t *			/* O - File pointer or NULL */
get_file(const char *name,		/* I - Name */
	 int        request_id,		/* I - Request ID */
	 const char *subdir,		/* I - Subdirectory for file */
	 char       *buffer,		/* I - Filename buffer */
	 size_t     bufsize,		/* I - Size of filename buffer */
	 char       **subfile)		/* O - Sub-filename */
{
  cups_file_t	*fp;			/* File pointer */
  const char	*datadir;		/* CUPS_DATADIR env var */
  char		*bufptr,		/* Pointer into filename buffer */
		message[2048];		/* status-message */
#ifdef __APPLE__
  const char	*printerDriver,		/* Pointer to .printerDriver extension */
		*slash;			/* Pointer to next slash */
#endif /* __APPLE__ */


  if (subfile)
    *subfile = NULL;

  while (*name == '/')
    name ++;

  if (strstr(name, "../") || strstr(name, "/.."))
  {
   /*
    * Bad name...
    */

    fprintf(stderr, "ERROR: [cups-driverd] Bad PPD name \"%s\".\n", name);

    if (request_id)
    {
      snprintf(message, sizeof(message), "Bad PPD name \"%s\".", name);

      cupsdSendIPPHeader(IPP_NOT_FOUND, request_id);
      cupsdSendIPPGroup(IPP_TAG_OPERATION);
      cupsdSendIPPString(IPP_TAG_CHARSET, "attributes-charset", "utf-8");
      cupsdSendIPPString(IPP_TAG_LANGUAGE, "attributes-natural-language",
			 "en-US");
      cupsdSendIPPString(IPP_TAG_TEXT, "status-message", message);
      cupsdSendIPPTrailer();
    }

    return (NULL);
  }

 /*
  * Try opening the file...
  */

#ifdef __APPLE__
  if (!strncmp(name, "System/Library/Printers/PPDs/Contents/Resources/", 48) ||
      !strncmp(name, "Library/Printers/PPDs/Contents/Resources/", 41) ||
      (!strncmp(name, "System/Library/Printers/", 24) &&
       (printerDriver =
	    strstr(name + 24,
		   ".printerDriver/Contents/Resources/PPDs")) != NULL &&
       (slash = strchr(name + 24, '/')) != NULL &&
       slash > printerDriver) ||
      (!strncmp(name, "Library/Printers/", 17) &&
       (printerDriver =
	    strstr(name + 17,
		   ".printerDriver/Contents/Resources/PPDs")) != NULL &&
       (slash = strchr(name + 17, '/')) != NULL &&
       slash > printerDriver))
  {
   /*
    * Map ppd-name to macOS standard locations...
    */

    snprintf(buffer, bufsize, "/%s", name);
  }
  else

#elif defined(__linux)
  if (!strncmp(name, "lsb/usr/", 8))
  {
   /*
    * Map ppd-name to LSB standard /usr/share/ppd location...
    */

    snprintf(buffer, bufsize, "/usr/share/ppd/%s", name + 8);
  }
  else if (!strncmp(name, "lsb/opt/", 8))
  {
   /*
    * Map ppd-name to LSB standard /opt/share/ppd location...
    */

    snprintf(buffer, bufsize, "/opt/share/ppd/%s", name + 8);
  }
  else if (!strncmp(name, "lsb/local/", 10))
  {
   /*
    * Map ppd-name to LSB standard /usr/local/share/ppd location...
    */

    snprintf(buffer, bufsize, "/usr/local/share/ppd/%s", name + 10);
  }
  else

#endif /* __APPLE__ */
  {
    if ((datadir = getenv("CUPS_DATADIR")) == NULL)
      datadir = CUPS_DATADIR;

    snprintf(buffer, bufsize, "%s/%s/%s", datadir, subdir, name);
  }

 /*
  * Strip anything after ".drv/", ".drv.gz/", ".tar/",  or ".tar.gz/"...
  */

  if (subfile)
  {
    if ((bufptr = strstr(buffer, ".drv/")) != NULL)
      bufptr += 4;
    else if ((bufptr = strstr(buffer, ".drv.gz/")) != NULL)
      bufptr += 7;
    else if ((bufptr = strstr(buffer, ".tar/")) != NULL)
      bufptr += 4;
    else if ((bufptr = strstr(buffer, ".tar.gz/")) != NULL)
      bufptr += 7;

    if (bufptr)
    {
      *bufptr++ = '\0';
      *subfile  = bufptr;
    }
  }

 /*
  * Try opening the file...
  */

  if ((fp = cupsFileOpen(buffer, "r")) == NULL)
  {
    fprintf(stderr, "ERROR: [cups-driverd] Unable to open \"%s\" - %s\n",
	    buffer, strerror(errno));

    if (request_id)
    {
      snprintf(message, sizeof(message), "Unable to open \"%s\" - %s",
	       buffer, strerror(errno));

      cupsdSendIPPHeader(IPP_NOT_FOUND, request_id);
      cupsdSendIPPGroup(IPP_TAG_OPERATION);
      cupsdSendIPPString(IPP_TAG_CHARSET, "attributes-charset", "utf-8");
      cupsdSendIPPString(IPP_TAG_LANGUAGE, "attributes-natural-language",
			 "en-US");
      cupsdSendIPPString(IPP_TAG_TEXT, "status-message", message);
      cupsdSendIPPTrailer();
    }

    return (NULL);
  }

  return (fp);
}


/*
 * 'list_ppds()' - List PPD files.
 */

static void
list_ppds(int        request_id,	/* I - Request ID */
          int        limit,		/* I - Limit */
	  const char *opt)		/* I - Option argument */
{
  int		i;			/* Looping vars */
  int		count;			/* Number of PPDs to send */
  ppd_info_t	*ppd;			/* Current PPD file */
  cups_file_t	*fp;			/* ppds.dat file */
  char		filename[1024],		/* ppds.dat filename */
		model[1024];		/* Model directory */
  const char	*cups_datadir;		/* CUPS_DATADIR environment variable */
  int		num_options;		/* Number of options */
  cups_option_t	*options;		/* Options */
  cups_array_t	*requested,		/* requested-attributes values */
		*include,		/* PPD schemes to include */
		*exclude;		/* PPD schemes to exclude */
  const char	*device_id,		/* ppd-device-id option */
		*language,		/* ppd-natural-language option */
		*make,			/* ppd-make option */
		*make_and_model,	/* ppd-make-and-model option */
		*model_number_str,	/* ppd-model-number option */
		*product,		/* ppd-product option */
		*psversion,		/* ppd-psversion option */
		*type_str;		/* ppd-type option */
  int		model_number,		/* ppd-model-number value */
		type,			/* ppd-type value */
		send_device_id,		/* Send ppd-device-id? */
		send_make,		/* Send ppd-make? */
		send_make_and_model,	/* Send ppd-make-and-model? */
		send_model_number,	/* Send ppd-model-number? */
		send_name,		/* Send ppd-name? */
		send_natural_language,	/* Send ppd-natural-language? */
		send_product,		/* Send ppd-product? */
		send_psversion,		/* Send ppd-psversion? */
		send_type,		/* Send ppd-type? */
		sent_header;		/* Sent the IPP header? */
  size_t	make_and_model_len,	/* Length of ppd-make-and-model */
		product_len;		/* Length of ppd-product */
  regex_t	*device_id_re,		/* Regular expression for matching device ID */
		*make_and_model_re;	/* Regular expression for matching make and model */
  regmatch_t	re_matches[6];		/* Regular expression matches */
  cups_array_t	*matches;		/* Matching PPDs */


  fprintf(stderr,
          "DEBUG2: [cups-driverd] list_ppds(request_id=%d, limit=%d, "
          "opt=\"%s\"\n", request_id, limit, opt);

 /*
  * See if we a PPD database file...
  */

  filename[0] = '\0';
  load_ppds_dat(filename, sizeof(filename), 1);

 /*
  * Load all PPDs in the specified directory and below...
  */

  if ((cups_datadir = getenv("CUPS_DATADIR")) == NULL)
    cups_datadir = CUPS_DATADIR;

  Inodes = cupsArrayNew((cups_array_func_t)compare_inodes, NULL);

  snprintf(model, sizeof(model), "%s/model", cups_datadir);
  load_ppds(model, "", 1);

  snprintf(model, sizeof(model), "%s/drv", cups_datadir);
  load_ppds(model, "", 1);

#ifdef __APPLE__
 /*
  * Load PPDs from standard macOS locations...
  */

  load_ppds("/Library/Printers",
            "Library/Printers", 0);
  load_ppds("/Library/Printers/PPDs/Contents/Resources",
            "Library/Printers/PPDs/Contents/Resources", 0);
  load_ppds("/Library/Printers/PPDs/Contents/Resources/en.lproj",
            "Library/Printers/PPDs/Contents/Resources/en.lproj", 0);
  load_ppds("/System/Library/Printers",
            "System/Library/Printers", 0);
  load_ppds("/System/Library/Printers/PPDs/Contents/Resources",
            "System/Library/Printers/PPDs/Contents/Resources", 0);
  load_ppds("/System/Library/Printers/PPDs/Contents/Resources/en.lproj",
            "System/Library/Printers/PPDs/Contents/Resources/en.lproj", 0);

#elif defined(__linux)
 /*
  * Load PPDs from LSB-defined locations...
  */

  if (!access("/usr/local/share/ppd", 0))
    load_ppds("/usr/local/share/ppd", "lsb/local", 1);
  if (!access("/usr/share/ppd", 0))
    load_ppds("/usr/share/ppd", "lsb/usr", 1);
  if (!access("/opt/share/ppd", 0))
    load_ppds("/opt/share/ppd", "lsb/opt", 1);
#endif /* __APPLE__ */

 /*
  * Cull PPD files that are no longer present...
  */

  for (ppd = (ppd_info_t *)cupsArrayFirst(PPDsByName);
       ppd;
       ppd = (ppd_info_t *)cupsArrayNext(PPDsByName))
    if (!ppd->found)
    {
     /*
      * Remove this PPD file from the list...
      */

      cupsArrayRemove(PPDsByName, ppd);
      cupsArrayRemove(PPDsByMakeModel, ppd);
      free(ppd);

      ChangedPPD = 1;
    }

 /*
  * Write the new ppds.dat file...
  */

  fprintf(stderr, "DEBUG: [cups-driverd] ChangedPPD=%d\n", ChangedPPD);

  if (ChangedPPD)
  {
    char	newname[1024];		/* New filename */

    snprintf(newname, sizeof(newname), "%s.%d", filename, (int)getpid());

    if ((fp = cupsFileOpen(newname, "w")) != NULL)
    {
      unsigned ppdsync = PPD_SYNC;	/* Sync word */

      cupsFileWrite(fp, (char *)&ppdsync, sizeof(ppdsync));

      for (ppd = (ppd_info_t *)cupsArrayFirst(PPDsByName);
	   ppd;
	   ppd = (ppd_info_t *)cupsArrayNext(PPDsByName))
	cupsFileWrite(fp, (char *)&(ppd->record), sizeof(ppd_rec_t));

      cupsFileClose(fp);

      if (rename(newname, filename))
	fprintf(stderr, "ERROR: [cups-driverd] Unable to rename \"%s\" - %s\n",
		newname, strerror(errno));
      else
	fprintf(stderr, "INFO: [cups-driverd] Wrote \"%s\", %d PPDs...\n",
		filename, cupsArrayCount(PPDsByName));
    }
    else
      fprintf(stderr, "ERROR: [cups-driverd] Unable to write \"%s\" - %s\n",
              filename, strerror(errno));
  }
  else
    fputs("INFO: [cups-driverd] No new or changed PPDs...\n", stderr);

 /*
  * Scan for dynamic PPD files...
  */

  num_options = cupsParseOptions(opt, 0, &options);
  exclude     = cupsdCreateStringsArray(cupsGetOption("exclude-schemes",
                                                      num_options, options));
  include     = cupsdCreateStringsArray(cupsGetOption("include-schemes",
						      num_options, options));

  load_drivers(include, exclude);

 /*
  * Add the raw driver...
  */

  add_ppd("", "raw", "en", "Raw", "Raw Queue", "", "", "", 0, 0, 0,
          PPD_TYPE_UNKNOWN, "raw");

 /*
  * Send IPP attributes...
  */

  requested        = cupsdCreateStringsArray(
                         cupsGetOption("requested-attributes", num_options,
			               options));
  device_id        = cupsGetOption("ppd-device-id", num_options, options);
  language         = cupsGetOption("ppd-natural-language", num_options, options);
  make             = cupsGetOption("ppd-make", num_options, options);
  make_and_model   = cupsGetOption("ppd-make-and-model", num_options, options);
  model_number_str = cupsGetOption("ppd-model-number", num_options, options);
  product          = cupsGetOption("ppd-product", num_options, options);
  psversion        = cupsGetOption("ppd-psversion", num_options, options);
  type_str         = cupsGetOption("ppd-type", num_options, options);

  if (make_and_model)
    make_and_model_len = strlen(make_and_model);
  else
    make_and_model_len = 0;

  if (product)
    product_len = strlen(product);
  else
    product_len = 0;

  if (model_number_str)
    model_number = atoi(model_number_str);
  else
    model_number = 0;

  if (type_str)
  {
    for (type = 0;
         type < (int)(sizeof(PPDTypes) / sizeof(PPDTypes[0]));
	 type ++)
      if (!strcmp(type_str, PPDTypes[type]))
        break;

    if (type >= (int)(sizeof(PPDTypes) / sizeof(PPDTypes[0])))
    {
      fprintf(stderr, "ERROR: [cups-driverd] Bad ppd-type=\"%s\" ignored!\n",
              type_str);
      type_str = NULL;
    }
  }
  else
    type = 0;

  for (i = 0; i < num_options; i ++)
    fprintf(stderr, "DEBUG2: [cups-driverd] %s=\"%s\"\n", options[i].name,
            options[i].value);

  if (!requested || cupsArrayFind(requested, (void *)"all") != NULL)
  {
    send_name             = 1;
    send_make             = 1;
    send_make_and_model   = 1;
    send_model_number     = 1;
    send_natural_language = 1;
    send_device_id        = 1;
    send_product          = 1;
    send_psversion        = 1;
    send_type             = 1;
  }
  else
  {
    send_name             = cupsArrayFind(requested,
                                          (void *)"ppd-name") != NULL;
    send_make             = cupsArrayFind(requested,
                                          (void *)"ppd-make") != NULL;
    send_make_and_model   = cupsArrayFind(requested,
                                          (void *)"ppd-make-and-model") != NULL;
    send_model_number     = cupsArrayFind(requested,
                                          (void *)"ppd-model-number") != NULL;
    send_natural_language = cupsArrayFind(requested,
                                          (void *)"ppd-natural-language") != NULL;
    send_device_id        = cupsArrayFind(requested,
                                          (void *)"ppd-device-id") != NULL;
    send_product          = cupsArrayFind(requested,
                                          (void *)"ppd-product") != NULL;
    send_psversion        = cupsArrayFind(requested,
                                          (void *)"ppd-psversion") != NULL;
    send_type             = cupsArrayFind(requested,
                                          (void *)"ppd-type") != NULL;
  }

 /*
  * Send the content type header to the scheduler; request_id can only be
  * 0 when run manually since the scheduler enforces the IPP requirement for
  * a request ID from 1 to 2^31-1...
  */

  if (request_id > 0)
    puts("Content-Type: application/ipp\n");

  sent_header = 0;

  if (limit <= 0 || limit > cupsArrayCount(PPDsByMakeModel))
    count = cupsArrayCount(PPDsByMakeModel);
  else
    count = limit;

  if (device_id || language || make || make_and_model || model_number_str ||
      product)
  {
    matches = cupsArrayNew((cups_array_func_t)compare_matches, NULL);

    if (device_id)
      device_id_re = regex_device_id(device_id);
    else
      device_id_re = NULL;

    if (make_and_model)
      make_and_model_re = regex_string(make_and_model);
    else
      make_and_model_re = NULL;

    for (ppd = (ppd_info_t *)cupsArrayFirst(PPDsByMakeModel);
	 ppd;
	 ppd = (ppd_info_t *)cupsArrayNext(PPDsByMakeModel))
    {
     /*
      * Filter PPDs based on make, model, product, language, model number,
      * and/or device ID using the "matches" score value.  An exact match
      * for product, make-and-model, or device-id adds 3 to the score.
      * Partial matches for make-and-model yield 1 or 2 points, and matches
      * for the make and language add a single point.  Results are then sorted
      * by score, highest score first.
      */

      if (ppd->record.type < PPD_TYPE_POSTSCRIPT ||
	  ppd->record.type >= PPD_TYPE_DRV)
	continue;

      if (cupsArrayFind(exclude, ppd->record.scheme) ||
          (include && !cupsArrayFind(include, ppd->record.scheme)))
        continue;

      ppd->matches = 0;

      if (device_id_re &&
	  !regexec(device_id_re, ppd->record.device_id,
                   (size_t)(sizeof(re_matches) / sizeof(re_matches[0])),
		   re_matches, 0))
      {
       /*
        * Add the number of matching values from the device ID - it will be
	* at least 2 (manufacturer and model), and as much as 3 (command set).
	*/

        for (i = 1; i < (int)(sizeof(re_matches) / sizeof(re_matches[0])); i ++)
	  if (re_matches[i].rm_so >= 0)
	    ppd->matches ++;
      }

      if (language)
      {
	for (i = 0; i < PPD_MAX_LANG; i ++)
	  if (!ppd->record.languages[i][0])
	    break;
	  else if (!strcmp(ppd->record.languages[i], language))
	  {
	    ppd->matches ++;
	    break;
	  }
      }

      if (make && !_cups_strcasecmp(ppd->record.make, make))
        ppd->matches ++;

      if (make_and_model_re &&
          !regexec(make_and_model_re, ppd->record.make_and_model,
	           (size_t)(sizeof(re_matches) / sizeof(re_matches[0])),
		   re_matches, 0))
      {
	// See how much of the make-and-model string we matched...
	if (re_matches[0].rm_so == 0)
	{
	  if ((size_t)re_matches[0].rm_eo == make_and_model_len)
	    ppd->matches += 3;		// Exact match
	  else
	    ppd->matches += 2;		// Prefix match
	}
	else
	  ppd->matches ++;		// Infix match
      }

      if (model_number_str && ppd->record.model_number == model_number)
        ppd->matches ++;

      if (product)
      {
	for (i = 0; i < PPD_MAX_PROD; i ++)
	  if (!ppd->record.products[i][0])
	    break;
	  else if (!_cups_strcasecmp(ppd->record.products[i], product))
	  {
	    ppd->matches += 3;
	    break;
	  }
	  else if (!_cups_strncasecmp(ppd->record.products[i], product,
	                              product_len))
	  {
	    ppd->matches += 2;
	    break;
	  }
      }

      if (psversion)
      {
	for (i = 0; i < PPD_MAX_VERS; i ++)
	  if (!ppd->record.psversions[i][0])
	    break;
	  else if (!_cups_strcasecmp(ppd->record.psversions[i], psversion))
	  {
	    ppd->matches ++;
	    break;
	  }
      }

      if (type_str && ppd->record.type == type)
        ppd->matches ++;

      if (ppd->matches)
      {
        fprintf(stderr, "DEBUG2: [cups-driverd] %s matches with score %d!\n",
	        ppd->record.name, ppd->matches);
        cupsArrayAdd(matches, ppd);
      }
    }
  }
  else if (include || exclude)
  {
    matches = cupsArrayNew((cups_array_func_t)compare_ppds, NULL);

    for (ppd = (ppd_info_t *)cupsArrayFirst(PPDsByMakeModel);
	 ppd;
	 ppd = (ppd_info_t *)cupsArrayNext(PPDsByMakeModel))
    {
     /*
      * Filter PPDs based on the include/exclude lists.
      */

      if (ppd->record.type < PPD_TYPE_POSTSCRIPT ||
	  ppd->record.type >= PPD_TYPE_DRV)
	continue;

      if (cupsArrayFind(exclude, ppd->record.scheme) ||
          (include && !cupsArrayFind(include, ppd->record.scheme)))
        continue;

      cupsArrayAdd(matches, ppd);
    }
  }
  else
    matches = PPDsByMakeModel;

  for (ppd = (ppd_info_t *)cupsArrayFirst(matches);
       count > 0 && ppd;
       ppd = (ppd_info_t *)cupsArrayNext(matches))
  {
   /*
    * Skip invalid PPDs...
    */

    if (ppd->record.type < PPD_TYPE_POSTSCRIPT ||
        ppd->record.type >= PPD_TYPE_DRV)
      continue;

   /*
    * Send this PPD...
    */

    if (!sent_header)
    {
      sent_header = 1;

      if (request_id)
      {
	cupsdSendIPPHeader(IPP_OK, request_id);
	cupsdSendIPPGroup(IPP_TAG_OPERATION);
	cupsdSendIPPString(IPP_TAG_CHARSET, "attributes-charset", "utf-8");
	cupsdSendIPPString(IPP_TAG_LANGUAGE, "attributes-natural-language",
			   "en-US");
      }
    }

    fprintf(stderr, "DEBUG2: [cups-driverd] Sending %s (%s)...\n",
	    ppd->record.name, ppd->record.make_and_model);

    count --;

    if (request_id)
    {
      cupsdSendIPPGroup(IPP_TAG_PRINTER);

      if (send_name)
	cupsdSendIPPString(IPP_TAG_NAME, "ppd-name", ppd->record.name);

      if (send_natural_language)
      {
	cupsdSendIPPString(IPP_TAG_LANGUAGE, "ppd-natural-language",
			   ppd->record.languages[0]);

	for (i = 1; i < PPD_MAX_LANG && ppd->record.languages[i][0]; i ++)
	  cupsdSendIPPString(IPP_TAG_LANGUAGE, "", ppd->record.languages[i]);
      }

      if (send_make)
	cupsdSendIPPString(IPP_TAG_TEXT, "ppd-make", ppd->record.make);

      if (send_make_and_model)
	cupsdSendIPPString(IPP_TAG_TEXT, "ppd-make-and-model",
			   ppd->record.make_and_model);

      if (send_device_id)
	cupsdSendIPPString(IPP_TAG_TEXT, "ppd-device-id",
			   ppd->record.device_id);

      if (send_product)
      {
	cupsdSendIPPString(IPP_TAG_TEXT, "ppd-product",
			   ppd->record.products[0]);

	for (i = 1; i < PPD_MAX_PROD && ppd->record.products[i][0]; i ++)
	  cupsdSendIPPString(IPP_TAG_TEXT, "", ppd->record.products[i]);
      }

      if (send_psversion)
      {
	cupsdSendIPPString(IPP_TAG_TEXT, "ppd-psversion",
			   ppd->record.psversions[0]);

	for (i = 1; i < PPD_MAX_VERS && ppd->record.psversions[i][0]; i ++)
	  cupsdSendIPPString(IPP_TAG_TEXT, "", ppd->record.psversions[i]);
      }

      if (send_type)
      {
        if (ppd->record.type < PPD_TYPE_POSTSCRIPT || ppd->record.type > PPD_TYPE_ARCHIVE)
        {
         /*
          * This cache file is corrupted, remove it!
          */

          unlink(filename);

	  cupsdSendIPPString(IPP_TAG_KEYWORD, "ppd-type", PPDTypes[PPD_TYPE_UNKNOWN]);
        }
        else
	  cupsdSendIPPString(IPP_TAG_KEYWORD, "ppd-type", PPDTypes[ppd->record.type]);
      }

      if (send_model_number)
	cupsdSendIPPInteger(IPP_TAG_INTEGER, "ppd-model-number",
			    ppd->record.model_number);
    }
    else
      printf("%s (%s)\n", ppd->record.name, ppd->record.make_and_model);

   /*
    * If we have only requested the ppd-make attribute, then skip
    * the remaining PPDs with this make...
    */

    if (cupsArrayFind(requested, (void *)"ppd-make") &&
        cupsArrayCount(requested) == 1)
    {
      const char	*this_make;	/* This ppd-make */


      for (this_make = ppd->record.make,
               ppd = (ppd_info_t *)cupsArrayNext(matches);
	   ppd;
	   ppd = (ppd_info_t *)cupsArrayNext(matches))
	if (_cups_strcasecmp(this_make, ppd->record.make))
	  break;

      cupsArrayPrev(matches);
    }
  }

  if (!sent_header && request_id)
  {
    cupsdSendIPPHeader(IPP_NOT_FOUND, request_id);
    cupsdSendIPPGroup(IPP_TAG_OPERATION);
    cupsdSendIPPString(IPP_TAG_CHARSET, "attributes-charset", "utf-8");
    cupsdSendIPPString(IPP_TAG_LANGUAGE, "attributes-natural-language", "en-US");
  }

  if (request_id)
    cupsdSendIPPTrailer();

  exit(0);
}


/*
 * 'load_drv()' - Load the PPDs from a driver information file.
 */

static int				/* O - 1 on success, 0 on failure */
load_drv(const char  *filename,		/* I - Actual filename */
         const char  *name,		/* I - Name to the rest of the world */
         cups_file_t *fp,		/* I - File to read from */
	 time_t      mtime,		/* I - Mod time of driver info file */
	 off_t       size)		/* I - Size of driver info file */
{
  ppdcSource	*src;			// Driver information file
  ppdcDriver	*d;			// Current driver
  ppdcAttr	*device_id,		// 1284DeviceID attribute
		*product,		// Current product value
		*ps_version,		// PSVersion attribute
		*cups_fax,		// cupsFax attribute
		*nick_name;		// NickName attribute
  ppdcFilter	*filter;		// Current filter
  ppd_info_t	*ppd;			// Current PPD
  int		products_found;		// Number of products found
  char		uri[1024],		// Driver URI
		make_model[1024];	// Make and model
  int		type;			// Driver type


 /*
  * Load the driver info file...
  */

  src = new ppdcSource(filename, fp);

  if (src->drivers->count == 0)
  {
    fprintf(stderr,
            "ERROR: [cups-driverd] Bad driver information file \"%s\"!\n",
	    filename);
    src->release();
    return (0);
  }

 /*
  * Add a dummy entry for the file...
  */

  add_ppd(name, name, "", "", "", "", "", "", mtime, (size_t)size, 0, PPD_TYPE_DRV, "drv");
  ChangedPPD = 1;

 /*
  * Then the drivers in the file...
  */

  for (d = (ppdcDriver *)src->drivers->first();
       d;
       d = (ppdcDriver *)src->drivers->next())
  {
    httpAssembleURIf(HTTP_URI_CODING_ALL, uri, sizeof(uri), "drv", "", "", 0,
                     "/%s/%s", name,
		     d->file_name ? d->file_name->value :
		                    d->pc_file_name->value);

    device_id  = d->find_attr("1284DeviceID", NULL);
    ps_version = d->find_attr("PSVersion", NULL);
    nick_name  = d->find_attr("NickName", NULL);

    if (nick_name)
      strlcpy(make_model, nick_name->value->value, sizeof(make_model));
    else if (_cups_strncasecmp(d->model_name->value, d->manufacturer->value,
                         strlen(d->manufacturer->value)))
      snprintf(make_model, sizeof(make_model), "%s %s, %s",
               d->manufacturer->value, d->model_name->value,
	       d->version->value);
    else
      snprintf(make_model, sizeof(make_model), "%s, %s", d->model_name->value,
               d->version->value);

    if ((cups_fax = d->find_attr("cupsFax", NULL)) != NULL &&
        !_cups_strcasecmp(cups_fax->value->value, "true"))
      type = PPD_TYPE_FAX;
    else if (d->type == PPDC_DRIVER_PS)
      type = PPD_TYPE_POSTSCRIPT;
    else if (d->type != PPDC_DRIVER_CUSTOM)
      type = PPD_TYPE_RASTER;
    else
    {
      for (filter = (ppdcFilter *)d->filters->first(),
               type = PPD_TYPE_POSTSCRIPT;
	   filter;
	   filter = (ppdcFilter *)d->filters->next())
        if (_cups_strcasecmp(filter->mime_type->value, "application/vnd.cups-raster"))
	  type = PPD_TYPE_RASTER;
        else if (_cups_strcasecmp(filter->mime_type->value,
	                    "application/vnd.cups-pdf"))
	  type = PPD_TYPE_PDF;
    }

    for (product = (ppdcAttr *)d->attrs->first(), products_found = 0,
             ppd = NULL;
         product;
	 product = (ppdcAttr *)d->attrs->next())
      if (!strcmp(product->name->value, "Product"))
      {
        if (!products_found)
	  ppd = add_ppd(name, uri, "en", d->manufacturer->value, make_model, device_id ? device_id->value->value : "", product->value->value,
		        ps_version ? ps_version->value->value : "(3010) 0", mtime, (size_t)size, d->model_number, type, "drv");
	else if (products_found < PPD_MAX_PROD)
	  strlcpy(ppd->record.products[products_found], product->value->value, sizeof(ppd->record.products[0]));
	else
	  break;

	products_found ++;
      }

    if (!products_found)
      add_ppd(name, uri, "en", d->manufacturer->value, make_model, device_id ? device_id->value->value : "", d->model_name->value, ps_version ? ps_version->value->value : "(3010) 0", mtime, (size_t)size, d->model_number, type, "drv");
  }

  src->release();

  return (1);
}


/*
 * 'load_drivers()' - Load driver-generated PPD files.
 */

static int				/* O - 1 on success, 0 on failure */
load_drivers(cups_array_t *include,	/* I - Drivers to include */
             cups_array_t *exclude)	/* I - Drivers to exclude */
{
  int		i;			/* Looping var */
  char		*start,			/* Start of value */
		*ptr;			/* Pointer into string */
  const char	*server_bin,		/* CUPS_SERVERBIN env variable */
		*scheme,		/* Scheme for this driver */
		*scheme_end;		/* Pointer to end of scheme */
  char		drivers[1024];		/* Location of driver programs */
  int		pid;			/* Process ID for driver program */
  cups_file_t	*fp;			/* Pipe to driver program */
  cups_dir_t	*dir;			/* Directory pointer */
  cups_dentry_t *dent;			/* Directory entry */
  char		*argv[3],		/* Arguments for command */
		filename[1024],		/* Name of driver */
		line[2048],		/* Line from driver */
		name[256],		/* ppd-name */
		make[128],		/* ppd-make */
		make_and_model[128],	/* ppd-make-and-model */
		device_id[256],		/* ppd-device-id */
		languages[128],		/* ppd-natural-language */
		product[128],		/* ppd-product */
		psversion[128],		/* ppd-psversion */
		type_str[128];		/* ppd-type */
  int		type;			/* PPD type */
  ppd_info_t	*ppd;			/* Newly added PPD */


 /*
  * Try opening the driver directory...
  */

  if ((server_bin = getenv("CUPS_SERVERBIN")) == NULL)
    server_bin = CUPS_SERVERBIN;

  snprintf(drivers, sizeof(drivers), "%s/driver", server_bin);

  if ((dir = cupsDirOpen(drivers)) == NULL)
  {
    fprintf(stderr, "ERROR: [cups-driverd] Unable to open driver directory "
		    "\"%s\": %s\n",
	    drivers, strerror(errno));
    return (0);
  }

 /*
  * Loop through all of the device drivers...
  */

  argv[1] = (char *)"list";
  argv[2] = NULL;

  while ((dent = cupsDirRead(dir)) != NULL)
  {
   /*
    * Only look at executable files...
    */

    if (!(dent->fileinfo.st_mode & 0111) || !S_ISREG(dent->fileinfo.st_mode))
      continue;

   /*
    * Include/exclude specific drivers...
    */

    if (exclude)
    {
     /*
      * Look for "scheme" or "scheme*" (prefix match), and skip any matches.
      */

      for (scheme = (char *)cupsArrayFirst(exclude);
	   scheme;
	   scheme = (char *)cupsArrayNext(exclude))
      {
        fprintf(stderr, "DEBUG: [cups-driverd] Exclude \"%s\" with \"%s\"?\n",
		dent->filename, scheme);
	scheme_end = scheme + strlen(scheme) - 1;

	if ((scheme_end > scheme && *scheme_end == '*' &&
	     !strncmp(scheme, dent->filename, (size_t)(scheme_end - scheme))) ||
	    !strcmp(scheme, dent->filename))
	{
	  fputs("DEBUG: [cups-driverd] Yes, exclude!\n", stderr);
	  break;
	}
      }

      if (scheme)
        continue;
    }

    if (include)
    {
     /*
      * Look for "scheme" or "scheme*" (prefix match), and skip any non-matches.
      */

      for (scheme = (char *)cupsArrayFirst(include);
	   scheme;
	   scheme = (char *)cupsArrayNext(include))
      {
        fprintf(stderr, "DEBUG: [cups-driverd] Include \"%s\" with \"%s\"?\n",
		dent->filename, scheme);
	scheme_end = scheme + strlen(scheme) - 1;

	if ((scheme_end > scheme && *scheme_end == '*' &&
	     !strncmp(scheme, dent->filename, (size_t)(scheme_end - scheme))) ||
	    !strcmp(scheme, dent->filename))
	{
	  fputs("DEBUG: [cups-driverd] Yes, include!\n", stderr);
	  break;
	}
      }

      if (!scheme)
        continue;
    }
    else
      scheme = dent->filename;

   /*
    * Run the driver with no arguments and collect the output...
    */

    snprintf(filename, sizeof(filename), "%s/%s", drivers, dent->filename);

    if (_cupsFileCheck(filename, _CUPS_FILE_CHECK_PROGRAM, !geteuid(),
                       _cupsFileCheckFilter, NULL))
      continue;

    argv[0] = dent->filename;

    if ((fp = cupsdPipeCommand(&pid, filename, argv, 0)) != NULL)
    {
      while (cupsFileGets(fp, line, sizeof(line)))
      {
       /*
        * Each line is of the form:
	*
	*   "ppd-name" ppd-natural-language "ppd-make" "ppd-make-and-model" \
	*       "ppd-device-id" "ppd-product" "ppd-psversion"
	*/

        device_id[0] = '\0';
	product[0]   = '\0';
	psversion[0] = '\0';
	strlcpy(type_str, "postscript", sizeof(type_str));

        if (sscanf(line, "\"%255[^\"]\"%127s%*[ \t]\"%127[^\"]\""
	                 "%*[ \t]\"%127[^\"]\"%*[ \t]\"%255[^\"]\""
			 "%*[ \t]\"%127[^\"]\"%*[ \t]\"%127[^\"]\""
			 "%*[ \t]\"%127[^\"]\"",
	           name, languages, make, make_and_model,
		   device_id, product, psversion, type_str) < 4)
        {
	 /*
	  * Bad format; strip trailing newline and write an error message.
	  */

          if (line[strlen(line) - 1] == '\n')
	    line[strlen(line) - 1] = '\0';

	  fprintf(stderr, "ERROR: [cups-driverd] Bad line from \"%s\": %s\n",
	          dent->filename, line);
	  break;
        }
	else
	{
	 /*
	  * Add the device to the array of available devices...
	  */

          if ((start = strchr(languages, ',')) != NULL)
	    *start++ = '\0';

	  for (type = 0;
               type < (int)(sizeof(PPDTypes) / sizeof(PPDTypes[0]));
	       type ++)
	    if (!strcmp(type_str, PPDTypes[type]))
              break;

	  if (type >= (int)(sizeof(PPDTypes) / sizeof(PPDTypes[0])))
	  {
	    fprintf(stderr,
	            "ERROR: [cups-driverd] Bad ppd-type \"%s\" ignored!\n",
        	    type_str);
	    type = PPD_TYPE_UNKNOWN;
	  }

          ppd = add_ppd(filename, name, languages, make, make_and_model,
                        device_id, product, psversion, 0, 0, 0, type, scheme);

          if (!ppd)
	  {
            cupsDirClose(dir);
	    cupsFileClose(fp);
	    return (0);
	  }

          if (start && *start)
	  {
	    for (i = 1; i < PPD_MAX_LANG && *start; i ++)
	    {
	      if ((ptr = strchr(start, ',')) != NULL)
	        *ptr++ = '\0';
	      else
	        ptr = start + strlen(start);

              strlcpy(ppd->record.languages[i], start,
	              sizeof(ppd->record.languages[0]));

	      start = ptr;
	    }
          }

          fprintf(stderr, "DEBUG2: [cups-driverd] Added dynamic PPD \"%s\"...\n",
	          name);
	}
      }

      cupsFileClose(fp);
    }
    else
      fprintf(stderr, "WARNING: [cups-driverd] Unable to execute \"%s\": %s\n",
              filename, strerror(errno));
  }

  cupsDirClose(dir);

  return (1);
}


/*
 * 'load_ppd()' - Load a PPD file.
 */

static void
load_ppd(const char  *filename,		/* I - Real filename */
         const char  *name,		/* I - Virtual filename */
         const char  *scheme,		/* I - PPD scheme */
         struct stat *fileinfo,		/* I - File information */
         ppd_info_t  *ppd,		/* I - Existing PPD file or NULL */
         cups_file_t *fp,		/* I - File to read from */
         off_t       end)		/* I - End of file position or 0 */
{
  int		i;			/* Looping var */
  char		line[256],		/* Line from file */
		*ptr,			/* Pointer into line */
		lang_version[64],	/* PPD LanguageVersion */
		lang_encoding[64],	/* PPD LanguageEncoding */
		country[64],		/* Country code */
		manufacturer[256],	/* Manufacturer */
		make_model[256],	/* Make and Model */
		model_name[256],	/* ModelName */
		nick_name[256],		/* NickName */
		device_id[256],		/* 1284DeviceID */
		product[256],		/* Product */
		psversion[256],		/* PSVersion */
		temp[512];		/* Temporary make and model */
  int		install_group,		/* In the installable options group? */
		model_number,		/* cupsModelNumber */
		type;			/* ppd-type */
  cups_array_t	*products,		/* Product array */
		*psversions,		/* PSVersion array */
		*cups_languages;	/* cupsLanguages array */
  int		new_ppd;		/* Is this a new PPD? */
  struct				/* LanguageVersion translation table */
  {
    const char	*version,		/* LanguageVersion string */
		*language;		/* Language code */
  }		languages[] =
  {
    { "chinese",		"zh" },
    { "czech",			"cs" },
    { "danish",			"da" },
    { "dutch",			"nl" },
    { "english",		"en" },
    { "finnish",		"fi" },
    { "french",			"fr" },
    { "german",			"de" },
    { "greek",			"el" },
    { "hungarian",		"hu" },
    { "italian",		"it" },
    { "japanese",		"ja" },
    { "korean",			"ko" },
    { "norwegian",		"no" },
    { "polish",			"pl" },
    { "portuguese",		"pt" },
    { "russian",		"ru" },
    { "simplified chinese",	"zh_CN" },
    { "slovak",			"sk" },
    { "spanish",		"es" },
    { "swedish",		"sv" },
    { "traditional chinese",	"zh_TW" },
    { "turkish",		"tr" }
  };


 /*
  * Now read until we get the required fields...
  */

  cups_languages = cupsArrayNew(NULL, NULL);
  products       = cupsArrayNew(NULL, NULL);
  psversions     = cupsArrayNew(NULL, NULL);

  model_name[0]    = '\0';
  nick_name[0]     = '\0';
  manufacturer[0]  = '\0';
  device_id[0]     = '\0';
  lang_encoding[0] = '\0';
  strlcpy(lang_version, "en", sizeof(lang_version));
  model_number     = 0;
  install_group    = 0;
  type             = PPD_TYPE_POSTSCRIPT;

  while ((end == 0 || cupsFileTell(fp) < end) &&
	 cupsFileGets(fp, line, sizeof(line)))
  {
    if (!strncmp(line, "*Manufacturer:", 14))
      sscanf(line, "%*[^\"]\"%255[^\"]", manufacturer);
    else if (!strncmp(line, "*ModelName:", 11))
      sscanf(line, "%*[^\"]\"%127[^\"]", model_name);
    else if (!strncmp(line, "*LanguageEncoding:", 18))
      sscanf(line, "%*[^:]:%63s", lang_encoding);
    else if (!strncmp(line, "*LanguageVersion:", 17))
      sscanf(line, "%*[^:]:%63s", lang_version);
    else if (!strncmp(line, "*NickName:", 10))
      sscanf(line, "%*[^\"]\"%255[^\"]", nick_name);
    else if (!_cups_strncasecmp(line, "*1284DeviceID:", 14))
    {
      sscanf(line, "%*[^\"]\"%255[^\"]", device_id);

      // Make sure device ID ends with a semicolon...
      if (device_id[0] && device_id[strlen(device_id) - 1] != ';')
	strlcat(device_id, ";", sizeof(device_id));
    }
    else if (!strncmp(line, "*Product:", 9))
    {
      if (sscanf(line, "%*[^\"]\"(%255[^\"]", product) == 1)
      {
       /*
	* Make sure the value ends with a right parenthesis - can't stop at
	* the first right paren since the product name may contain escaped
	* parenthesis...
	*/

	ptr = product + strlen(product) - 1;
	if (ptr > product && *ptr == ')')
	{
	 /*
	  * Yes, ends with a parenthesis, so remove it from the end and
	  * add the product to the list...
	  */

	  *ptr = '\0';
	  cupsArrayAdd(products, strdup(product));
	}
      }
    }
    else if (!strncmp(line, "*PSVersion:", 11))
    {
      sscanf(line, "%*[^\"]\"%255[^\"]", psversion);
      cupsArrayAdd(psversions, strdup(psversion));
    }
    else if (!strncmp(line, "*cupsLanguages:", 15))
    {
      char	*start;			/* Start of language */


      for (start = line + 15; *start && isspace(*start & 255); start ++);

      if (*start++ == '\"')
      {
	while (*start)
	{
	  for (ptr = start + 1;
	       *ptr && *ptr != '\"' && !isspace(*ptr & 255);
	       ptr ++);

	  if (*ptr)
	  {
	    *ptr++ = '\0';

	    while (isspace(*ptr & 255))
	      *ptr++ = '\0';
	  }

	  cupsArrayAdd(cups_languages, strdup(start));
	  start = ptr;
	}
      }
    }
    else if (!strncmp(line, "*cupsFax:", 9))
    {
      for (ptr = line + 9; isspace(*ptr & 255); ptr ++);

      if (!_cups_strncasecmp(ptr, "true", 4))
	type = PPD_TYPE_FAX;
    }
    else if ((!strncmp(line, "*cupsFilter:", 12) || !strncmp(line, "*cupsFilter2:", 13)) && type == PPD_TYPE_POSTSCRIPT)
    {
      if (strstr(line + 12, "application/vnd.cups-raster"))
	type = PPD_TYPE_RASTER;
      else if (strstr(line + 12, "application/vnd.cups-pdf"))
	type = PPD_TYPE_PDF;
    }
    else if (!strncmp(line, "*cupsModelNumber:", 17))
      sscanf(line, "*cupsModelNumber:%d", &model_number);
    else if (!strncmp(line, "*OpenGroup: Installable", 23))
      install_group = 1;
    else if (!strncmp(line, "*CloseGroup:", 12))
      install_group = 0;
    else if (!strncmp(line, "*OpenUI", 7))
    {
     /*
      * Stop early if we have a NickName or ModelName attributes
      * before the first non-installable OpenUI...
      */

      if (!install_group && (model_name[0] || nick_name[0]) &&
	  cupsArrayCount(products) > 0 && cupsArrayCount(psversions) > 0)
	break;
    }
  }

 /*
  * See if we got all of the required info...
  */

  if (nick_name[0])
    cupsCharsetToUTF8((cups_utf8_t *)make_model, nick_name,
		      sizeof(make_model), _ppdGetEncoding(lang_encoding));
  else
    strlcpy(make_model, model_name, sizeof(make_model));

  while (isspace(make_model[0] & 255))
    _cups_strcpy(make_model, make_model + 1);

  if (!make_model[0] || cupsArrayCount(products) == 0 ||
      cupsArrayCount(psversions) == 0)
  {
   /*
    * We don't have all the info needed, so skip this file...
    */

    if (!make_model[0])
      fprintf(stderr, "WARNING: Missing NickName and ModelName in %s!\n",
	      filename);

    if (cupsArrayCount(products) == 0)
      fprintf(stderr, "WARNING: Missing Product in %s!\n", filename);

    if (cupsArrayCount(psversions) == 0)
      fprintf(stderr, "WARNING: Missing PSVersion in %s!\n", filename);

    free_array(products);
    free_array(psversions);
    free_array(cups_languages);

    return;
  }

  if (model_name[0])
    cupsArrayAdd(products, strdup(model_name));

 /*
  * Normalize the make and model string...
  */

  while (isspace(manufacturer[0] & 255))
    _cups_strcpy(manufacturer, manufacturer + 1);

  if (!_cups_strncasecmp(make_model, manufacturer, strlen(manufacturer)))
    strlcpy(temp, make_model, sizeof(temp));
  else
    snprintf(temp, sizeof(temp), "%s %s", manufacturer, make_model);

  _ppdNormalizeMakeAndModel(temp, make_model, sizeof(make_model));

 /*
  * See if we got a manufacturer...
  */

  if (!manufacturer[0] || !strcmp(manufacturer, "ESP"))
  {
   /*
    * Nope, copy the first part of the make and model then...
    */

    strlcpy(manufacturer, make_model, sizeof(manufacturer));

   /*
    * Truncate at the first space, dash, or slash, or make the
    * manufacturer "Other"...
    */

    for (ptr = manufacturer; *ptr; ptr ++)
      if (*ptr == ' ' || *ptr == '-' || *ptr == '/')
	break;

    if (*ptr && ptr > manufacturer)
      *ptr = '\0';
    else
      strlcpy(manufacturer, "Other", sizeof(manufacturer));
  }
  else if (!_cups_strncasecmp(manufacturer, "LHAG", 4) ||
	   !_cups_strncasecmp(manufacturer, "linotype", 8))
    strlcpy(manufacturer, "LHAG", sizeof(manufacturer));
  else if (!_cups_strncasecmp(manufacturer, "Hewlett", 7))
    strlcpy(manufacturer, "HP", sizeof(manufacturer));

 /*
  * Fix the lang_version as needed...
  */

  if ((ptr = strchr(lang_version, '-')) != NULL)
    *ptr++ = '\0';
  else if ((ptr = strchr(lang_version, '_')) != NULL)
    *ptr++ = '\0';

  if (ptr)
  {
   /*
    * Setup the country suffix...
    */

    country[0] = '_';
    _cups_strcpy(country + 1, ptr);
  }
  else
  {
   /*
    * No country suffix...
    */

    country[0] = '\0';
  }

  for (i = 0; i < (int)(sizeof(languages) / sizeof(languages[0])); i ++)
    if (!_cups_strcasecmp(languages[i].version, lang_version))
      break;

  if (i < (int)(sizeof(languages) / sizeof(languages[0])))
  {
   /*
    * Found a known language...
    */

    snprintf(lang_version, sizeof(lang_version), "%s%s",
	     languages[i].language, country);
  }
  else
  {
   /*
    * Unknown language; use "xx"...
    */

    strlcpy(lang_version, "xx", sizeof(lang_version));
  }

 /*
  * Record the PPD file...
  */

  new_ppd = !ppd;

  if (new_ppd)
  {
   /*
    * Add new PPD file...
    */

    fprintf(stderr, "DEBUG2: [cups-driverd] Adding ppd \"%s\"...\n", name);

    ppd = add_ppd(name, name, lang_version, manufacturer, make_model, device_id, (char *)cupsArrayFirst(products), (char *)cupsArrayFirst(psversions), fileinfo->st_mtime, (size_t)fileinfo->st_size, model_number, type, scheme);

    if (!ppd)
      return;
  }
  else
  {
   /*
    * Update existing record...
    */

    fprintf(stderr, "DEBUG2: [cups-driverd] Updating ppd \"%s\"...\n", name);

    memset(ppd, 0, sizeof(ppd_info_t));

    ppd->found               = 1;
    ppd->record.mtime        = fileinfo->st_mtime;
    ppd->record.size         = fileinfo->st_size;
    ppd->record.model_number = model_number;
    ppd->record.type         = type;

    strlcpy(ppd->record.filename, name, sizeof(ppd->record.filename));
    strlcpy(ppd->record.name, name, sizeof(ppd->record.name));
    strlcpy(ppd->record.languages[0], lang_version,
	    sizeof(ppd->record.languages[0]));
    strlcpy(ppd->record.products[0], (char *)cupsArrayFirst(products),
	    sizeof(ppd->record.products[0]));
    strlcpy(ppd->record.psversions[0], (char *)cupsArrayFirst(psversions),
	    sizeof(ppd->record.psversions[0]));
    strlcpy(ppd->record.make, manufacturer, sizeof(ppd->record.make));
    strlcpy(ppd->record.make_and_model, make_model,
	    sizeof(ppd->record.make_and_model));
    strlcpy(ppd->record.device_id, device_id, sizeof(ppd->record.device_id));
    strlcpy(ppd->record.scheme, scheme, sizeof(ppd->record.scheme));
  }

 /*
  * Add remaining products, versions, and languages...
  */

  for (i = 1;
       i < PPD_MAX_PROD && (ptr = (char *)cupsArrayNext(products)) != NULL;
       i ++)
    strlcpy(ppd->record.products[i], ptr,
	    sizeof(ppd->record.products[0]));

  for (i = 1;
       i < PPD_MAX_VERS && (ptr = (char *)cupsArrayNext(psversions)) != NULL;
       i ++)
    strlcpy(ppd->record.psversions[i], ptr,
	    sizeof(ppd->record.psversions[0]));

  for (i = 1, ptr = (char *)cupsArrayFirst(cups_languages);
       i < PPD_MAX_LANG && ptr;
       i ++, ptr = (char *)cupsArrayNext(cups_languages))
    strlcpy(ppd->record.languages[i], ptr,
	    sizeof(ppd->record.languages[0]));

 /*
  * Free products, versions, and languages...
  */

  free_array(cups_languages);
  free_array(products);
  free_array(psversions);

  ChangedPPD = 1;
}


/*
 * 'load_ppds()' - Load PPD files recursively.
 */

static int				/* O - 1 on success, 0 on failure */
load_ppds(const char *d,		/* I - Actual directory */
          const char *p,		/* I - Virtual path in name */
	  int        descend)		/* I - Descend into directories? */
{
  struct stat	dinfo,			/* Directory information */
		*dinfoptr;		/* Pointer to match */
  cups_file_t	*fp;			/* Pointer to file */
  cups_dir_t	*dir;			/* Directory pointer */
  cups_dentry_t	*dent;			/* Directory entry */
  char		filename[1024],		/* Name of PPD or directory */
		line[256],		/* Line from file */
		*ptr,			/* Pointer into name */
		name[256];		/* Name of PPD file */
  ppd_info_t	*ppd,			/* New PPD file */
		key;			/* Search key */


 /*
  * See if we've loaded this directory before...
  */

  if (stat(d, &dinfo))
  {
    if (errno != ENOENT)
      fprintf(stderr, "ERROR: [cups-driverd] Unable to stat \"%s\": %s\n", d,
	      strerror(errno));

    return (0);
  }
  else if (cupsArrayFind(Inodes, &dinfo))
  {
    fprintf(stderr, "ERROR: [cups-driverd] Skipping \"%s\": loop detected!\n",
            d);
    return (1);
  }

 /*
  * Nope, add it to the Inodes array and continue...
  */

  dinfoptr = (struct stat *)malloc(sizeof(struct stat));
  memcpy(dinfoptr, &dinfo, sizeof(struct stat));
  cupsArrayAdd(Inodes, dinfoptr);

 /*
  * Check permissions...
  */

  if (_cupsFileCheck(d, _CUPS_FILE_CHECK_DIRECTORY, !geteuid(),
		     _cupsFileCheckFilter, NULL))
    return (0);

  if ((dir = cupsDirOpen(d)) == NULL)
  {
    if (errno != ENOENT)
      fprintf(stderr,
	      "ERROR: [cups-driverd] Unable to open PPD directory \"%s\": %s\n",
	      d, strerror(errno));

    return (0);
  }

  fprintf(stderr, "DEBUG: [cups-driverd] Loading \"%s\"...\n", d);

  while ((dent = cupsDirRead(dir)) != NULL)
  {
   /*
    * Skip files/directories starting with "."...
    */

    if (dent->filename[0] == '.')
      continue;

   /*
    * See if this is a file...
    */

    snprintf(filename, sizeof(filename), "%s/%s", d, dent->filename);

    if (p[0])
      snprintf(name, sizeof(name), "%s/%s", p, dent->filename);
    else
      strlcpy(name, dent->filename, sizeof(name));

    if (S_ISDIR(dent->fileinfo.st_mode))
    {
     /*
      * Do subdirectory...
      */

      if (descend)
      {
	if (!load_ppds(filename, name, 1))
	{
	  cupsDirClose(dir);
	  return (1);
	}
      }
      else if ((ptr = filename + strlen(filename) - 14) > filename &&
	       !strcmp(ptr, ".printerDriver"))
      {
       /*
        * Load PPDs in a printer driver bundle.
	*/

	if (_cupsFileCheck(filename, _CUPS_FILE_CHECK_DIRECTORY, !geteuid(),
			   _cupsFileCheckFilter, NULL))
	  continue;

	strlcat(filename, "/Contents/Resources/PPDs", sizeof(filename));
	strlcat(name, "/Contents/Resources/PPDs", sizeof(name));

	load_ppds(filename, name, 0);
      }

      continue;
    }
    else if (strstr(filename, ".plist"))
    {
     /*
      * Skip plist files in the PPDs directory...
      */

      continue;
    }
    else if (_cupsFileCheck(filename, _CUPS_FILE_CHECK_FILE_ONLY, !geteuid(),
		            _cupsFileCheckFilter, NULL))
      continue;

   /*
    * See if this file has been scanned before...
    */

    strlcpy(key.record.filename, name, sizeof(key.record.filename));
    strlcpy(key.record.name, name, sizeof(key.record.name));

    ppd = (ppd_info_t *)cupsArrayFind(PPDsByName, &key);

    if (ppd &&
	ppd->record.size == dent->fileinfo.st_size &&
	ppd->record.mtime == dent->fileinfo.st_mtime)
    {
     /*
      * Rewind to the first entry for this file...
      */

      while ((ppd = (ppd_info_t *)cupsArrayPrev(PPDsByName)) != NULL &&
	     !strcmp(ppd->record.filename, name));

     /*
      * Then mark all of the matches for this file as found...
      */

      while ((ppd = (ppd_info_t *)cupsArrayNext(PPDsByName)) != NULL &&
	     !strcmp(ppd->record.filename, name))
        ppd->found = 1;

      continue;
    }

   /*
    * No, file is new/changed, so re-scan it...
    */

    if ((fp = cupsFileOpen(filename, "r")) == NULL)
      continue;

   /*
    * Now see if this is a PPD file...
    */

    line[0] = '\0';
    cupsFileGets(fp, line, sizeof(line));

    if (!strncmp(line, "*PPD-Adobe:", 11))
    {
     /*
      * Yes, load it...
      */

      load_ppd(filename, name, "file", &dent->fileinfo, ppd, fp, 0);
    }
    else
    {
     /*
      * Nope, treat it as a driver information file or archive...
      */

      cupsFileRewind(fp);

      if ((ptr = strstr(filename, ".tar")) != NULL &&
          (!strcmp(ptr, ".tar") || !strcmp(ptr, ".tar.gz")))
        load_tar(filename, name, fp, dent->fileinfo.st_mtime,
                 dent->fileinfo.st_size);
      else
	load_drv(filename, name, fp, dent->fileinfo.st_mtime,
		 dent->fileinfo.st_size);
    }

   /*
    * Close the file...
    */

    cupsFileClose(fp);
  }

  cupsDirClose(dir);

  return (1);
}


/*
 * 'load_ppds_dat()' - Load the ppds.dat file.
 */

static void
load_ppds_dat(char   *filename,		/* I - Filename buffer */
              size_t filesize,		/* I - Size of filename buffer */
              int    verbose)		/* I - Be verbose? */
{
  ppd_info_t	*ppd;			/* Current PPD file */
  cups_file_t	*fp;			/* ppds.dat file */
  struct stat	fileinfo;		/* ppds.dat information */
  const char	*cups_cachedir;		/* CUPS_CACHEDIR environment variable */


  PPDsByName      = cupsArrayNew((cups_array_func_t)compare_names, NULL);
  PPDsByMakeModel = cupsArrayNew((cups_array_func_t)compare_ppds, NULL);
  ChangedPPD      = 0;

  if (!filename[0])
  {
    if ((cups_cachedir = getenv("CUPS_CACHEDIR")) == NULL)
      cups_cachedir = CUPS_CACHEDIR;

    snprintf(filename, filesize, "%s/ppds.dat", cups_cachedir);
  }

  if ((fp = cupsFileOpen(filename, "r")) != NULL)
  {
   /*
    * See if we have the right sync word...
    */

    unsigned ppdsync;			/* Sync word */
    int      num_ppds;			/* Number of PPDs */

    if ((size_t)cupsFileRead(fp, (char *)&ppdsync, sizeof(ppdsync)) == sizeof(ppdsync) &&
        ppdsync == PPD_SYNC &&
        !stat(filename, &fileinfo) &&
	(((size_t)fileinfo.st_size - sizeof(ppdsync)) % sizeof(ppd_rec_t)) == 0 &&
	(num_ppds = ((size_t)fileinfo.st_size - sizeof(ppdsync)) / sizeof(ppd_rec_t)) > 0)
    {
     /*
      * We have a ppds.dat file, so read it!
      */

      for (; num_ppds > 0; num_ppds --)
      {
	if ((ppd = (ppd_info_t *)calloc(1, sizeof(ppd_info_t))) == NULL)
	{
	  if (verbose)
	    fputs("ERROR: [cups-driverd] Unable to allocate memory for PPD!\n",
		  stderr);
	  exit(1);
	}

	if (cupsFileRead(fp, (char *)&(ppd->record), sizeof(ppd_rec_t)) > 0)
	{
	  cupsArrayAdd(PPDsByName, ppd);
	  cupsArrayAdd(PPDsByMakeModel, ppd);
	}
	else
	{
	  free(ppd);
	  break;
	}
      }

      if (verbose)
	fprintf(stderr, "INFO: [cups-driverd] Read \"%s\", %d PPDs...\n",
		filename, cupsArrayCount(PPDsByName));
    }

    cupsFileClose(fp);
  }
}


/*
 * 'load_tar()' - Load archived PPD files.
 */

static int				/* O - 1 on success, 0 on failure */
load_tar(const char  *filename,		/* I - Actual filename */
         const char  *name,		/* I - Name to the rest of the world */
         cups_file_t *fp,		/* I - File to read from */
	 time_t      mtime,		/* I - Mod time of driver info file */
	 off_t       size)		/* I - Size of driver info file */
{
  char		curname[256],		/* Current archive file name */
		uri[1024];		/* Virtual file URI */
  const char	*curext;		/* Extension on file */
  struct stat	curinfo;		/* Current archive file information */
  off_t		next;			/* Position for next header */


 /*
  * Add a dummy entry for the file...
  */

  (void)filename;

  add_ppd(name, name, "", "", "", "", "", "", mtime, (size_t)size, 0, PPD_TYPE_ARCHIVE, "file");
  ChangedPPD = 1;

 /*
  * Scan for PPDs in the archive...
  */

  while (read_tar(fp, curname, sizeof(curname), &curinfo))
  {
    next = cupsFileTell(fp) + ((curinfo.st_size + TAR_BLOCK - 1) &
                               ~(TAR_BLOCK - 1));

    if ((curext = strrchr(curname, '.')) != NULL &&
        !_cups_strcasecmp(curext, ".ppd"))
    {
      httpAssembleURIf(HTTP_URI_CODING_ALL, uri, sizeof(uri), "file", "", "",
                       0, "/%s/%s", name, curname);
      load_ppd(name, uri, "file", &curinfo, NULL, fp, next);
    }

    if (cupsFileTell(fp) != next)
      cupsFileSeek(fp, next);
  }

  return (1);
}


/*
 * 'read_tar()' - Read a file header from an archive.
 *
 * This function skips all directories and special files.
 */

static int				/* O - 1 if found, 0 on EOF */
read_tar(cups_file_t *fp,		/* I - Archive to read */
         char        *name,		/* I - Filename buffer */
         size_t      namesize,		/* I - Size of filename buffer */
         struct stat *info)		/* O - File information */
{
  tar_rec_t	record;			/* Record from file */


  while ((size_t)cupsFileRead(fp, (char *)&record, sizeof(record)) == sizeof(record))
  {
   /*
    * Check for a valid tar header...
    */

    if (memcmp(record.header.magic, TAR_MAGIC, 6) ||
        memcmp(record.header.version, TAR_VERSION, 2))
    {
      if (record.header.magic[0] ||
          memcmp(record.header.magic, record.header.magic + 1, 5))
	fputs("ERROR: [cups-driverd] Bad tar magic/version.\n", stderr);
      break;
    }

   /*
    * Ignore non-files...
    */

    if (record.header.linkflag != TAR_OLDNORMAL &&
        record.header.linkflag != TAR_NORMAL)
      continue;

   /*
    * Grab size and name from tar header and return...
    */

    if (record.header.prefix[0])
      snprintf(name, namesize, "%s/%s", record.header.prefix,
               record.header.pathname);
    else
      strlcpy(name, record.header.pathname, namesize);

    info->st_mtime = strtol(record.header.mtime, NULL, 8);
    info->st_size  = strtoll(record.header.size, NULL, 8);

    return (1);
  }

  return (0);
}


/*
 * 'regex_device_id()' - Compile a regular expression based on the 1284 device
 *                       ID.
 */

static regex_t *			/* O - Regular expression */
regex_device_id(const char *device_id)	/* I - IEEE-1284 device ID */
{
  char		res[2048],		/* Regular expression string */
		*ptr;			/* Pointer into string */
  regex_t	*re;			/* Regular expression */
  int		cmd;			/* Command set string? */


  fprintf(stderr, "DEBUG: [cups-driverd] regex_device_id(\"%s\")\n", device_id);

 /*
  * Scan the device ID string and insert class, command set, manufacturer, and
  * model attributes to match.  We assume that the device ID in the PPD and the
  * device ID reported by the device itself use the same attribute names and
  * order of attributes.
  */

  ptr = res;

  while (*device_id && ptr < (res + sizeof(res) - 6))
  {
    cmd = !_cups_strncasecmp(device_id, "COMMAND SET:", 12) ||
          !_cups_strncasecmp(device_id, "CMD:", 4);

    if (cmd || !_cups_strncasecmp(device_id, "MANUFACTURER:", 13) ||
        !_cups_strncasecmp(device_id, "MFG:", 4) ||
        !_cups_strncasecmp(device_id, "MFR:", 4) ||
        !_cups_strncasecmp(device_id, "MODEL:", 6) ||
        !_cups_strncasecmp(device_id, "MDL:", 4))
    {
      if (ptr > res)
      {
        *ptr++ = '.';
	*ptr++ = '*';
      }

      *ptr++ = '(';

      while (*device_id && *device_id != ';' && ptr < (res + sizeof(res) - 8))
      {
        if (strchr("[]{}().*\\|", *device_id))
	  *ptr++ = '\\';
        if (*device_id == ':')
	{
	 /*
	  * KEY:.*value
	  */

	  *ptr++ = *device_id++;
	  *ptr++ = '.';
	  *ptr++ = '*';
	}
	else
	  *ptr++ = *device_id++;
      }

      if (*device_id == ';' || !*device_id)
      {
       /*
        * KEY:.*value.*;
	*/

	*ptr++ = '.';
	*ptr++ = '*';
        *ptr++ = ';';
      }
      *ptr++ = ')';
      if (cmd)
        *ptr++ = '?';
    }
    else if ((device_id = strchr(device_id, ';')) == NULL)
      break;
    else
      device_id ++;
  }

  *ptr = '\0';

  fprintf(stderr, "DEBUG: [cups-driverd] regex_device_id: \"%s\"\n", res);

 /*
  * Compile the regular expression and return...
  */

  if (res[0] && (re = (regex_t *)calloc(1, sizeof(regex_t))) != NULL)
  {
    if (!regcomp(re, res, REG_EXTENDED | REG_ICASE))
    {
      fputs("DEBUG: [cups-driverd] regex_device_id: OK\n", stderr);
      return (re);
    }

    free(re);
  }

  return (NULL);
}


/*
 * 'regex_string()' - Construct a regular expression to compare a simple string.
 */

static regex_t *			/* O - Regular expression */
regex_string(const char *s)		/* I - String to compare */
{
  char		res[2048],		/* Regular expression string */
		*ptr;			/* Pointer into string */
  regex_t	*re;			/* Regular expression */


  fprintf(stderr, "DEBUG: [cups-driverd] regex_string(\"%s\")\n", s);

 /*
  * Convert the string to a regular expression, escaping special characters
  * as needed.
  */

  ptr = res;

  while (*s && ptr < (res + sizeof(res) - 2))
  {
    if (strchr("[]{}().*\\", *s))
      *ptr++ = '\\';

    *ptr++ = *s++;
  }

  *ptr = '\0';

  fprintf(stderr, "DEBUG: [cups-driverd] regex_string: \"%s\"\n", res);

 /*
  * Create a case-insensitive regular expression...
  */

  if (res[0] && (re = (regex_t *)calloc(1, sizeof(regex_t))) != NULL)
  {
    if (!regcomp(re, res, REG_ICASE))
    {
      fputs("DEBUG: [cups-driverd] regex_string: OK\n", stderr);
      return (re);
    }

    free(re);
  }

  return (NULL);
}
