]> git.ipfire.org Git - thirdparty/cups.git/blobdiff - scheduler/cups-driverd.cxx
Merge changes from CUPS 1.6svn-r9939.
[thirdparty/cups.git] / scheduler / cups-driverd.cxx
index 962cb9362dc69f1f2abb6768638280016f15783d..c8865dbde8aed2f62b57a54c9a393514fb1005b1 100644 (file)
@@ -1,13 +1,13 @@
 /*
  * "$Id$"
  *
- *   PPD/driver support for the Common UNIX Printing System (CUPS).
+ *   PPD/driver support for CUPS.
  *
- *   This program handles listing and installing both static PPD files
- *   in CUPS_DATADIR/model and dynamically generated PPD files using
- *   the driver helper programs in CUPS_SERVERBIN/driver.
+ *   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 2007-2008 by Apple Inc.
+ *   Copyright 2007-2011 by Apple Inc.
  *   Copyright 1997-2007 by Easy Software Products.
  *
  *   These coded instructions, statements, and computer programs are the
  *
  * Contents:
  *
- *   main()          - Scan for drivers and return an IPP response.
- *   add_ppd()       - Add a PPD file.
- *   cat_drv()       - Generate a PPD from a driver info file.
- *   cat_ppd()       - Copy a PPD file to stdout.
- *   copy_static()   - Copy a static PPD file to stdout.
- *   compare_names() - Compare PPD filenames for sorting.
- *   compare_ppds()  - Compare PPD file make and model names for sorting.
- *   free_array()    - Free an array of strings.
- *   list_ppds()     - List PPD files.
- *   load_ppds()     - Load PPD files recursively.
- *   load_drv()      - Load the PPDs from a driver information file.
- *   load_drivers()  - Load driver-generated PPD files.
+ *   main()            - Scan for drivers and return an IPP response.
+ *   add_ppd()         - Add a PPD file.
+ *   cat_drv()         - Generate a PPD from a driver info file.
+ *   cat_ppd()         - Copy a PPD file to stdout.
+ *   copy_static()     - Copy a static PPD file to stdout.
+ *   compare_inodes()  - Compare two inodes.
+ *   compare_matches() - Compare PPD match scores for sorting.
+ *   compare_names()   - Compare PPD filenames for sorting.
+ *   compare_ppds()    - Compare PPD file make and model names for sorting.
+ *   dump_ppds_dat()   - Dump the contents of the ppds.dat file.
+ *   free_array()      - Free an array of strings.
+ *   list_ppds()       - List PPD files.
+ *   load_drv()        - Load the PPDs from a driver information file.
+ *   load_drivers()    - Load driver-generated PPD files.
+ *   load_ppds()       - Load PPD files recursively.
+ *   load_ppds_dat()   - Load the ppds.dat file.
+ *   regex_device_id() - Compile a regular expression based on the 1284 device
+ *                       ID.
+ *   regex_string()    - Construct a regular expression to compare a simple
+ *                       string.
  */
 
 /*
 #include <cups/transcode.h>
 #include <cups/ppd-private.h>
 #include <ppdc/ppdc.h>
+#include <regex.h>
 
 
 /*
  * Constants...
  */
 
-#define PPD_SYNC       0x50504434      /* Sync word for ppds.dat (PPD4) */
+#define PPD_SYNC       0x50504437      /* Sync word for ppds.dat (PPD7) */
 #define PPD_MAX_LANG   32              /* Maximum languages */
-#define PPD_MAX_PROD                 /* Maximum products */
-#define PPD_MAX_VERS                 /* Maximum versions */
+#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 */
 
 static const char * const ppd_types[] =        /* ppd-type values */
 {
@@ -64,7 +74,8 @@ static const char * const ppd_types[] =       /* ppd-type values */
   "pdf",
   "raster",
   "fax",
-  "unknown"
+  "unknown",
+  "drv"
 };
 
 
@@ -75,10 +86,11 @@ static const char * const ppd_types[] =     /* ppd-type values */
 typedef struct                         /**** PPD record ****/
 {
   time_t       mtime;                  /* Modification time */
-  size_t       size;                   /* Size in bytes */
+  off_t                size;                   /* Size in bytes */
   int          model_number;           /* cupsModelNumber */
   int          type;                   /* ppd-type */
-  char         name[512],              /* PPD name */
+  char         filename[512],          /* Filename */
+               name[512],              /* PPD name */
                languages[PPD_MAX_LANG][6],
                                        /* LanguageVersion/cupsLanguages */
                products[PPD_MAX_PROD][128],
@@ -87,12 +99,14 @@ typedef struct                              /**** PPD record ****/
                                        /* PSVersion strings */
                make[128],              /* Manufacturer */
                make_and_model[128],    /* NickName/ModelName */
-               device_id[128];         /* IEEE 1284 Device ID */
-} ppd_rec_t; 
+               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;
 
@@ -101,10 +115,9 @@ typedef struct                             /**** In-memory record ****/
  * Globals...
  */
 
-int            NumPPDs,                /* Number of PPD files */
-               SortedPPDs,             /* Number of sorted PPD files */
-               AllocPPDs;              /* Number of allocated entries */
-ppd_info_t     *PPDs;                  /* PPD file info */
+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 */
 int            ChangedPPD;             /* Did we change the PPD database? */
 
 
@@ -112,24 +125,35 @@ int               ChangedPPD;             /* Did we change the PPD database? */
  * Local functions...
  */
 
