You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

2917 lines
78 KiB

This file contains invisible Unicode characters!

This file contains invisible Unicode characters that may be processed differently from what appears below. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to reveal hidden characters.

/*
* PPD/driver support for CUPS.
*
* This program handles listing and installing static PPD files, PPD files
* created from driver information files, and dynamically generated PPD files
* using driver helper programs.
*
* Copyright © 2007-2019 by Apple Inc.
* Copyright © 1997-2007 by Easy Software Products.
*
* Licensed under Apache License v2.0. See the file "LICENSE" for more
* information.
*/
/*
* Include necessary headers...
*/
#include "util.h"
#include <cups/dir.h>
#include <cups/transcode.h>
#include <cups/ppd-private.h>
#include <ppdc/ppdc.h>
#include <regex.h>
/*
* Constants...
*/
#define PPD_SYNC 0x50504441 /* Sync word for ppds.dat (PPDA) */
#define PPD_MAX_LANG 32 /* Maximum languages */
#define PPD_MAX_PROD 32 /* Maximum products */
#define PPD_MAX_VERS 32 /* Maximum versions */
#define PPD_TYPE_POSTSCRIPT 0 /* PostScript PPD */
#define PPD_TYPE_PDF 1 /* PDF PPD */
#define PPD_TYPE_RASTER 2 /* CUPS raster PPD */
#define PPD_TYPE_FAX 3 /* Facsimile/MFD PPD */
#define PPD_TYPE_UNKNOWN 4 /* Other/hybrid PPD */
#define PPD_TYPE_DRV 5 /* Driver info file */
#define PPD_TYPE_ARCHIVE 6 /* Archive file */
#define TAR_BLOCK 512 /* Number of bytes in a block */
#define TAR_BLOCKS 10 /* Blocking factor */
#define TAR_MAGIC "ustar" /* 5 chars and a null */
#define TAR_VERSION "00" /* POSIX tar version */
#define TAR_OLDNORMAL '\0' /* Normal disk file, Unix compat */
#define TAR_NORMAL '0' /* Normal disk file */
#define TAR_LINK '1' /* Link to previously dumped file */
#define TAR_SYMLINK '2' /* Symbolic link */
#define TAR_CHR '3' /* Character special file */
#define TAR_BLK '4' /* Block special file */
#define TAR_DIR '5' /* Directory */
#define TAR_FIFO '6' /* FIFO special file */
#define TAR_CONTIG '7' /* Contiguous file */
/*
* PPD information structures...
*/
typedef struct /**** PPD record ****/
{
time_t mtime; /* Modification time */
off_t size; /* Size in bytes */
int model_number; /* cupsModelNumber */
int type; /* ppd-type */
char filename[512], /* Filename */
name[256], /* PPD name */
languages[PPD_MAX_LANG][6],
/* LanguageVersion/cupsLanguages */
products[PPD_MAX_PROD][128],
/* Product strings */
psversions[PPD_MAX_VERS][32],
/* PSVersion strings */
make[128], /* Manufacturer */
make_and_model[128], /* NickName/ModelName */
device_id[256], /* IEEE 1284 Device ID */
scheme[128]; /* PPD scheme */
} ppd_rec_t;
typedef struct /**** In-memory record ****/
{
int found; /* 1 if PPD is found */
int matches; /* Match count */
ppd_rec_t record; /* PPDs.dat record */
} ppd_info_t;
typedef union /**** TAR record format ****/
{
unsigned char all[TAR_BLOCK]; /* Raw data block */
struct
{
char pathname[100], /* Destination path */
mode[8], /* Octal file permissions */
uid[8], /* Octal user ID */
gid[8], /* Octal group ID */
size[12], /* Octal size in bytes */
mtime[12], /* Octal modification time */
chksum[8], /* Octal checksum value */
linkflag, /* File type */
linkname[100], /* Source path for link */
magic[6], /* Magic string */
version[2], /* Format version */
uname[32], /* User name */
gname[32], /* Group name */
devmajor[8], /* Octal device major number */
devminor[8], /* Octal device minor number */
prefix[155]; /* Prefix for long filenames */
} header;
} tar_rec_t;
/*
* Globals...
*/
static cups_array_t *Inodes = NULL, /* Inodes of directories we've visited */
*PPDsByName = NULL,
/* PPD files sorted by filename and name */
*PPDsByMakeModel = NULL;
/* PPD files sorted by make and model */
static int ChangedPPD; /* Did we change the PPD database? */
static const char * const PPDTypes[] = /* ppd-type values */
{
"postscript",
"pdf",
"raster",
"fax",
"object",
"object-direct",
"object-storage",
"unknown",
"drv",
"archive"
};
/*
* Local functions...
*/
static ppd_info_t *add_ppd(const char *filename, const char *name,
const char *language, const char *make,
const char *make_and_model,
const char *device_id, const char *product,
const char *psversion, time_t mtime,
size_t size, int model_number, int type,
const char *scheme);
static int cat_drv(const char *name, int request_id);
static void cat_ppd(const char *name, int request_id);
static int cat_static(const char *name, int request_id);
static int cat_tar(const char *name, int request_id);
static int compare_inodes(struct stat *a, struct stat *b);
static int compare_matches(const ppd_info_t *p0,
const ppd_info_t *p1);
static int compare_names(const ppd_info_t *p0,
const ppd_info_t *p1);
static int compare_ppds(const ppd_info_t *p0,
const ppd_info_t *p1);
static void dump_ppds_dat(const char *filename);
static void free_array(cups_array_t *a);
static cups_file_t *get_file(const char *name, int request_id,
const char *subdir, char *buffer,
size_t bufsize, char **subfile);
static void list_ppds(int request_id, int limit, const char *opt);
static int load_drivers(cups_array_t *include,
cups_array_t *exclude);
static int load_drv(const char *filename, const char *name,
cups_file_t *fp, time_t mtime, off_t size);
static void load_ppd(const char *filename, const char *name,
const char *scheme, struct stat *fileinfo,
ppd_info_t *ppd, cups_file_t *fp, off_t end);
static int load_ppds(const char *d, const char *p, int descend);
static void load_ppds_dat(char *filename, size_t filesize,
int verbose);
static int load_tar(const char *filename, const char *name,
cups_file_t *fp, time_t mtime, off_t size);
static int read_tar(cups_file_t *fp, char *name, size_t namesize,
struct stat *info);
static regex_t *regex_device_id(const char *device_id);
static regex_t *regex_string(const char *s);
/*
* 'main()' - Scan for drivers and return an IPP response.
*
* Usage:
*
* cups-driverd request_id limit options
*/
int /* O - Exit code */
main(int argc, /* I - Number of command-line args */
char *argv[]) /* I - Command-line arguments */
{
/*
* Install or list PPDs...
*/
if (argc == 3 && !strcmp(argv[1], "cat"))
cat_ppd(argv[2], 0);
else if ((argc == 2 || argc == 3) && !strcmp(argv[1], "dump"))
dump_ppds_dat(argv[2]);
else if (argc == 4 && !strcmp(argv[1], "get"))
cat_ppd(argv[3], atoi(argv[2]));
else if (argc == 5 && !strcmp(argv[1], "list"))
list_ppds(atoi(argv[2]), atoi(argv[3]), argv[4]);
else
{
fputs("Usage: cups-driverd cat ppd-name\n", stderr);
fputs("Usage: cups-driverd dump\n", stderr);
fputs("Usage: cups-driverd get request_id ppd-name\n", stderr);
fputs("Usage: cups-driverd list request_id limit options\n", stderr);
return (1);
}
}
/*
* 'add_ppd()' - Add a PPD file.
*/
static ppd_info_t * /* O - PPD */
add_ppd(const char *filename, /* I - PPD filename */
const char *name, /* I - PPD name */
const char *language, /* I - LanguageVersion */
const char *make, /* I - Manufacturer */
const char *make_and_model, /* I - NickName/ModelName */
const char *device_id, /* I - 1284DeviceID */
const char *product, /* I - Product */
const char *psversion, /* I - PSVersion */
time_t mtime, /* I - Modification time */
size_t size, /* I - File size */
int model_number, /* I - Model number */
int type, /* I - Driver type */
const char *scheme) /* I - PPD scheme */
{
ppd_info_t *ppd; /* PPD */
char *recommended; /* Foomatic driver string */
/*
* Add a new PPD file...
*/
if ((ppd = (ppd_info_t *)calloc(1, sizeof(ppd_info_t))) == NULL)
{
fprintf(stderr,
"ERROR: [cups-driverd] Ran out of memory for %d PPD files!\n",
cupsArrayCount(PPDsByName));
return (NULL);
}
/*
* Zero-out the PPD data and copy the values over...
*/
ppd->found = 1;
ppd->record.mtime = mtime;
ppd->record.size = (off_t)size;
ppd->record.model_number = model_number;
ppd->record.type = type;
strlcpy(ppd->record.filename, filename, sizeof(ppd->record.filename));
strlcpy(ppd->record.name, name, sizeof(ppd->record.name));
strlcpy(ppd->record.languages[0], language,
sizeof(ppd->record.languages[0]));
strlcpy(ppd->record.products[0], product, sizeof(ppd->record.products[0]));
strlcpy(ppd->record.psversions[0], psversion,
sizeof(ppd->record.psversions[0]));
strlcpy(ppd->record.make, make, sizeof(ppd->record.make));
strlcpy(ppd->record.make_and_model, make_and_model,
sizeof(ppd->record.make_and_model));
strlcpy(ppd->record.device_id, device_id, sizeof(ppd->record.device_id));
strlcpy(ppd->record.scheme, scheme, sizeof(ppd->record.scheme));
/*
* Strip confusing (and often wrong) "recommended" suffix added by
* Foomatic drivers...
*/
if ((recommended = strstr(ppd->record.make_and_model,
" (recommended)")) != NULL)
*recommended = '\0';
/*
* Add the PPD to the PPD arrays...
*/
cupsArrayAdd(PPDsByName, ppd);
cupsArrayAdd(PPDsByMakeModel, ppd);
/*
* Return the new PPD pointer...
*/
return (ppd);
}
/*
* 'cat_drv()' - Generate a PPD from a driver info file.
*/
static int /* O - Exit code */
cat_drv(const char *name, /* I - PPD name */
int request_id) /* I - Request ID for response? */
{
cups_file_t *fp; // File pointer
ppdcSource *src; // PPD source file data
ppdcDriver *d; // Current driver
cups_file_t *out; // Stdout via CUPS file API
char message[2048], // status-message
filename[1024], // Full path to .drv file(s)
scheme[32], // URI scheme ("drv")
userpass[256], // User/password info (unused)
host[2], // Hostname (unused)
resource[1024], // Resource path (/dir/to/filename.drv)
*pc_file_name; // Filename portion of URI
int port; // Port number (unused)
// Pull out the path to the .drv file...
if (httpSeparateURI(HTTP_URI_CODING_ALL, name, scheme, sizeof(scheme),
userpass, sizeof(userpass), host, sizeof(host), &port,
resource, sizeof(resource)) < HTTP_URI_OK)
{
fprintf(stderr, "ERROR: Bad PPD name \"%s\".\n", name);
if (request_id)
{
snprintf(message, sizeof(message), "Bad PPD name \"%s\".", name);
cupsdSendIPPHeader(IPP_NOT_FOUND, request_id);
cupsdSendIPPGroup(IPP_TAG_OPERATION);
cupsdSendIPPString(IPP_TAG_CHARSET, "attributes-charset", "utf-8");
cupsdSendIPPString(IPP_TAG_LANGUAGE, "attributes-natural-language",
"en-US");
cupsdSendIPPString(IPP_TAG_TEXT, "status-message", message);
cupsdSendIPPTrailer();
}
return (1);
}
if ((fp = get_file(resource, request_id, "drv", filename, sizeof(filename), &pc_file_name)) == NULL || !pc_file_name)
return (1);
src = new ppdcSource(filename, fp);
for (d = (ppdcDriver *)src->drivers->first();
d;
d = (ppdcDriver *)src->drivers->next())
if (!strcmp(pc_file_name, d->pc_file_name->value) ||
(d->file_name && !strcmp(pc_file_name, d->file_name->value)))
break;
if (d)
{
ppdcArray *locales; // Locale names
ppdcCatalog *catalog; // Message catalog in .drv file
fprintf(stderr, "DEBUG2: [cups-driverd] %u locales defined in \"%s\"...\n", (unsigned)src->po_files->count, filename);
locales = new ppdcArray();
for (catalog = (ppdcCatalog *)src->po_files->first();
catalog;
catalog = (ppdcCatalog *)src->po_files->next())
{
fprintf(stderr, "DEBUG2: [cups-driverd] Adding locale \"%s\"...\n",
catalog->locale->value);
catalog->locale->retain();
locales->add(catalog->locale);
}
if (request_id)
{
cupsdSendIPPHeader(IPP_OK, request_id);
cupsdSendIPPGroup(IPP_TAG_OPERATION);
cupsdSendIPPString(IPP_TAG_CHARSET, "attributes-charset", "utf-8");
cupsdSendIPPString(IPP_TAG_LANGUAGE, "attributes-natural-language",
"en-US");
cupsdSendIPPTrailer();
fflush(stdout);
}
out = cupsFileStdout();
d->write_ppd_file(out, NULL, locales, src, PPDC_LFONLY);
cupsFileClose(out);
locales->release();
}
else
{
fprintf(stderr, "ERROR: PPD \"%s\" not found.\n", name);
if (request_id)
{
snprintf(message, sizeof(message), "PPD \"%s\" not found.", name);
cupsdSendIPPHeader(IPP_NOT_FOUND, request_id);
cupsdSendIPPGroup(IPP_TAG_OPERATION);
cupsdSendIPPString(IPP_TAG_CHARSET, "attributes-charset", "utf-8");
cupsdSendIPPString(IPP_TAG_LANGUAGE, "attributes-natural-language",
"en-US");
cupsdSendIPPString(IPP_TAG_TEXT, "status-message", message);
cupsdSendIPPTrailer();
}
}
src->release();
cupsFileClose(fp);
return (!d);
}
/*
* 'cat_ppd()' - Copy a PPD file to stdout.
*/
static void
cat_ppd(const char *name, /* I - PPD name */
int request_id) /* I - Request ID for response? */
{
char scheme[256], /* Scheme from PPD name */
*sptr, /* Pointer into scheme */
line[1024], /* Line/filename */
message[2048]; /* status-message */
/*
* Figure out if this is a static or dynamic PPD file...
*/
if (strstr(name, "../"))
{
fputs("ERROR: Invalid PPD name.\n", stderr);
exit(1);
}
strlcpy(scheme, name, sizeof(scheme));
if ((sptr = strchr(scheme, ':')) != NULL)
{
*sptr = '\0';
if (!strcmp(scheme, "file"))
{
/*
* "file:name" == "name"...
*/
name += 5;
while (*name == '/')
name ++;
if (!strstr(name, ".tar/") && !strstr(name, ".tar.gz/"))
scheme[0] = '\0';
}
}
else
scheme[0] = '\0';
if (request_id > 0)
puts("Content-Type: application/ipp\n");
if (!scheme[0])
exit(cat_static(name, request_id));
else if (!strcmp(scheme, "drv"))
exit(cat_drv(name, request_id));
else if (!strcmp(scheme, "file"))
exit(cat_tar(name, request_id));
else
{
/*
* Dynamic PPD, see if we have a driver program to support it...
*/
const char *serverbin; /* CUPS_SERVERBIN env var */
char *argv[4]; /* Arguments for program */
if ((serverbin = getenv("CUPS_SERVERBIN")) == NULL)
serverbin = CUPS_SERVERBIN;
snprintf(line, sizeof(line), "%s/driver/%s", serverbin, scheme);
if (access(line, X_OK))
{
/*
* File does not exist or is not executable...
*/
fprintf(stderr, "ERROR: [cups-driverd] Unable to access \"%s\" - %s\n",
line, strerror(errno));
if (request_id > 0)
{
snprintf(message, sizeof(message), "Unable to access \"%s\" - %s",
line, strerror(errno));
cupsdSendIPPHeader(IPP_NOT_FOUND, request_id);
cupsdSendIPPGroup(IPP_TAG_OPERATION);
cupsdSendIPPString(IPP_TAG_CHARSET, "attributes-charset", "utf-8");
cupsdSendIPPString(IPP_TAG_LANGUAGE, "attributes-natural-language",
"en-US");
cupsdSendIPPString(IPP_TAG_TEXT, "status-message", message);
cupsdSendIPPTrailer();
}
exit(1);
}
/*
* Yes, let it cat the PPD file...
*/
if (request_id)
{
cupsdSendIPPHeader(IPP_OK, request_id);
cupsdSendIPPGroup(IPP_TAG_OPERATION);
cupsdSendIPPString(IPP_TAG_CHARSET, "attributes-charset", "utf-8");
cupsdSendIPPString(IPP_TAG_LANGUAGE, "attributes-natural-language",
"en-US");
cupsdSendIPPTrailer();
}
argv[0] = scheme;
argv[1] = (char *)"cat";
argv[2] = (char *)name;
argv[3] = NULL;
if (cupsdExec(line, argv))
{
/*
* Unable to execute driver...
*/
fprintf(stderr, "ERROR: [cups-driverd] Unable to execute \"%s\" - %s\n",
line, strerror(errno));
exit(1);
}
}
/*
* Exit with no errors...
*/
exit(0);
}
/*
* 'copy_static()' - Copy a static PPD file to stdout.
*/
static int /* O - Exit code */
cat_static(const char *name, /* I - PPD name */
int request_id) /* I - Request ID for response? */
{
cups_file_t *fp; /* PPD file */
char filename[1024], /* PPD filename */
line[1024]; /* Line buffer */
if ((fp = get_file(name, request_id, "model", filename, sizeof(filename),
NULL)) == NULL)
return (1);
if (request_id)
{
cupsdSendIPPHeader(IPP_OK, request_id);
cupsdSendIPPGroup(IPP_TAG_OPERATION);
cupsdSendIPPString(IPP_TAG_CHARSET, "attributes-charset", "utf-8");
cupsdSendIPPString(IPP_TAG_LANGUAGE, "attributes-natural-language",
"en-US");
cupsdSendIPPTrailer();
}
/*
* Now copy the file to stdout...
*/
while (cupsFileGets(fp, line, sizeof(line)))
puts(line);
cupsFileClose(fp);
return (0);
}
/*
* 'cat_tar()' - Copy an archived PPD file to stdout.
*/
static int /* O - Exit code */
cat_tar(const char *name, /* I - PPD name */
int request_id) /* I - Request ID */
{
cups_file_t *fp; /* Archive file pointer */
char filename[1024], /* Archive filename */
*ppdname, /* PPD filename in archive */
curname[256], /* Current name in archive */
buffer[8192]; /* Copy buffer */
struct stat curinfo; /* Current file info in archive */
off_t total, /* Total bytes copied */
next; /* Offset for next record in archive */
ssize_t bytes; /* Bytes read */
/*
* Open the archive file...
*/
if ((fp = get_file(name, request_id, "model", filename, sizeof(filename),
&ppdname)) == NULL || !ppdname)
return (1);
/*
* Scan the archive for the PPD...
*/
while (read_tar(fp, curname, sizeof(curname), &curinfo))
{
next = cupsFileTell(fp) + ((curinfo.st_size + TAR_BLOCK - 1) &
~(TAR_BLOCK - 1));
if (!strcmp(ppdname, curname))
{
if (request_id)
{
cupsdSendIPPHeader(IPP_OK, request_id);
cupsdSendIPPGroup(IPP_TAG_OPERATION);
cupsdSendIPPString(IPP_TAG_CHARSET, "attributes-charset", "utf-8");
cupsdSendIPPString(IPP_TAG_LANGUAGE, "attributes-natural-language",
"en-US");
cupsdSendIPPTrailer();
}
for (total = 0; total < curinfo.st_size; total += bytes)
{
if ((size_t)(bytes = (curinfo.st_size - total)) > sizeof(buffer))
bytes = sizeof(buffer);
if ((bytes = cupsFileRead(fp, buffer, (size_t)bytes)) < 0)
{
if (errno == EINTR || errno == EAGAIN)
{
bytes = 0;
}
else
{
perror("ERROR: [cups-driverd] Read error");
break;
}
}
else if (bytes > 0 && fwrite(buffer, (size_t)bytes, 1, stdout) != 1)
break;
}
cupsFileClose(fp);
return (0);
}
if (cupsFileTell(fp) != next)
cupsFileSeek(fp, next);
}
cupsFileClose(fp);
fprintf(stderr, "ERROR: PPD \"%s\" not found.\n", name);
if (request_id)
{
snprintf(buffer, sizeof(buffer), "PPD \"%s\" not found.", name);
cupsdSendIPPHeader(IPP_NOT_FOUND, request_id);
cupsdSendIPPGroup(IPP_TAG_OPERATION);
cupsdSendIPPString(IPP_TAG_CHARSET, "attributes-charset", "utf-8");
cupsdSendIPPString(IPP_TAG_LANGUAGE, "attributes-natural-language",
"en-US");
cupsdSendIPPString(IPP_TAG_TEXT, "status-message", buffer);
cupsdSendIPPTrailer();
}
return (1);
}
/*
* 'compare_inodes()' - Compare two inodes.
*/
static int /* O - Result of comparison */
compare_inodes(struct stat *a, /* I - First inode */
struct stat *b) /* I - Second inode */
{
if (a->st_dev != b->st_dev)
return (a->st_dev - b->st_dev);
else
return (a->st_ino - b->st_ino);
}
/*
* 'compare_matches()' - Compare PPD match scores for sorting.
*/
static int
compare_matches(const ppd_info_t *p0, /* I - First PPD */
const ppd_info_t *p1) /* I - Second PPD */
{
if (p1->matches != p0->matches)
return (p1->matches - p0->matches);
else
return (cupsdCompareNames(p0->record.make_and_model,
p1->record.make_and_model));
}
/*
* 'compare_names()' - Compare PPD filenames for sorting.
*/
static int /* O - Result of comparison */
compare_names(const ppd_info_t *p0, /* I - First PPD file */
const ppd_info_t *p1) /* I - Second PPD file */
{
int diff; /* Difference between strings */
if ((diff = strcmp(p0->record.filename, p1->record.filename)) != 0)
return (diff);
else
return (strcmp(p0->record.name, p1->record.name));
}
/*
* 'compare_ppds()' - Compare PPD file make and model names for sorting.
*/
static int /* O - Result of comparison */
compare_ppds(const ppd_info_t *p0, /* I - First PPD file */
const ppd_info_t *p1) /* I - Second PPD file */
{
int diff; /* Difference between strings */
/*
* First compare manufacturers...
*/
if ((diff = _cups_strcasecmp(p0->record.make, p1->record.make)) != 0)
return (diff);
else if ((diff = cupsdCompareNames(p0->record.make_and_model,
p1->record.make_and_model)) != 0)
return (diff);
else if ((diff = strcmp(p0->record.languages[0],
p1->record.languages[0])) != 0)
return (diff);
else
return (compare_names(p0, p1));
}
/*
* 'dump_ppds_dat()' - Dump the contents of the ppds.dat file.
*/
static void
dump_ppds_dat(const char *filename) /* I - Filename */
{
char temp[1024]; /* ppds.dat filename */
ppd_info_t *ppd; /* Current PPD */
/*
* See if we a PPD database file...
*/
if (filename)
strlcpy(temp, filename, sizeof(temp));
else
temp[0] = '\0';
load_ppds_dat(temp, sizeof(temp), 0);
puts("mtime,size,model_number,type,filename,name,languages0,products0,"
"psversions0,make,make_and_model,device_id,scheme");
for (ppd = (ppd_info_t *)cupsArrayFirst(PPDsByName);
ppd;
ppd = (ppd_info_t *)cupsArrayNext(PPDsByName))
printf("%d,%ld,%d,%d,\"%s\",\"%s\",\"%s\",\"%s\",\"%s\",\"%s\",\"%s\","
"\"%s\",\"%s\"\n",
(int)ppd->record.mtime, (long)ppd->record.size,
ppd->record.model_number, ppd->record.type, ppd->record.filename,
ppd->record.name, ppd->record.languages[0], ppd->record.products[0],
ppd->record.psversions[0], ppd->record.make,
ppd->record.make_and_model, ppd->record.device_id,
ppd->record.scheme);
exit(0);
}
/*
* 'free_array()' - Free an array of strings.
*/
static void
free_array(cups_array_t *a) /* I - Array to free */
{
char *ptr; /* Pointer to string */
for (ptr = (char *)cupsArrayFirst(a);
ptr;
ptr = (char *)cupsArrayNext(a))
free(ptr);
cupsArrayDelete(a);
}
/*
* 'get_file()' - Get the filename associated with a request.
*/
static cups_file_t * /* O - File pointer or NULL */
get_file(const char *name, /* I - Name */
int request_id, /* I - Request ID */
const char *subdir, /* I - Subdirectory for file */
char *buffer, /* I - Filename buffer */
size_t bufsize, /* I - Size of filename buffer */
char **subfile) /* O - Sub-filename */
{
cups_file_t *fp; /* File pointer */
const char *datadir; /* CUPS_DATADIR env var */
char *bufptr, /* Pointer into filename buffer */
message[2048]; /* status-message */
#ifdef __APPLE__
const char *printerDriver, /* Pointer to .printerDriver extension */
*slash; /* Pointer to next slash */
#endif /* __APPLE__ */
if (subfile)
*subfile = NULL;
while (*name == '/')
name ++;
if (strstr(name, "../") || strstr(name, "/.."))
{
/*
* Bad name...
*/
fprintf(stderr, "ERROR: [cups-driverd] Bad PPD name \"%s\".\n", name);
if (request_id)
{
snprintf(message, sizeof(message), "Bad PPD name \"%s\".", name);
cupsdSendIPPHeader(IPP_NOT_FOUND, request_id);
cupsdSendIPPGroup(IPP_TAG_OPERATION);
cupsdSendIPPString(IPP_TAG_CHARSET, "attributes-charset", "utf-8");
cupsdSendIPPString(IPP_TAG_LANGUAGE, "attributes-natural-language",
"en-US");
cupsdSendIPPString(IPP_TAG_TEXT, "status-message", message);
cupsdSendIPPTrailer();
}
return (NULL);
}
/*
* Try opening the file...
*/
#ifdef __APPLE__
if (!strncmp(name, "System/Library/Printers/PPDs/Contents/Resources/", 48) ||
!strncmp(name, "Library/Printers/PPDs/Contents/Resources/", 41) ||
(!strncmp(name, "System/Library/Printers/", 24) &&
(printerDriver =
strstr(name + 24,
".printerDriver/Contents/Resources/PPDs")) != NULL &&
(slash = strchr(name + 24, '/')) != NULL &&
slash > printerDriver) ||
(!strncmp(name, "Library/Printers/", 17) &&
(printerDriver =
strstr(name + 17,
".printerDriver/Contents/Resources/PPDs")) != NULL &&
(slash = strchr(name + 17, '/')) != NULL &&
slash > printerDriver))
{
/*
* Map ppd-name to macOS standard locations...
*/
snprintf(buffer, bufsize, "/%s", name);
}
else
#elif defined(__linux)
if (!strncmp(name, "lsb/usr/", 8))
{
/*
* Map ppd-name to LSB standard /usr/share/ppd location...
*/
snprintf(buffer, bufsize, "/usr/share/ppd/%s", name + 8);
}
else if (!strncmp(name, "lsb/opt/", 8))
{
/*
* Map ppd-name to LSB standard /opt/share/ppd location...
*/
snprintf(buffer, bufsize, "/opt/share/ppd/%s", name + 8);
}
else if (!strncmp(name, "lsb/local/", 10))
{
/*
* Map ppd-name to LSB standard /usr/local/share/ppd location...
*/
snprintf(buffer, bufsize, "/usr/local/share/ppd/%s", name + 10);
}
else
#endif /* __APPLE__ */
{
if ((datadir = getenv("CUPS_DATADIR")) == NULL)
datadir = CUPS_DATADIR;
snprintf(buffer, bufsize, "%s/%s/%s", datadir, subdir, name);
}
/*
* Strip anything after ".drv/", ".drv.gz/", ".tar/", or ".tar.gz/"...
*/
if (subfile)
{
if ((bufptr = strstr(buffer, ".drv/")) != NULL)
bufptr += 4;
else if ((bufptr = strstr(buffer, ".drv.gz/")) != NULL)
bufptr += 7;
else if ((bufptr = strstr(buffer, ".tar/")) != NULL)
bufptr += 4;
else if ((bufptr = strstr(buffer, ".tar.gz/")) != NULL)
bufptr += 7;
if (bufptr)
{
*bufptr++ = '\0';
*subfile = bufptr;
}
}
/*
* Try opening the file...
*/
if ((fp = cupsFileOpen(buffer, "r")) == NULL)
{
fprintf(stderr, "ERROR: [cups-driverd] Unable to open \"%s\" - %s\n",
buffer, strerror(errno));
if (request_id)
{
snprintf(message, sizeof(message), "Unable to open \"%s\" - %s",
buffer, strerror(errno));
cupsdSendIPPHeader(IPP_NOT_FOUND, request_id);
cupsdSendIPPGroup(IPP_TAG_OPERATION);
cupsdSendIPPString(IPP_TAG_CHARSET, "attributes-charset", "utf-8");
cupsdSendIPPString(IPP_TAG_LANGUAGE, "attributes-natural-language",
"en-US");
cupsdSendIPPString(IPP_TAG_TEXT, "status-message", message);
cupsdSendIPPTrailer();
}
return (NULL);
}
return (fp);
}
/*
* 'list_ppds()' - List PPD files.
*/
static void
list_ppds(int request_id, /* I - Request ID */
int limit, /* I - Limit */
const char *opt) /* I - Option argument */
{
int i; /* Looping vars */
int count; /* Number of PPDs to send */
ppd_info_t *ppd; /* Current PPD file */
cups_file_t *fp; /* ppds.dat file */
char filename[1024], /* ppds.dat filename */
model[1024]; /* Model directory */
const char *cups_datadir; /* CUPS_DATADIR environment variable */
int num_options; /* Number of options */
cups_option_t *options; /* Options */
cups_array_t *requested, /* requested-attributes values */
*include, /* PPD schemes to include */
*exclude; /* PPD schemes to exclude */
const char *device_id, /* ppd-device-id option */
*language, /* ppd-natural-language option */
*make, /* ppd-make option */
*make_and_model, /* ppd-make-and-model option */
*model_number_str, /* ppd-model-number option */
*product, /* ppd-product option */
*psversion, /* ppd-psversion option */
*type_str; /* ppd-type option */
int model_number, /* ppd-model-number value */
type, /* ppd-type value */
send_device_id, /* Send ppd-device-id? */
send_make, /* Send ppd-make? */
send_make_and_model, /* Send ppd-make-and-model? */
send_model_number, /* Send ppd-model-number? */
send_name, /* Send ppd-name? */
send_natural_language, /* Send ppd-natural-language? */
send_product, /* Send ppd-product? */
send_psversion, /* Send ppd-psversion? */
send_type, /* Send ppd-type? */
sent_header; /* Sent the IPP header? */
size_t make_and_model_len, /* Length of ppd-make-and-model */
product_len; /* Length of ppd-product */
regex_t *device_id_re, /* Regular expression for matching device ID */
*make_and_model_re; /* Regular expression for matching make and model */
regmatch_t re_matches[6]; /* Regular expression matches */
cups_array_t *matches; /* Matching PPDs */
fprintf(stderr,
"DEBUG2: [cups-driverd] list_ppds(request_id=%d, limit=%d, "
"opt=\"%s\"\n", request_id, limit, opt);
/*
* See if we a PPD database file...
*/
filename[0] = '\0';
load_ppds_dat(filename, sizeof(filename), 1);
/*
* Load all PPDs in the specified directory and below...
*/
if ((cups_datadir = getenv("CUPS_DATADIR")) == NULL)
cups_datadir = CUPS_DATADIR;
Inodes = cupsArrayNew((cups_array_func_t)compare_inodes, NULL);
snprintf(model, sizeof(model), "%s/model", cups_datadir);
load_ppds(model, "", 1);
snprintf(model, sizeof(model), "%s/drv", cups_datadir);
load_ppds(model, "", 1);
#ifdef __APPLE__
/*
* Load PPDs from standard macOS locations...
*/
load_ppds("/Library/Printers",
"Library/Printers", 0);
load_ppds("/Library/Printers/PPDs/Contents/Resources",
"Library/Printers/PPDs/Contents/Resources", 0);
load_ppds("/Library/Printers/PPDs/Contents/Resources/en.lproj",
"Library/Printers/PPDs/Contents/Resources/en.lproj", 0);
load_ppds("/System/Library/Printers",
"System/Library/Printers", 0);
load_ppds("/System/Library/Printers/PPDs/Contents/Resources",
"System/Library/Printers/PPDs/Contents/Resources", 0);
load_ppds("/System/Library/Printers/PPDs/Contents/Resources/en.lproj",
"System/Library/Printers/PPDs/Contents/Resources/en.lproj", 0);
#elif defined(__linux)
/*
* Load PPDs from LSB-defined locations...
*/
if (!access("/usr/local/share/ppd", 0))
load_ppds("/usr/local/share/ppd", "lsb/local", 1);
if (!access("/usr/share/ppd", 0))
load_ppds("/usr/share/ppd", "lsb/usr", 1);
if (!access("/opt/share/ppd", 0))
load_ppds("/opt/share/ppd", "lsb/opt", 1);
#endif /* __APPLE__ */
/*
* Cull PPD files that are no longer present...
*/
for (ppd = (ppd_info_t *)cupsArrayFirst(PPDsByName);
ppd;
ppd = (ppd_info_t *)cupsArrayNext(PPDsByName))
if (!ppd->found)
{
/*
* Remove this PPD file from the list...
*/
cupsArrayRemove(PPDsByName, ppd);
cupsArrayRemove(PPDsByMakeModel, ppd);
free(ppd);
ChangedPPD = 1;
}
/*
* Write the new ppds.dat file...
*/
fprintf(stderr, "DEBUG: [cups-driverd] ChangedPPD=%d\n", ChangedPPD);
if (ChangedPPD)
{
char newname[1024]; /* New filename */
snprintf(newname, sizeof(newname), "%s.%d", filename, (int)getpid());
if ((fp = cupsFileOpen(newname, "w")) != NULL)
{
unsigned ppdsync = PPD_SYNC; /* Sync word */
cupsFileWrite(fp, (char *)&ppdsync, sizeof(ppdsync));
for (ppd = (ppd_info_t *)cupsArrayFirst(PPDsByName);
ppd;
ppd = (ppd_info_t *)cupsArrayNext(PPDsByName))
cupsFileWrite(fp, (char *)&(ppd->record), sizeof(ppd_rec_t));
cupsFileClose(fp);
if (rename(newname, filename))
fprintf(stderr, "ERROR: [cups-driverd] Unable to rename \"%s\" - %s\n",
newname, strerror(errno));
else
fprintf(stderr, "INFO: [cups-driverd] Wrote \"%s\", %d PPDs...\n",
filename, cupsArrayCount(PPDsByName));
}
else
fprintf(stderr, "ERROR: [cups-driverd] Unable to write \"%s\" - %s\n",
filename, strerror(errno));
}
else
fputs("INFO: [cups-driverd] No new or changed PPDs...\n", stderr);
/*
* Scan for dynamic PPD files...
*/
num_options = cupsParseOptions(opt, 0, &options);
exclude = cupsdCreateStringsArray(cupsGetOption("exclude-schemes",
num_options, options));
include = cupsdCreateStringsArray(cupsGetOption("include-schemes",
num_options, options));
load_drivers(include, exclude);
/*
* Add the raw driver...
*/
add_ppd("", "raw", "en", "Raw", "Raw Queue", "", "", "", 0, 0, 0,
PPD_TYPE_UNKNOWN, "raw");
/*
* Send IPP attributes...
*/
requested = cupsdCreateStringsArray(
cupsGetOption("requested-attributes", num_options,
options));
device_id = cupsGetOption("ppd-device-id", num_options, options);
language = cupsGetOption("ppd-natural-language", num_options, options);
make = cupsGetOption("ppd-make", num_options, options);
make_and_model = cupsGetOption("ppd-make-and-model", num_options, options);
model_number_str = cupsGetOption("ppd-model-number", num_options, options);
product = cupsGetOption("ppd-product", num_options, options);
psversion = cupsGetOption("ppd-psversion", num_options, options);
type_str = cupsGetOption("ppd-type", num_options, options);
if (make_and_model)
make_and_model_len = strlen(make_and_model);
else
make_and_model_len = 0;
if (product)
product_len = strlen(product);
else
product_len = 0;
if (model_number_str)
model_number = atoi(model_number_str);
else
model_number = 0;
if (type_str)
{
for (type = 0;
type < (int)(sizeof(PPDTypes) / sizeof(PPDTypes[0]));
type ++)
if (!strcmp(type_str, PPDTypes[type]))
break;
if (type >= (int)(sizeof(PPDTypes) / sizeof(PPDTypes[0])))
{
fprintf(stderr, "ERROR: [cups-driverd] Bad ppd-type=\"%s\" ignored!\n",
type_str);
type_str = NULL;
}
}
else
type = 0;
for (i = 0; i < num_options; i ++)
fprintf(stderr, "DEBUG2: [cups-driverd] %s=\"%s\"\n", options[i].name,
options[i].value);
if (!requested || cupsArrayFind(requested, (void *)"all") != NULL)
{
send_name = 1;
send_make = 1;
send_make_and_model = 1;
send_model_number = 1;
send_natural_language = 1;
send_device_id = 1;
send_product = 1;
send_psversion = 1;
send_type = 1;
}
else
{
send_name = cupsArrayFind(requested,
(void *)"ppd-name") != NULL;
send_make = cupsArrayFind(requested,
(void *)"ppd-make") != NULL;
send_make_and_model = cupsArrayFind(requested,
(void *)"ppd-make-and-model") != NULL;
send_model_number = cupsArrayFind(requested,
(void *)"ppd-model-number") != NULL;
send_natural_language = cupsArrayFind(requested,
(void *)"ppd-natural-language") != NULL;
send_device_id = cupsArrayFind(requested,
(void *)"ppd-device-id") != NULL;
send_product = cupsArrayFind(requested,
(void *)"ppd-product") != NULL;
send_psversion = cupsArrayFind(requested,
(void *)"ppd-psversion") != NULL;
send_type = cupsArrayFind(requested,
(void *)"ppd-type") != NULL;
}
/*
* Send the content type header to the scheduler; request_id can only be
* 0 when run manually since the scheduler enforces the IPP requirement for
* a request ID from 1 to 2^31-1...
*/
if (request_id > 0)
puts("Content-Type: application/ipp\n");
sent_header = 0;
if (limit <= 0 || limit > cupsArrayCount(PPDsByMakeModel))
count = cupsArrayCount(PPDsByMakeModel);
else
count = limit;
if (device_id || language || make || make_and_model || model_number_str ||
product)
{
matches = cupsArrayNew((cups_array_func_t)compare_matches, NULL);
if (device_id)
device_id_re = regex_device_id(device_id);
else
device_id_re = NULL;
if (make_and_model)
make_and_model_re = regex_string(make_and_model);
else
make_and_model_re = NULL;
for (ppd = (ppd_info_t *)cupsArrayFirst(PPDsByMakeModel);
ppd;
ppd = (ppd_info_t *)cupsArrayNext(PPDsByMakeModel))
{
/*
* Filter PPDs based on make, model, product, language, model number,
* and/or device ID using the "matches" score value. An exact match
* for product, make-and-model, or device-id adds 3 to the score.
* Partial matches for make-and-model yield 1 or 2 points, and matches
* for the make and language add a single point. Results are then sorted
* by score, highest score first.
*/
if (ppd->record.type < PPD_TYPE_POSTSCRIPT ||
ppd->record.type >= PPD_TYPE_DRV)
continue;
if (cupsArrayFind(exclude, ppd->record.scheme) ||
(include && !cupsArrayFind(include, ppd->record.scheme)))
continue;
ppd->matches = 0;
if (device_id_re &&
!regexec(device_id_re, ppd->record.device_id,
(size_t)(sizeof(re_matches) / sizeof(re_matches[0])),
re_matches, 0))
{
/*
* Add the number of matching values from the device ID - it will be
* at least 2 (manufacturer and model), and as much as 3 (command set).
*/
for (i = 1; i < (int)(sizeof(re_matches) / sizeof(re_matches[0])); i ++)
if (re_matches[i].rm_so >= 0)
ppd->matches ++;
}
if (language)
{
for (i = 0; i < PPD_MAX_LANG; i ++)
if (!ppd->record.languages[i][0])
break;
else if (!strcmp(ppd->record.languages[i], language))
{
ppd->matches ++;
break;
}
}
if (make && !_cups_strcasecmp(ppd->record.make, make))
ppd->matches ++;
if (make_and_model_re &&
!regexec(make_and_model_re, ppd->record.make_and_model,
(size_t)(sizeof(re_matches) / sizeof(re_matches[0])),
re_matches, 0))
{
// See how much of the make-and-model string we matched...
if (re_matches[0].rm_so == 0)
{
if ((size_t)re_matches[0].rm_eo == make_and_model_len)
ppd->matches += 3; // Exact match
else
ppd->matches += 2; // Prefix match
}
else
ppd->matches ++; // Infix match
}
if (model_number_str && ppd->record.model_number == model_number)
ppd->matches ++;
if (product)
{
for (i = 0; i < PPD_MAX_PROD; i ++)
if (!ppd->record.products[i][0])
break;
else if (!_cups_strcasecmp(ppd->record.products[i], product))
{
ppd->matches += 3;
break;
}
else if (!_cups_strncasecmp(ppd->record.products[i], product,
product_len))
{
ppd->matches += 2;
break;
}
}
if (psversion)
{
for (i = 0; i < PPD_MAX_VERS; i ++)
if (!ppd->record.psversions[i][0])
break;
else if (!_cups_strcasecmp(ppd->record.psversions[i], psversion))
{
ppd->matches ++;
break;
}
}
if (type_str && ppd->record.type == type)
ppd->matches ++;
if (ppd->matches)
{
fprintf(stderr, "DEBUG2: [cups-driverd] %s matches with score %d!\n",
ppd->record.name, ppd->matches);
cupsArrayAdd(matches, ppd);
}
}
}
else if (include || exclude)
{
matches = cupsArrayNew((cups_array_func_t)compare_ppds, NULL);
for (ppd = (ppd_info_t *)cupsArrayFirst(PPDsByMakeModel);
ppd;
ppd = (ppd_info_t *)cupsArrayNext(PPDsByMakeModel))
{
/*
* Filter PPDs based on the include/exclude lists.
*/
if (ppd->record.type < PPD_TYPE_POSTSCRIPT ||
ppd->record.type >= PPD_TYPE_DRV)
continue;
if (cupsArrayFind(exclude, ppd->record.scheme) ||
(include && !cupsArrayFind(include, ppd->record.scheme)))
continue;
cupsArrayAdd(matches, ppd);
}
}
else
matches = PPDsByMakeModel;
for (ppd = (ppd_info_t *)cupsArrayFirst(matches);
count > 0 && ppd;
ppd = (ppd_info_t *)cupsArrayNext(matches))
{
/*
* Skip invalid PPDs...
*/
if (ppd->record.type < PPD_TYPE_POSTSCRIPT ||
ppd->record.type >= PPD_TYPE_DRV)
continue;
/*
* Send this PPD...
*/
if (!sent_header)
{
sent_header = 1;
if (request_id)
{
cupsdSendIPPHeader(IPP_OK, request_id);
cupsdSendIPPGroup(IPP_TAG_OPERATION);
cupsdSendIPPString(IPP_TAG_CHARSET, "attributes-charset", "utf-8");
cupsdSendIPPString(IPP_TAG_LANGUAGE, "attributes-natural-language",
"en-US");
}
}
fprintf(stderr, "DEBUG2: [cups-driverd] Sending %s (%s)...\n",
ppd->record.name, ppd->record.make_and_model);
count --;
if (request_id)
{
cupsdSendIPPGroup(IPP_TAG_PRINTER);
if (send_name)
cupsdSendIPPString(IPP_TAG_NAME, "ppd-name", ppd->record.name);
if (send_natural_language)
{
cupsdSendIPPString(IPP_TAG_LANGUAGE, "ppd-natural-language",
ppd->record.languages[0]);
for (i = 1; i < PPD_MAX_LANG && ppd->record.languages[i][0]; i ++)
cupsdSendIPPString(IPP_TAG_LANGUAGE, "", ppd->record.languages[i]);
}
if (send_make)
cupsdSendIPPString(IPP_TAG_TEXT, "ppd-make", ppd->record.make);
if (send_make_and_model)
cupsdSendIPPString(IPP_TAG_TEXT, "ppd-make-and-model",
ppd->record.make_and_model);
if (send_device_id)
cupsdSendIPPString(IPP_TAG_TEXT, "ppd-device-id",
ppd->record.device_id);
if (send_product)
{
cupsdSendIPPString(IPP_TAG_TEXT, "ppd-product",
ppd->record.products[0]);
for (i = 1; i < PPD_MAX_PROD && ppd->record.products[i][0]; i ++)
cupsdSendIPPString(IPP_TAG_TEXT, "", ppd->record.products[i]);
}
if (send_psversion)
{
cupsdSendIPPString(IPP_TAG_TEXT, "ppd-psversion",
ppd->record.psversions[0]);
for (i = 1; i < PPD_MAX_VERS && ppd->record.psversions[i][0]; i ++)
cupsdSendIPPString(IPP_TAG_TEXT, "", ppd->record.psversions[i]);
}
if (send_type)
{
if (ppd->record.type < PPD_TYPE_POSTSCRIPT || ppd->record.type > PPD_TYPE_ARCHIVE)
{
/*
* This cache file is corrupted, remove it!
*/
unlink(filename);
cupsdSendIPPString(IPP_TAG_KEYWORD, "ppd-type", PPDTypes[PPD_TYPE_UNKNOWN]);
}
else
cupsdSendIPPString(IPP_TAG_KEYWORD, "ppd-type", PPDTypes[ppd->record.type]);
}
if (send_model_number)
cupsdSendIPPInteger(IPP_TAG_INTEGER, "ppd-model-number",
ppd->record.model_number);
}
else
printf("%s (%s)\n", ppd->record.name, ppd->record.make_and_model);
/*
* If we have only requested the ppd-make attribute, then skip
* the remaining PPDs with this make...
*/
if (cupsArrayFind(requested, (void *)"ppd-make") &&
cupsArrayCount(requested) == 1)
{
const char *this_make; /* This ppd-make */
for (this_make = ppd->record.make,
ppd = (ppd_info_t *)cupsArrayNext(matches);
ppd;
ppd = (ppd_info_t *)cupsArrayNext(matches))
if (_cups_strcasecmp(this_make, ppd->record.make))
break;
cupsArrayPrev(matches);
}
}
if (!sent_header && request_id)
{
cupsdSendIPPHeader(IPP_NOT_FOUND, request_id);
cupsdSendIPPGroup(IPP_TAG_OPERATION);
cupsdSendIPPString(IPP_TAG_CHARSET, "attributes-charset", "utf-8");
cupsdSendIPPString(IPP_TAG_LANGUAGE, "attributes-natural-language", "en-US");
}
if (request_id)
cupsdSendIPPTrailer();
exit(0);
}
/*
* 'load_drv()' - Load the PPDs from a driver information file.
*/
static int /* O - 1 on success, 0 on failure */
load_drv(const char *filename, /* I - Actual filename */
const char *name, /* I - Name to the rest of the world */
cups_file_t *fp, /* I - File to read from */
time_t mtime, /* I - Mod time of driver info file */
off_t size) /* I - Size of driver info file */
{
ppdcSource *src; // Driver information file
ppdcDriver *d; // Current driver
ppdcAttr *device_id, // 1284DeviceID attribute
*product, // Current product value
*ps_version, // PSVersion attribute
*cups_fax, // cupsFax attribute
*nick_name; // NickName attribute
ppdcFilter *filter; // Current filter
ppd_info_t *ppd; // Current PPD
int products_found; // Number of products found
char uri[1024], // Driver URI
make_model[1024]; // Make and model
int type; // Driver type
/*
* Load the driver info file...
*/
src = new ppdcSource(filename, fp);
if (src->drivers->count == 0)
{
fprintf(stderr,
"ERROR: [cups-driverd] Bad driver information file \"%s\"!\n",
filename);
src->release();
return (0);
}
/*
* Add a dummy entry for the file...
*/
add_ppd(name, name, "", "", "", "", "", "", mtime, (size_t)size, 0, PPD_TYPE_DRV, "drv");
ChangedPPD = 1;
/*
* Then the drivers in the file...
*/
for (d = (ppdcDriver *)src->drivers->first();
d;
d = (ppdcDriver *)src->drivers->next())
{
httpAssembleURIf(HTTP_URI_CODING_ALL, uri, sizeof(uri), "drv", "", "", 0,
"/%s/%s", name,
d->file_name ? d->file_name->value :
d->pc_file_name->value);
device_id = d->find_attr("1284DeviceID", NULL);
ps_version = d->find_attr("PSVersion", NULL);
nick_name = d->find_attr("NickName", NULL);
if (nick_name)
strlcpy(make_model, nick_name->value->value, sizeof(make_model));
else if (_cups_strncasecmp(d->model_name->value, d->manufacturer->value,
strlen(d->manufacturer->value)))
snprintf(make_model, sizeof(make_model), "%s %s, %s",
d->manufacturer->value, d->model_name->value,
d->version->value);
else
snprintf(make_model, sizeof(make_model), "%s, %s", d->model_name->value,
d->version->value);
if ((cups_fax = d->find_attr("cupsFax", NULL)) != NULL &&
!_cups_strcasecmp(cups_fax->value->value, "true"))
type = PPD_TYPE_FAX;
else if (d->type == PPDC_DRIVER_PS)
type = PPD_TYPE_POSTSCRIPT;
else if (d->type != PPDC_DRIVER_CUSTOM)
type = PPD_TYPE_RASTER;
else
{
for (filter = (ppdcFilter *)d->filters->first(),
type = PPD_TYPE_POSTSCRIPT;
filter;
filter = (ppdcFilter *)d->filters->next())
if (_cups_strcasecmp(filter->mime_type->value, "application/vnd.cups-raster"))
type = PPD_TYPE_RASTER;
else if (_cups_strcasecmp(filter->mime_type->value,
"application/vnd.cups-pdf"))
type = PPD_TYPE_PDF;
}
for (product = (ppdcAttr *)d->attrs->first(), products_found = 0,
ppd = NULL;
product;
product = (ppdcAttr *)d->attrs->next())
if (!strcmp(product->name->value, "Product"))
{
if (!products_found)
ppd = add_ppd(name, uri, "en", d->manufacturer->value, make_model, device_id ? device_id->value->value : "", product->value->value,
ps_version ? ps_version->value->value : "(3010) 0", mtime, (size_t)size, d->model_number, type, "drv");
else if (products_found < PPD_MAX_PROD)
strlcpy(ppd->record.products[products_found], product->value->value, sizeof(ppd->record.products[0]));
else
break;
products_found ++;
}
if (!products_found)
add_ppd(name, uri, "en", d->manufacturer->value, make_model, device_id ? device_id->value->value : "", d->model_name->value, ps_version ? ps_version->value->value : "(3010) 0", mtime, (size_t)size, d->model_number, type, "drv");
}
src->release();
return (1);
}
/*
* 'load_drivers()' - Load driver-generated PPD files.
*/
static int /* O - 1 on success, 0 on failure */
load_drivers(cups_array_t *include, /* I - Drivers to include */
cups_array_t *exclude) /* I - Drivers to exclude */
{
int i; /* Looping var */
char *start, /* Start of value */
*ptr; /* Pointer into string */
const char *server_bin, /* CUPS_SERVERBIN env variable */
*scheme, /* Scheme for this driver */
*scheme_end; /* Pointer to end of scheme */
char drivers[1024]; /* Location of driver programs */
int pid; /* Process ID for driver program */
cups_file_t *fp; /* Pipe to driver program */
cups_dir_t *dir; /* Directory pointer */
cups_dentry_t *dent; /* Directory entry */
char *argv[3], /* Arguments for command */
filename[1024], /* Name of driver */
line[2048], /* Line from driver */
name[256], /* ppd-name */
make[128], /* ppd-make */
make_and_model[128], /* ppd-make-and-model */
device_id[256], /* ppd-device-id */
languages[128], /* ppd-natural-language */
product[128], /* ppd-product */
psversion[128], /* ppd-psversion */
type_str[128]; /* ppd-type */
int type; /* PPD type */
ppd_info_t *ppd; /* Newly added PPD */
/*
* Try opening the driver directory...
*/
if ((server_bin = getenv("CUPS_SERVERBIN")) == NULL)
server_bin = CUPS_SERVERBIN;
snprintf(drivers, sizeof(drivers), "%s/driver", server_bin);
if ((dir = cupsDirOpen(drivers)) == NULL)
{
fprintf(stderr, "ERROR: [cups-driverd] Unable to open driver directory "
"\"%s\": %s\n",
drivers, strerror(errno));
return (0);
}
/*
* Loop through all of the device drivers...
*/
argv[1] = (char *)"list";
argv[2] = NULL;
while ((dent = cupsDirRead(dir)) != NULL)
{
/*
* Only look at executable files...
*/
if (!(dent->fileinfo.st_mode & 0111) || !S_ISREG(dent->fileinfo.st_mode))
continue;
/*
* Include/exclude specific drivers...
*/
if (exclude)
{
/*
* Look for "scheme" or "scheme*" (prefix match), and skip any matches.
*/
for (scheme = (char *)cupsArrayFirst(exclude);
scheme;
scheme = (char *)cupsArrayNext(exclude))
{
fprintf(stderr, "DEBUG: [cups-driverd] Exclude \"%s\" with \"%s\"?\n",
dent->filename, scheme);
scheme_end = scheme + strlen(scheme) - 1;
if ((scheme_end > scheme && *scheme_end == '*' &&
!strncmp(scheme, dent->filename, (size_t)(scheme_end - scheme))) ||
!strcmp(scheme, dent->filename))
{
fputs("DEBUG: [cups-driverd] Yes, exclude!\n", stderr);
break;
}
}
if (scheme)
continue;
}
if (include)
{
/*
* Look for "scheme" or "scheme*" (prefix match), and skip any non-matches.
*/
for (scheme = (char *)cupsArrayFirst(include);
scheme;
scheme = (char *)cupsArrayNext(include))
{
fprintf(stderr, "DEBUG: [cups-driverd] Include \"%s\" with \"%s\"?\n",
dent->filename, scheme);
scheme_end = scheme + strlen(scheme) - 1;
if ((scheme_end > scheme && *scheme_end == '*' &&
!strncmp(scheme, dent->filename, (size_t)(scheme_end - scheme))) ||
!strcmp(scheme, dent->filename))
{
fputs("DEBUG: [cups-driverd] Yes, include!\n", stderr);
break;
}
}
if (!scheme)
continue;
}
else
scheme = dent->filename;
/*
* Run the driver with no arguments and collect the output...
*/
snprintf(filename, sizeof(filename), "%s/%s", drivers, dent->filename);
if (_cupsFileCheck(filename, _CUPS_FILE_CHECK_PROGRAM, !geteuid(),
_cupsFileCheckFilter, NULL))
continue;
argv[0] = dent->filename;
if ((fp = cupsdPipeCommand(&pid, filename, argv, 0)) != NULL)
{
while (cupsFileGets(fp, line, sizeof(line)))
{
/*
* Each line is of the form:
*
* "ppd-name" ppd-natural-language "ppd-make" "ppd-make-and-model" \
* "ppd-device-id" "ppd-product" "ppd-psversion"
*/
device_id[0] = '\0';
product[0] = '\0';
psversion[0] = '\0';
strlcpy(type_str, "postscript", sizeof(type_str));
if (sscanf(line, "\"%255[^\"]\"%127s%*[ \t]\"%127[^\"]\""
"%*[ \t]\"%127[^\"]\"%*[ \t]\"%255[^\"]\""
"%*[ \t]\"%127[^\"]\"%*[ \t]\"%127[^\"]\""
"%*[ \t]\"%127[^\"]\"",
name, languages, make, make_and_model,
device_id, product, psversion, type_str) < 4)
{
/*
* Bad format; strip trailing newline and write an error message.
*/
if (line[strlen(line) - 1] == '\n')
line[strlen(line) - 1] = '\0';
fprintf(stderr, "ERROR: [cups-driverd] Bad line from \"%s\": %s\n",
dent->filename, line);
break;
}
else
{
/*
* Add the device to the array of available devices...
*/
if ((start = strchr(languages, ',')) != NULL)
*start++ = '\0';
for (type = 0;
type < (int)(sizeof(PPDTypes) / sizeof(PPDTypes[0]));
type ++)
if (!strcmp(type_str, PPDTypes[type]))
break;
if (type >= (int)(sizeof(PPDTypes) / sizeof(PPDTypes[0])))
{
fprintf(stderr,
"ERROR: [cups-driverd] Bad ppd-type \"%s\" ignored!\n",
type_str);
type = PPD_TYPE_UNKNOWN;
}
ppd = add_ppd(filename, name, languages, make, make_and_model,
device_id, product, psversion, 0, 0, 0, type, scheme);
if (!ppd)
{
cupsDirClose(dir);
cupsFileClose(fp);
return (0);
}
if (start && *start)
{
for (i = 1; i < PPD_MAX_LANG && *start; i ++)
{
if ((ptr = strchr(start, ',')) != NULL)
*ptr++ = '\0';
else
ptr = start + strlen(start);
strlcpy(ppd->record.languages[i], start,
sizeof(ppd->record.languages[0]));
start = ptr;
}
}
fprintf(stderr, "DEBUG2: [cups-driverd] Added dynamic PPD \"%s\"...\n",
name);
}
}
cupsFileClose(fp);
}
else
fprintf(stderr, "WARNING: [cups-driverd] Unable to execute \"%s\": %s\n",
filename, strerror(errno));
}
cupsDirClose(dir);
return (1);
}
/*
* 'load_ppd()' - Load a PPD file.
*/
static void
load_ppd(const char *filename, /* I - Real filename */
const char *name, /* I - Virtual filename */
const char *scheme, /* I - PPD scheme */
struct stat *fileinfo, /* I - File information */
ppd_info_t *ppd, /* I - Existing PPD file or NULL */
cups_file_t *fp, /* I - File to read from */
off_t end) /* I - End of file position or 0 */
{
int i; /* Looping var */
char line[256], /* Line from file */
*ptr, /* Pointer into line */
lang_version[64], /* PPD LanguageVersion */
lang_encoding[64], /* PPD LanguageEncoding */
country[64], /* Country code */
manufacturer[256], /* Manufacturer */
make_model[256], /* Make and Model */
model_name[256], /* ModelName */
nick_name[256], /* NickName */
device_id[256], /* 1284DeviceID */
product[256], /* Product */
psversion[256], /* PSVersion */
temp[512]; /* Temporary make and model */
int install_group, /* In the installable options group? */
model_number, /* cupsModelNumber */
type; /* ppd-type */
cups_array_t *products, /* Product array */
*psversions, /* PSVersion array */
*cups_languages; /* cupsLanguages array */
int new_ppd; /* Is this a new PPD? */
struct /* LanguageVersion translation table */
{
const char *version, /* LanguageVersion string */
*language; /* Language code */
} languages[] =
{
{ "chinese", "zh" },
{ "czech", "cs" },
{ "danish", "da" },
{ "dutch", "nl" },
{ "english", "en" },
{ "finnish", "fi" },
{ "french", "fr" },
{ "german", "de" },
{ "greek", "el" },
{ "hungarian", "hu" },
{ "italian", "it" },
{ "japanese", "ja" },
{ "korean", "ko" },
{ "norwegian", "no" },
{ "polish", "pl" },
{ "portuguese", "pt" },
{ "russian", "ru" },
{ "simplified chinese", "zh_CN" },
{ "slovak", "sk" },
{ "spanish", "es" },
{ "swedish", "sv" },
{ "traditional chinese", "zh_TW" },
{ "turkish", "tr" }
};
/*
* Now read until we get the required fields...
*/
cups_languages = cupsArrayNew(NULL, NULL);
products = cupsArrayNew(NULL, NULL);
psversions = cupsArrayNew(NULL, NULL);
model_name[0] = '\0';
nick_name[0] = '\0';
manufacturer[0] = '\0';
device_id[0] = '\0';
lang_encoding[0] = '\0';
strlcpy(lang_version, "en", sizeof(lang_version));
model_number = 0;
install_group = 0;
type = PPD_TYPE_POSTSCRIPT;
while ((end == 0 || cupsFileTell(fp) < end) &&
cupsFileGets(fp, line, sizeof(line)))
{
if (!strncmp(line, "*Manufacturer:", 14))
sscanf(line, "%*[^\"]\"%255[^\"]", manufacturer);
else if (!strncmp(line, "*ModelName:", 11))
sscanf(line, "%*[^\"]\"%127[^\"]", model_name);
else if (!strncmp(line, "*LanguageEncoding:", 18))
sscanf(line, "%*[^:]:%63s", lang_encoding);
else if (!strncmp(line, "*LanguageVersion:", 17))
sscanf(line, "%*[^:]:%63s", lang_version);
else if (!strncmp(line, "*NickName:", 10))
sscanf(line, "%*[^\"]\"%255[^\"]", nick_name);
else if (!_cups_strncasecmp(line, "*1284DeviceID:", 14))
{
sscanf(line, "%*[^\"]\"%255[^\"]", device_id);
// Make sure device ID ends with a semicolon...
if (device_id[0] && device_id[strlen(device_id) - 1] != ';')
strlcat(device_id, ";", sizeof(device_id));
}
else if (!strncmp(line, "*Product:", 9))
{
if (sscanf(line, "%*[^\"]\"(%255[^\"]", product) == 1)
{
/*
* Make sure the value ends with a right parenthesis - can't stop at
* the first right paren since the product name may contain escaped
* parenthesis...
*/
ptr = product + strlen(product) - 1;
if (ptr > product && *ptr == ')')
{
/*
* Yes, ends with a parenthesis, so remove it from the end and
* add the product to the list...
*/
*ptr = '\0';
cupsArrayAdd(products, strdup(product));
}
}
}
else if (!strncmp(line, "*PSVersion:", 11))
{
sscanf(line, "%*[^\"]\"%255[^\"]", psversion);
cupsArrayAdd(psversions, strdup(psversion));
}
else if (!strncmp(line, "*cupsLanguages:", 15))
{
char *start; /* Start of language */
for (start = line + 15; *start && isspace(*start & 255); start ++);
if (*start++ == '\"')
{
while (*start)
{
for (ptr = start + 1;
*ptr && *ptr != '\"' && !isspace(*ptr & 255);
ptr ++);
if (*ptr)
{
*ptr++ = '\0';
while (isspace(*ptr & 255))
*ptr++ = '\0';
}
cupsArrayAdd(cups_languages, strdup(start));
start = ptr;
}
}
}
else if (!strncmp(line, "*cupsFax:", 9))
{
for (ptr = line + 9; isspace(*ptr & 255); ptr ++);
if (!_cups_strncasecmp(ptr, "true", 4))
type = PPD_TYPE_FAX;
}
else if ((!strncmp(line, "*cupsFilter:", 12) || !strncmp(line, "*cupsFilter2:", 13)) && type == PPD_TYPE_POSTSCRIPT)
{
if (strstr(line + 12, "application/vnd.cups-raster"))
type = PPD_TYPE_RASTER;
else if (strstr(line + 12, "application/vnd.cups-pdf"))
type = PPD_TYPE_PDF;
}
else if (!strncmp(line, "*cupsModelNumber:", 17))
sscanf(line, "*cupsModelNumber:%d", &model_number);
else if (!strncmp(line, "*OpenGroup: Installable", 23))
install_group = 1;
else if (!strncmp(line, "*CloseGroup:", 12))
install_group = 0;
else if (!strncmp(line, "*OpenUI", 7))
{
/*
* Stop early if we have a NickName or ModelName attributes
* before the first non-installable OpenUI...
*/
if (!install_group && (model_name[0] || nick_name[0]) &&
cupsArrayCount(products) > 0 && cupsArrayCount(psversions) > 0)
break;
}
}
/*
* See if we got all of the required info...
*/
if (nick_name[0])
cupsCharsetToUTF8((cups_utf8_t *)make_model, nick_name,
sizeof(make_model), _ppdGetEncoding(lang_encoding));
else
strlcpy(make_model, model_name, sizeof(make_model));
while (isspace(make_model[0] & 255))
_cups_strcpy(make_model, make_model + 1);
if (!make_model[0] || cupsArrayCount(products) == 0 ||
cupsArrayCount(psversions) == 0)
{
/*
* We don't have all the info needed, so skip this file...
*/
if (!make_model[0])
fprintf(stderr, "WARNING: Missing NickName and ModelName in %s!\n",
filename);
if (cupsArrayCount(products) == 0)
fprintf(stderr, "WARNING: Missing Product in %s!\n", filename);
if (cupsArrayCount(psversions) == 0)
fprintf(stderr, "WARNING: Missing PSVersion in %s!\n", filename);
free_array(products);
free_array(psversions);
free_array(cups_languages);
return;
}
if (model_name[0])
cupsArrayAdd(products, strdup(model_name));
/*
* Normalize the make and model string...
*/
while (isspace(manufacturer[0] & 255))
_cups_strcpy(manufacturer, manufacturer + 1);
if (!_cups_strncasecmp(make_model, manufacturer, strlen(manufacturer)))
strlcpy(temp, make_model, sizeof(temp));
else
snprintf(temp, sizeof(temp), "%s %s", manufacturer, make_model);
_ppdNormalizeMakeAndModel(temp, make_model, sizeof(make_model));
/*
* See if we got a manufacturer...
*/
if (!manufacturer[0] || !strcmp(manufacturer, "ESP"))
{
/*
* Nope, copy the first part of the make and model then...
*/
strlcpy(manufacturer, make_model, sizeof(manufacturer));
/*
* Truncate at the first space, dash, or slash, or make the
* manufacturer "Other"...
*/
for (ptr = manufacturer; *ptr; ptr ++)
if (*ptr == ' ' || *ptr == '-' || *ptr == '/')
break;
if (*ptr && ptr > manufacturer)
*ptr = '\0';
else
strlcpy(manufacturer, "Other", sizeof(manufacturer));
}
else if (!_cups_strncasecmp(manufacturer, "LHAG", 4) ||
!_cups_strncasecmp(manufacturer, "linotype", 8))
strlcpy(manufacturer, "LHAG", sizeof(manufacturer));
else if (!_cups_strncasecmp(manufacturer, "Hewlett", 7))
strlcpy(manufacturer, "HP", sizeof(manufacturer));
/*
* Fix the lang_version as needed...
*/
if ((ptr = strchr(lang_version, '-')) != NULL)
*ptr++ = '\0';
else if ((ptr = strchr(lang_version, '_')) != NULL)
*ptr++ = '\0';
if (ptr)
{
/*
* Setup the country suffix...
*/
country[0] = '_';
_cups_strcpy(country + 1, ptr);
}
else
{
/*
* No country suffix...
*/
country[0] = '\0';
}
for (i = 0; i < (int)(sizeof(languages) / sizeof(languages[0])); i ++)
if (!_cups_strcasecmp(languages[i].version, lang_version))
break;
if (i < (int)(sizeof(languages) / sizeof(languages[0])))
{
/*
* Found a known language...
*/
snprintf(lang_version, sizeof(lang_version), "%s%s",
languages[i].language, country);
}
else
{
/*
* Unknown language; use "xx"...
*/
strlcpy(lang_version, "xx", sizeof(lang_version));
}
/*
* Record the PPD file...
*/
new_ppd = !ppd;
if (new_ppd)
{
/*
* Add new PPD file...
*/
fprintf(stderr, "DEBUG2: [cups-driverd] Adding ppd \"%s\"...\n", name);
ppd = add_ppd(name, name, lang_version, manufacturer, make_model, device_id, (char *)cupsArrayFirst(products), (char *)cupsArrayFirst(psversions), fileinfo->st_mtime, (size_t)fileinfo->st_size, model_number, type, scheme);
if (!ppd)
return;
}
else
{
/*
* Update existing record...
*/
fprintf(stderr, "DEBUG2: [cups-driverd] Updating ppd \"%s\"...\n", name);
memset(ppd, 0, sizeof(ppd_info_t));
ppd->found = 1;
ppd->record.mtime = fileinfo->st_mtime;
ppd->record.size = fileinfo->st_size;
ppd->record.model_number = model_number;
ppd->record.type = type;
strlcpy(ppd->record.filename, name, sizeof(ppd->record.filename));
strlcpy(ppd->record.name, name, sizeof(ppd->record.name));
strlcpy(ppd->record.languages[0], lang_version,
sizeof(ppd->record.languages[0]));
strlcpy(ppd->record.products[0], (char *)cupsArrayFirst(products),
sizeof(ppd->record.products[0]));
strlcpy(ppd->record.psversions[0], (char *)cupsArrayFirst(psversions),
sizeof(ppd->record.psversions[0]));
strlcpy(ppd->record.make, manufacturer, sizeof(ppd->record.make));
strlcpy(ppd->record.make_and_model, make_model,
sizeof(ppd->record.make_and_model));
strlcpy(ppd->record.device_id, device_id, sizeof(ppd->record.device_id));
strlcpy(ppd->record.scheme, scheme, sizeof(ppd->record.scheme));
}
/*
* Add remaining products, versions, and languages...
*/
for (i = 1;
i < PPD_MAX_PROD && (ptr = (char *)cupsArrayNext(products)) != NULL;
i ++)
strlcpy(ppd->record.products[i], ptr,
sizeof(ppd->record.products[0]));
for (i = 1;
i < PPD_MAX_VERS && (ptr = (char *)cupsArrayNext(psversions)) != NULL;
i ++)
strlcpy(ppd->record.psversions[i], ptr,
sizeof(ppd->record.psversions[0]));
for (i = 1, ptr = (char *)cupsArrayFirst(cups_languages);
i < PPD_MAX_LANG && ptr;
i ++, ptr = (char *)cupsArrayNext(cups_languages))
strlcpy(ppd->record.languages[i], ptr,
sizeof(ppd->record.languages[0]));
/*
* Free products, versions, and languages...
*/
free_array(cups_languages);
free_array(products);
free_array(psversions);
ChangedPPD = 1;
}
/*
* 'load_ppds()' - Load PPD files recursively.
*/
static int /* O - 1 on success, 0 on failure */
load_ppds(const char *d, /* I - Actual directory */
const char *p, /* I - Virtual path in name */
int descend) /* I - Descend into directories? */
{
struct stat dinfo, /* Directory information */
*dinfoptr; /* Pointer to match */
cups_file_t *fp; /* Pointer to file */
cups_dir_t *dir; /* Directory pointer */
cups_dentry_t *dent; /* Directory entry */
char filename[1024], /* Name of PPD or directory */
line[256], /* Line from file */
*ptr, /* Pointer into name */
name[256]; /* Name of PPD file */
ppd_info_t *ppd, /* New PPD file */
key; /* Search key */
/*
* See if we've loaded this directory before...
*/
if (stat(d, &dinfo))
{
if (errno != ENOENT)
fprintf(stderr, "ERROR: [cups-driverd] Unable to stat \"%s\": %s\n", d,
strerror(errno));
return (0);
}
else if (cupsArrayFind(Inodes, &dinfo))
{
fprintf(stderr, "ERROR: [cups-driverd] Skipping \"%s\": loop detected!\n",
d);
return (1);
}
/*
* Nope, add it to the Inodes array and continue...
*/
dinfoptr = (struct stat *)malloc(sizeof(struct stat));
memcpy(dinfoptr, &dinfo, sizeof(struct stat));
cupsArrayAdd(Inodes, dinfoptr);
/*
* Check permissions...
*/
if (_cupsFileCheck(d, _CUPS_FILE_CHECK_DIRECTORY, !geteuid(),
_cupsFileCheckFilter, NULL))
return (0);
if ((dir = cupsDirOpen(d)) == NULL)
{
if (errno != ENOENT)
fprintf(stderr,
"ERROR: [cups-driverd] Unable to open PPD directory \"%s\": %s\n",
d, strerror(errno));
return (0);
}
fprintf(stderr, "DEBUG: [cups-driverd] Loading \"%s\"...\n", d);
while ((dent = cupsDirRead(dir)) != NULL)
{
/*
* Skip files/directories starting with "."...
*/
if (dent->filename[0] == '.')
continue;
/*
* See if this is a file...
*/
snprintf(filename, sizeof(filename), "%s/%s", d, dent->filename);
if (p[0])
snprintf(name, sizeof(name), "%s/%s", p, dent->filename);
else
strlcpy(name, dent->filename, sizeof(name));
if (S_ISDIR(dent->fileinfo.st_mode))
{
/*
* Do subdirectory...
*/
if (descend)
{
if (!load_ppds(filename, name, 1))
{
cupsDirClose(dir);
return (1);
}
}
else if ((ptr = filename + strlen(filename) - 14) > filename &&
!strcmp(ptr, ".printerDriver"))
{
/*
* Load PPDs in a printer driver bundle.
*/
if (_cupsFileCheck(filename, _CUPS_FILE_CHECK_DIRECTORY, !geteuid(),
_cupsFileCheckFilter, NULL))
continue;
strlcat(filename, "/Contents/Resources/PPDs", sizeof(filename));
strlcat(name, "/Contents/Resources/PPDs", sizeof(name));
load_ppds(filename, name, 0);
}
continue;
}
else if (strstr(filename, ".plist"))
{
/*
* Skip plist files in the PPDs directory...
*/
continue;
}
else if (_cupsFileCheck(filename, _CUPS_FILE_CHECK_FILE_ONLY, !geteuid(),
_cupsFileCheckFilter, NULL))
continue;
/*
* See if this file has been scanned before...
*/
strlcpy(key.record.filename, name, sizeof(key.record.filename));
strlcpy(key.record.name, name, sizeof(key.record.name));
ppd = (ppd_info_t *)cupsArrayFind(PPDsByName, &key);
if (ppd &&
ppd->record.size == dent->fileinfo.st_size &&
ppd->record.mtime == dent->fileinfo.st_mtime)
{
/*
* Rewind to the first entry for this file...
*/
while ((ppd = (ppd_info_t *)cupsArrayPrev(PPDsByName)) != NULL &&
!strcmp(ppd->record.filename, name));
/*
* Then mark all of the matches for this file as found...
*/
while ((ppd = (ppd_info_t *)cupsArrayNext(PPDsByName)) != NULL &&
!strcmp(ppd->record.filename, name))
ppd->found = 1;
continue;
}
/*
* No, file is new/changed, so re-scan it...
*/
if ((fp = cupsFileOpen(filename, "r")) == NULL)
continue;
/*
* Now see if this is a PPD file...
*/
line[0] = '\0';
cupsFileGets(fp, line, sizeof(line));
if (!strncmp(line, "*PPD-Adobe:", 11))
{
/*
* Yes, load it...
*/
load_ppd(filename, name, "file", &dent->fileinfo, ppd, fp, 0);
}
else
{
/*
* Nope, treat it as a driver information file or archive...
*/
cupsFileRewind(fp);
if ((ptr = strstr(filename, ".tar")) != NULL &&
(!strcmp(ptr, ".tar") || !strcmp(ptr, ".tar.gz")))
load_tar(filename, name, fp, dent->fileinfo.st_mtime,
dent->fileinfo.st_size);
else
load_drv(filename, name, fp, dent->fileinfo.st_mtime,
dent->fileinfo.st_size);
}
/*
* Close the file...
*/
cupsFileClose(fp);
}
cupsDirClose(dir);
return (1);
}
/*
* 'load_ppds_dat()' - Load the ppds.dat file.
*/
static void
load_ppds_dat(char *filename, /* I - Filename buffer */
size_t filesize, /* I - Size of filename buffer */
int verbose) /* I - Be verbose? */
{
ppd_info_t *ppd; /* Current PPD file */
cups_file_t *fp; /* ppds.dat file */
struct stat fileinfo; /* ppds.dat information */
const char *cups_cachedir; /* CUPS_CACHEDIR environment variable */
PPDsByName = cupsArrayNew((cups_array_func_t)compare_names, NULL);
PPDsByMakeModel = cupsArrayNew((cups_array_func_t)compare_ppds, NULL);
ChangedPPD = 0;
if (!filename[0])
{
if ((cups_cachedir = getenv("CUPS_CACHEDIR")) == NULL)
cups_cachedir = CUPS_CACHEDIR;
snprintf(filename, filesize, "%s/ppds.dat", cups_cachedir);
}
if ((fp = cupsFileOpen(filename, "r")) != NULL)
{
/*
* See if we have the right sync word...
*/
unsigned ppdsync; /* Sync word */
int num_ppds; /* Number of PPDs */
if ((size_t)cupsFileRead(fp, (char *)&ppdsync, sizeof(ppdsync)) == sizeof(ppdsync) &&
ppdsync == PPD_SYNC &&
!stat(filename, &fileinfo) &&
(((size_t)fileinfo.st_size - sizeof(ppdsync)) % sizeof(ppd_rec_t)) == 0 &&
(num_ppds = ((size_t)fileinfo.st_size - sizeof(ppdsync)) / sizeof(ppd_rec_t)) > 0)
{
/*
* We have a ppds.dat file, so read it!
*/
for (; num_ppds > 0; num_ppds --)
{
if ((ppd = (ppd_info_t *)calloc(1, sizeof(ppd_info_t))) == NULL)
{
if (verbose)
fputs("ERROR: [cups-driverd] Unable to allocate memory for PPD!\n",
stderr);
exit(1);
}
if (cupsFileRead(fp, (char *)&(ppd->record), sizeof(ppd_rec_t)) > 0)
{
cupsArrayAdd(PPDsByName, ppd);
cupsArrayAdd(PPDsByMakeModel, ppd);
}
else
{
free(ppd);
break;
}
}
if (verbose)
fprintf(stderr, "INFO: [cups-driverd] Read \"%s\", %d PPDs...\n",
filename, cupsArrayCount(PPDsByName));
}
cupsFileClose(fp);
}
}
/*
* 'load_tar()' - Load archived PPD files.
*/
static int /* O - 1 on success, 0 on failure */
load_tar(const char *filename, /* I - Actual filename */
const char *name, /* I - Name to the rest of the world */
cups_file_t *fp, /* I - File to read from */
time_t mtime, /* I - Mod time of driver info file */
off_t size) /* I - Size of driver info file */
{
char curname[256], /* Current archive file name */
uri[1024]; /* Virtual file URI */
const char *curext; /* Extension on file */
struct stat curinfo; /* Current archive file information */
off_t next; /* Position for next header */
/*
* Add a dummy entry for the file...
*/
(void)filename;
add_ppd(name, name, "", "", "", "", "", "", mtime, (size_t)size, 0, PPD_TYPE_ARCHIVE, "file");
ChangedPPD = 1;
/*
* Scan for PPDs in the archive...
*/
while (read_tar(fp, curname, sizeof(curname), &curinfo))
{
next = cupsFileTell(fp) + ((curinfo.st_size + TAR_BLOCK - 1) &
~(TAR_BLOCK - 1));
if ((curext = strrchr(curname, '.')) != NULL &&
!_cups_strcasecmp(curext, ".ppd"))
{
httpAssembleURIf(HTTP_URI_CODING_ALL, uri, sizeof(uri), "file", "", "",
0, "/%s/%s", name, curname);
load_ppd(name, uri, "file", &curinfo, NULL, fp, next);
}
if (cupsFileTell(fp) != next)
cupsFileSeek(fp, next);
}
return (1);
}
/*
* 'read_tar()' - Read a file header from an archive.
*
* This function skips all directories and special files.
*/
static int /* O - 1 if found, 0 on EOF */
read_tar(cups_file_t *fp, /* I - Archive to read */
char *name, /* I - Filename buffer */
size_t namesize, /* I - Size of filename buffer */
struct stat *info) /* O - File information */
{
tar_rec_t record; /* Record from file */
while ((size_t)cupsFileRead(fp, (char *)&record, sizeof(record)) == sizeof(record))
{
/*
* Check for a valid tar header...
*/
if (memcmp(record.header.magic, TAR_MAGIC, 6) ||
memcmp(record.header.version, TAR_VERSION, 2))
{
if (record.header.magic[0] ||
memcmp(record.header.magic, record.header.magic + 1, 5))
fputs("ERROR: [cups-driverd] Bad tar magic/version.\n", stderr);
break;
}
/*
* Ignore non-files...
*/
if (record.header.linkflag != TAR_OLDNORMAL &&
record.header.linkflag != TAR_NORMAL)
continue;
/*
* Grab size and name from tar header and return...
*/
if (record.header.prefix[0])
snprintf(name, namesize, "%s/%s", record.header.prefix,
record.header.pathname);
else
strlcpy(name, record.header.pathname, namesize);
info->st_mtime = strtol(record.header.mtime, NULL, 8);
info->st_size = strtoll(record.header.size, NULL, 8);
return (1);
}
return (0);
}
/*
* 'regex_device_id()' - Compile a regular expression based on the 1284 device
* ID.
*/
static regex_t * /* O - Regular expression */
regex_device_id(const char *device_id) /* I - IEEE-1284 device ID */
{
char res[2048], /* Regular expression string */
*ptr; /* Pointer into string */
regex_t *re; /* Regular expression */
int cmd; /* Command set string? */
fprintf(stderr, "DEBUG: [cups-driverd] regex_device_id(\"%s\")\n", device_id);
/*
* Scan the device ID string and insert class, command set, manufacturer, and
* model attributes to match. We assume that the device ID in the PPD and the
* device ID reported by the device itself use the same attribute names and
* order of attributes.
*/
ptr = res;
while (*device_id && ptr < (res + sizeof(res) - 6))
{
cmd = !_cups_strncasecmp(device_id, "COMMAND SET:", 12) ||
!_cups_strncasecmp(device_id, "CMD:", 4);
if (cmd || !_cups_strncasecmp(device_id, "MANUFACTURER:", 13) ||
!_cups_strncasecmp(device_id, "MFG:", 4) ||
!_cups_strncasecmp(device_id, "MFR:", 4) ||
!_cups_strncasecmp(device_id, "MODEL:", 6) ||
!_cups_strncasecmp(device_id, "MDL:", 4))
{
if (ptr > res)
{
*ptr++ = '.';
*ptr++ = '*';
}
*ptr++ = '(';
while (*device_id && *device_id != ';' && ptr < (res + sizeof(res) - 8))
{
if (strchr("[]{}().*\\|", *device_id))
*ptr++ = '\\';
if (*device_id == ':')
{
/*
* KEY:.*value
*/
*ptr++ = *device_id++;
*ptr++ = '.';
*ptr++ = '*';
}
else
*ptr++ = *device_id++;
}
if (*device_id == ';' || !*device_id)
{
/*
* KEY:.*value.*;
*/
*ptr++ = '.';
*ptr++ = '*';
*ptr++ = ';';
}
*ptr++ = ')';
if (cmd)
*ptr++ = '?';
}
else if ((device_id = strchr(device_id, ';')) == NULL)
break;
else
device_id ++;
}
*ptr = '\0';
fprintf(stderr, "DEBUG: [cups-driverd] regex_device_id: \"%s\"\n", res);
/*
* Compile the regular expression and return...
*/
if (res[0] && (re = (regex_t *)calloc(1, sizeof(regex_t))) != NULL)
{
if (!regcomp(re, res, REG_EXTENDED | REG_ICASE))
{
fputs("DEBUG: [cups-driverd] regex_device_id: OK\n", stderr);
return (re);
}
free(re);
}
return (NULL);
}
/*
* 'regex_string()' - Construct a regular expression to compare a simple string.
*/
static regex_t * /* O - Regular expression */
regex_string(const char *s) /* I - String to compare */
{
char res[2048], /* Regular expression string */
*ptr; /* Pointer into string */
regex_t *re; /* Regular expression */
fprintf(stderr, "DEBUG: [cups-driverd] regex_string(\"%s\")\n", s);
/*
* Convert the string to a regular expression, escaping special characters
* as needed.
*/
ptr = res;
while (*s && ptr < (res + sizeof(res) - 2))
{
if (strchr("[]{}().*\\", *s))
*ptr++ = '\\';
*ptr++ = *s++;
}
*ptr = '\0';
fprintf(stderr, "DEBUG: [cups-driverd] regex_string: \"%s\"\n", res);
/*
* Create a case-insensitive regular expression...
*/
if (res[0] && (re = (regex_t *)calloc(1, sizeof(regex_t))) != NULL)
{
if (!regcomp(re, res, REG_ICASE))
{
fputs("DEBUG: [cups-driverd] regex_string: OK\n", stderr);
return (re);
}
free(re);
}
return (NULL);
}