/* Invoke a another command multiple times with args from standard input.
   Copyright (C) 1985 Richard M. Stallman.

This program is distributed in the hope that it will be useful,
but without any warranty.  No author or distributor
accepts responsibility to anyone for the consequences of using it
or for whether it serves any particular purpose or works at all,
unless he says so in writing.

   Permission is granted to anyone to distribute verbatim copies
   of this program's source code as received, in any medium, provided that
   the copyright notice, the nonwarraty notice above
   and this permission notice are preserved,
   and that the distributor grants the recipient all rights
   for further redistribution as permitted by this notice,
   and informs him of these rights.

   Permission is granted to distribute modified versions of this
   program's source code, or of portions of it, under the above
   conditions, plus the conditions that all changed files carry
   prominent notices stating who last changed them and that the
   derived material, including anything packaged together with it and
   conceptually functioning as a modification of it rather than an
   application of it, is in its entirety subject to a permission
   notice identical to this one.

   Permission is granted to distribute this program (verbatim or
   as modified) in compiled or executable form, provided verbatim
   redistribution is permitted as stated above for source code, and
    A.  it is accompanied by the corresponding machine-readable
      source code, under the above conditions, or
    B.  it is accompanied by a written offer, with no time limit,
      to distribute the corresponding machine-readable source code,
      under the above conditions, to any one, in return for reimbursement
      of the cost of distribution.   Verbatim redistribution of the
      written offer must be permitted.  Or,
    C.  it is distributed by someone who received only the
      compiled or executable form, and is accompanied by a copy of the
      written offer of source code which he received along with it.

   Permission is granted to distribute this program (verbatim or as modified)
   in executable form as part of a larger system provided that the source
   code for this program, including any modifications used,
   is also distributed or offered as stated in the preceding paragraph.

In other words, you are welcome to use, share and improve this program.
You are forbidden to forbid anyone else to use, share and improve
what you give them.   Help stamp out software-hoarding!  */

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <sys/file.h>

/* This string (if nonzero), when seen on stdin, is treated as
   end of file.  NOT YET IMPLEMENTED */

char *logical_eof;

/* If this string is nonzero, then instead of putting the
   args from stdin at the end of the subcommand argument list,
   they are all stuck into the initial args, replacing each
   occurrence of the `replace_string' in the initial args.  */

char *replace_string;

/* If nonzero, limits the total number of nonblank lines from stdin that
   can be used in a single invocation of the command.  */

int lines_per_call;

/* If nonzero, limits the total number of stdin arguments that
   can be used in a single invocation of the command.  */

int args_per_call;

/* If nonzero, limits total size of all arguments (from stdin)
   to be passed in a single command.   A stdin argument that 
   brings the total past this amount will wait for the next time.  */

int max_arglist_size;

/* nonzero means query about each command about to be executed.  */

int query_mode;

/* nonzero means print each command about to be executed, on stderr.  */

int trace_mode;

/* Vector that contains the args read from stdin, for the next
   invocation of the command.  */

char **inargv;

/* Number of elements space is allocated for in inargv */

int inargv_alloc_len;

/* Nonzero if have encountered end of file on stdin.
   Needed because ^D on the terminal seems to cause
   getchar to return -1 only once, not forevermore.  */

int at_eof;

/* Channel for reading answers to queries from /dev/tty */

FILE *query_channel;

/* forward declarations of functions found below. */
char *substitute ();
char *read_arg ();

extern int errno;
extern int sys_nerr;
extern char *sys_errlist[];

#define max(a, b) ((a) < (b) ? (b) : (a))

main (argc, argp)
     int argc;
     char **argp;
{
  register int returncode;
  register int nargs;
  char **command;
  int commandc;

  at_eof = 0;

  returncode = decode_switches (argc, argp);
  command = &argp[returncode];
  commandc = argc - returncode;

  if (query_mode)
    query_channel = fopen ("/dev/tty", "r");

  inargv_alloc_len = max (100, args_per_call);
  inargv = (char **) xmalloc (inargv_alloc_len * sizeof (char *));

  while (!feof (stdin))
    {
      nargs = read_args ();
      if (!nargs)
	break;			/* If we were at eof after all */
      returncode = do_command (commandc, command, nargs, inargv);
      if (returncode == -1)
	break;
      free_args (nargs);
    }

  return 0;
}

