/*
 * "$Id: cups-driverd.c 6458 2007-04-17 19:22:41Z mike $"
 *
 *   PPD/driver support for the Common UNIX Printing System (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.
 *
 *   Copyright 1997-2006 by Easy Software Products.
 *
 *   These coded instructions, statements, and computer programs are the
 *   property of Easy Software Products and are protected by Federal
 *   copyright law.  Distribution and use rights are outlined in the file
 *   "LICENSE.txt" which should have been included with this file.  If this
 *   file is missing or damaged please contact Easy Software Products
 *   at:
 *
 *       Attn: CUPS Licensing Information
 *       Easy Software Products
 *       44141 Airport View Drive, Suite 204
 *       Hollywood, Maryland 20636 USA
 *
 *       Voice: (301) 373-9600
 *       EMail: cups-info@cups.org
 *         WWW: http://www.cups.org
 *
 * Contents:
 *
 *   main()          - Scan for drivers and return an IPP response.
 *   add_ppd()       - Add a PPD file.
 *   cat_ppd()       - Copy a PPD file to stdout.
 *   compare_names() - Compare PPD filenames for sorting.
 *   compare_ppds()  - Compare PPD file make and model names for sorting.
 *   list_ppds()     - List PPD files.
 *   load_ppds()     - Load PPD files recursively.
 *   load_drivers()  - Load driver-generated PPD files.
 */

/*
 * Include necessary headers...
 */

#include "util.h"
#include <cups/dir.h>
#include <cups/transcode.h>


/*
 * Private PPD functions...
 */

extern cups_encoding_t	_ppdGetEncoding(const char *name);


/*
 * PPD information structures...
 */

typedef struct				/**** PPD record ****/
{
  time_t	mtime;			/* Modification time */
  size_t	size;			/* Size in bytes */
  char		name[512 - sizeof(time_t) - sizeof(size_t)],
					/* PPD name */
		natural_language[128],	/* Natural language(s) */
		make[128],		/* Manufacturer */
		make_and_model[128],	/* Make and model */
		device_id[128];		/* IEEE 1284 Device ID */
} ppd_rec_t;

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


/*
 * 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 */
int		ChangedPPD;		/* Did we change the PPD database? */


/*
 * Local functions...
 */

static ppd_info_t	*add_ppd(const char *name, const char *natural_language,
		         const char *make, const char *make_and_model,
			 const char *device_id, time_t mtime, size_t size);
static int		cat_ppd(const char *name);
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		list_ppds(int request_id, int limit, const char *opt);
static int		load_drivers(void);
static int		load_ppds(const char *d, const char *p);


/*
 * '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"))
    return (cat_ppd(argv[2]));
  else if (argc == 5 && !strcmp(argv[1], "list"))
    return (list_ppds(atoi(argv[2]), atoi(argv[3]), argv[4]));
  else
  {
    fputs("Usage: cups-driverd cat 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 *name,		/* I - PPD name */
        const char *natural_language,	/* I - Language(s) */
        const char *make,		/* I - Manufacturer */
	const char *make_and_model,	/* I - NickName */
	const char *device_id,		/* I - 1284DeviceID */
        time_t     mtime,		/* I - Modification time */
	size_t     size)		/* I - File size */
{
  ppd_info_t	*ppd;			/* PPD */
  char		*recommended;		/* Foomatic driver string */


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

  if (NumPPDs >= AllocPPDs)
  {
   /*
    * Allocate (more) memory for the PPD files...
    */

    AllocPPDs += 128;

    if (!PPDs)
      ppd = malloc(sizeof(ppd_info_t) * AllocPPDs);
    else
      ppd = 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;
  }

  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;

  strlcpy(ppd->record.name, name, sizeof(ppd->record.name));
  strlcpy(ppd->record.natural_language, natural_language,
          sizeof(ppd->record.natural_language));
  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));

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

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

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

  return (ppd);
}


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

