/*=-=-=-=-=-=-=-=-=-=-  RCCL/RCI Notice of Copyright  -=-=-=-=-=-=-=-=-=-=-=*
 *									    *
 *	Copyright (C) 1987 by Mike Parker. Permission to use, distribute    *
 *	and copy is granted for non-commercial and research purposes only,  *
 *	providing the authors are acknowledged and this copyright notice is *
 *	displayed. The authors provide this software in hope that it will   *
 *	useful, but do not assume responsibility for its use.		    *
 *									    *
 *	This software was developed using the facilities of the McGill 	    *
 *	University Research Center for Intelligent Machines, Montreal,	    *
 *	Canada, for a project undertaken and supported by the RCA Advanced  *
 *	Technology Laboratories, Moorestown, New Jersey.		    *
 *									    *
 *=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-*/

/*
 * xargs - accumulate arguments for a program
 *
 * Xargs reads its standard input and accumulates the strings it gets.  When
 *  it has enough, either total length or number of strings, it executes the
 *  program named as its argument.  This is useful for using programs like rm
 *  in conjunction with find:
 *
 *	find / -name \*.CKP -print | xargs rm -f
 *
 * Any options to xargs must of course occur before the name of the command
 *  xargs is to execute.  The first argument not beginning with a - sign (or
 *  being an argument to a flag) indicates the beginning of the command.
 *
 * The options are:
 *
 *	-l	causes xargs to log the command it is about to execute to
 *		  stderr just before executing it.
 *
 *	-n	don't execute the commands.  This is pretty useless except in
 *		  conjunction with -l.
 *
 *	-N<n>	tells xargs to fork and exec as soon as it has <n> arguments
 *		  accumulated (not counting any on the xargs command line).
 */

#include <stdio.h>
#include <errno.h>
#include <sys/wait.h>

/* MAXLENGTH is the maximum total length before xargs will fork and exec */
/* MAXNUMBER is the maximum number of args before fork/exec */
/* MAXARGLEN is the maximum length of a single argument */

#define MAXLENGTH 5120
#define MAXNUMBER 128
#define MAXARGLEN 1024

extern int errno;

char buf[MAXLENGTH];
char *args[MAXNUMBER+1]; /* one for the terminating null */
char arg[MAXARGLEN];

char **argvec;

int bufbase;
int argsbase;
int bufptr;
int argsptr;
int noexec;
int dolog;
int fexecn;

int getarg()
{
 int l;

 if (fgets(arg,sizeof(arg),stdin) != arg)
  { return(0);
  }
 l = strlen(arg);
 if ((l > 0) && (arg[l-1] == '\n'))
  { arg[l-1] = '\0';
  }
 return(1);
}

int Fork()
{
 while (1)
  { register int pid;
    pid = fork();
    switch (pid)
     { case 0:
	  return(0);
	  break;
       case -1:
	  fprintf(stderr,"%s: cannot fork: ",argvec[0]);
	  perror((char *)0);
	  sleep(1);
	  break;
       default:
	  return(pid);
	  break;
     }
  }
}

main(ac,av)
int ac;
char **av;
{
 int skip;
 int errs;
 int rest;

 argvec = av;
 noexec = 0;
 dolog = 0;
 fexecn = -1;
 errs = 0;
 skip = 0;
 for (ac--,av++;ac;ac--,av++)
  { if (skip > 0)
     { skip --;
       continue;
     }
    if (**av == '-')
     { rest = 0;
       for (++*av;**av;++*av)
	{ if (rest)
	   { continue;
	   }
	  switch (**av)
	   { case 'l':
		dolog = 1;
		break;
	     case 'n':
		noexec = 1;
		break;
	     case 'N':
		++*av;
		sscanf(*av,"%d",&fexecn);
		rest = 1;
		break;
	     default:
		fprintf(stderr,"%s: bad flag -%c\n",argvec[0],**av);
		errs ++;
		break;
	   }
	}
     }
    else
     { break;
     }
  }
 bufbase = 0;
 argsbase = 0;
 for (;ac;ac--,av++)
  { int l;
    args[argsbase++] = &buf[bufbase];
    if (argsbase >= MAXNUMBER)
     { fprintf(stderr,"%s: too many provided arguments\n",argvec[0]);
       exit(1);
     }
    l = strlen(*av) + 1;
    if (bufbase+l >= MAXLENGTH)
     { fprintf(stderr,"%s: provided arguments too long\n",argvec[0]);
       exit(1);
     }
    strcpy(buf+bufbase,*av);
    bufbase += l;
  }
 if (fexecn > 0)
  { fexecn += argsbase;
  }
 if ((fexecn <= argsbase) || (fexecn > MAXNUMBER))
  { fexecn = MAXNUMBER;
  }
 if (errs)
  { exit(1);
  }
 reset_ptrs();
 while (getarg())
  { int l;
    l = strlen(arg) + 1;
    if ((bufptr+l >= MAXLENGTH) || (argsptr >= fexecn))
     { do_cmd();
     }
    if (bufptr+l >= MAXLENGTH)
     { do_one_cmd();
     }
    else
     { args[argsptr++] = buf + bufptr;
       strcpy(buf+bufptr,arg);
       bufptr += l;
     }
  }
 if (argsptr > argsbase)
  { do_cmd();
  }
 exit(0);
}

reset_ptrs()
{
 bufptr = bufbase;
 argsptr = argsbase;
}

do_cmd()
{
 args[argsptr++] = 0;
 do_fork();
 reset_ptrs();
}

do_one_cmd()
{
 args[argsptr++] = arg;
 args[argsptr++] = 0;
 do_fork();
 reset_ptrs();
}

do_fork()
{
 if (dolog)
  { int i;
    for (i=0;args[i];i++)
     { fprintf(stderr,"%s%c",args[i],args[i+1]?' ':'\n');
     }
  }
 if (! noexec)
  { int child;
    child = Fork();
    if (child)
     { union wait st;
       int dead;
       while (1)
	{ dead = wait(&st);
	  if (dead < 0)
	   { if (errno != EINTR)
	      { fprintf(stderr,"%s: error waiting for child: ",argvec[0]);
		perror((char *)0);
	      }
	     continue;
	   }
	  if (dead == child)
	   { break;
	   }
	  /* don't print messages; there's a bug in sh: for x|y|z, it seems to make x and y children of z. */
	  /* fprintf(stderr,"%s: unknown child %d died (ignored)\n",argvec[0],dead); */
	}
     }
    else
     { execvp(args[0],args);
       fprintf(stderr,"%s: cannot exec %s\n",argvec[0],args[0]);
       _exit(1);
     }
  }
}
