/* $Id: edinplace.c,v 1.11 2006/02/12 22:31:12 dm Exp $ */

/*
 *
 * Copyright (C) 2004 David Mazieres (dm@uun.org)
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation; either version 2, or (at
 * your option) any later version.
 *
 * This program 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
 * General Public License for more details.
 *
 * You should have received a copy of the GNU 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
 *
 */

#include "avutil.h"
#include <signal.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <sys/resource.h>
#include "getopt_long.h"

#ifdef NEED_PREAD_DECL
ssize_t pread(int fd, void *buf, size_t size, off_t offset);
#endif /* NEED_PREAD_DECL */
#ifdef NEED_PWRITE_DECL
ssize_t pwrite(int fd, const void *buf, size_t size, off_t offset);
#endif /* NEED_PWRITE_DECL */

char *progname;

struct filter_state {
  int file;
  int filter;
  char file_eof;
  char filter_eof;
  char filter_weof;
  off_t read_offset;
  off_t write_offset;
  size_t read_pos;
  size_t read_lim;
  size_t write_buf_size;
  size_t write_pos;
  size_t write_lim;
  char read_buf[8192];
  char *write_buf;
};
typedef struct filter_state filter_state;
pid_t filter_pid;
int error_exit = 1;

static void
close_on_exec (int s)
{
  if (fcntl (s, F_SETFD, 1) < 0) {
    perror ("F_SETFD");
    exit (error_exit);
  }
}
static int
make_async (int s)
{
  int n;
  if ((n = fcntl (s, F_GETFL)) < 0
      || fcntl (s, F_SETFL, n | O_NONBLOCK) < 0)
    return -1;
  return 0;
}

int
spawnit (int argc, char **argv)
{
  char **av = xmalloc ((argc + 1) * sizeof (*av));
  int socks[2];
  int errpipe[2];

  memcpy (av, argv, argc * sizeof (*av));
  av[argc] = NULL;

  if (socketpair (AF_UNIX, SOCK_STREAM, 0, socks) < 0) {
    perror ("socketpair");
    exit (error_exit);
  }
  close_on_exec (socks[0]);
  close_on_exec (socks[1]);
  make_async (socks[0]);
  if (pipe (errpipe) < 0) {
    perror ("socketpair");
    exit (error_exit);
  }
  close_on_exec (errpipe[0]);
  close_on_exec (errpipe[1]);

  filter_pid = fork ();
  if (filter_pid < 0) {
    perror ("fork");
    exit (error_exit);
  }
  if (!filter_pid) {
    dup2 (socks[1], 0);
    dup2 (socks[1], 1);
    execvp (av[0], av);
    perror (av[0]);
    write (errpipe[1], &errno, sizeof (errno));
    _exit (1);
  }

  close (socks[1]);
  close (errpipe[1]);
  if (read (errpipe[0], &errno, sizeof (errno)) > 0)
    exit (error_exit);
  close (errpipe[0]);
  return socks[0];
}

static void
maybe_write_to_file (filter_state *sp)
{
  ssize_t n;

  n = sp->write_lim - sp->write_pos;
  if (!sp->file_eof) {
    off_t d = sp->read_offset - sp->write_offset;
    if (n > d)
      n = d;
  }

  if (n > 0) {
    n = pwrite (sp->file, sp->write_buf + sp->write_pos, n, sp->write_offset);
    if (n < 0) {
      perror ("file");
      exit (error_exit);
      sp->write_offset += n;
      sp->write_pos += n;
    }
    sp->write_pos += n;
    sp->write_offset += n;
  }
}

void
tofilter (filter_state *sp)
{
  int n;

  if (!sp->file_eof && sp->read_pos == sp->read_lim) {
    sp->read_pos = sp->read_lim = 0;
    n = pread (sp->file, sp->read_buf, sizeof (sp->read_buf), sp->read_offset);
    if (n > 0) {
      sp->read_lim = n;
      sp->read_offset += n;
    }
    else {
      sp->file_eof = 1;
      sp->read_offset = -1;
    }
    maybe_write_to_file (sp);
  }

  if (sp->read_lim > sp->read_pos) {
    n = write (sp->filter, sp->read_buf + sp->read_pos,
	       sp->read_lim - sp->read_pos);
    if (n < 0 && errno != EAGAIN) {
      if (errno != EPIPE)
	perror ("filter");
      sp->filter_weof = 1;
      return;
    }
    else if (n >= 0)
      sp->read_pos += n;
  }

  if (sp->file_eof && sp->read_pos == sp->read_lim) {
    shutdown (sp->filter, 1);
    sp->filter_weof = 1;
  }
}

void
fromfilter (filter_state *sp)
{
  ssize_t n;

  if (sp->filter_eof)
    return;

  if (sp->write_pos == sp->write_lim)
    sp->write_pos = sp->write_lim = 0;
  else if (sp->write_pos > 0) {
    memmove (sp->write_buf, sp->write_buf + sp->write_pos,
	     sp->write_lim - sp->write_pos);
    sp->write_lim -= sp->write_pos;
    sp->write_pos = 0;
  }

  if (sp->write_lim == sp->write_buf_size) {
    sp->write_buf_size *= 2;
    sp->write_buf = realloc (sp->write_buf, sp->write_buf_size);
    if (!sp->write_buf) {
      fprintf (stderr, "out of memory\n");
      abort ();
    }
  }

  n = read (sp->filter, sp->write_buf + sp->write_lim,
	    sp->write_buf_size - sp->write_lim);
  if (n > 0) {
    sp->write_lim += n;
    maybe_write_to_file (sp);
  }
  else if (!n || errno != EAGAIN)
    sp->filter_eof = 1;
}