static int				/* O - Exit code */
cat_ppd(const char *name)		/* I - PPD name */
{
  char		scheme[256],		/* Scheme from PPD name */
		*sptr;			/* Pointer into scheme */
  char		line[1024];		/* Line/filename */


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

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

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

      name += 5;
      scheme[0] = '\0';
    }
  }
  else
    scheme[0] = '\0';

  if (scheme[0])
  {
   /*
    * Dynamic PPD, see if we have a driver program to support it...
    */

    const char	*serverbin;		/* CUPS_SERVERBIN env var */


    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));
      return (1);
    }

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

    if (execl(line, scheme, "cat", name, (char *)NULL))
    {
     /*
      * Unable to execute driver...
      */

      fprintf(stderr, "ERROR: [cups-driverd] Unable to execute \"%s\" - %s\n",
              line, strerror(errno));
      return (1);
    }
  }
  else
  {
   /*
    * Static PPD, see if we have a valid path and it exists...
    */

    cups_file_t	*fp;			/* PPD file */
    const char	*datadir;		/* CUPS_DATADIR env var */


    if (name[0] == '/' || strstr(name, "../") || strstr(name, "/.."))
    {
     /*
      * Bad name...
      */

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

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

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

    snprintf(line, sizeof(line), "%s/model/%s", datadir, name);
    if ((fp = cupsFileOpen(line, "r")) == NULL)
    {
      fprintf(stderr, "ERROR: [cups-driverd] Unable to open \"%s\" - %s\n",
              line, strerror(errno));
      return (1);
    }

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

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

    cupsFileClose(fp);
  }

 /*
  * Return with no errors...
  */

  return (0);
}


/*
 * '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 */
{
  return (strcasecmp(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 = 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
    return (strcasecmp(p0->record.natural_language,
                       p1->record.natural_language));
}


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

static int				/* O - Exit code */
list_ppds(int        request_id,	/* I - Request ID */
          int        limit,		/* I - Limit */
	  const char *opt)		/* I - Option argument */
{
  int		i;			/* Looping var */
  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 */
		*make;			/* ppd-make option */
  int		send_natural_language,	/* Send ppd-natural-language attribute? */
		send_make,		/* Send ppd-make attribute? */
		send_make_and_model,	/* Send ppd-make-and-model attribute? */
		send_name,		/* Send ppd-name attribute? */
		send_device_id;		/* Send ppd-device-id attribute? */


  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...
  */

  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 (!stat(filename, &fileinfo) &&
      (fileinfo.st_size % sizeof(ppd_rec_t)) == 0 &&
      (NumPPDs = fileinfo.st_size / sizeof(ppd_rec_t)) > 0)
  {
   /*
    * We have a ppds.dat file, so read it!
    */

    AllocPPDs = NumPPDs;

    if ((PPDs = malloc(sizeof(ppd_info_t) * NumPPDs)) == NULL)
    {
      fprintf(stderr,
              "ERROR: [cups-driverd] Unable to allocate memory for %d "
	      "PPD files!\n", NumPPDs);
      NumPPDs   = 0;
      AllocPPDs = 0;
    }
    else if ((fp = cupsFileOpen(filename, "r")) != NULL)
    {
      for (i = NumPPDs, ppd = PPDs; i > 0; i --, ppd ++)
      {
        cupsFileRead(fp, (char *)&(ppd->record), sizeof(ppd_rec_t));
	ppd->found = 0;
      }

      cupsFileClose(fp);

      fprintf(stderr, "INFO: [cups-driverd] Read \"%s\", %d PPDs...\n",
              filename, NumPPDs);
    }
    else
    {
      fprintf(stderr, "ERROR: [cups-driverd] Unable to read \"%s\" - %s\n", filename,
              strerror(errno));
      NumPPDs = 0;
    }
  }

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

  SortedPPDs = NumPPDs;

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

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

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

  for (i = NumPPDs, ppd = PPDs; i > 0; i --, ppd ++)
    if (!ppd->found)
    {
     /*
      * Remove this PPD file from the list...
      */

      if (i > 1)
        memmove(ppd, ppd + 1, (i - 1) * sizeof(ppd_info_t));

      NumPPDs --;
      ppd --;
    }

 /*
  * 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...
  */

  if (ChangedPPD)
  {
    if ((fp = cupsFileOpen(filename, "w")) != NULL)
    {
      for (i = NumPPDs, ppd = PPDs; i > 0; i --, ppd ++)
	cupsFileWrite(fp, (char *)&(ppd->record), sizeof(ppd_rec_t));

      cupsFileClose(fp);

      fprintf(stderr, "INFO: [cups-driverd] Wrote \"%s\", %d PPDs...\n",
              filename, NumPPDs);
    }
    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...
  */

  load_drivers();

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

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

 /*
  * Sort the PPDs by make and model...
  */

  if (NumPPDs > 1)
    qsort(PPDs, NumPPDs, sizeof(ppd_info_t),
          (int (*)(const void *, const void *))compare_ppds);

 /*
  * Send IPP attributes...
  */

  num_options = cupsParseOptions(opt, 0, &options);
  requested   = cupsGetOption("requested-attributes", num_options, options);
  make        = cupsGetOption("ppd-make", num_options, options);

  fprintf(stderr, "DEBUG: [cups-driverd] requested=\"%s\"\n",
          requested ? requested : "(nil)");

  if (!requested || strstr(requested, "all"))
  {
    send_name             = 1;
    send_make             = 1;
    send_make_and_model   = 1;
    send_natural_language = 1;
    send_device_id        = 1;
  }
  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_natural_language = strstr(requested, "ppd-natural-language") != NULL;
    send_device_id        = strstr(requested, "ppd-device-id") != NULL;
  }

  puts("Content-Type: application/ipp\n");

  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");

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

  for (i = NumPPDs, ppd = PPDs; count > 0 && i > 0; i --, ppd ++)
    if (!make || !strcasecmp(ppd->record.make, make))
    {
     /*
      * Send this PPD...
      */

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

      count --;

      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.natural_language);

      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 we have only requested the ppd-make attribute, then skip
      * the remaining PPDs with this make...
      */

      if (requested && !strcmp(requested, "ppd-make"))
      {
        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))
	    break;

        i ++;
	ppd --;
      }
    }

  cupsdSendIPPTrailer();

  return (0);
}


