From edf2a1e3c810027857215df006d179a20edbf31d Mon Sep 17 00:00:00 2001
From: James Youngman <jay@gnu.org>
Date: Sun, 11 Apr 2010 23:27:45 +0100
Subject: [PATCH] Implement xargs --process-slot-var.
To: findutils-patches@gnu.org

* xargs/xargs.c (set_slot_var): New function; sets an environment
variable to the index of the entry in pids[] that represents the
relevant child process.  This can be used in rudimentary load
distribution systems.
(slot_var_name): the name of the variable to use (selected by
--process-slot-var).
(enum LongOptionIdentifier): Unique identifiers for long options
with no short option equivalent (--process-slot-var is the first).
(longopts): Add --process-slot-var.
(add_proc): return the index within pids[] that we selected.
(main): Pass &option_index to getopt_long (option_index is a new
variable) in order to identify which long option was passed.
Handle --process-slot-var.
(prep_child_for_exec): Call set_slot_var.
(usage): Mention --process-slot-var.
* doc/find.texi (xargs options): Document --process-slot-var.
* xargs/xargs.1: Likewise.
* NEWS: Mention this change.

Signed-off-by: James Youngman <jay@gnu.org>
---
 ChangeLog     |   20 +++++++++++
 NEWS          |    9 +++++
 doc/find.texi |    6 +++
 xargs/xargs.1 |   12 +++++++
 xargs/xargs.c |  103 +++++++++++++++++++++++++++++++++++++++++++++++++++++---
 5 files changed, 144 insertions(+), 6 deletions(-)

diff --git a/ChangeLog b/ChangeLog
index 28d2255..4cae90f 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -401,6 +401,26 @@
 
 2010-04-11  James Youngman  <jay@gnu.org>
 
+	Implement xargs --process-slot-var.
+	* xargs/xargs.c (set_slot_var): New function; sets an environment
+	variable to the index of the entry in pids[] that represents the
+	relevant child process.  This can be used in rudimentary load
+	distribution systems.
+	(slot_var_name): the name of the variable to use (selected by
+	--process-slot-var).
+	(enum LongOptionIdentifier): Unique identifiers for long options
+	with no short option equivalent (--process-slot-var is the first).
+	(longopts): Add --process-slot-var.
+	(add_proc): return the index within pids[] that we selected.
+	(main): Pass &option_index to getopt_long (option_index is a new
+	variable) in order to identify which long option was passed.
+	Handle --process-slot-var.
+	(prep_child_for_exec): Call set_slot_var.
+	(usage): Mention --process-slot-var.
+	* doc/find.texi (xargs options): Document --process-slot-var.
+	* xargs/xargs.1: Likewise.
+	* NEWS: Mention this change.
+
 	Fix syntax checks problems for check sc_unmarked_diagnostics.
 	* find/tree.c (get_pred_cost): Clarify an error message and mark
 	it for translation.
diff --git a/NEWS b/NEWS
index 98f9859..fb8205e 100644
--- a/NEWS
+++ b/NEWS
@@ -2,6 +2,15 @@ GNU findutils NEWS - User visible changes.      -*- outline -*- (allout)
 
 * Major changes in release 4.5.11-git, YYYY-MM-DD
 
+** Functional Enhancements to xargs
+
+A new option is provided, --process-slot-var.   If you set this, xargs
+will set the indicated environment variable in each child.  The value
+of the environment variable is a unique decimal number lower than the
+current value of the --max-procs limit at the time the process was
+started.  These numbers are re-used, but no executing child process
+will have the same value as another executing child process.
+
 * Major changes in release 4.5.10, 2011-05-11
 
 ** Documentation Changes
diff --git a/doc/find.texi b/doc/find.texi
index 1f278de..a088c01 100644
--- a/doc/find.texi
+++ b/doc/find.texi
@@ -3689,6 +3689,12 @@ Exit if the size (see the @samp{-s} option) is exceeded.
 Run simultaneously up to @var{max-procs} processes at once; the default is 1.  If
 @var{max-procs} is 0, @code{xargs} will run as many processes as
 possible simultaneously.
