diff options
Diffstat (limited to 'src/posix-io.c')
| -rw-r--r-- | src/posix-io.c | 655 | 
1 files changed, 655 insertions, 0 deletions
| diff --git a/src/posix-io.c b/src/posix-io.c new file mode 100644 index 00000000..a7047bd2 --- /dev/null +++ b/src/posix-io.c @@ -0,0 +1,655 @@ +/* posix-io.c - Posix I/O functions +   Copyright (C) 2000 Werner Koch (dd9jn) +   Copyright (C) 2001, 2002, 2004, 2005, 2007 g10 Code GmbH + +   This file is part of GPGME. +  +   GPGME is free software; you can redistribute it and/or modify it +   under the terms of the GNU Lesser General Public License as +   published by the Free Software Foundation; either version 2.1 of +   the License, or (at your option) any later version. +    +   GPGME is distributed in the hope that it will be useful, but +   WITHOUT ANY WARRANTY; without even the implied warranty of +   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU +   Lesser General Public License for more details. +    +   You should have received a copy of the GNU Lesser General Public +   License along with this program; if not, write to the Free Software +   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA +   02111-1307, USA.  */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <assert.h> +#include <errno.h> +#include <signal.h> +#include <fcntl.h> +#include <unistd.h> +#include <sys/time.h> +#include <sys/types.h> +#include <sys/wait.h> +#ifdef HAVE_SYS_UIO_H +# include <sys/uio.h> +#endif +#include <ctype.h> +#include <sys/resource.h> +#include <unistd.h> + +#include "util.h" +#include "priv-io.h" +#include "sema.h" +#include "ath.h" +#include "debug.h" + + +void +_gpgme_io_subsystem_init (void) +{ +  struct sigaction act; + +  sigaction (SIGPIPE, NULL, &act); +  if (act.sa_handler == SIG_DFL) +    { +      act.sa_handler = SIG_IGN; +      sigemptyset (&act.sa_mask); +      act.sa_flags = 0; +      sigaction (SIGPIPE, &act, NULL); +    } +} + + +/* Write the printable version of FD to the buffer BUF of length +   BUFLEN.  The printable version is the representation on the command +   line that the child process expects.  */ +int +_gpgme_io_fd2str (char *buf, int buflen, int fd) +{ +  return snprintf (buf, buflen, "%d", fd); +} + + +static struct +{ +  _gpgme_close_notify_handler_t handler; +  void *value; +} notify_table[256]; + + +int +_gpgme_io_read (int fd, void *buffer, size_t count) +{ +  int nread; +  TRACE_BEG2 (DEBUG_SYSIO, "_gpgme_io_read", fd, +	      "buffer=%p, count=%u", buffer, count); + +  do +    { +      nread = _gpgme_ath_read (fd, buffer, count); +    } +  while (nread == -1 && errno == EINTR); + +  TRACE_LOGBUF (buffer, nread); +  return TRACE_SYSRES (nread); +} + + +int +_gpgme_io_write (int fd, const void *buffer, size_t count) +{ +  int nwritten; +  TRACE_BEG2 (DEBUG_SYSIO, "_gpgme_io_write", fd, +	      "buffer=%p, count=%u", buffer, count); +  TRACE_LOGBUF (buffer, count); + +  do +    { +      nwritten = _gpgme_ath_write (fd, buffer, count); +    } +  while (nwritten == -1 && errno == EINTR); + +  return TRACE_SYSRES (nwritten); +} + + +int +_gpgme_io_pipe (int filedes[2], int inherit_idx) +{ +  int saved_errno; +  int err; +  TRACE_BEG2 (DEBUG_SYSIO, "_gpgme_io_pipe", filedes, +	      "inherit_idx=%i (GPGME uses it for %s)", +	      inherit_idx, inherit_idx ? "reading" : "writing"); + +  err = pipe (filedes); +  if (err < 0) +    return TRACE_SYSRES (err); + +  /* FIXME: Should get the old flags first.  */ +  err = fcntl (filedes[1 - inherit_idx], F_SETFD, FD_CLOEXEC); +  saved_errno = errno; +  if (err < 0) +    { +      close (filedes[0]); +      close (filedes[1]); +    } +  errno = saved_errno; +  if (err) +    return TRACE_SYSRES (err); + +  return TRACE_SUC2 ("read=0x%x, write=0x%x", filedes[0], filedes[1]); +} + + +int +_gpgme_io_close (int fd) +{ +  int res; + +  TRACE_BEG (DEBUG_SYSIO, "_gpgme_io_close", fd); + +  if (fd == -1) +    { +      errno = EINVAL; +      return TRACE_SYSRES (-1); +    } + +  /* First call the notify handler.  */ +  if (fd >= 0 && fd < (int) DIM (notify_table)) +    { +      if (notify_table[fd].handler) +	{ +	  TRACE_LOG2 ("invoking close handler %p/%p", +		      notify_table[fd].handler, notify_table[fd].value); +	  notify_table[fd].handler (fd, notify_table[fd].value); +	  notify_table[fd].handler = NULL; +	  notify_table[fd].value = NULL; +        } +    } +  /* Then do the close.  */     +  res = close (fd); +  return TRACE_SYSRES (res); +} + + +int +_gpgme_io_set_close_notify (int fd, _gpgme_close_notify_handler_t handler, +			    void *value) +{ +  TRACE_BEG2 (DEBUG_SYSIO, "_gpgme_io_set_close_notify", fd, +	      "close_handler=%p/%p", handler, value); + +  assert (fd != -1); + +  if (fd < 0 || fd >= (int) DIM (notify_table)) +    { +      errno = EINVAL; +      return TRACE_SYSRES (-1); +    } +  notify_table[fd].handler = handler; +  notify_table[fd].value = value; +  return TRACE_SYSRES (0); +} + + +int +_gpgme_io_set_nonblocking (int fd) +{ +  int flags; +  int res; +  TRACE_BEG (DEBUG_SYSIO, "_gpgme_io_set_nonblocking", fd); + +  flags = fcntl (fd, F_GETFL, 0); +  if (flags == -1) +    return TRACE_SYSRES (-1); +  flags |= O_NONBLOCK; +  res = fcntl (fd, F_SETFL, flags); +  return TRACE_SYSRES (res); +} + + +static long int +get_max_fds (void) +{ +  char *source = NULL; +  long int fds = -1; +  int rc; + +#ifdef RLIMIT_NOFILE +  { +    struct rlimit rl; +    rc = getrlimit (RLIMIT_NOFILE, &rl); +    if (rc == 0) +      { +	source = "RLIMIT_NOFILE"; +	fds = rl.rlim_max; +      } +  } +#endif +#ifdef RLIMIT_OFILE +  if (fds == -1) +    { +      struct rlimit rl; +      rc = getrlimit (RLIMIT_OFILE, &rl); +      if (rc == 0) +	{ +	  source = "RLIMIT_OFILE"; +	  fds = rl.rlim_max; +	} +    } +#endif +#ifdef _SC_OPEN_MAX +  if (fds == -1) +    { +      long int scres; +      scres = sysconf (_SC_OPEN_MAX); +      if (scres >= 0) +	{ +	  source = "_SC_OPEN_MAX"; +	  return scres; +	} +    } +#endif +#ifdef OPEN_MAX +  if (fds == -1) +    { +      source = "OPEN_MAX"; +      fds = OPEN_MAX; +    } +#endif + +#if !defined(RLIMIT_NOFILE) && !defined(RLIMIT_OFILE) \ +  && !defined(_SC_OPEN_MAX) && !defined(OPEN_MAX) +#warning "No known way to get the maximum number of file descriptors." +#endif +  if (fds == -1) +    { +      source = "arbitrary"; +      /* Arbitrary limit.  */ +      fds = 1024; +    } + +  TRACE2 (DEBUG_SYSIO, "gpgme:max_fds", NULL, "max fds=%i (%s)", fds, source); +  return fds; +} + + +static int +_gpgme_io_waitpid (int pid, int hang, int *r_status, int *r_signal) +{ +  int status; + +  *r_status = 0; +  *r_signal = 0; +  if (_gpgme_ath_waitpid (pid, &status, hang? 0 : WNOHANG) == pid) +    { +      if (WIFSIGNALED (status)) +	{ +	  *r_status = 4; /* Need some value here.  */ +	  *r_signal = WTERMSIG (status); +	} +      else if (WIFEXITED (status)) +	*r_status = WEXITSTATUS (status); +      else +	*r_status = 4; /* Oops.  */ +      return 1; +    } +  return 0; +} + + +/* Returns 0 on success, -1 on error.  */ +int +_gpgme_io_spawn (const char *path, char *const argv[], +		 struct spawn_fd_item_s *fd_list, pid_t *r_pid) +{ +  pid_t pid; +  int i; +  int status; +  int signo; +  TRACE_BEG1 (DEBUG_SYSIO, "_gpgme_io_spawn", path, +	      "path=%s", path); +  i = 0; +  while (argv[i]) +    { +      TRACE_LOG2 ("argv[%2i] = %s", i, argv[i]); +      i++; +    } +  for (i = 0; fd_list[i].fd != -1; i++) +    if (fd_list[i].dup_to == -1) +      TRACE_LOG2 ("fd[%i] = 0x%x", i, fd_list[i].fd); +    else +      TRACE_LOG3 ("fd[%i] = 0x%x -> 0x%x", i, fd_list[i].fd, fd_list[i].dup_to); + +  pid = fork (); +  if (pid == -1)  +    return TRACE_SYSRES (-1); + +  if (!pid) +    { +      /* Intermediate child to prevent zombie processes.  */ +      if ((pid = fork ()) == 0) +	{ +	  int max_fds = get_max_fds (); +	  int fd; + +	  /* Child.  */ +	  int seen_stdin = 0; +	  int seen_stderr = 0; + +	  /* First close all fds which will not be inherited.  */ +	  for (fd = 0; fd < max_fds; fd++) +	    { +	      for (i = 0; fd_list[i].fd != -1; i++) +		if (fd_list[i].fd == fd) +		  break; +	      if (fd_list[i].fd == -1) +		close (fd); +	    } + +	  /* And now dup and close those to be duplicated.  */ +	  for (i = 0; fd_list[i].fd != -1; i++) +	    { +	      int child_fd; +	      int res; + +	      if (fd_list[i].dup_to != -1) +		child_fd = fd_list[i].dup_to; +	      else +		child_fd = fd_list[i].fd; + +	      if (child_fd == 0) +		seen_stdin = 1; +	      else if (child_fd == 2) +		seen_stderr = 1; + +	      if (fd_list[i].dup_to == -1) +		continue; + +	      res = dup2 (fd_list[i].fd, fd_list[i].dup_to); +	      if (res < 0) +		{ +#if 0 +		  /* FIXME: The debug file descriptor is not +		     dup'ed anyway, so we can't see this.  */ +		  TRACE_LOG1 ("dup2 failed in child: %s\n", +			      strerror (errno)); +#endif +		  _exit (8); +		} + +	      close (fd_list[i].fd); +	    } +	   +	  if (! seen_stdin || ! seen_stderr) +	    { +	      fd = open ("/dev/null", O_RDWR); +	      if (fd == -1) +		{ +#if 0 +		  /* FIXME: The debug file descriptor is not dup'ed +		     anyway, so we can't see this.  */ +		  TRACE_LOG1 ("can't open `/dev/null': %s\n", +			      strerror (errno)); +#endif +		  _exit (8); +		} +	      /* Make sure that the process has a connected stdin.  */ +	      if (! seen_stdin && fd != 0) +		{ +		  if (dup2 (fd, 0) == -1) +		    { +#if 0 +		  /* FIXME: The debug file descriptor is not dup'ed +		     anyway, so we can't see this.  */ +		      TRACE_LOG1 ("dup2(/dev/null, 0) failed: %s\n", +				  strerror (errno)); +#endif +		      _exit (8); +		    } +		} +	      if (! seen_stderr && fd != 2) +		if (dup2 (fd, 2) == -1) +		  { +#if 0 +		    /* FIXME: The debug file descriptor is not dup'ed +		       anyway, so we can't see this.  */ +		    TRACE_LOG1 ("dup2(dev/null, 2) failed: %s\n", +				strerror (errno)); +#endif +		    _exit (8); +		  } +	      if (fd != 0 && fd != 2) +		close (fd); +	    } +     +	  execv (path, (char *const *) argv); +	  /* Hmm: in that case we could write a special status code to the +	     status-pipe.  */ +#if 0 +	  /* FIXME: The debug file descriptor is not dup'ed anyway, so +	     we can't see this.  */ +	  TRACE_LOG1 ("exec of `%s' failed\n", path); +#endif +	  _exit (8); +	  /* End child.  */ +	} +      if (pid == -1) +	_exit (1); +      else +	_exit (0); +    } + +  TRACE_LOG1 ("waiting for child process pid=%i", pid); +  _gpgme_io_waitpid (pid, 1, &status, &signo); +  if (status) +    return TRACE_SYSRES (-1); + +  for (i = 0; fd_list[i].fd != -1; i++) +    { +      _gpgme_io_close (fd_list[i].fd); +      /* No handle translation.  */ +      fd_list[i].peer_name = fd_list[i].fd; +    } + +  if (r_pid) +    *r_pid = pid; + +  return TRACE_SYSRES (0); +} + + +/* Select on the list of fds.  Returns: -1 = error, 0 = timeout or +   nothing to select, > 0 = number of signaled fds.  */ +int +_gpgme_io_select (struct io_select_fd_s *fds, size_t nfds, int nonblock) +{ +  fd_set readfds; +  fd_set writefds; +  unsigned int i; +  int any; +  int max_fd; +  int n; +  int count; +  /* Use a 1s timeout.  */ +  struct timeval timeout = { 1, 0 }; +  void *dbg_help = NULL; +  TRACE_BEG2 (DEBUG_SYSIO, "_gpgme_io_select", fds, +	      "nfds=%u, nonblock=%u", nfds, nonblock); + +  FD_ZERO (&readfds); +  FD_ZERO (&writefds); +  max_fd = 0; +  if (nonblock) +    timeout.tv_sec = 0; + +  TRACE_SEQ (dbg_help, "select on [ "); + +  any = 0; +  for (i = 0; i < nfds; i++) +    { +      if (fds[i].fd == -1)  +	continue; +      if (fds[i].for_read) +	{ +	  assert (!FD_ISSET (fds[i].fd, &readfds)); +	  FD_SET (fds[i].fd, &readfds); +	  if (fds[i].fd > max_fd) +	    max_fd = fds[i].fd; +	  TRACE_ADD1 (dbg_help, "r0x%x ", fds[i].fd); +	  any = 1; +        } +      else if (fds[i].for_write) +	{ +	  assert (!FD_ISSET (fds[i].fd, &writefds)); +	  FD_SET (fds[i].fd, &writefds); +	  if (fds[i].fd > max_fd) +	    max_fd = fds[i].fd; +	  TRACE_ADD1 (dbg_help, "w0x%x ", fds[i].fd); +	  any = 1; +        } +      fds[i].signaled = 0; +    } +  TRACE_END (dbg_help, "]");  +  if (!any) +    return TRACE_SYSRES (0); + +  do +    { +      count = _gpgme_ath_select (max_fd + 1, &readfds, &writefds, NULL, +				 &timeout); +    } +  while (count < 0 && errno == EINTR); +  if (count < 0) +    return TRACE_SYSRES (-1); + +  TRACE_SEQ (dbg_help, "select OK [ "); +  if (TRACE_ENABLED (dbg_help)) +    { +      for (i = 0; i <= max_fd; i++) +	{ +	  if (FD_ISSET (i, &readfds)) +	    TRACE_ADD1 (dbg_help, "r0x%x ", i); +	  if (FD_ISSET (i, &writefds)) +	    TRACE_ADD1 (dbg_help, "w0x%x ", i); +        } +      TRACE_END (dbg_help, "]"); +    } +     +  /* The variable N is used to optimize it a little bit.  */ +  for (n = count, i = 0; i < nfds && n; i++) +    { +      if (fds[i].fd == -1) +	; +      else if (fds[i].for_read) +	{ +	  if (FD_ISSET (fds[i].fd, &readfds)) +	    { +	      fds[i].signaled = 1; +	      n--; +            } +        } +      else if (fds[i].for_write) +	{ +	  if (FD_ISSET (fds[i].fd, &writefds)) +	    { +	      fds[i].signaled = 1; +	      n--; +            } +        } +    } +  return TRACE_SYSRES (count); +} + + +int +_gpgme_io_recvmsg (int fd, struct msghdr *msg, int flags) +{ +  int nread; +  int saved_errno; +  struct iovec *iov; +  TRACE_BEG2 (DEBUG_SYSIO, "_gpgme_io_recvmsg", fd, +	      "msg=%p, flags=%i", msg, flags); + +  nread = 0; +  iov = msg->msg_iov; +  while (iov < msg->msg_iov + msg->msg_iovlen) +    { +      nread += iov->iov_len; +      iov++; +    } +   +  TRACE_LOG1 ("about to receive %d bytes", nread); + +  do +    { +      nread = _gpgme_ath_recvmsg (fd, msg, flags); +    } +  while (nread == -1 && errno == EINTR); +  saved_errno = errno; +  if (nread > 0) +    { +      int nr = nread; + +      iov = msg->msg_iov; +      while (nr > 0) +	{ +	  int len = nr > iov->iov_len ? iov->iov_len : nr; +	  TRACE_LOGBUF (msg->msg_iov->iov_base, len); +	  iov++; +	  nr -= len; +	} +    } +  errno = saved_errno; +  return TRACE_SYSRES (nread); +} + + +int +_gpgme_io_sendmsg (int fd, const struct msghdr *msg, int flags) +{ +  int nwritten; +  struct iovec *iov; +  TRACE_BEG2 (DEBUG_SYSIO, "_gpgme_io_sendmsg", fd, +	      "msg=%p, flags=%i", msg, flags); + +  nwritten = 0; +  iov = msg->msg_iov; +  while (iov < msg->msg_iov + msg->msg_iovlen) +    { +      nwritten += iov->iov_len; +      iov++; +    } + +  TRACE_LOG1 ("about to receive %d bytes", nwritten); +  iov = msg->msg_iov; +  while (nwritten > 0) +    { +      int len = nwritten > iov->iov_len ? iov->iov_len : nwritten; +      TRACE_LOGBUF (msg->msg_iov->iov_base, len); +      iov++; +      nwritten -= len; +    } + +  do +    { +      nwritten = _gpgme_ath_sendmsg (fd, msg, flags); +    } +  while (nwritten == -1 && errno == EINTR); +  return TRACE_SYSRES (nwritten); +} + + +int +_gpgme_io_dup (int fd) +{ +  int new_fd = dup (fd); + +  TRACE1 (DEBUG_SYSIO, "_gpgme_io_dup", fd, "new fd==%i", new_fd); + +  return new_fd; +} | 