/* Process the arguments that are switches for xargs,
   setting up the option variables appropriately,
   then return the index of the first argument that is not
   a switch for xargs.  This argument is the name of the
   command for xargs to run, and the following arguments
   are the initial arguments for that command.  */

int
decode_switches (argc, argv)
     int argc;
     char **argv;
{
  register int i;
  register char *p;
  register char c;

  logical_eof = "_";
  replace_string = 0;
  args_per_call = 0;
  lines_per_call = 0;
  max_arglist_size = 0;
  query_mode = 0;
  trace_mode = 0;

  for (i = 1; i < argc && argv[i][0] == '-'; i++)
    {
      if (argv[i][0] == '-')
	{
	  p = &argv[i][1];
	  while (c = *p++)
	    switch (c)
	      {
	      case 'e':
		if (*p)
		  {
		    logical_eof = p;
		    p = "";
		  }
		else
		  logical_eof = argv[++i];
		break;

	      case 'i':
		if (*p)
		  {
		    replace_string = p;
		    p = "";
		  }
		else
		  replace_string = argv[++i];
		lines_per_call = 1;
		break;

	      case 'l':
		if (*p)
		  {
		    lines_per_call = atoi (p);
		    p = "";
		  }
		else
		  lines_per_call = atoi (argv[++i]);
		break;

	      case 'n':
		if (*p)
		  {
		    args_per_call = atoi (p);
		    p = "";
		  }
		else
		  args_per_call = atoi (argv[++i]);
		break;

	      case 'r':
		if (*p)
		  {
		    replace_string = p;
		    p = "";
		  }
		else
		  replace_string = argv[++i];
		break;

	      case 's':
		if (*p)
		  {
		    max_arglist_size = atoi (p);
		    p = "";
		  }
		else
		  max_arglist_size = atoi (argv[++i]);
		break;

	      case 'p':
	      case 'q':
		query_mode = 1;
	      case 't':
		trace_mode = 1;
		break;
	      }
	}
    }

  if (lines_per_call == 0 && args_per_call == 0 && max_arglist_size == 0)
    max_arglist_size = 80;

  return i;
}

/* Read args from stdin one by one into inargv,
   tracking number of args, number of lines and size of args,
   until some specified limit is exceeded or end of input is reached.

   At that point, return the number of args that were read
   (and are now in inargv).  */

read_args ()
{
  int nargs = 0;
  register char *arg;
  int lines = 0;
  register int args_size = 0;
  register int c;
  
  while (1)
    {
      if (args_per_call && nargs >= args_per_call)
	break;

      if (lines_per_call)
	{
	  c = getchar ();
	  while (c == ' ' || c == '\t')
	    c = getchar ();
	  if (c == '\n')
	    lines++;
	  else if (c >= 0)
	    ungetc (c, stdin);
	  else
	    at_eof = 1;
	    
	  if (lines >= lines_per_call)
	    break;
	}

      if (max_arglist_size && args_size >= max_arglist_size)
	break;

      arg = read_arg ();
      if (!arg)			/* Zero means eof was reached.  */
	break;

      if (nargs == inargv_alloc_len)
	{
	  inargv_alloc_len *= 2;
	  inargv = (char **) xrealloc (inargv, inargv_alloc_len);
	}
      inargv[nargs++] = arg;
      args_size += strlen (arg) + 1;
    }
  return nargs;
}

/* Free the args that are now in inargv.
   The argument nargs says how many of them are valid (to be freed).  */

free_args (nargs)
     int nargs;
{
  register int i;
  for (i = 0; i < nargs; i++)
    free (inargv[i]);
}

/* Read the next argument from stdin, and return the string address.  */
/* Returns 0 if there is no arg due to eof */