-static ppd_info_t      *add_ppd(const char *name, const char *language,
-                                const char *make, const char *make_and_model,
+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);
+                                size_t size, int model_number, int type,
+                                const char *scheme);
 static int             cat_drv(const char *name, int request_id);
 static int             cat_ppd(const char *name, int request_id);
 static int             cat_static(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 int             dump_ppds_dat(void);
 static void            free_array(cups_array_t *a);
 static int             list_ppds(int request_id, int limit, const char *opt);
-static int             load_drivers(void);
+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 int             load_ppds(const char *d, const char *p, int descend);
+static void            load_ppds_dat(char *filename, size_t filesize,
+                                     int verbose);
+static regex_t         *regex_device_id(const char *device_id);
+static regex_t         *regex_string(const char *s);
 
 
 /*
@@ -150,6 +174,8 @@ main(int  argc,                             /* I - Number of command-line args */
 
   if (argc == 3 && !strcmp(argv[1], "cat"))
     return (cat_ppd(argv[2], 0));
+  else if (argc == 2 && !strcmp(argv[1], "dump"))
+    return (dump_ppds_dat());
   else if (argc == 4 && !strcmp(argv[1], "get"))
     return (cat_ppd(argv[3], atoi(argv[2])));
   else if (argc == 5 && !strcmp(argv[1], "list"))
@@ -157,6 +183,7 @@ main(int  argc,                             /* I - Number of command-line args */
   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);
@@ -169,7 +196,8 @@ main(int  argc,                             /* I - Number of command-line args */
  */
 
 static ppd_info_t *                    /* O - PPD */
-add_ppd(const char *name,              /* I - PPD name */
+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 */
@@ -179,7 +207,8 @@ add_ppd(const char *name,           /* I - PPD name */
         time_t     mtime,              /* I - Modification time */
        size_t     size,                /* I - File size */
        int        model_number,        /* I - Model number */
-       int        type)                /* I - Driver type */
+       int        type,                /* I - Driver type */
+       const char *scheme)             /* I - PPD scheme */
 {
   ppd_info_t   *ppd;                   /* PPD */
   char         *recommended;           /* Foomatic driver string */
@@ -189,45 +218,25 @@ add_ppd(const char *name,         /* I - PPD name */
   * Add a new PPD file...
   */
 
-  if (NumPPDs >= AllocPPDs)
+  if ((ppd = (ppd_info_t *)calloc(1, sizeof(ppd_info_t))) == NULL)
   {
-   /*
-    * Allocate (more) memory for the PPD files...
-    */
-
-    AllocPPDs += 128;
-
-    if (!PPDs)
-      ppd = (ppd_info_t *)malloc(sizeof(ppd_info_t) * AllocPPDs);
-    else
-      ppd = (ppd_info_t *)realloc(PPDs, sizeof(ppd_info_t) * AllocPPDs);
-
-    if (ppd == NULL)
-    {
-      fprintf(stderr,
-              "ERROR: [cups-driverd] Ran out of memory for %d PPD files!\n",
-             AllocPPDs);
-      return (NULL);
-    }
-
-    PPDs = ppd;
+    fprintf(stderr,
+           "ERROR: [cups-driverd] Ran out of memory for %d PPD files!\n",
+           cupsArrayCount(PPDsByName));
+    return (NULL);
   }
 
-  ppd = PPDs + NumPPDs;
-  NumPPDs ++;
-
  /*
   * Zero-out the PPD data and copy the values over...
   */
 
-  memset(ppd, 0, sizeof(ppd_info_t));
-
   ppd->found               = 1;
   ppd->record.mtime        = mtime;
   ppd->record.size         = 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]));
@@ -238,6 +247,7 @@ add_ppd(const char *name,           /* I - PPD name */
   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
@@ -248,6 +258,13 @@ add_ppd(const char *name,          /* I - PPD name */
                             " (recommended)")) != NULL)
     *recommended = '\0';
 
+ /*
+  * Add the PPD to the PPD arrays...
+  */
+
+  cupsArrayAdd(PPDsByName, ppd);
+  cupsArrayAdd(PPDsByMakeModel, ppd);
+
  /*
   * Return the new PPD pointer...
   */