/*
 * '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		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 */
  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" },
    { "danish",		"da" },
    { "dutch",		"nl" },
    { "english",	"en" },
    { "finnish",	"fi" },
    { "french",		"fr" },
    { "german",		"de" },
    { "greek",		"el" },
    { "italian",	"it" },
    { "japanese",	"ja" },
    { "norwegian",	"no" },
    { "polish",		"pl" },
    { "portuguese",	"pt" },
    { "russian",	"ru" },
    { "slovak",		"sk" },
    { "spanish",	"es" },
    { "swedish",	"sv" },
    { "turkish",	"tr" }
  };


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

  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 (!load_ppds(filename, name))
      {
        cupsDirClose(dir);
        return (1);
      }

      continue;
    }

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

    if (SortedPPDs > 0)
    {
      strcpy(key.record.name, name);

      ppd = bsearch(&key, PPDs, SortedPPDs, sizeof(ppd_info_t),
                    (int (*)(const void *, const void *))compare_names);

      if (ppd &&
          ppd->record.size == dent->fileinfo.st_size &&
	  ppd->record.mtime == dent->fileinfo.st_mtime)
      {
        ppd->found = 1;
        continue;
      }
    }
    else
      ppd = NULL;

   /*
    * 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))
    {
     /*
      * Nope, close the file and continue...
      */

      cupsFileClose(fp);

      continue;
    }

   /*
    * Now read until we get the NickName field...
    */

    model_name[0]    = '\0';
    nick_name[0]     = '\0';
    manufacturer[0]  = '\0';
    device_id[0]     = '\0';
    lang_encoding[0] = '\0';
    strcpy(lang_version, "en");

    while (cupsFileGets(fp, line, sizeof(line)) != NULL)
    {
      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 (!strncasecmp(line, "*1284DeviceID:", 14))
	sscanf(line, "%*[^\"]\"%255[^\"]", device_id);
      else if (!strncmp(line, "*OpenUI", 7))
      {
       /*
        * Stop early if we have a NickName or ModelName attributes
	* before the first OpenUI...
	*/

        if (model_name[0] || nick_name[0])
	  break;
      }
    }

   /*
    * Close the file...
    */

    cupsFileClose(fp);

   /*
    * 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
      strcpy(make_model, model_name);

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

    if (!make_model[0])
      continue;	/* Nope... */

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

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

    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 if (!strncasecmp(manufacturer, "agfa", 4))
	strcpy(manufacturer, "AGFA");
      else if (!strncasecmp(manufacturer, "herk", 4) ||
               !strncasecmp(manufacturer, "linotype", 8))
	strcpy(manufacturer, "LHAG");
      else
	strcpy(manufacturer, "Other");

     /*
      * Hack for various vendors...
      */

      if (!strcasecmp(manufacturer, "XPrint"))
	strcpy(manufacturer, "Xerox");
      else if (!strcasecmp(manufacturer, "Eastman"))
	strcpy(manufacturer, "Kodak");
      else if (!strcasecmp(manufacturer, "laserwriter"))
	strcpy(manufacturer, "Apple");
      else if (!strcasecmp(manufacturer, "colorpoint"))
	strcpy(manufacturer, "Seiko");
      else if (!strcasecmp(manufacturer, "fiery"))
	strcpy(manufacturer, "EFI");
      else if (!strcasecmp(manufacturer, "ps") ||
               !strcasecmp(manufacturer, "colorpass"))
	strcpy(manufacturer, "Canon");
      else if (!strncasecmp(manufacturer, "primera", 7))
	strcpy(manufacturer, "Fargo");
      else if (!strcasecmp(manufacturer, "designjet"))
	strcpy(manufacturer, "HP");
    }
    else if (!strncasecmp(manufacturer, "LHAG", 4) ||
             !strncasecmp(manufacturer, "linotype", 8))
      strcpy(manufacturer, "LHAG");

   /*
    * 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 (!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"...
      */

      strcpy(lang_version, "xx");
    }

   /*
    * Add the PPD file...
    */

    new_ppd = !ppd;

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

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

      if (!add_ppd(name, lang_version, manufacturer, make_model, device_id,
                   dent->fileinfo.st_mtime, dent->fileinfo.st_size))
      {
        cupsDirClose(dir);
      	return (0);
      }
    }
    else
    {
     /*
      * Update existing record...
      */

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

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

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

      strlcpy(ppd->record.name, name, sizeof(ppd->record.name));
      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.natural_language, lang_version,
              sizeof(ppd->record.natural_language));
      strlcpy(ppd->record.device_id, device_id, sizeof(ppd->record.device_id));
    }

    ChangedPPD = 1;
  }

  cupsDirClose(dir);

  return (1);
}


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

