diff options
Diffstat (limited to 'frontend')
-rw-r--r-- | frontend/saned.c | 395 | ||||
-rw-r--r-- | frontend/scanimage.c | 2 |
2 files changed, 237 insertions, 160 deletions
diff --git a/frontend/saned.c b/frontend/saned.c index d71d428..5ca81cc 100644 --- a/frontend/saned.c +++ b/frontend/saned.c @@ -79,6 +79,9 @@ #include <arpa/inet.h> #include <sys/wait.h> +#ifdef HAVE_PIDFD_OPEN +#include <sys/pidfd.h> +#endif #include <pwd.h> #include <grp.h> @@ -168,8 +171,6 @@ poll (struct pollfd *ufds, unsigned int nfds, int timeout) # define SANED_SERVICE_DNS "_sane-port._tcp" # define SANED_NAME "saned" -pid_t avahi_pid = -1; - char *avahi_svc_name; static AvahiClient *avahi_client = NULL; @@ -222,13 +223,36 @@ static AvahiEntryGroup *avahi_group = NULL; # define PATH_MAX 1024 #endif +/* Linked list of child processes */ +enum saned_child_type { + SANED_CHILD_CLIENT, + SANED_CHILD_AVAHI, +}; struct saned_child { + enum saned_child_type type; pid_t pid; + int pidfd; struct saned_child *next; }; struct saned_child *children; int numchildren; +/* Linked list of fds to be polled */ +enum saned_fd_type { + SANED_FD_LISTENER, + SANED_FD_PROCESS, +}; +struct saned_fd +{ + enum saned_fd_type type; + int fd; + short interesting_events; + struct saned_fd *next; +}; +struct saned_fd *saned_fds; +int num_saned_fds; + + #define SANED_CONFIG_FILE "saned.conf" #define SANED_PID_FILE "/var/run/saned.pid" @@ -254,6 +278,7 @@ static int debug; static int run_mode; static int run_foreground; static int run_once; +static int allow_network; static int data_connect_timeout = 4000; static Handle *handle; static char *bind_addr; @@ -1869,7 +1894,7 @@ process_request (Wire * w) reply.status = sane_get_devices ((const SANE_Device ***) &reply.device_list, - SANE_TRUE); + !allow_network); sanei_w_reply (w, (WireCodecFunc) sanei_w_get_devices_reply, &reply); } break; @@ -2294,67 +2319,103 @@ process_request (Wire * w) return 0; } - static int -wait_child (pid_t pid, int *status, int options) +add_fd (int fd, enum saned_fd_type type, short interesting_events) { - struct saned_child *c; - struct saned_child *p = NULL; - int ret; - - ret = waitpid(pid, status, options); - - if (ret <= 0) - return ret; + struct saned_fd *f; -#if WITH_AVAHI - if ((avahi_pid > 0) && (ret == avahi_pid)) - { - avahi_pid = -1; - numchildren--; - return ret; - } -#endif /* WITH_AVAHI */ + f = (struct saned_fd *) malloc (sizeof(struct saned_fd)); + if (f == NULL) + goto fail; - for (c = children; (c != NULL) && (c->next != NULL); p = c, c = c->next) - { - if (c->pid == ret) - { - if (c == children) - children = c->next; - else if (p != NULL) - p->next = c->next; + f->type = type; + f->fd = fd; + f->interesting_events = interesting_events; + f->next = saned_fds; - free(c); + saned_fds = f; + num_saned_fds++; - numchildren--; + return fd; - break; - } - } +fail: + DBG (DBG_ERR, "add_fd: cannot manage fd, %s\n", strerror(errno)); + close(fd); + return -1; +} - return ret; +static void +close_fds(unsigned type_mask, int specific_fd) +{ + struct saned_fd *f, **nextp; + + for (nextp = &saned_fds; (f = *nextp); /* */) { + if (type_mask & (1 << f->type) || + specific_fd == f->fd) { + close(f->fd); + *nextp = f->next; + num_saned_fds--; + free(f); + } else + nextp = &f->next; + } } -static int -add_child (pid_t pid) +static void +add_child (pid_t pid, enum saned_child_type type) { struct saned_child *c; c = (struct saned_child *) malloc (sizeof(struct saned_child)); - if (c == NULL) - { - DBG (DBG_ERR, "add_child: out of memory\n"); - return -1; - } + goto fail; + c->type = type; c->pid = pid; + c->pidfd = -1; c->next = children; +#ifdef HAVE_PIDFD_OPEN + c->pidfd = pidfd_open(pid, 0); + if (c->pidfd == -1) + DBG (DBG_DBG, "add_child: could not open pidfd for child process, %s\n", strerror(errno)); + else + add_fd(c->pidfd, SANED_FD_PROCESS, POLLIN); +#endif + children = c; + numchildren++; + return; - return 0; +fail: + /* If the child process cannot be managed, kill it now. */ + DBG (DBG_ERR, "add_child: cannot manage child process, %s\n", strerror(errno)); + kill(pid, SIGTERM); +} + + +static int +wait_child (pid_t pid, int *status, int options) +{ + struct saned_child *c, **nextp; + int ret; + + ret = waitpid(pid, status, options); + + if (ret <= 0) + return ret; + + for (nextp = &children; (c = *nextp); /* */) { + if (c->pid == ret) { + *nextp = c->next; + numchildren--; + close_fds(0, c->pidfd); + free(c); + break; + } else + nextp = &c->next; + } + return ret; } @@ -2383,7 +2444,7 @@ handle_connection (int fd) p = getprotobyname ("tcp"); if (p == 0) { - DBG (DBG_WARN, "handle_connection: cannot look up `tcp' protocol number"); + DBG (DBG_WARN, "handle_connection: cannot look up `tcp' protocol number\n"); } else level = p->p_proto; @@ -2391,7 +2452,7 @@ handle_connection (int fd) # endif /* SOL_TCP */ if (level == -1 || setsockopt (wire.io.fd, level, TCP_NODELAY, &on, sizeof (on))) - DBG (DBG_WARN, "handle_connection: failed to put socket in TCP_NODELAY mode (%s)", + DBG (DBG_WARN, "handle_connection: failed to put socket in TCP_NODELAY mode (%s)\n", strerror (errno)); #endif /* !TCP_NODELAY */ @@ -2433,8 +2494,8 @@ handle_client (int fd) else if (pid > 0) { /* parent */ - add_child (pid); close(fd); + add_child (pid, SANED_CHILD_CLIENT); } else { @@ -2445,20 +2506,28 @@ handle_client (int fd) } static void +kill_children(int sig) +{ + struct saned_child *c; + + /* The only type of child we kill is Avahi. */ + for (c = children; c; c = c->next) + if (c->type == SANED_CHILD_AVAHI) + kill(c->pid, sig); +} + +static void bail_out (int error) { DBG (DBG_ERR, "%sbailing out, waiting for children...\n", (error) ? "FATAL ERROR; " : ""); -#if WITH_AVAHI - if (avahi_pid > 0) - kill (avahi_pid, SIGTERM); -#endif /* WITH_AVAHI */ - + kill_children(SIGTERM); while (numchildren > 0) wait_child (-1, NULL, 0); DBG (DBG_ERR, "bail_out: all children exited\n"); + close_fds(-1, -1); exit ((error) ? 1 : 0); } @@ -2480,7 +2549,7 @@ sig_int_term_handler (int signum) #if WITH_AVAHI static void -saned_avahi (struct pollfd *fds, int nfds); +saned_avahi (void); static void saned_create_avahi_services (AvahiClient *c); @@ -2493,16 +2562,16 @@ saned_avahi_group_callback (AvahiEntryGroup *g, AvahiEntryGroupState state, void static void -saned_avahi (struct pollfd *fds, int nfds) +saned_avahi (void) { - struct pollfd *fdp = NULL; + int avahi_pid; int error; avahi_pid = fork (); if (avahi_pid > 0) { - numchildren++; + add_child(avahi_pid, SANED_CHILD_AVAHI); return; } else if (avahi_pid < 0) @@ -2514,11 +2583,8 @@ saned_avahi (struct pollfd *fds, int nfds) signal (SIGINT, NULL); signal (SIGTERM, NULL); - /* Close network fds */ - for (fdp = fds; nfds > 0; nfds--, fdp++) - close (fdp->fd); - - free(fds); + /* Close parent fds */ + close_fds(-1, -1); avahi_svc_name = avahi_strdup(SANED_NAME); @@ -2853,17 +2919,15 @@ read_config (void) #ifdef SANED_USES_AF_INDEP static void -do_bindings_family (int family, int *nfds, struct pollfd **fds, struct addrinfo *res) +do_bindings_family (int family, struct addrinfo *res) { struct addrinfo *resp; - struct pollfd *fdp; short sane_port; int fd = -1; int on = 1; int i; sane_port = bind_port; - fdp = *fds; for (resp = res, i = 0; resp != NULL; resp = resp->ai_next, i++) { @@ -2953,23 +3017,15 @@ do_bindings_family (int family, int *nfds, struct pollfd **fds, struct addrinfo } } - fdp->fd = fd; - fdp->events = POLLIN; - - (*nfds)++; - fdp++; + add_fd(fd, SANED_FD_LISTENER, POLLIN); } - - *fds = fdp; } static void -do_bindings (int *nfds, struct pollfd **fds) +do_bindings (void) { struct addrinfo *res; - struct addrinfo *resp; struct addrinfo hints; - struct pollfd *fdp; int err; DBG (DBG_DBG, "do_bindings: trying to get port for service \"%s\" (getaddrinfo)\n", SANED_SERVICE_NAME); @@ -2994,31 +3050,15 @@ do_bindings (int *nfds, struct pollfd **fds) } } - for (resp = res, *nfds = 0; resp != NULL; resp = resp->ai_next, (*nfds)++) - ; - - *fds = malloc (*nfds * sizeof (struct pollfd)); - - if (fds == NULL) - { - DBG (DBG_ERR, "do_bindings: not enough memory for fds\n"); - freeaddrinfo (res); - bail_out (1); - } - - fdp = *fds; - *nfds = 0; - /* bind IPv6 first, IPv4 second */ #ifdef ENABLE_IPV6 - do_bindings_family (AF_INET6, nfds, &fdp, res); + do_bindings_family (AF_INET6, res); #endif - do_bindings_family (AF_INET, nfds, &fdp, res); + do_bindings_family (AF_INET, res); - resp = NULL; freeaddrinfo (res); - if (*nfds <= 0) + if (res == NULL) { DBG (DBG_ERR, "do_bindings: couldn't bind an address. Exiting.\n"); bail_out (1); @@ -3028,7 +3068,7 @@ do_bindings (int *nfds, struct pollfd **fds) #else /* !SANED_USES_AF_INDEP */ static void -do_bindings (int *nfds, struct pollfd **fds) +do_bindings (void) { struct sockaddr_in sin; struct servent *serv; @@ -3052,15 +3092,6 @@ do_bindings (int *nfds, struct pollfd **fds) DBG (DBG_WARN, "do_bindings: to your /etc/services file (or equivalent). Proceeding anyway.\n"); } - *nfds = 1; - *fds = malloc (*nfds * sizeof (struct pollfd)); - - if (fds == NULL) - { - DBG (DBG_ERR, "do_bindings: not enough memory for fds\n"); - bail_out (1); - } - memset (&sin, 0, sizeof (sin)); sin.sin_family = AF_INET; @@ -3075,24 +3106,23 @@ do_bindings (int *nfds, struct pollfd **fds) DBG (DBG_DBG, "do_bindings: setsockopt ()\n"); if (setsockopt (fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof (on))) - DBG (DBG_ERR, "do_bindings: failed to put socket in SO_REUSEADDR mode (%s)", strerror (errno)); + DBG (DBG_ERR, "do_bindings: failed to put socket in SO_REUSEADDR mode (%s)\n", strerror (errno)); DBG (DBG_DBG, "do_bindings: bind ()\n"); if (bind (fd, (struct sockaddr *) &sin, sizeof (sin)) < 0) { - DBG (DBG_ERR, "do_bindings: bind failed: %s", strerror (errno)); + DBG (DBG_ERR, "do_bindings: bind failed: %s\n", strerror (errno)); bail_out (1); } DBG (DBG_DBG, "do_bindings: listen ()\n"); if (listen (fd, 1) < 0) { - DBG (DBG_ERR, "do_bindings: listen failed: %s", strerror (errno)); + DBG (DBG_ERR, "do_bindings: listen failed: %s\n", strerror (errno)); bail_out (1); } - (*fds)->fd = fd; - (*fds)->events = POLLIN; + add_fd(fd, SANED_FD_LISTENER, POLLIN); } #endif /* SANED_USES_AF_INDEP */ @@ -3209,16 +3239,16 @@ runas_user (char *user) static void run_standalone (char *user) { - struct pollfd *fds = NULL; - struct pollfd *fdp = NULL; - int nfds; + struct pollfd *poll_set = NULL; + int poll_set_valid = SANE_FALSE; + int running = SANE_TRUE; int fd = -1; int i; int ret; FILE *pidfile; - do_bindings (&nfds, &fds); + do_bindings (); if (run_foreground == SANE_FALSE) { @@ -3254,7 +3284,11 @@ run_standalone (char *user) else DBG (DBG_ERR, "Could not write PID file: %s\n", strerror (errno)); - chdir ("/"); + if (chdir ("/") != 0) + { + DBG (DBG_ERR, "Could not change to root directory: %s\n", strerror (errno)); + exit(1); + } dup2 (fd, STDIN_FILENO); dup2 (fd, STDOUT_FILENO); @@ -3263,26 +3297,55 @@ run_standalone (char *user) close (fd); setsid (); - - signal(SIGINT, sig_int_term_handler); - signal(SIGTERM, sig_int_term_handler); } + signal(SIGINT, sig_int_term_handler); + signal(SIGTERM, sig_int_term_handler); + if (user) runas_user(user); #if WITH_AVAHI DBG (DBG_INFO, "run_standalone: spawning Avahi process\n"); - saned_avahi (fds, nfds); + saned_avahi (); /* NOT REACHED (Avahi process) */ #endif /* WITH_AVAHI */ DBG (DBG_MSG, "run_standalone: waiting for control connection\n"); - while (1) + while (running) { - ret = poll (fds, nfds, 500); + struct saned_child *child; + struct saned_fd *sfd; + int timeout_needed = SANE_FALSE; + int do_rebind = SANE_FALSE; + int do_reap; + + for (child = children; child; child = child->next) + if (child->pidfd == -1) + timeout_needed = SANE_TRUE; + do_reap = timeout_needed; + + if (!poll_set_valid) + { + void *new_poll_set = realloc(poll_set, num_saned_fds * sizeof *poll_set); + if (new_poll_set == NULL && num_saned_fds != 0) + { + DBG (DBG_ERR, "run_standalone: poll set allocation failed: %s\n", strerror (errno)); + free(poll_set); + bail_out (1); + } + poll_set = (struct pollfd *) new_poll_set; + for (sfd = saned_fds, i = 0; sfd; sfd = sfd->next, i++) { + poll_set[i].fd = sfd->fd; + poll_set[i].events = sfd->interesting_events; + } + assert(i == num_saned_fds); + poll_set_valid = SANE_TRUE; + } + + ret = poll (poll_set, num_saned_fds, timeout_needed ? 500 : -1); if (ret < 0) { if (errno == EINTR) @@ -3290,59 +3353,67 @@ run_standalone (char *user) else { DBG (DBG_ERR, "run_standalone: poll failed: %s\n", strerror (errno)); - free (fds); + close_fds(-1, -1); bail_out (1); } } - /* Wait for children */ - while (wait_child (-1, NULL, WNOHANG) > 0) - ; - - if (ret == 0) - continue; - - for (i = 0, fdp = fds; i < nfds; i++, fdp++) + /* Do not allow fd list to change while iterating over poll events, otherwise + * we shall have to look them up each time. */ + for (sfd = saned_fds, i = 0; ret != 0 && i < num_saned_fds; i++, sfd = sfd->next) { - /* Error on an fd */ - if (fdp->revents & (POLLERR | POLLHUP | POLLNVAL)) - { - for (i = 0, fdp = fds; i < nfds; i++, fdp++) - close (fdp->fd); - - free (fds); - - DBG (DBG_WARN, "run_standalone: invalid fd in set, attempting to re-bind\n"); + struct pollfd *pfd = poll_set + i; - /* Reopen sockets */ - do_bindings (&nfds, &fds); + assert(sfd); + assert(sfd->fd == pfd->fd); - break; - } - else if (! (fdp->revents & POLLIN)) + if (pfd->revents == 0) continue; + else + ret--; - fd = accept (fdp->fd, 0, 0); - if (fd < 0) - { - DBG (DBG_ERR, "run_standalone: accept failed: %s", strerror (errno)); - continue; + switch (sfd->type) { + case SANED_FD_LISTENER: + if (pfd->revents & (POLLERR | POLLHUP | POLLNVAL)) + do_rebind = SANE_TRUE; + else if (pfd->revents & POLLIN) + { + fd = accept (sfd->fd, 0, 0); + if (fd < 0 && errno != EAGAIN) + DBG (DBG_ERR, "run_standalone: accept failed: %s\n", strerror (errno)); + else if (fd >= 0) + { + handle_client (fd); + if (run_once == SANE_TRUE) + running = SANE_FALSE; /* We have handled the only connection we're going to handle */ + } + } + break; + case SANED_FD_PROCESS: + if (pfd->revents & POLLIN) { + do_reap = SANE_TRUE; + poll_set_valid = SANE_FALSE; /* We will expect to drop a pidfd */ } + } + } - handle_client (fd); - - if (run_once == SANE_TRUE) - break; /* We have handled the only connection we're going to handle */ + if (do_rebind) + { + DBG (DBG_WARN, "run_standalone: invalid fd in set, attempting to re-bind\n"); + close_fds(1 << SANED_FD_LISTENER, -1); + do_bindings (); + poll_set_valid = SANE_FALSE; } - if (run_once == SANE_TRUE) - break; + if (do_reap) + while (wait_child (-1, NULL, WNOHANG) > 0); } - for (i = 0, fdp = fds; i < nfds; i++, fdp++) - close (fdp->fd); - - free (fds); + free(poll_set); + close_fds(-1, -1); + kill_children(SIGTERM); + while (numchildren > 0) + wait_child (-1, NULL, 0); } @@ -3389,7 +3460,7 @@ run_inetd (char __sane_unused__ *sock) if (fd == -1) { - DBG (DBG_ERR, "run_inetd: duplicating fd failed: %s", strerror (errno)); + DBG (DBG_ERR, "run_inetd: duplicating fd failed: %s\n", strerror (errno)); return; } } @@ -3399,7 +3470,7 @@ run_inetd (char __sane_unused__ *sock) dave_null = open ("/dev/null", O_RDWR); if (dave_null < 0) { - DBG (DBG_ERR, "run_inetd: could not open /dev/null: %s", strerror (errno)); + DBG (DBG_ERR, "run_inetd: could not open /dev/null: %s\n", strerror (errno)); return; } @@ -3436,6 +3507,7 @@ static void usage(char *me, int err) " -a, --alone[=user] equal to `-l -D -u user'\n" " -l, --listen run in standalone mode (listen for connection)\n" " -u, --user=user run as `user'\n" + " -n, --allow-network allow saned to use network scanners\n" " -D, --daemonize run in background\n" " -o, --once exit after first client disconnects\n" " -d, --debug=level set debug level `level' (default is 2)\n" @@ -3457,6 +3529,7 @@ static struct option long_options[] = {"alone", optional_argument, 0, 'a'}, {"listen", no_argument, 0, 'l'}, {"user", required_argument, 0, 'u'}, + {"allow-network", no_argument, 0, 'n'}, {"daemonize", no_argument, 0, 'D'}, {"once", no_argument, 0, 'o'}, {"debug", required_argument, 0, 'd'}, @@ -3488,8 +3561,9 @@ main (int argc, char *argv[]) run_mode = SANED_RUN_INETD; run_foreground = SANE_TRUE; run_once = SANE_FALSE; + allow_network = SANE_FALSE; - while((c = getopt_long(argc, argv,"ha::lu:Dod:eb:p:B:", long_options, &long_index )) != -1) + while((c = getopt_long(argc, argv,"ha::lu:nDod:eb:p:B:", long_options, &long_index )) != -1) { switch(c) { case 'a': @@ -3504,6 +3578,9 @@ main (int argc, char *argv[]) case 'u': user = optarg; break; + case 'n': + allow_network = SANE_TRUE; + break; case 'D': run_foreground = SANE_FALSE; break; diff --git a/frontend/scanimage.c b/frontend/scanimage.c index ff53157..d79052c 100644 --- a/frontend/scanimage.c +++ b/frontend/scanimage.c @@ -2372,7 +2372,7 @@ Parameters are separated by a blank from single-character options (e.g.\n\ %%n (newline)\n\ -b, --batch[=FORMAT] working in batch mode, FORMAT is `out%%d.pnm' `out%%d.tif'\n\ `out%%d.png' or `out%%d.jpg' by default depending on --format\n\ - This option is incompatible with --output-file."); + This option is incompatible with --output-file.\n"); printf ("\ --batch-start=# page number to start naming files with\n\ --batch-count=# how many pages to scan in batch mode\n\ |