@@ -267,7 +284,7 @@ cat_drv(const char *name,           /* I - PPD name */
   const char   *datadir;               // CUPS_DATADIR env var
   ppdcSource   *src;                   // PPD source file data
   ppdcDriver   *d;                     // Current driver
-  cups_file_t  *out;                   // Stdout via CUPS file API 
+  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")
@@ -282,7 +299,7 @@ cat_drv(const char *name,           /* I - PPD name */
   if ((datadir = getenv("CUPS_DATADIR")) == NULL)
     datadir = CUPS_DATADIR;
 
-  // Pull out the 
+  // 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 ||
@@ -311,7 +328,8 @@ cat_drv(const char *name,           /* I - PPD name */
   *pc_file_name++ = '\0';
 
 #ifdef __APPLE__
-  if (!strncmp(resource, "/Library/Printers/PPDs.drv/", 27))
+  if (!strncmp(resource, "/Library/Printers/PPDs/Contents/Resources/", 42) ||
+      !strncmp(resource, "/System/Library/Printers/PPDs/Contents/Resources/", 49))
     strlcpy(filename, resource, sizeof(filename));
   else
 #endif // __APPLE__
@@ -326,8 +344,8 @@ cat_drv(const char *name,           /* I - PPD name */
   for (d = (ppdcDriver *)src->drivers->first();
        d;
        d = (ppdcDriver *)src->drivers->next())
-    if (!strcasecmp(pc_file_name, d->pc_file_name->value) ||
-        (d->file_name && !strcasecmp(pc_file_name, d->file_name->value)))
+    if (!strcmp(pc_file_name, d->pc_file_name->value) ||
+        (d->file_name && !strcmp(pc_file_name, d->file_name->value)))
       break;
 
   if (d)
@@ -336,7 +354,7 @@ cat_drv(const char *name,           /* I - PPD name */
     ppdcCatalog        *catalog;               // Message catalog in .drv file
 
 
-    fprintf(stderr, "DEBUG: %d locales defined in \"%s\"...\n",
+    fprintf(stderr, "DEBUG2: [cups-driverd] %d locales defined in \"%s\"...\n",
             src->po_files->count, filename);
 
     locales = new ppdcArray();
@@ -344,8 +362,9 @@ cat_drv(const char *name,           /* I - PPD name */
          catalog;
         catalog = (ppdcCatalog *)src->po_files->next())
     {
-      fprintf(stderr, "DEBUG: Adding locale \"%s\"...\n",
+      fprintf(stderr, "DEBUG2: [cups-driverd] Adding locale \"%s\"...\n",
               catalog->locale->value);
+      catalog->locale->retain();
       locales->add(catalog->locale);
     }
 
@@ -364,7 +383,7 @@ cat_drv(const char *name,           /* I - PPD name */
     d->write_ppd_file(out, NULL, locales, src, PPDC_LFONLY);
     cupsFileClose(out);
 
-    delete locales;
+    locales->release();
   }
   else
   {
@@ -384,7 +403,7 @@ cat_drv(const char *name,           /* I - PPD name */
     }
   }
 
-  delete src;
+  src->release();
 
   return (!d);
 }
@@ -524,6 +543,10 @@ cat_static(const char *name,               /* I - PPD name */
   const char   *datadir;               /* CUPS_DATADIR env var */
   char         line[1024],             /* Line/filename */
                message[2048];          /* status-message */
+#ifdef __APPLE__
+  const char   *printerDriver,         /* Pointer to .printerDriver extension */
+               *slash;                 /* Pointer to next slash */
+#endif /* __APPLE__ */
 
 
   if (name[0] == '/' || strstr(name, "../") || strstr(name, "/.."))
@@ -556,7 +579,19 @@ cat_static(const char *name,               /* I - PPD name */
 
 #ifdef __APPLE__
   if (!strncmp(name, "System/Library/Printers/PPDs/Contents/Resources/", 48) ||
-      !strncmp(name, "Library/Printers/PPDs/Contents/Resources/", 41))
+      !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 Mac OS X standard locations...
@@ -646,6 +681,37 @@ cat_static(const char *name,               /* I - PPD name */
 }
 
 
+/*
+ * '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.
  */
@@ -654,7 +720,13 @@ 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 */
 {
-  return (strcasecmp(p0->record.name, p1->record.name));
+  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));
 }
 
 
@@ -673,14 +745,51 @@ compare_ppds(const ppd_info_t *p0,        /* I - First PPD file */
   * First compare manufacturers...
   */
 
-  if ((diff = strcasecmp(p0->record.make, p1->record.make)) != 0)
+  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 (strcasecmp(p0->record.languages[0],
-                       p1->record.languages[0]));
+    return (compare_names(p0, p1));
+}
+
+
+/*
+ * 'dump_ppds_dat()' - Dump the contents of the ppds.dat file.
+ */
+
+static int                             /* O - Exit status */
+dump_ppds_dat(void)
+{
+  char         filename[1024];         /* ppds.dat filename */
+  ppd_info_t   *ppd;                   /* Current PPD */
+
+
+ /*
+  * See if we a PPD database file...
+  */
+
+  load_ppds_dat(filename, sizeof(filename), 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);
+
+  return (0);
 }
 
 
@@ -712,19 +821,19 @@ list_ppds(int        request_id,  /* I - Request ID */
           int        limit,            /* I - Limit */
          const char *opt)              /* I - Option argument */
 {
-  int          i, j;                   /* Looping vars */
+  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 */
-  struct stat  fileinfo;               /* ppds.dat information */
   char         filename[1024],         /* ppds.dat filename */
                model[1024];            /* Model directory */
-  const char   *cups_cachedir;         /* CUPS_CACHEDIR environment variable */
   const char   *cups_datadir;          /* CUPS_DATADIR environment variable */
   int          num_options;            /* Number of options */
   cups_option_t        *options;               /* Options */
-  const char   *requested,             /* requested-attributes option */
-               *device_id,             /* ppd-device-id option */
+  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 */
@@ -734,8 +843,8 @@ list_ppds(int        request_id,    /* I - Request ID */
                *type_str;              /* ppd-type option */
   int          model_number,           /* ppd-model-number value */
                type,                   /* ppd-type value */
-               mam_len,                /* Length of ppd-make-and-model */
-               device_id_len,          /* Length of ppd-device-id */
+               make_and_model_len,     /* Length of ppd-make-and-model */
+               product_len,            /* Length of ppd-product */
                send_device_id,         /* Send ppd-device-id? */
                send_make,              /* Send ppd-make? */
                send_make_and_model,    /* Send ppd-make-and-model? */
@@ -746,6 +855,10 @@ list_ppds(int        request_id,   /* I - Request ID */
                send_psversion,         /* Send ppd-psversion? */
                send_type,              /* Send ppd-type? */
                sent_header;            /* Sent the IPP header? */
+  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,
@@ -756,66 +869,17 @@ list_ppds(int        request_id,  /* I - Request ID */
   * See if we a PPD database file...
   */
 
-  NumPPDs    = 0;
-  AllocPPDs  = 0;
-  PPDs       = (ppd_info_t *)NULL;
-  ChangedPPD = 0;
-
-  if ((cups_cachedir = getenv("CUPS_CACHEDIR")) == NULL)
-    cups_cachedir = CUPS_CACHEDIR;
-
-  snprintf(filename, sizeof(filename), "%s/ppds.dat", cups_cachedir);
-  if ((fp = cupsFileOpen(filename, "r")) != NULL)
-  {
-   /*
-    * See if we have the right sync word...
-    */
-
-    unsigned ppdsync;                  /* Sync word */
-
-    if (cupsFileRead(fp, (char *)&ppdsync, sizeof(ppdsync))
-            == sizeof(ppdsync) &&
-        ppdsync == PPD_SYNC &&
-        !stat(filename, &fileinfo) &&
-       ((fileinfo.st_size - sizeof(ppdsync)) % sizeof(ppd_rec_t)) == 0 &&
-       (NumPPDs = (fileinfo.st_size - sizeof(ppdsync)) /
-                  sizeof(ppd_rec_t)) > 0)
-    {
-     /*
-      * We have a ppds.dat file, so read it!
-      */
-
-      if ((PPDs = (ppd_info_t *)malloc(sizeof(ppd_info_t) * NumPPDs)) == NULL)
-       fprintf(stderr,
-               "ERROR: [cups-driverd] Unable to allocate memory for %d "
-               "PPD files!\n", NumPPDs);
-      else
-      {
-        AllocPPDs = NumPPDs;
-
-       for (i = NumPPDs, ppd = PPDs; i > 0; i --, ppd ++)
-       {
-         cupsFileRead(fp, (char *)&(ppd->record), sizeof(ppd_rec_t));
-         ppd->found = 0;
-       }
-
-       fprintf(stderr, "INFO: [cups-driverd] Read \"%s\", %d PPDs...\n",
-               filename, NumPPDs);
-      }
-    }
+  load_ppds_dat(filename, sizeof(filename), 1);
 
-    cupsFileClose(fp);
-  }
-  
  /*
   * Load all PPDs in the specified directory and below...
   */
 
-  SortedPPDs = NumPPDs;
-
   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);
 
@@ -827,10 +891,14 @@ list_ppds(int        request_id,  /* I - Request ID */
   * Load PPDs from standard Mac OS X 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",
@@ -853,48 +921,53 @@ list_ppds(int        request_id,  /* I - Request ID */
   * Cull PPD files that are no longer present...
   */
 
-  for (i = NumPPDs, ppd = PPDs; i > 0; i --, ppd ++)
+  for (ppd = (ppd_info_t *)cupsArrayFirst(PPDsByName);
+       ppd;
+       ppd = (ppd_info_t *)cupsArrayNext(PPDsByName))
     if (!ppd->found)
     {
      /*
       * Remove this PPD file from the list...
       */
 
-      if (i > 1)
-        memmove(ppd, ppd + 1, (i - 1) * sizeof(ppd_info_t));
+      cupsArrayRemove(PPDsByName, ppd);
+      cupsArrayRemove(PPDsByMakeModel, ppd);
+      free(ppd);
 
-      NumPPDs --;
-      ppd --;
+      ChangedPPD = 1;
     }
 
- /*
-  * Sort the PPDs by name...
-  */
-
-  if (NumPPDs > 1)
-    qsort(PPDs, NumPPDs, sizeof(ppd_info_t),
-          (int (*)(const void *, const void *))compare_names);
-
  /*
   * Write the new ppds.dat file...
   */
 
+  fprintf(stderr, "DEBUG: [cups-driverd] ChangedPPD=%d\n", ChangedPPD);
+
   if (ChangedPPD)
   {
-    if ((fp = cupsFileOpen(filename, "w")) != NULL)
+    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 (i = NumPPDs, ppd = PPDs; i > 0; i --, ppd ++)
+      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);
 
-      fprintf(stderr, "INFO: [cups-driverd] Wrote \"%s\", %d PPDs...\n",
-              filename, NumPPDs);
+      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",
@@ -907,29 +980,28 @@ list_ppds(int        request_id,  /* I - Request ID */
   * Scan for dynamic PPD files...
   */
 
-  load_drivers();
-
- /*
-  * Add the raw driver...
-  */
+  num_options = cupsParseOptions(opt, 0, &options);
+  exclude     = cupsdCreateStringsArray(cupsGetOption("exclude-schemes",
+                                                      num_options, options));
+  include     = cupsdCreateStringsArray(cupsGetOption("include-schemes",
+                                                     num_options, options));
 
-  add_ppd("raw", "en", "Raw", "Raw Queue", "", "", "", 0, 0, 0,
-          PPD_TYPE_UNKNOWN);
+  load_drivers(include, exclude);
 
  /*
-  * Sort the PPDs by make and model...
+  * Add the raw driver...
   */
 
-  if (NumPPDs > 1)
-    qsort(PPDs, NumPPDs, sizeof(ppd_info_t),
-          (int (*)(const void *, const void *))compare_ppds);
+  add_ppd("", "raw", "en", "Raw", "Raw Queue", "", "", "", 0, 0, 0,
+          PPD_TYPE_UNKNOWN, "raw");
 
  /*
   * Send IPP attributes...
   */
 
-  num_options      = cupsParseOptions(opt, 0, &options);
-  requested        = cupsGetOption("requested-attributes", num_options, options);
+  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);
@@ -940,14 +1012,14 @@ list_ppds(int        request_id, /* I - Request ID */
   type_str         = cupsGetOption("ppd-type", num_options, options);
 
   if (make_and_model)
-    mam_len = strlen(make_and_model);
+    make_and_model_len = strlen(make_and_model);
   else
-    mam_len = 0;
+    make_and_model_len = 0;
 
-  if (device_id)
-    device_id_len = strlen(device_id);
+  if (product)
+    product_len = strlen(product);
   else
-    device_id_len = 0;
+    product_len = 0;
 
   if (model_number_str)
     model_number = atoi(model_number_str);
@@ -972,34 +1044,11 @@ list_ppds(int        request_id, /* I - Request ID */
   else
     type = 0;
 
-  if (requested)
-    fprintf(stderr, "DEBUG: [cups-driverd] requested-attributes=\"%s\"\n",
-           requested);
-  if (device_id)
-    fprintf(stderr, "DEBUG: [cups-driverd] ppd-device-id=\"%s\"\n",
-           device_id);
-  if (language)
-    fprintf(stderr, "DEBUG: [cups-driverd] ppd-natural-language=\"%s\"\n",
-           language);
-  if (make)
-    fprintf(stderr, "DEBUG: [cups-driverd] ppd-make=\"%s\"\n",
-           make);
-  if (make_and_model)
-    fprintf(stderr, "DEBUG: [cups-driverd] ppd-make-and-model=\"%s\"\n",
-           make_and_model);
-  if (model_number_str)
-    fprintf(stderr, "DEBUG: [cups-driverd] ppd-model-number=\"%s\"\n",
-           model_number_str);
-  if (product)
-    fprintf(stderr, "DEBUG: [cups-driverd] ppd-product=\"%s\"\n",
-           product);
-  if (psversion)
-    fprintf(stderr, "DEBUG: [cups-driverd] ppd-psversion=\"%s\"\n",
-           psversion);
-  if (type_str)
-    fprintf(stderr, "DEBUG: [cups-driverd] ppd-type=\"%s\"\n", type_str);
+  for (i = 0; i < num_options; i ++)
+    fprintf(stderr, "DEBUG2: [cups-driverd] %s=\"%s\"\n", options[i].name,
+            options[i].value);
 
-  if (!requested || strstr(requested, "all"))
+  if (!requested || cupsArrayFind(requested, (void *)"all") != NULL)
   {
     send_name             = 1;
     send_make             = 1;
@@ -1013,82 +1062,201 @@ list_ppds(int        request_id,       /* I - Request ID */
   }
   else
   {
-    send_name             = strstr(requested, "ppd-name") != NULL;
-    send_make             = strstr(requested, "ppd-make,") != NULL ||
-                            strstr(requested, ",ppd-make") != NULL ||
-                            !strcmp(requested, "ppd-make");
-    send_make_and_model   = strstr(requested, "ppd-make-and-model") != NULL;
-    send_model_number     = strstr(requested, "ppd-model-number") != NULL;
-    send_natural_language = strstr(requested, "ppd-natural-language") != NULL;
-    send_device_id        = strstr(requested, "ppd-device-id") != NULL;
-    send_product          = strstr(requested, "ppd-product") != NULL;
-    send_psversion        = strstr(requested, "ppd-psversion") != NULL;
-    send_type             = strstr(requested, "ppd-type") != NULL;
+    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;
   }
 
-  puts("Content-Type: application/ipp\n");
+ /*
+  * 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 > NumPPDs)
-    count = NumPPDs;
+  if (limit <= 0 || limit > cupsArrayCount(PPDsByMakeModel))
+    count = cupsArrayCount(PPDsByMakeModel);
   else
     count = limit;
 
-  for (i = NumPPDs, ppd = PPDs; count > 0 && i > 0; i --, ppd ++)
+  if (device_id || language || make || make_and_model || model_number_str ||
+      product)
   {
-   /*
-    * Filter PPDs based on make, model, or device ID...
-    */
+    matches = cupsArrayNew((cups_array_func_t)compare_matches, NULL);
 
-    if (device_id && strncasecmp(ppd->record.device_id, device_id,
-                                 device_id_len))
-      continue;                                /* TODO: implement smart compare */
+    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;
 
-    if (language)
+    for (ppd = (ppd_info_t *)cupsArrayFirst(PPDsByMakeModel);
+        ppd;
+        ppd = (ppd_info_t *)cupsArrayNext(PPDsByMakeModel))
     {
-      for (j = 0; j < PPD_MAX_LANG; j ++)
-       if (!ppd->record.languages[j][0] ||
-           !strcasecmp(ppd->record.languages[j], language))
-         break;
+     /*
+      * 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 (j >= PPD_MAX_LANG || !ppd->record.languages[j][0])
+      if (ppd->record.type < PPD_TYPE_POSTSCRIPT ||
+         ppd->record.type >= PPD_TYPE_DRV)
        continue;
-    }
 
-    if (make && strcasecmp(ppd->record.make, make))
-      continue;
+      if (cupsArrayFind(exclude, ppd->record.scheme) ||
+          (include && !cupsArrayFind(include, ppd->record.scheme)))
+        continue;
 
-    if (make_and_model && strncasecmp(ppd->record.make_and_model,
-                                      make_and_model, mam_len))
-      continue;
+      ppd->matches = 0;
 
-    if (model_number_str && ppd->record.model_number != model_number)
-      continue;
+      if (device_id_re &&
+         !regexec(device_id_re, ppd->record.device_id,
+                   (int)(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).
+       */
 
-    if (product)
-    {
-      for (j = 0; j < PPD_MAX_PROD; j ++)
-       if (!ppd->record.products[j][0] ||
-           !strcasecmp(ppd->record.products[j], product))
-         break;
+        for (i = 1; i < (int)(sizeof(re_matches) / sizeof(re_matches[0])); i ++)
+         if (re_matches[i].rm_so >= 0)
+           ppd->matches ++;
+      }
 
-      if (j >= PPD_MAX_PROD || !ppd->record.products[j][0])
-       continue;
+      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,
+                  (int)(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 (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;
+         }
+      }
+
+      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);
 
-    if (psversion)
+    for (ppd = (ppd_info_t *)cupsArrayFirst(PPDsByMakeModel);
+        ppd;
+        ppd = (ppd_info_t *)cupsArrayNext(PPDsByMakeModel))
     {
-      for (j = 0; j < PPD_MAX_VERS; j ++)
-       if (!ppd->record.psversions[j][0] ||
-           !strcasecmp(ppd->record.psversions[j], psversion))
-         break;
+     /*
+      * Filter PPDs based on the include/exclude lists.
+      */
 
-      if (j >= PPD_MAX_VERS || !ppd->record.psversions[j][0])
+      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 (type_str && ppd->record.type != type)
+    if (ppd->record.type < PPD_TYPE_POSTSCRIPT ||
+        ppd->record.type >= PPD_TYPE_DRV)
       continue;
 
    /*
@@ -1102,10 +1270,11 @@ list_ppds(int        request_id,        /* I - 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");
+      cupsdSendIPPString(IPP_TAG_LANGUAGE, "attributes-natural-language",
+                         "en-US");
     }
 
-    fprintf(stderr, "DEBUG: [cups-driverd] Sending %s (%s)...\n",
+    fprintf(stderr, "DEBUG2: [cups-driverd] Sending %s (%s)...\n",
            ppd->record.name, ppd->record.make_and_model);
 
     count --;
@@ -1120,8 +1289,8 @@ list_ppds(int        request_id,  /* I - Request ID */
       cupsdSendIPPString(IPP_TAG_LANGUAGE, "ppd-natural-language",
                         ppd->record.languages[0]);
 
-      for (j = 1; j < PPD_MAX_LANG && ppd->record.languages[j][0]; j ++)
-       cupsdSendIPPString(IPP_TAG_LANGUAGE, "", ppd->record.languages[j]);
+      for (i = 1; i < PPD_MAX_LANG && ppd->record.languages[i][0]; i ++)
+       cupsdSendIPPString(IPP_TAG_LANGUAGE, "", ppd->record.languages[i]);
     }
 
     if (send_make)
@@ -1140,8 +1309,8 @@ list_ppds(int        request_id,  /* I - Request ID */
       cupsdSendIPPString(IPP_TAG_TEXT, "ppd-product",
                         ppd->record.products[0]);
 
-      for (j = 1; j < PPD_MAX_PROD && ppd->record.products[j][0]; j ++)
-       cupsdSendIPPString(IPP_TAG_TEXT, "", ppd->record.products[j]);
+      for (i = 1; i < PPD_MAX_PROD && ppd->record.products[i][0]; i ++)
+       cupsdSendIPPString(IPP_TAG_TEXT, "", ppd->record.products[i]);
     }
 
     if (send_psversion)
@@ -1149,8 +1318,8 @@ list_ppds(int        request_id,  /* I - Request ID */
       cupsdSendIPPString(IPP_TAG_TEXT, "ppd-psversion",
                         ppd->record.psversions[0]);
 
-      for (j = 1; j < PPD_MAX_VERS && ppd->record.psversions[j][0]; j ++)
-       cupsdSendIPPString(IPP_TAG_TEXT, "", ppd->record.psversions[j]);
+      for (i = 1; i < PPD_MAX_VERS && ppd->record.psversions[i][0]; i ++)
+       cupsdSendIPPString(IPP_TAG_TEXT, "", ppd->record.psversions[i]);
     }
 
     if (send_type)
@@ -1166,17 +1335,20 @@ list_ppds(int        request_id,        /* I - Request ID */
     * the remaining PPDs with this make...
     */
 
-    if (requested && !strcmp(requested, "ppd-make"))
+    if (cupsArrayFind(requested, (void *)"ppd-make") &&
+        cupsArrayCount(requested) == 1)
     {
       const char       *this_make;     /* This ppd-make */
 
 
-      for (this_make = ppd->record.make, i --, ppd ++; i > 0; i --, ppd ++)
-       if (strcasecmp(this_make, ppd->record.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;
 
-      i ++;
-      ppd --;
+      cupsArrayPrev(matches);
     }
   }
 
@@ -1195,48 +1367,422 @@ list_ppds(int        request_id,       /* I - Request ID */
 
 
 /*
- * 'load_ppds()' - Load PPD files recursively.
+ * 'load_drv()' - Load the PPDs from a driver information file.
  */
 
 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? */
+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 */
 {
-  int          i;                      /* Looping var */
-  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 backend */
-               *ptr,                   /* Pointer into name */
-               name[128],              /* Name of PPD file */
-               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          model_number,           /* cupsModelNumber */
-               type;                   /* ppd-type */
-  cups_array_t *products,              /* Product array */
-               *psversions,            /* PSVersion array */
-               *cups_languages;        /* cupsLanguages array */
-  ppd_info_t   *ppd,                   /* New PPD file */
-               key;                    /* Search key */
-  int          new_ppd;                /* Is this a new PPD? */
-  struct                               /* LanguageVersion translation table */
+  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)
   {
-    const char *version,               /* LanguageVersion string */
-               *language;              /* Language code */
-  }            languages[] =
+    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, 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())
   {
-    { "chinese",               "zh" },
+    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, 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, 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[512],              /* 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, 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, 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';
+       strcpy(type_str, "postscript");
+
+        if (sscanf(line, "\"%511[^\"]\"%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(ppd_types) / sizeof(ppd_types[0]));
+              type ++)
+           if (!strcmp(type_str, ppd_types[type]))
+              break;
+
+         if (type >= (int)(sizeof(ppd_types) / sizeof(ppd_types[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_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 */
+  int          i;                      /* Looping var */
+  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 backend */
+               *ptr,                   /* Pointer into name */
+               name[128],              /* Name of PPD file */
+               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          model_number,           /* cupsModelNumber */
+               type;                   /* ppd-type */
+  cups_array_t *products,              /* Product array */
+               *psversions,            /* PSVersion array */
+               *cups_languages;        /* cupsLanguages array */
+  ppd_info_t   *ppd,                   /* New PPD file */
+               key;                    /* Search key */
+  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" },
@@ -1262,7 +1808,40 @@ load_ppds(const char *d,         /* I - Actual directory */
   };
 
 
-  fprintf(stderr, "DEBUG: [cups-driverd] Loading \"%s\"...\n", d);
+ /*
+  * 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 (0);
+  }
+
+ /*
+  * 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)
   {
@@ -1274,6 +1853,8 @@ load_ppds(const char *d,          /* I - Actual directory */
     return (0);
   }
 
+  fprintf(stderr, "DEBUG: [cups-driverd] Loading \"%s\"...\n", d);
+
   while ((dent = cupsDirRead(dir)) != NULL)
   {
    /*
@@ -1301,36 +1882,75 @@ load_ppds(const char *d,                /* I - Actual directory */
       */
 
       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 ((ptr = filename + strlen(filename) - 6) > filename &&
+             !strcmp(ptr, ".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...
     */
 
-    if (SortedPPDs > 0)
+    strcpy(key.record.filename, name);
+    strcpy(key.record.name, name);
+
+    ppd = (ppd_info_t *)cupsArrayFind(PPDsByName, &key);
+
+    if (ppd &&
+       ppd->record.size == dent->fileinfo.st_size &&
+       ppd->record.mtime == dent->fileinfo.st_mtime)
     {
-      strcpy(key.record.name, name);
+     /*
+      * Rewind to the first entry for this file...
+      */
 
-      ppd = (ppd_info_t *)bsearch(&key, PPDs, SortedPPDs, sizeof(ppd_info_t),
-                                  (cupsd_compare_func_t)compare_names);
+      while ((ppd = (ppd_info_t *)cupsArrayPrev(PPDsByName)) != NULL &&
+            !strcmp(ppd->record.filename, name));
 
-      if (ppd &&
-          ppd->record.size == dent->fileinfo.st_size &&
-         ppd->record.mtime == dent->fileinfo.st_mtime)
-      {
+     /*
+      * 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;
-      }
+
+      continue;
     }
-    else
-      ppd = NULL;
 
    /*
     * No, file is new/changed, so re-scan it...
@@ -1386,12 +2006,36 @@ load_ppds(const char *d,                /* I - Actual directory */
        sscanf(line, "%*[^:]:%63s", lang_version);
       else if (!strncmp(line, "*NickName:", 10))
        sscanf(line, "%*[^\"]\"%255[^\"]", nick_name);
-      else if (!strncasecmp(line, "*1284DeviceID:", 14))
+      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)
-         cupsArrayAdd(products, strdup(product));
+       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))
       {
@@ -1430,7 +2074,7 @@ load_ppds(const char *d,          /* I - Actual directory */
       {
         for (ptr = line + 9; isspace(*ptr & 255); ptr ++);
 
-       if (!strncasecmp(ptr, "true", 4))
+       if (!_cups_strncasecmp(ptr, "true", 4))
           type = PPD_TYPE_FAX;
       }
       else if (!strncmp(line, "*cupsFilter:", 12) && type == PPD_TYPE_POSTSCRIPT)
@@ -1508,7 +2152,7 @@ load_ppds(const char *d,          /* I - Actual directory */
     while (isspace(manufacturer[0] & 255))
       _cups_strcpy(manufacturer, manufacturer + 1);
 
-    if (!strncasecmp(make_model, manufacturer, strlen(manufacturer)))
+    if (!_cups_strncasecmp(make_model, manufacturer, strlen(manufacturer)))
       strlcpy(temp, make_model, sizeof(temp));
     else
       snprintf(temp, sizeof(temp), "%s %s", manufacturer, make_model);
@@ -1541,10 +2185,10 @@ load_ppds(const char *d,                /* I - Actual directory */
       else
        strcpy(manufacturer, "Other");
     }
-    else if (!strncasecmp(manufacturer, "LHAG", 4) ||
-             !strncasecmp(manufacturer, "linotype", 8))
+    else if (!_cups_strncasecmp(manufacturer, "LHAG", 4) ||
+             !_cups_strncasecmp(manufacturer, "linotype", 8))
       strcpy(manufacturer, "LHAG");
-    else if (!strncasecmp(manufacturer, "Hewlett", 7))
+    else if (!_cups_strncasecmp(manufacturer, "Hewlett", 7))
       strcpy(manufacturer, "HP");
 
    /*
@@ -1575,7 +2219,7 @@ load_ppds(const char *d,          /* I - Actual directory */
     }
 
     for (i = 0; i < (int)(sizeof(languages) / sizeof(languages[0])); i ++)
-      if (!strcasecmp(languages[i].version, lang_version))
+      if (!_cups_strcasecmp(languages[i].version, lang_version))
         break;
 
     if (i < (int)(sizeof(languages) / sizeof(languages[0])))
@@ -1608,13 +2252,13 @@ load_ppds(const char *d,                /* I - Actual directory */
       * Add new PPD file...
       */
 
-      fprintf(stderr, "DEBUG: [cups-driverd] Adding ppd \"%s\"...\n", name);
+      fprintf(stderr, "DEBUG2: [cups-driverd] Adding ppd \"%s\"...\n", name);
 
-      ppd = add_ppd(name, lang_version, manufacturer, make_model, device_id,
-                    (char *)cupsArrayFirst(products),
+      ppd = add_ppd(name, name, lang_version, manufacturer, make_model,
+                    device_id, (char *)cupsArrayFirst(products),
                     (char *)cupsArrayFirst(psversions),
                     dent->fileinfo.st_mtime, dent->fileinfo.st_size,
-                   model_number, type);
+                   model_number, type, "file");
 
       if (!ppd)
       {
@@ -1628,7 +2272,7 @@ load_ppds(const char *d,          /* I - Actual directory */
       * Update existing record...
       */
 
-      fprintf(stderr, "DEBUG: [cups-driverd] Updating ppd \"%s\"...\n", name);
+      fprintf(stderr, "DEBUG2: [cups-driverd] Updating ppd \"%s\"...\n", name);
 
       memset(ppd, 0, sizeof(ppd_info_t));
 
@@ -1690,283 +2334,236 @@ load_ppds(const char *d,              /* I - Actual directory */
 }
 
 
-/*
- * '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
-  bool         product_found;          // Found product?
-  char         uri[1024],              // Driver URI
-               make_model[1024];       // Make and model
-  int          type;                   // Driver type
-
-
-  src = new ppdcSource(filename, fp);
+/*
+ * 'load_ppds_dat()' - Load the ppds.dat file.
+ */
 
-  if (src->drivers->count == 0)
-  {
-    fprintf(stderr,
-            "ERROR: [cups-driverd] Bad driver information file \"%s\"!\n",
-           filename);
-    delete src;
-    return (0);
-  }
+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 */
 
-  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);
+  PPDsByName      = cupsArrayNew((cups_array_func_t)compare_names, NULL);
+  PPDsByMakeModel = cupsArrayNew((cups_array_func_t)compare_ppds, NULL);
+  ChangedPPD      = 0;
 
-    if (nick_name)
-      strlcpy(make_model, nick_name->value->value, sizeof(make_model));
-    else if (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_cachedir = getenv("CUPS_CACHEDIR")) == NULL)
+    cups_cachedir = CUPS_CACHEDIR;
 
-    if ((cups_fax = d->find_attr("cupsFax", NULL)) != NULL &&
-        !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
+  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 (cupsFileRead(fp, (char *)&ppdsync, sizeof(ppdsync))
+            == sizeof(ppdsync) &&
+        ppdsync == PPD_SYNC &&
+        !stat(filename, &fileinfo) &&
+       ((fileinfo.st_size - sizeof(ppdsync)) % sizeof(ppd_rec_t)) == 0 &&
+       (num_ppds = (fileinfo.st_size - sizeof(ppdsync)) /
+                   sizeof(ppd_rec_t)) > 0)
     {
-      for (filter = (ppdcFilter *)d->filters->first(),
-               type = PPD_TYPE_POSTSCRIPT;
-          filter;
-          filter = (ppdcFilter *)d->filters->next())
-        if (strcasecmp(filter->mime_type->value, "application/vnd.cups-raster"))
-         type = PPD_TYPE_RASTER;
-        else if (strcasecmp(filter->mime_type->value,
-                           "application/vnd.cups-pdf"))
-         type = PPD_TYPE_PDF;
-    }
+     /*
+      * We have a ppds.dat file, so read it!
+      */
 
-    for (product = (ppdcAttr *)d->attrs->first(), product_found = false;
-         product;
-        product = (ppdcAttr *)d->attrs->next())
-      if (!strcmp(product->name->value, "Product"))
+      for (; num_ppds > 0; num_ppds --)
       {
-        product_found = true;
+       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);
+       }
 
-       add_ppd(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, d->model_number, type);
+       if (cupsFileRead(fp, (char *)&(ppd->record), sizeof(ppd_rec_t)) > 0)
+       {
+         cupsArrayAdd(PPDsByName, ppd);
+         cupsArrayAdd(PPDsByMakeModel, ppd);
+       }
+       else
+       {
+         free(ppd);
+         break;
+       }
       }
 
-    if (!product_found)
-      add_ppd(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, d->model_number, type);
-  }
-
-  delete src;
+      if (verbose)
+       fprintf(stderr, "INFO: [cups-driverd] Read \"%s\", %d PPDs...\n",
+               filename, cupsArrayCount(PPDsByName));
+    }
 
-  return (1);
+    cupsFileClose(fp);
+  }
 }
 
 
 /*
- * 'load_drivers()' - Load driver-generated PPD files.
+ * 'regex_device_id()' - Compile a regular expression based on the 1284 device
+ *                       ID.
  */
 
-static int                             /* O - 1 on success, 0 on failure */
-load_drivers(void)
+static regex_t *                       /* O - Regular expression */
+regex_device_id(const char *device_id) /* I - IEEE-1284 device ID */
 {
-  int          i;                      /* Looping var */
-  char         *start,                 /* Start of value */
+  char         res[2048],              /* Regular expression string */
                *ptr;                   /* Pointer into string */
-  const char   *server_bin;            /* CUPS_SERVERBIN env variable */
-  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[512],              /* ppd-name */
-               make[128],              /* ppd-make */
-               make_and_model[128],    /* ppd-make-and-model */
-               device_id[128],         /* 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 */
-
+  regex_t      *re;                    /* Regular expression */
+  int          cmd;                    /* Command set string? */
 
- /*
-  * 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);
-  }
+  fprintf(stderr, "DEBUG: [cups-driverd] regex_device_id(\"%s\")\n", device_id);
 
  /*
-  * Loop through all of the device drivers...
+  * 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.
   */
 
-  argv[1] = (char *)"list";
-  argv[2] = NULL;
+  ptr = res;
 
-  while ((dent = cupsDirRead(dir)) != NULL)
+  while (*device_id && ptr < (res + sizeof(res) - 6))
   {
-   /*
-    * Only look at executable files...
-    */
+    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++ = '*';
+      }
 
-    if (!(dent->fileinfo.st_mode & 0111) || !S_ISREG(dent->fileinfo.st_mode))
-      continue;
+      *ptr++ = '(';
 
-   /*
-    * Run the driver with no arguments and collect the output...
-    */
+      while (*device_id && *device_id != ';' && ptr < (res + sizeof(res) - 8))
+      {
+        if (strchr("[]{}().*\\|", *device_id))
+         *ptr++ = '\\';
+        if (*device_id == ':')
+       {
+        /*
+         * KEY:.*value
+         */
 
-    argv[0] = dent->filename;
-    snprintf(filename, sizeof(filename), "%s/%s", drivers, dent->filename);
+         *ptr++ = *device_id++;
+         *ptr++ = '.';
+         *ptr++ = '*';
+       }
+       else
+         *ptr++ = *device_id++;
+      }
 
-    if ((fp = cupsdPipeCommand(&pid, filename, argv, 0)) != NULL)
-    {
-      while (cupsFileGets(fp, line, sizeof(line)))
+      if (*device_id == ';' || !*device_id)
       {
        /*
-        * Each line is of the form:
-       *
-       *   "ppd-name" ppd-natural-language "ppd-make" "ppd-make-and-model" \
-       *       "ppd-device-id" "ppd-product" "ppd-psversion"
+        * KEY:.*value.*;
        */
 
-        device_id[0] = '\0';
-       product[0]   = '\0';
-       psversion[0] = '\0';
-       strcpy(type_str, "postscript");
+       *ptr++ = '.';
+       *ptr++ = '*';
+        *ptr++ = ';';
+      }
+      *ptr++ = ')';
+      if (cmd)
+        *ptr++ = '?';
+    }
+    else if ((device_id = strchr(device_id, ';')) == NULL)
+      break;
+    else
+      device_id ++;
+  }
 
-        if (sscanf(line, "\"%511[^\"]\"%127s%*[ \t]\"%127[^\"]\""
-                        "%*[ \t]\"%127[^\"]\"%*[ \t]\"%127[^\"]\""
-                        "%*[ \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.
-         */
+  *ptr = '\0';
 
-          if (line[strlen(line) - 1] == '\n')
-           line[strlen(line) - 1] = '\0';
+  fprintf(stderr, "DEBUG: [cups-driverd] regex_device_id: \"%s\"\n", res);
 
-         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...
-         */
+ /*
+  * Compile the regular expression and return...
+  */
 
-          if ((start = strchr(languages, ',')) != NULL)
-           *start++ = '\0';
+  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);
+    }
 
-         for (type = 0;
-               type < (int)(sizeof(ppd_types) / sizeof(ppd_types[0]));
-              type ++)
-           if (!strcmp(type_str, ppd_types[type]))
-              break;
+    free(re);
+  }
 
-         if (type >= (int)(sizeof(ppd_types) / sizeof(ppd_types[0])))
-         {
-           fprintf(stderr,
-                   "ERROR: [cups-driverd] Bad ppd-type \"%s\" ignored!\n",
-                   type_str);
-           type = PPD_TYPE_UNKNOWN;
-         }
+  return (NULL);
+}
 
-          ppd = add_ppd(name, languages, make, make_and_model, device_id,
-                       product, psversion, 0, 0, 0, type);
 
-          if (!ppd)
-         {
-            cupsDirClose(dir);
-           cupsFileClose(fp);
-           return (0);
-         }
+/*
+ * 'regex_string()' - Construct a regular expression to compare a simple string.
+ */
 
-          if (start && *start)
-         {
-           for (i = 1; i < PPD_MAX_LANG && *start; i ++)
-           {
-             if ((ptr = strchr(start, ',')) != NULL)
-               *ptr++ = '\0';
-             else
-               ptr = start + strlen(start);
+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 */
 
-              strlcpy(ppd->record.languages[i], start,
-                     sizeof(ppd->record.languages[0]));
 
-             start = ptr;
-           }
-          }
+  fprintf(stderr, "DEBUG: [cups-driverd] regex_string(\"%s\")\n", s);
 
-          fprintf(stderr, "DEBUG: [cups-driverd] Added dynamic PPD \"%s\"...\n",
-                 name);
-       }
-      }
+ /*
+  * Convert the string to a regular expression, escaping special characters
+  * as needed.
+  */
 
-      cupsFileClose(fp);
-    }
-    else
-      fprintf(stderr, "WARNING: [cups-driverd] Unable to execute \"%s\": %s\n",
-              filename, strerror(errno));
+  ptr = res;
+
+  while (*s && ptr < (res + sizeof(res) - 2))
+  {
+    if (strchr("[]{}().*\\", *s))
+      *ptr++ = '\\';
+
+    *ptr++ = *s++;
   }
 
-  cupsDirClose(dir);
+  *ptr = '\0';
 
-  return (1);
+  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);
 }