diff options
Diffstat (limited to 'backend/ipc.c')
-rw-r--r-- | backend/ipc.c | 563 |
1 files changed, 563 insertions, 0 deletions
diff --git a/backend/ipc.c b/backend/ipc.c new file mode 100644 index 0000000..e1d6437 --- /dev/null +++ b/backend/ipc.c @@ -0,0 +1,563 @@ +/* ipc.c -- inter-process communication (IPC) support + * Copyright (C) 2019 SEIKO EPSON Corporation + * + * License: EPSON END USER SOFTWARE LICENSE + * Author : SEIKO EPSON Corporation + * + * This file is part of Image Scan! for Linux. + * It is distributed under the terms of the EPSON END USER SOFTWARE LICENSE. + * + * You should have received a verbatim copy of the EPSON END USER SOFTWARE + * LICENSE along with the software. + */ + + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include "ipc.h" + +#include <errno.h> +#include <netinet/in.h> +#include <signal.h> +#include <stdlib.h> +#include <string.h> +#include <sys/socket.h> +#include <sys/time.h> +#include <sys/wait.h> +#include <unistd.h> + +#include "defines.h" +#include "message.h" + + +/*! Attempts to read all the data up to \a size bytes. + * MERR is returned if an error, such as a timeout, occurs. + * MEOF is returned if EOF is reached. + * Otherwise, returns the number of bytes read, which must be greater than 0. + */ +static ssize_t +recv_all (int fd, void *buf, size_t size) +{ + ssize_t n = 0; + ssize_t t = 1; + + if (0 == size) return MERR; + + while (n < size && t > 0) + { + errno = 0; + t = read (fd, buf + n, size - n); + if (0 > t) + { + err_major ("read failed: %s", strerror (errno)); + return MERR; + } + else + { + n += t; + log_call ("transferred %zd bytes, total %zd/%zd", t, n, size); + } + if (0 == t) return MEOF; + } + + return n; +} + +/*! Attempts to write all the data up to \a size bytes. + * MERR is returned if an error, such as a timeout, occurs. + * Otherwise, returns the number of bytes written. + * A return value of zero indicates that nothing was written. + */ +static ssize_t +send_all (int fd, const void *buf, size_t size) +{ + ssize_t n = 0; + ssize_t t = 1; + + if (0 == size) return MERR; + + while (n < size && t > 0) + { + errno = 0; + t = write (fd, buf + n, size - n); + if (0 > t) + { + err_major ("write failed: %s", strerror (errno)); + return MERR; + } + else + { + n += t; + log_call ("transferred %zd bytes, total %zd/%zd", t, n, size); + } + } + + return n; +} + +/*! MERR is returned if an error, such as a timeout, occurs. + * MERR is also returned if writing the ipc header failed. + * Otherwise, the number of bytes of the payload that were successfully + * written is returned. + * A return value of zero indicates that nothing was written, this should + * only occur when the payload is of zero size. + */ +ssize_t +ipc_send (int sock, uint16_t id, uint8_t type_status, + size_t size, const void* payload) +{ + ssize_t n = 0; + + n = send_all (sock, &id, sizeof (id)); + if (0 >= n) return MERR; + + n = send_all (sock, &type_status, sizeof (type_status)); + if (0 >= n) return MERR; + + n = send_all (sock, &size, sizeof (size)); + if (0 >= n) return MERR; + + if (0 == size) return 0; + if (!payload) return MERR; + + n = send_all (sock, payload, size); + + log_info ("send packet {key: %d, msg: 0x%02x, size: %zd}", + id, type_status, size); + + if (ENABLE_DEBUG && 0 < n) + { + if (MSG_DBG_IMG_THRESHOLD < n) + dbg_img (payload, n); + else + dbg_hex (payload, n); + } + + return n; +} + +/*! Caller must have allocated space for \a id, and \a type_status. + * \a payload is automatically allocated based on the size field of the + * ipcling header, and it is the responsibility of the caller to + * deallocate it. + * + * MERR is returned if an error, such as a timeout, occurs. + * MEOF is returned if EOF is reached. + * Otherwise, returns the number of bytes read. + * A return value of zero is valid, as ipc packets do not necessarily + * have to contain a payload. + */ +ssize_t +ipc_recv (int sock, uint16_t *id, uint8_t *type_status, + void** payload) +{ + size_t size = 0; + char* buf = NULL; + ssize_t n = 0; + + n = recv_all (sock, id, sizeof (*id)); + if (0 > n) return n; + + n = recv_all (sock, type_status, sizeof (*type_status)); + if (0 > n) return n; + + n = recv_all (sock, &size, sizeof (size)); + if (0 > n) return n; + + if (0 == size) return 0; + if (!payload) return MERR; + + buf = t_malloc (size, char); + if (!buf) return MERR; + + n = recv_all (sock, buf, size); + + *payload = buf; + + log_info ("recv packet {key: %d, msg: 0x%02x, size: %zd}", + *id, *type_status, size); + + if (ENABLE_DEBUG && 0 < n) + { + if (MSG_DBG_IMG_THRESHOLD < n) + dbg_img (*payload, n); + else + dbg_hex (*payload, n); + } + + return n; +} + +/*! \brief Does the real work of starting the \a child process + */ +static +SANE_Status +ipc_fork (process *child) +{ + SANE_Status s = SANE_STATUS_GOOD; + + int pipe_fd[2]; + + require (child); + + if (-1 == pipe (pipe_fd)) + { + err_fatal ("pipe: %s", strerror (errno)); + return SANE_STATUS_ACCESS_DENIED; + } + + child->pid = fork (); + if (0 == child->pid) + { + /* replace child process with a plugin program + */ + close (pipe_fd[0]); /* unused read end */ + if (0 <= dup2 (pipe_fd[1], STDOUT_FILENO)) + { + log_info ("%s[%d]: starting", child->name, getpid ()); + if (-1 == execl (child->name, child->name, NULL)) + { + err_fatal ("%s[%d]: %s", child->name, getpid (), + strerror (errno)); + } + } + else + { + err_major ("%s[%d]: %s", child->name, getpid (), + strerror (errno)); + } + + /* notify the parent process that we're done here */ + write (pipe_fd[1], "-1\n", strlen ("-1\n")); + fsync (pipe_fd[1]); + + close (pipe_fd[1]); + exit (EXIT_FAILURE); + } + + if (0 > child->pid) + { + err_fatal ("fork: %s", strerror (errno)); + s = SANE_STATUS_CANCELLED; + } + else + { + /* Check whether child process has (unexpectedly) exited. We + don't want to have zombies in our closet ;-) + */ + pid_t w = waitpid (child->pid, NULL, WNOHANG); + if (-1 == w) + { + err_minor ("waitpid: %s", strerror (errno)); + } + if (0 != w) + { + log_info ("%s[%d]: exited prematurely", child->name, child->pid); + s = SANE_STATUS_CANCELLED; + } + else + { + FILE *fp = fdopen (pipe_fd[0], "rb"); + if (fp) + { + if (1 != fscanf (fp, "%d", &(child->port))) + { + err_major ("fscanf: %s", strerror (errno)); + } + fclose (fp); + } + else + { + err_fatal ("%s", strerror (errno)); + } + } + } + close (pipe_fd[0]); + close (pipe_fd[1]); + + if (0 > child->port) + s = SANE_STATUS_CANCELLED; + + return s; +} + +/*! \brief Requests a connection to a \a child + */ +static +SANE_Status +ipc_connect (process *child) +{ + struct sockaddr_in addr; + struct timeval t; + int rv; + + require (child); + + log_call ("(%s, %d)", child->name, child->port); + + errno = 0; + child->socket = socket (AF_INET, SOCK_STREAM, 0); + if (0 > child->socket) + { + err_major ("socket: %s", strerror (errno)); + return SANE_STATUS_IO_ERROR; + } + + t.tv_sec = 30; + t.tv_usec = 0; + errno = 0; + rv = setsockopt (child->socket, SOL_SOCKET, SO_RCVTIMEO, &t, sizeof (t)); + if (0 > rv) + { + err_minor ("socket option: %s", strerror (errno)); + } + + errno = 0; + rv = setsockopt (child->socket, SOL_SOCKET, SO_SNDTIMEO, &t, sizeof (t)); + if (0 > rv) + { + err_minor ("socket option: %s", strerror (errno)); + } + + memset (&addr, 0, sizeof (addr)); + addr.sin_family = AF_INET; + addr.sin_port = htons (child->port); + addr.sin_addr.s_addr = htonl (INADDR_LOOPBACK); + + if (0 != connect (child->socket, (struct sockaddr *) &addr, sizeof (addr))) + { + err_major ("connect: %s", strerror (errno)); + return SANE_STATUS_IO_ERROR; + } + + return SANE_STATUS_GOOD; +} + +process * +ipc_exec (const char *program, const char *pkglibdir, SANE_Status *status) +{ + SANE_Status s = SANE_STATUS_GOOD; + process *child = NULL; + + log_call ("(%s, %s, %p)", program, pkglibdir, status); + + child = t_malloc (1, process); + if (!child) + { + if (status) *status = SANE_STATUS_NO_MEM; + return NULL; + } + + child->pid = -1; + child->port = -1; + child->socket = -1; + child->name = NULL; + + if (!pkglibdir) + { + child->name = strdup (program); + } + else + { + int n = (strlen (pkglibdir) + strlen (FILE_SEP_STR) + + strlen (program) + 1); + char *name = t_malloc (n, char); + + if (name) + { + sprintf (name, "%s%s%s", pkglibdir, FILE_SEP_STR, program); + child->name = name; + } + } + + if (!child->name) + { + s = SANE_STATUS_NO_MEM; + } + else if (access (child->name, X_OK)) + { + s = SANE_STATUS_ACCESS_DENIED; + } + + if (SANE_STATUS_GOOD != s) + { + if (status) *status = s; + delete (child); + return NULL; + } + + s = ipc_fork (child); + if (SANE_STATUS_GOOD == s) + { + int tries = 5; + + do + { + if (SANE_STATUS_GOOD != s) + sleep (1); + s = ipc_connect (child); + } + while (0 < --tries && SANE_STATUS_GOOD != s); + } + + if (SANE_STATUS_GOOD != s) + { + child = ipc_kill (child); + promise (!child); + } + else + { + promise (child); + promise (0 < child->pid); + promise (0 < child->port); + promise (0 < child->socket); + promise (child->name); + } + + if (status) *status = s; + + return child; +} + +process * +ipc_kill (process *child) +{ + log_call ("(%p)", child); + + if (child) + { + int status = 0; + + log_info ("terminating %s (port %d)", child->name, child->port); + + if (0 <= child->socket) + { + if (0 != close (child->socket)) + { + err_minor ("%s", strerror (errno)); + } + } + if (1 < child->pid) + { + if (0 != kill (child->pid, SIGHUP)) + { + err_minor ("%s", strerror (errno)); + } + if (child->pid != waitpid (child->pid, &status, 0)) + { + err_major ("%s", strerror (errno)); + } + + if (!WIFSIGNALED (status)) + { + err_major ("%s[%d]: went off the deep end!", + child->name, child->pid); + } + else + { + if (SIGHUP != WTERMSIG (status)) + { + err_major ("%s[%d]: %s", child->name, child->pid, + strsignal (WTERMSIG (status))); + } + } + } + + const_delete (child->name, char *); + delete (child); + } + + return child; +} + +void +ipc_dip_proc (process *child, int flag, const ipc_dip_parms *p, + SANE_Parameters *ctx, void **buffer) +{ + int socket; + uint8_t status = STATUS_NG; + uint16_t id = 0; + ssize_t n; + + require (child); + socket = child->socket; + + require (TYPE_DIP_SKEW_FLAG == flag || TYPE_DIP_CROP_FLAG == flag); + require (0 < socket && p && ctx && buffer && *buffer); + + /* inter-process procedure call, status will be STATUS_NG in case + * anything goes wrong during IPC call sequence + */ + { + n = ipc_send (socket, id, flag | TYPE_DIP_CTOR, + strlen (p->fw_name), p->fw_name); + if (strlen (p->fw_name) == n) + { + n = ipc_recv (socket, &id, &status, NULL); + if (STATUS_OK == status) + { + if (sizeof (*p) != ipc_send (socket, id, flag | TYPE_DIP_PARM, + sizeof (*p), p)) + { + status = STATUS_NG; + } + else + { + ipc_recv (socket, &id, &status, NULL); + if (STATUS_OK == status) + { + ssize_t size = ctx->bytes_per_line * ctx->lines; + + if (size != ipc_send (socket, id, flag | TYPE_DIP_DATA, + size, *buffer)) + { + err_minor ("image truncated"); + status = STATUS_NG; + } + } + } + } + } + } + + if (STATUS_NG == status) /* abort further processing */ + { + ipc_send (socket, id, flag | TYPE_DIP_DTOR, 0, NULL); + ipc_recv (socket, &id, &status, NULL); + return; + } + + /* acquire DIP results */ + { + uint8_t req = flag | TYPE_DIP_PARM; + void *buf = NULL; + ipc_dip_parms par; + + if (sizeof (par) == ipc_recv (socket, &id, &req, &buf)) + { + ssize_t size; + + memcpy (&par, buf, sizeof (par)); + size = par.parms.bytes_per_line * par.parms.lines; + + req = flag | TYPE_DIP_DATA; + delete (buf); + + if (size == ipc_recv (socket, &id, &req, &buf)) + { + memcpy (ctx, &par.parms, sizeof (*ctx)); + delete (*buffer); + *buffer = buf; + } + else + { + err_minor ("image truncated"); + delete (buf); + } + } + } + ipc_send (socket, id, flag | TYPE_DIP_DTOR, 0, NULL); + ipc_recv (socket, &id, &status, NULL); +} |