#include "util.h"
#include <cups/array.h>
#include <cups/dir.h>
#include <fcntl.h>
#include <sys/wait.h>
#include <poll.h>
#define MAX_BACKENDS 200
typedef struct
{
char *name;
int pid,
status;
cups_file_t *pipe;
int count;
} cupsd_backend_t;
typedef struct
{
char device_class[128],
device_info[128],
device_uri[1024];
} cupsd_device_t;
static int num_backends = 0,
active_backends = 0;
static cupsd_backend_t backends[MAX_BACKENDS];
static struct pollfd backend_fds[MAX_BACKENDS];
static cups_array_t *devices;
static int normal_user;
static int device_limit;
static int send_class,
send_info,
send_make_and_model,
send_uri,
send_id,
send_location;
static int dead_children = 0;
static int add_device(const char *device_class,
const char *device_make_and_model,
const char *device_info,
const char *device_uri,
const char *device_id,
const char *device_location);
static int compare_devices(cupsd_device_t *p0,
cupsd_device_t *p1);
static double get_current_time(void);
static int get_device(cupsd_backend_t *backend);
static void process_children(void);
static void sigchld_handler(int sig);
static int start_backend(const char *backend, int root);
int
main(int argc,
char *argv[])
{
int i;
int request_id;
int timeout;
const char *server_bin;
char filename[1024];
cups_dir_t *dir;
cups_dentry_t *dent;
double current_time,
end_time;
int num_options;
cups_option_t *options;
cups_array_t *requested,
*exclude,
*include;
#if defined(HAVE_SIGACTION) && !defined(HAVE_SIGSET)
struct sigaction action;
#endif
setbuf(stderr, NULL);
if (argc != 6)
{
fputs("Usage: cups-deviced request-id limit timeout user-id options\n", stderr);
return (1);
}
request_id = atoi(argv[1]);
if (request_id < 1)
{
fprintf(stderr, "ERROR: [cups-deviced] Bad request ID %d!\n", request_id);
return (1);
}
device_limit = atoi(argv[2]);
if (device_limit < 0)
{
fprintf(stderr, "ERROR: [cups-deviced] Bad limit %d!\n", device_limit);
return (1);
}
timeout = atoi(argv[3]);
if (timeout < 1)
{
fprintf(stderr, "ERROR: [cups-deviced] Bad timeout %d!\n", timeout);
return (1);
}
normal_user = atoi(argv[4]);
if (normal_user <= 0)
{
fprintf(stderr, "ERROR: [cups-deviced] Bad user %d!\n", normal_user);
return (1);
}
num_options = cupsParseOptions(argv[5], 0, &options);
requested = cupsdCreateStringsArray(cupsGetOption("requested-attributes",
num_options, options));
exclude = cupsdCreateStringsArray(cupsGetOption("exclude-schemes",
num_options, options));
include = cupsdCreateStringsArray(cupsGetOption("include-schemes",
num_options, options));
if (!requested || cupsArrayFind(requested, "all") != NULL)
{
send_class = send_info = send_make_and_model = send_uri = send_id =
send_location = 1;
}
else
{
send_class = cupsArrayFind(requested, "device-class") != NULL;
send_info = cupsArrayFind(requested, "device-info") != NULL;
send_make_and_model = cupsArrayFind(requested, "device-make-and-model") != NULL;
send_uri = cupsArrayFind(requested, "device-uri") != NULL;
send_id = cupsArrayFind(requested, "device-id") != NULL;
send_location = cupsArrayFind(requested, "device-location") != NULL;
}
#ifdef HAVE_SIGSET
sigset(SIGCHLD, sigchld_handler);
#elif defined(HAVE_SIGACTION)
memset(&action, 0, sizeof(action));
sigemptyset(&action.sa_mask);
sigaddset(&action.sa_mask, SIGCHLD);
action.sa_handler = sigchld_handler;
sigaction(SIGCHLD, &action, NULL);
#else
signal(SIGCLD, sigchld_handler);
#endif
if ((server_bin = getenv("CUPS_SERVERBIN")) == NULL)
server_bin = CUPS_SERVERBIN;
snprintf(filename, sizeof(filename), "%s/backend", server_bin);
if ((dir = cupsDirOpen(filename)) == NULL)
{
fprintf(stderr, "ERROR: [cups-deviced] Unable to open backend directory "
"\"%s\": %s", filename, strerror(errno));
return (1);
}
devices = cupsArrayNew((cups_array_func_t)compare_devices, NULL);
while ((dent = cupsDirRead(dir)) != NULL)
{
if (!S_ISREG(dent->fileinfo.st_mode) ||
!isalnum(dent->filename[0] & 255) ||
(dent->fileinfo.st_mode & (S_IRUSR | S_IXUSR)) != (S_IRUSR | S_IXUSR))
continue;
if (cupsArrayFind(exclude, dent->filename) ||
(include && !cupsArrayFind(include, dent->filename)))
continue;
start_backend(dent->filename,
!(dent->fileinfo.st_mode & (S_IRWXG | S_IRWXO)));
}
cupsDirClose(dir);
if (getenv("SOFTWARE"))
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");
end_time = get_current_time() + timeout;
while (active_backends > 0 && (current_time = get_current_time()) < end_time)
{
timeout = (int)(1000 * (end_time - current_time));
if (poll(backend_fds, num_backends, timeout) > 0)
{
for (i = 0; i < num_backends; i ++)
if (backend_fds[i].revents && backends[i].pipe)
{
cups_file_t *bpipe = backends[i].pipe;
do
{
if (get_device(backends + i))
{
backend_fds[i].fd = 0;
backend_fds[i].events = 0;
break;
}
}
while (bpipe->ptr &&
memchr(bpipe->ptr, '\n', bpipe->end - bpipe->ptr));
}
}
if (dead_children)
process_children();
}
cupsdSendIPPTrailer();
if (active_backends > 0)
{
for (i = 0; i < num_backends; i ++)
if (backends[i].pid)
kill(backends[i].pid, SIGTERM);
}
return (0);
}
static int
add_device(
const char *device_class,
const char *device_make_and_model,
const char *device_info,
const char *device_uri,
const char *device_id,
const char *device_location)
{
cupsd_device_t *device;
if ((device = calloc(1, sizeof(cupsd_device_t))) == NULL)
{
fputs("ERROR: [cups-deviced] Ran out of memory allocating a device!\n",
stderr);
return (-1);
}
strlcpy(device->device_class, device_class, sizeof(device->device_class));
strlcpy(device->device_info, device_info, sizeof(device->device_info));
strlcpy(device->device_uri, device_uri, sizeof(device->device_uri));
if (cupsArrayFind(devices, device))
{
free(device);
}
else
{
cupsArrayAdd(devices, device);
if (device_limit <= 0 || cupsArrayCount(devices) < device_limit)
{
cupsdSendIPPGroup(IPP_TAG_PRINTER);
if (send_class)
cupsdSendIPPString(IPP_TAG_KEYWORD, "device-class",
device_class);
if (send_info)
cupsdSendIPPString(IPP_TAG_TEXT, "device-info", device_info);
if (send_make_and_model)
cupsdSendIPPString(IPP_TAG_TEXT, "device-make-and-model",
device_make_and_model);
if (send_uri)
cupsdSendIPPString(IPP_TAG_URI, "device-uri", device_uri);
if (send_id)
cupsdSendIPPString(IPP_TAG_TEXT, "device-id",
device_id ? device_id : "");
if (send_location)
cupsdSendIPPString(IPP_TAG_TEXT, "device-location",
device_location ? device_location : "");
fflush(stdout);
fputs("DEBUG: Flushed attributes...\n", stderr);
}
}
return (0);
}
static int
compare_devices(cupsd_device_t *d0,
cupsd_device_t *d1)
{
int diff;
if ((diff = cupsdCompareNames(d0->device_info, d1->device_info)) != 0)
return (diff);
else if ((diff = _cups_strcasecmp(d0->device_class, d1->device_class)) != 0)
return (diff);
else
return (_cups_strcasecmp(d0->device_uri, d1->device_uri));
}
static double
get_current_time(void)
{
struct timeval curtime;
gettimeofday(&curtime, NULL);
return (curtime.tv_sec + 0.000001 * curtime.tv_usec);
}
static int
get_device(cupsd_backend_t *backend)
{
char line[2048],
temp[2048],
*ptr,
*dclass,
*uri,
*make_model,
*info,
*device_id,
*location;
if (cupsFileGets(backend->pipe, line, sizeof(line)))
{
strlcpy(temp, line, sizeof(temp));
dclass = temp;
for (ptr = temp; *ptr; ptr ++)
if (isspace(*ptr & 255))
break;
while (isspace(*ptr & 255))
*ptr++ = '\0';
if (!*ptr)
goto error;
for (uri = ptr; *ptr; ptr ++)
if (isspace(*ptr & 255))
break;
while (isspace(*ptr & 255))
*ptr++ = '\0';
if (*ptr != '\"')
goto error;
for (ptr ++, make_model = ptr; *ptr && *ptr != '\"'; ptr ++)
{
if (*ptr == '\\' && ptr[1])
_cups_strcpy(ptr, ptr + 1);
}
if (*ptr != '\"')
goto error;
for (*ptr++ = '\0'; isspace(*ptr & 255); *ptr++ = '\0');
if (*ptr != '\"')
goto error;
for (ptr ++, info = ptr; *ptr && *ptr != '\"'; ptr ++)
{
if (*ptr == '\\' && ptr[1])
_cups_strcpy(ptr, ptr + 1);
}
if (*ptr != '\"')
goto error;
for (*ptr++ = '\0'; isspace(*ptr & 255); *ptr++ = '\0');
if (*ptr == '\"')
{
for (ptr ++, device_id = ptr; *ptr && *ptr != '\"'; ptr ++)
{
if (*ptr == '\\' && ptr[1])
_cups_strcpy(ptr, ptr + 1);
}
if (*ptr != '\"')
goto error;
for (*ptr++ = '\0'; isspace(*ptr & 255); *ptr++ = '\0');
if (*ptr == '\"')
{
for (ptr ++, location = ptr; *ptr && *ptr != '\"'; ptr ++)
{
if (*ptr == '\\' && ptr[1])
_cups_strcpy(ptr, ptr + 1);
}
if (*ptr != '\"')
goto error;
*ptr = '\0';
}
else
location = NULL;
}
else
{
device_id = NULL;
location = NULL;
}
if (!add_device(dclass, make_model, info, uri, device_id, location))
fprintf(stderr, "DEBUG: [cups-deviced] Found device \"%s\"...\n", uri);
return (0);
}
cupsFileClose(backend->pipe);
backend->pipe = NULL;
return (-1);
error:
if (line[strlen(line) - 1] == '\n')
line[strlen(line) - 1] = '\0';
fprintf(stderr, "ERROR: [cups-deviced] Bad line from \"%s\": %s\n",
backend->name, line);
return (0);
}
static void
process_children(void)
{
int i;
int status;
int pid;
cupsd_backend_t *backend;
const char *name;
dead_children = 0;
#ifdef HAVE_WAITPID
while ((pid = waitpid(-1, &status, WNOHANG)) > 0)
#elif defined(HAVE_WAIT3)
while ((pid = wait3(&status, WNOHANG, NULL)) > 0)
#else
if ((pid = wait(&status)) > 0)
#endif
{
if (status == SIGTERM)
status = 0;
for (i = num_backends, backend = backends; i > 0; i --, backend ++)
if (backend->pid == pid)
break;
if (i > 0)
{
name = backend->name;
backend->pid = 0;
backend->status = status;
active_backends --;
}
else
name = "Unknown";
if (status)
{
if (WIFEXITED(status))
fprintf(stderr,
"ERROR: [cups-deviced] PID %d (%s) stopped with status %d!\n",
pid, name, WEXITSTATUS(status));
else
fprintf(stderr,
"ERROR: [cups-deviced] PID %d (%s) crashed on signal %d!\n",
pid, name, WTERMSIG(status));
}
else
fprintf(stderr,
"DEBUG: [cups-deviced] PID %d (%s) exited with no errors.\n",
pid, name);
}
}
static void
sigchld_handler(int sig)
{
(void)sig;
dead_children = 1;
#if !defined(HAVE_SIGSET) && !defined(HAVE_SIGACTION)
signal(SIGCLD, sigchld_handler);
#endif
}
static int
start_backend(const char *name,
int root)
{
const char *server_bin;
char program[1024];
cupsd_backend_t *backend;
char *argv[2];
if (num_backends >= MAX_BACKENDS)
{
fprintf(stderr, "ERROR: Too many backends (%d)!\n", num_backends);
return (-1);
}
if ((server_bin = getenv("CUPS_SERVERBIN")) == NULL)
server_bin = CUPS_SERVERBIN;
snprintf(program, sizeof(program), "%s/backend/%s", server_bin, name);
if (_cupsFileCheck(program, _CUPS_FILE_CHECK_PROGRAM, !geteuid(),
_cupsFileCheckFilter, NULL))
return (-1);
backend = backends + num_backends;
argv[0] = (char *)name;
argv[1] = NULL;
if ((backend->pipe = cupsdPipeCommand(&(backend->pid), program, argv,
root ? 0 : normal_user)) == NULL)
{
fprintf(stderr, "ERROR: [cups-deviced] Unable to execute \"%s\" - %s\n",
program, strerror(errno));
return (-1);
}
fprintf(stderr, "DEBUG: [cups-deviced] Started backend %s (PID %d)\n",
program, backend->pid);
backend_fds[num_backends].fd = cupsFileNumber(backend->pipe);
backend_fds[num_backends].events = POLLIN;
backend->name = strdup(name);
backend->status = 0;
backend->count = 0;
active_backends ++;
num_backends ++;
return (0);
}