void
dofilter (int file, int filter, off_t start_offset)
{
  int n;
  filter_state state;
  fd_set rfds, wfds;

  bzero (&state, sizeof (state));
  state.read_offset = state.write_offset = start_offset;
  state.file = file;
  state.filter = filter;
  state.write_buf_size = 8192;
  state.write_buf = xmalloc (state.write_buf_size);

  FD_ZERO (&rfds);
  FD_ZERO (&wfds);

  for (;;) {
    if (state.filter_eof)
      FD_CLR (state.filter, &rfds);
    else
      FD_SET (state.filter, &rfds);
    if (state.filter_weof)
      FD_CLR (state.filter, &wfds);
    else
      FD_SET (state.filter, &wfds);

    n = select (1 + (state.filter > state.file ? state.filter : state.file),
		&rfds, &wfds, NULL, NULL);
    if (n < 0) {
      perror ("select");
      exit (error_exit);
    }
    if (FD_ISSET (state.filter, &wfds))
      tofilter (&state);
    else if (FD_ISSET (state.filter, &rfds))
      fromfilter (&state);

    if (state.filter_eof && state.filter_weof) {
      int status;
      ftruncate (state.file, state.write_offset);
      lseek (state.file, 0, SEEK_SET);
      close (state.file);
      close (state.filter);
      if (waitpid (filter_pid, &status, 0) < 0) {
	perror ("waitpid");
	exit (error_exit);
      }
      if (WIFEXITED (status))
	exit (WEXITSTATUS (status));
      if (WIFSIGNALED (status)) {
	struct rlimit rl;
	bzero (&rl, sizeof (rl));
	setrlimit (RLIMIT_CORE, &rl);
	raise (WTERMSIG (status));
      }
      exit (error_exit);
    }
  }
}

/* Assumes fd is already at offset zero; leaves offset at arbitrary
 * location. */
off_t
skipfrom (int fd)
{
  char buf[1024], *e;
  int n;
  off_t pos = 0;

  while (pos < 5) {
    n = read (fd, buf + pos, sizeof (buf) - pos);
    if (n == 0)
      return 0;
    if (n < 0)
      return -1;
    pos += n;
  }
  if (strncmp (buf, "From ", 5))
    return 0;

  n = pos;
  pos = 0;
  for (;;) {
    if ((e = memchr (buf, '\n', n)))
      return pos + (e - buf) + 1;
    pos += n;
    n = read (fd, buf, sizeof (buf));
    if (n < 0)
      return -1;
    if (n == 0)
      return pos;
  }
}

static void usage (void) __attribute__ ((noreturn));
static void
usage (void)
{
  fprintf (stderr,
	   "usage: %s [--skipfrom] [-x err_exit] [[-f file]"
	   " filter [arg ...]]\n",
	   progname);
  exit (error_exit);
}

int
main (int argc, char **argv)
{
  int c;
  struct option o[] = {
    { "skipfrom", no_argument, NULL, 'S' },
    { "file", required_argument, NULL, 'f' },
    { "error", required_argument, NULL, 'x' },
    { "version", no_argument, NULL, 'v' },
    { NULL, 0, NULL, 0 }
  };
  char *opt_file = NULL;
  int opt_skipfrom = 0;
  int fd = 0;
  int fil = -1;
  off_t start_offset = 0;

  progname = strrchr (argv[0], '/');
  if (progname)
    progname++;
  else
    progname = argv[0];

  while ((c = getopt_long (argc, argv, "+f:x:", o, NULL)) != -1)
    switch (c) {
    case 'S':
      opt_skipfrom = 1;
      break;
    case 'f':
      if (opt_file)
	usage ();
      opt_file = optarg;
      break;
    case 'x':
      error_exit = atoi (optarg);
      if (error_exit <= 0 || error_exit > 255)
	usage ();
      break;
    case 'v':
      version (progname, 1);
      break;
    default:
      usage ();
      break;
    }

  argv += optind;
  argc -= optind;

  if (opt_file) {
    if (!argc)
      usage ();
    fd = open (opt_file, O_RDWR, 0);
    if (fd < 0) {
      perror (opt_file);
      exit (error_exit);
    }
  }

  if (lseek (fd, 0, SEEK_SET) == -1) {
    fprintf (stderr, "cannot seek in input file (%s)\n", strerror (errno));
    exit (error_exit);
  }
  if (opt_skipfrom) {
    start_offset = skipfrom (fd);
    if (start_offset == -1) {
      fprintf (stderr, "cannot read input file (%s)\n", strerror (errno));
      exit (error_exit);
    }
  }

  if (!argc) {
    if (lseek (fd, start_offset, SEEK_SET) == -1) {
      fprintf (stderr, "cannot seek in input file (%s)\n", strerror (errno));
      exit (error_exit);
    }
    exit (0);
  }

  fil = spawnit (argc, argv);

  signal (SIGPIPE, SIG_IGN);

  dofilter (fd, fil, start_offset);

  return 0;
}


syntax highlighted by Code2HTML, v. 0.9.1