+
+@item --process-slot-var=@var{environment-variable-name}
+Set the environment variable @var{environment-variable-name} to a
+unique value in each running child process.  Each value is a decimal
+integer.  Values are reused once child processes exit.  This can be
+used in a rudimentary load distribution scheme, for example.
 @end table
 
 @node Invoking the shell from xargs
diff --git a/xargs/xargs.1 b/xargs/xargs.1
index f490b79..6900d6c 100644
--- a/xargs/xargs.1
+++ b/xargs/xargs.1
@@ -23,6 +23,7 @@ xargs \- build and execute command lines from standard input
 [\fB\-\-max\-chars\fR=\fImax-chars\fR]
 [\fB\-P \fImax-procs\fR]
 [\fB\-\-max\-procs\fR=\fImax-procs\fR]
+[\fB\-\-process\-slot\-var\fR=\fIname\fR]
 [\fB\-\-interactive\fR]
 [\fB\-\-verbose\fR]
 [\fB\-\-exit\fR]
@@ -333,6 +334,17 @@ or a SIGUSR2 to decrease the number.  You cannot decrease it below 1.
 never terminates its commands; when asked to decrease, it merely
 waits for more than one existing command to terminate before starting
 another.
+
+.TP
+.PD
+.BR \-\-process\-slot\-var "=\fIname\fR"
+Set the environment variable
+.I name
+to a unique value in each running child
+process.  Values are reused once child processes exit.
+This can be used in a rudimentary load distribution scheme, for
+example.
+
 .SH "EXAMPLES"
 .nf
 .B find /tmp \-name core \-type f \-print | xargs /bin/rm \-f
diff --git a/xargs/xargs.c b/xargs/xargs.c
index 3cc1832..55de2f3 100644
--- a/xargs/xargs.c
+++ b/xargs/xargs.c
@@ -177,6 +177,18 @@ static bool query_before_executing = false;
 static char input_delimiter = '\0';
 
 