char *
read_arg ()
{
  register char *arg;
  register int arg_alloc_len;
  int arglen;
  register int c;
  int c_string;

  if (at_eof)
    return 0;

  /* Skip whitespace; if reach eof, return 0.  */

  while (1)
    {
      c = getchar ();
      if (c != ' ' && c != '\t' && c != '\n')
	{
	  if (c < 0)
	    {
	      at_eof = 1;
	      return 0;
	    }

	  ungetc (c, stdin);
	  break;
	}
    }

  /* Initialize the argument accumulation,
     now that we know there really is an argument.  */

  arg_alloc_len = 8;
  arg = (char *) xmalloc (arg_alloc_len);
  arglen = 0;

  /* If first character is ", C string syntax is used.  */

  c_string = c == '"';
  if (c_string)
    getchar ();

  while (1)
    {
      /* Read one more effective character from the input.
	 This involves reading enough input to give one character
	 to put into the string.  */

      c = getchar ();
      if (c_string)
	/* Case where argument uses string syntax. */
	{
	  if (c < 0)
	    error ("unterminated C-string argument in stdin");

	  if (c == '"')
	    {
	      c = getchar ();
	      if (c != '"')
		{
		  ungetc (c, stdin);
		  break;
		}
	    }
	  if (c == '\\')
	    c = read_escape ();
	}
      else
	{
	  /* Argument does not use string syntax. */

	  if (c == ' ' || c == '\t' || c == '\n')
	    {
	      /* Must leave this so that read_args can see the
		 terminating character, to see whether end of line follows.  */
	      ungetc (c, stdin);
	      break;
	    }

	  if (c == '\\')
	    c = read_escape ();
	}

      /* If we just read \RET, don't store any character */
      if (c == -2)
	continue;

      /* If we just read eof, set flag so we don't try to read more args,
	 and also return with what we have got.  */
      if (c == -1)
	{
	  at_eof = 1;
	  break;
	}

      /* Store the character just read into the arg being accumulated.  */

      if (arglen == arg_alloc_len)
	{
	  arg_alloc_len *= 2;
	  arg = (char *) xrealloc (arg, arg_alloc_len);
	}
      arg[arglen++] = c;
    }

  arg[arglen] = 0;
  return arg;
}

/* Assuming a backslash has just been read,
   read the following characters of the escape sequence
   and return the character they stand for.

   Return -2 in the case of something like \RET which
   stands for no characters at all.

   Return -1 in case of eof (with error message, usually) */

read_escape ()
{
  register int c = getchar ();
  register int accum = 0;
  register int i;

  switch (c)
    {
    case 't':
      return '\t';

    case 'n':
      return '\n';

    case 'b':
      return '\b';

    case 'r':
      return '\r';

    case 'f':
      return '\f';

    case '\n':
      return -2;

    case '\\':
    case '"':
    case '\'':
      return c;

    default:
      if (c >= '0' && c <= '9')
	{
	  ungetc (c, stdin);
	  for (i = 0; i < 3; i++)
	    {
	      c = getchar ();
	      if (!(c >= '0' && c <= '9'))
		{
		  if (c < 0)
		    {
		      error ("end of input in middle of backslash-sequence");
		      return -1;
		    }
		  ungetc (c, stdin);
		  break;
		}
	      accum *= 8;
	      accum += c - '0';
	    }
	  return accum;
	}

      return c;
    }
}

/* Execute the specified subcommand once.
   The command name and initial arguments (from the args to `xargs')
   are in the vector cmdargv, with length in cmdargc.
   The arguments read from stdin this time are in
   the vector inargv, length in inargc.

   The two sets of arguments are merged in the appropriate fashion
   and then the command is executed,
   The status returned by the command becomes the value of do_command.  */