static int				/* O - 1 on success, 0 on failure */
load_drivers(void)
{
  const char	*server_bin;		/* CUPS_SERVERBIN environment variable */
  char		drivers[1024];		/* Location of driver programs */
  FILE		*fp;			/* Pipe to driver program */
  cups_dir_t	*dir;			/* Directory pointer */
  cups_dentry_t *dent;			/* Directory entry */
  char		filename[1024],		/* Name of driver */
		line[2048],		/* Line from driver */
		name[512],		/* ppd-name */
		natural_language[128],	/* ppd-natural-language */
		make[128],		/* ppd-make */
		make_and_model[256],	/* ppd-make-and-model */
		device_id[256];		/* ppd-device-id */


 /*
  * 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...
  */

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

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

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

    snprintf(filename, sizeof(filename), "%s/%s list", drivers, dent->filename);
    if ((fp = popen(filename, "r")) != NULL)
    {
      while (fgets(line, sizeof(line), fp) != NULL)
      {
       /*
        * Each line is of the form:
	*
	*   \"ppd-name\" ppd-natural-language "ppd-make" "ppd-make-and-model"
	*/

        device_id[0] = '\0';

        if (sscanf(line, "\"%511[^\"]\"%127s%*[ \t]\"%127[^\"]\""
	                 "%*[ \t]\"%256[^\"]\"%*[ \t]\"%256[^\"]\"",
	           name, natural_language, make, make_and_model,
		   device_id) < 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 (!add_ppd(name, natural_language, make, make_and_model, device_id,
	               0, 0))
	  {
            cupsDirClose(dir);
	    return (0);
	  }

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

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

  cupsDirClose(dir);

  return (1);
}


/*
 * End of "$Id: cups-driverd.c 6458 2007-04-17 19:22:41Z mike $".
 */