+/* Name of the environment variable which indicates which 'slot'
+ * the child process is in.   This can be used to do some kind of basic
+ * load distribution.   We guarantee not to allow two processes to run
+ * at the same time with the same value of this variable.
+ */
+static char* slot_var_name = NULL;
+
+enum LongOptionIdentifier
+  {
+    PROCESS_SLOT_VAR = CHAR_MAX+1
+  };
+
 static struct option const longopts[] =
 {
   {"null", no_argument, NULL, '0'},
@@ -193,6 +205,7 @@ static struct option const longopts[] =
   {"show-limits", no_argument, NULL, 'S'},
   {"exit", no_argument, NULL, 'x'},
   {"max-procs", required_argument, NULL, 'P'},
+  {"process-slot-var", required_argument, NULL, PROCESS_SLOT_VAR},
   {"version", no_argument, NULL, 'v'},
   {"help", no_argument, NULL, 'h'},
   {NULL, no_argument, NULL, 0}
@@ -228,7 +241,6 @@ static long parse_num (char *str, int option, long min, long max, int fatal);
 static void usage (FILE * stream);
 
 
-
 static char
 get_char_oct_or_hex_escape (const char *s)
 {
@@ -367,7 +379,7 @@ smaller_of (size_t a, size_t b)
 int
 main (int argc, char **argv)
 {
-  int optc;
+  int optc, option_index;
   int show_limits = 0;			/* --show-limits */
   int always_run_command = 1;
   char *input_file = "-"; /* "-" is stdin */
@@ -480,7 +492,7 @@ main (int argc, char **argv)
     }
 
   while ((optc = getopt_long (argc, argv, "+0a:E:e::i::I:l::L:n:prs:txP:d:",
-			      longopts, (int *) 0)) != -1)
+			      longopts, &option_index)) != -1)
     {
       switch (optc)
 	{
@@ -601,6 +613,27 @@ main (int argc, char **argv)
 	  display_findutils_version ("xargs");
 	  return 0;
 
+	case PROCESS_SLOT_VAR:
+	  if (strchr (optarg, '='))
+	    {
+	      error (EXIT_FAILURE, 0,
+		     _("option --%s may not be set to a value which includes `='"),
+		     longopts[option_index]);
+	    }
+	  slot_var_name = optarg;
+	  if (0 != unsetenv (slot_var_name))
+	    {
+	      /* This is a fatal error, otherwise some child process
+		 may not be able to guarantee that no two children
+		 have the same value for this variable; see
+		 set_slot_var.
+	      */
+	      error (EXIT_FAILURE, errno,
+		     _("failed to unset environment variable %s"),
+		     slot_var_name);
+	    }
+	  break;
+
 	default:
 	  usage (stderr);
 	  return 1;
@@ -1044,6 +1077,55 @@ print_args (bool ask)
   return false;
 }
 
+/* Set SOME_ENVIRONMENT_VARIABLE=n in the environment. */
+static void
+set_slot_var (unsigned int n)
+{
+  static const char *fmt = "%u";
+  int size;
+  char *buf;
+
+
+  /* Determine the length of the buffer we need.
+
+     If the result would be zero-length or have length (not value) >
+     INT_MAX, the assumptions we made about how snprintf behaves (or
+     what UINT_MAX is) are wrong.  Hence we have a design error (not
+     an environmental error).
+  */
+  size = snprintf (NULL, 0u, fmt, n);
+  assert (size > 0);
+
+
+  /* Failures here are undesirable but not fatal, since we can still
+     guarantee that this child does not have a duplicate value of the
+     indicated environment variable set (since the parent unset it on
+     startup).
+  */
+  if (NULL == (buf = malloc (size+1)))
+    {
+      error (0, errno, _("unable to allocate memory"));
+    }
+  else
+    {
+      snprintf (buf, size+1, fmt, n);
+
+      /* If the user doesn't want us to set the variable, there is
+	 nothing to do.  However, we defer the bail-out until this
+	 point in order to get better test coverage.
+      */
+      if (slot_var_name)
+	{
+	  if (setenv (slot_var_name, buf, 1) < 0)
+	    {
+	      error (0, errno,
+		     _("failed to set environment variable %s"), slot_var_name);
+	    }
+	}
+      free (buf);
+    }
+}
+
 
 /* Close stdin and attach /dev/null to it.
  * This resolves Savannah bug #3992.
@@ -1051,6 +1133,14 @@ print_args (bool ask)
 static void
 prep_child_for_exec (void)
 {
+  /* The parent will call add_proc to allocate a slot.  We do the same in the
+     child to make sure we get the same value.
+
+     We use 0 here in order to avoid generating a data structure that appears
+     to indicate that we (the child) have a child. */
+  unsigned int slot = add_proc (0);
+  set_slot_var (slot);
+
   if (!keep_stdin)
     {
       const char inputfile[] = "/dev/null";
@@ -1257,7 +1347,7 @@ exec_if_possible (void)
 /* Add the process with id PID to the list of processes that have
    been executed.  */
 
-static void
+static unsigned int
 add_proc (pid_t pid)
 {
   unsigned int i, j;
@@ -1282,6 +1372,7 @@ add_proc (pid_t pid)
   pids[i] = pid;
   procs_executing++;
   procs_executed = true;
+  return i;
 }
 
 
@@ -1511,8 +1602,8 @@ Usage: %s [-0prtx] [--interactive] [--null] [-d|--delimiter=delim]\n\
        [-L max-lines] [-l[max-lines]] [--max-lines[=max-lines]]\n\
        [-I replace-str] [-i[replace-str]] [--replace[=replace-str]]\n\
        [-n max-args] [--max-args=max-args]\n\
-       [-s max-chars] [--max-chars=max-chars]\n\
-       [-P max-procs]  [--max-procs=max-procs] [--show-limits]\n\
+       [-s max-chars] [--max-chars=max-chars] [--show-limits]\n\
+       [-P max-procs]  [--max-procs=max-procs] --process-slot-var=name\n\
        [--verbose] [--exit] [--no-run-if-empty] [--arg-file=file]\n\
        [--version] [--help] [command [initial-arguments]]\n"),
 	   program_name);
-- 
1.7.2.5