do_command (cmdargc, cmdargv, inargc, inargv)
     int cmdargc;
     char **cmdargv;
     int inargc;
     char **inargv;
{
  register char **argv;
  register int argc;
  register char c;
  register char answer;
  register int i;
  union wait waitblock;
  char *combined_inargs;
  int total;
  int minus_one;

  /* argv gets the merged vector of arguments, with length in argc.  */

  if (!replace_string)
    {
      /* the simple case: put the args from stdin
	 after the args from the command.  */
      argc = cmdargc + inargc;
      argv = (char **) alloca ((argc + 2) * sizeof (char *)) + 1;
      bcopy (cmdargv, argv, cmdargc * sizeof (char *));
      bcopy (inargv, &argv[cmdargc], inargc * sizeof (char *));
      argv[argc] = 0;
    }
  else
    {
      /* harder case: substitute the inargs
	 for each occurrence of the replace_string
	 among the command args.
	 Each command arg remains a single arg
	 even if spaces are inserted within it.  */
      argc = cmdargc;
      argv = (char **) alloca ((argc + 2) * sizeof (char *)) + 1;

      /* Initially, start out with the command args, unchanged */
      bcopy (cmdargv, argv, cmdargc * sizeof (char *));
      argv[argc] = 0;

      /* Combine the inargs into one string */
      for (i = 0; i < inargc; i++)
	total += strlen (inargv[i]) + 1;

      combined_inargs = (char *) xmalloc (total);

      for (i = 0, total = 0; i < inargc; i++)
	{
	  strcpy (&combined_inargs[total], inargv[i]);
	  total += strlen (inargv[i]) + 1;
	  combined_inargs[total - 1] = ' ';
	}

      combined_inargs[total - 1] = 0;

      /* Now look through each one, replacing when opportunity arises */
      for (i = 0; i < argc; i++)
	argv[i] = substitute (argv[i], replace_string, combined_inargs);
    }

  /* In trace mode, print the command about to be executed.  */

  if (trace_mode)
    {
      for (i = 0; i < argc; i++)
	{
	  if (i > 0)
	    putc (' ', stderr);
	  fprintf (stderr, "%s", argv[i]);
	}

      /* In query mode, ask user whether to run it really.  */

      if (query_mode)
	{
	  fprintf (stderr, "? ");
	  fflush (stderr);
	  c = getc (query_channel);
	  answer = c;
	  /* Dispose of an entire line of input */
	  while (c != '\n' && c >= 0)
	    c = getc (query_channel);
	  if (answer != 'y' && answer != 'Y')
	    return 0;
	}
      else
	putc ('\n', stderr);
    }
  fflush (stderr);

  if (!vfork ())
    {
      execvp (argv[0], argv);
      perror_with_name (argv[0]);
      fflush (stderr);
      _exit (-1);
    }

  /* Figure out what -1 looks like after truncation
     to fit in the unsigned field w_retcode */
  waitblock.w_retcode = -1;
  minus_one = waitblock.w_retcode;
  waitblock.w_retcode = 0;

  wait (&waitblock);

  /* Free any space allocated by calls to `substitute', above */

  for (i = 0; i < argc; i++)
    if (argv[i] != cmdargv[i])
      free (argv[i]);

  free (argv - 1);

  if (waitblock.w_retcode == minus_one)
    return -1;
  return (waitblock.w_retcode);
}

/* Search the string `instring' for occurrences of `forstring',
   and replace each one with `replacement'.  `replacement'
   itself is not searched for occurrences of `forstring'.
   The result is returned as a string that has been malloc'd.  */

char *
substitute (instring, forstring, replacement)
     char *instring;
     char *forstring;
     char *replacement;
{
  register char *p = instring;
  register char c;
  register char s = forstring[0];
  int allocated = 0;
  int len = strlen (forstring);
  int instringlen = strlen (instring);
  int replen = strlen (replacement);
  char *new;

  while (c = *p++)
    if (c == s && !strncmp (p, &forstring[1], len - 1))
      {
	p--;
	new = (char *) xmalloc (instringlen + replen - len + 1);
	bcopy (instring, new, p - instring);
	bcopy (replacement, &new[p - instring], replen);
	bcopy (&p[len], &new[p - instring + replen],
	       instringlen - (p - instring) - len);
	instringlen += replen - len;
	if (allocated)
	  free (instring);
	p += new - instring;
	instring = new;
	instring[instringlen] = 0;
	allocated = 1;
	p += replen;
      }
  return instring;
}

/* Various standard subroutines of general use,
   not related specifically to xargs.  */

xrealloc (obj, size)
     int obj, size;
{
  int val = realloc (obj, size);

  if (!val)
    fatal ("memory exhausted", 0, 0);

  return val;
}

xmalloc (size)
     int size;
{
  int val = malloc (size);

  if (!val)
    fatal ("memory exhausted", 0, 0);

  return val;
}

perror_with_name (name)
     char *name;
{
  int err = errno;

  if (err <= sys_nerr)
    error ("%s: %s", name, sys_errlist[err]);
  else
    error ("%s: %s", name, "unknown system error");
}

fatal (string, arg, arg2)
     char *string;
     int arg, arg2;
{
  error (string, arg, arg2);
  exit (1);
}

error (string, arg, arg2)
     char *string;
     int arg, arg2;
{
  fprintf (stderr, "xargs: ");
  fprintf (stderr, string, arg, arg2);
  fprintf (stderr, "\n");
}
