about summary refs log tree commit diff
diff options
context:
space:
mode:
authorLaurent Bercot <ska-skaware@skarnet.org>2024-11-06 08:05:33 +0000
committerLaurent Bercot <ska@appnovation.com>2024-11-06 08:05:33 +0000
commit3d91d9ce645efa020800b85be1ac2727ebcbad19 (patch)
tree4461d2c99abfb80f3955b4f775b26767e823fc6a
parent32ed930a967c83c5b683d1d22c286b46a9f792ea (diff)
downloadexecline-3d91d9ce645efa020800b85be1ac2727ebcbad19.tar.gz
execline-3d91d9ce645efa020800b85be1ac2727ebcbad19.tar.xz
execline-3d91d9ce645efa020800b85be1ac2727ebcbad19.zip
Prepare for 2.9.7.0 ; rework forx/forstdin -p, add -P maxpar
 Also refactor several things to keep global footprint low

Signed-off-by: Laurent Bercot <ska@appnovation.com>
-rw-r--r--NEWS5
-rw-r--r--doc/forstdin.html14
-rw-r--r--doc/forx.html14
-rw-r--r--doc/index.html2
-rw-r--r--doc/upgrade.html7
-rw-r--r--package/deps.mak8
-rw-r--r--package/info2
-rw-r--r--src/execline/forstdin.c89
-rw-r--r--src/execline/forx.c78
-rw-r--r--src/include/execline/execline.h17
-rw-r--r--src/libexecline/deps-lib/execline2
-rw-r--r--src/libexecline/el_forx_pidinfo.c25
-rw-r--r--src/libexecline/el_gspawn0.c8
-rw-r--r--src/libexecline/el_modif_and_exec.c5
-rw-r--r--src/libexecline/el_modif_and_spawn.c13
-rw-r--r--src/libexecline/el_modifs_and_exec.c5
-rw-r--r--src/libexecline/el_spawn0.c8
-rw-r--r--src/libexecline/el_trueargv.c7
18 files changed, 150 insertions, 159 deletions
diff --git a/NEWS b/NEWS
index 8e5602f..72be9f5 100644
--- a/NEWS
+++ b/NEWS
@@ -1,5 +1,10 @@
 Changelog for execline.
 
+In 2.9.7.0
+
+ - New "-P maxpar" option to forx and forstdin, for bounded parallelism.
+
+
 In 2.9.6.1
 ----------
 
diff --git a/doc/forstdin.html b/doc/forstdin.html
index 25f951f..14b9a3b 100644
--- a/doc/forstdin.html
+++ b/doc/forstdin.html
@@ -50,6 +50,11 @@ and 1 if it hasn't read anything. </li>
 instance to finish before spawning the next one. forstdin will
 still wait for all instances of <em>loop</em> to terminate before
 exiting, though. </li>
+ <li> <tt>-P</tt>&nbsp;<em>maxpar</em>&nbsp;: like <tt>-p</tt>, but only
+run up to <em>maxpar</em> instances at a time. Minimum is 1; maximum is
+10000. <tt>-p</tt> is equivalent to <tt>-P 10000</tt>, i.e. it can
+spawn a very large number of loop instances in parallel, but it's
+technically not infinite. </li>
  <li> <tt>-o</tt>&nbsp;<em>okcodes</em>&nbsp;: <em>okcodes</em> must
 be a comma-separated list of exit codes. If the <tt>-p</tt> flag
 hasn't been given and <em>loop</em> exits with one of the codes in
@@ -63,7 +68,7 @@ that will make forstdin break the loop and exit, and the unlisted exit
 codes will make it keep looping. </li>
  <li> <tt>-e</tt>&nbsp;: no autoimport. This is the default. </li>
  <li> <tt>-E</tt>&nbsp;: autoimport. Instead of spawning
-<em>loop...</em>, spawn <tt>importas -ui <em>variable</em> <em>variable</em>
+<em>loop...</em>, spawn <tt>importas -uSi <em>variable</em>
 <em>loop...</em></tt>. This substitutes <em>variable</em> into the command
 line instead of putting it into the environment. </li>
 </ul>
@@ -94,12 +99,5 @@ used simultaneously, the rightmost one wins. </li>
 the input is only split on newlines. </li>
 </ul>
 
-<h2> Notes </h2>
-
-<ul>
- <li> You can start <em>loop...</em> with <tt>importas -u <em>variable</em> <em>variable</em></tt>
-to perform variable substitution. </li>
-</ul>
- 
 </body>
 </html>
diff --git a/doc/forx.html b/doc/forx.html
index 8f1d558..b20ab65 100644
--- a/doc/forx.html
+++ b/doc/forx.html
@@ -29,7 +29,7 @@
 </p>
 
 <pre>
-     forx [ -E | -e ] [ -p ] [ -o <em>okcodes</em> | -x <em>breakcodes</em> ] <em>variable</em> { <em>args...</em> } <em>loop...</em>
+     forx [ -E | -e ] [ -p | -P <em>maxpar</em> ] [ -o <em>okcodes</em> | -x <em>breakcodes</em> ] <em>variable</em> { <em>args...</em> } <em>loop...</em>
 </pre>
 
 <ul>
@@ -65,16 +65,12 @@ list, else it will exit 1. If the <tt>-x</tt> option has been given,
 listed in the <em>breakcodes</em> list, else it will exit 1. </li>
  <li> <tt>-e</tt>&nbsp;: no autoimport. This is the default. </li>
  <li> <tt>-E</tt>&nbsp;: autoimport. Instead of spawning
-<em>loop...</em>, spawn <tt>importas -ui <em>variable</em> <em>variable</em>
+<em>loop...</em>, spawn <tt>importas -uSi <em>variable</em>
 <em>loop...</em></tt>. This substitutes <em>variable</em> into the command
 line instead of putting it into the environment. </li>
-</ul>
-
-<h2> Notes </h2>
-
-<ul>
- <li> You can start <em>loop</em> with "importas -u <em>variable</em> <em>variable</em>"
-if you want variable substitution. </li>
+ <li> <tt>-P</tt>&nbsp;<em>maxpar</em>&nbsp;: similar to <tt>-p</tt>, but run
+up to <em>maxpar</em> instances in parallel, i.e. never have more than <em>maxpar</em>
+children at the same time. Minimum is 1. </li>
 </ul>
 
 </body>
diff --git a/doc/index.html b/doc/index.html
index beb61bd..6dbab1c 100644
--- a/doc/index.html
+++ b/doc/index.html
@@ -77,7 +77,7 @@ want nsswitch-like functionality:
 <h3> Download </h3>
 
 <ul>
- <li> The current released version of execline is <a href="execline-2.9.6.1.tar.gz">2.9.6.1</a>. </li>
+ <li> The current released version of execline is <a href="execline-2.9.7.0.tar.gz">2.9.7.0</a>. </li>
  <li> Alternatively, you can checkout a copy of the
 <a href="//git.skarnet.org/cgi-bin/cgit.cgi/execline/">execline
 git repository</a>:
diff --git a/doc/upgrade.html b/doc/upgrade.html
index aeaf0ba..31ddf00 100644
--- a/doc/upgrade.html
+++ b/doc/upgrade.html
@@ -18,6 +18,13 @@
 
 <h1> What has changed in execline </h1>
 
+<h2> in 2.9.7.0 </h2>
+
+<ul>
+ <li> <em> New <tt>-P</tt> option to
+<a href="forx.html">forx</a> and <a href="forstdin.html">forstdin</a>. </li>
+</ul>
+
 <h2> in 2.9.6.1 </h2>
 
 <ul>
diff --git a/package/deps.mak b/package/deps.mak
index a8acd4d..1944f0c 100644
--- a/package/deps.mak
+++ b/package/deps.mak
@@ -53,6 +53,7 @@ src/execline/unexport.o src/execline/unexport.lo: src/execline/unexport.c
 src/execline/wait.o src/execline/wait.lo: src/execline/wait.c src/include/execline/config.h src/include/execline/execline.h
 src/execline/withstdinas.o src/execline/withstdinas.lo: src/execline/withstdinas.c src/include/execline/execline.h
 src/libexecline/el_execsequence.o src/libexecline/el_execsequence.lo: src/libexecline/el_execsequence.c src/include/execline/execline.h
+src/libexecline/el_forx_pidinfo.o src/libexecline/el_forx_pidinfo.lo: src/libexecline/el_forx_pidinfo.c src/include/execline/execline.h
 src/libexecline/el_getstrict.o src/libexecline/el_getstrict.lo: src/libexecline/el_getstrict.c src/include/execline/execline.h
 src/libexecline/el_gspawn0.o src/libexecline/el_gspawn0.lo: src/libexecline/el_gspawn0.c src/include/execline/config.h src/include/execline/execline.h
 src/libexecline/el_modif_and_exec.o src/libexecline/el_modif_and_exec.lo: src/libexecline/el_modif_and_exec.c src/include/execline/config.h src/include/execline/execline.h
@@ -69,6 +70,7 @@ src/libexecline/el_substandrun.o src/libexecline/el_substandrun.lo: src/libexecl
 src/libexecline/el_substandrun_str.o src/libexecline/el_substandrun_str.lo: src/libexecline/el_substandrun_str.c src/include/execline/execline.h src/include-local/exlsn.h
 src/libexecline/el_substitute.o src/libexecline/el_substitute.lo: src/libexecline/el_substitute.c src/include/execline/execline.h
 src/libexecline/el_transform.o src/libexecline/el_transform.lo: src/libexecline/el_transform.c src/include/execline/execline.h
+src/libexecline/el_trueargv.o src/libexecline/el_trueargv.lo: src/libexecline/el_trueargv.c src/include/execline/config.h src/include/execline/execline.h
 src/libexecline/el_vardupl.o src/libexecline/el_vardupl.lo: src/libexecline/el_vardupl.c src/include/execline/execline.h
 src/libexecline/exlp.o src/libexecline/exlp.lo: src/libexecline/exlp.c src/include/execline/execline.h src/include-local/exlsn.h
 src/libexecline/exlsn_define.o src/libexecline/exlsn_define.lo: src/libexecline/exlsn_define.c src/include/execline/execline.h src/include-local/exlsn.h
@@ -180,12 +182,12 @@ wait: src/execline/wait.o ${LIBEXECLINE}
 withstdinas: EXTRA_LIBS := -lskarnet
 withstdinas: src/execline/withstdinas.o ${LIBEXECLINE}
 ifeq ($(strip $(STATIC_LIBS_ARE_PIC)),)
-libexecline.a.xyzzy: src/libexecline/el_execsequence.o src/libexecline/el_getstrict.o src/libexecline/el_modif_and_exec.o src/libexecline/el_modif_and_spawn.o src/libexecline/el_modifs_and_exec.o src/libexecline/el_parse.o src/libexecline/el_parse_from_buffer.o src/libexecline/el_parse_from_string.o src/libexecline/el_popenv.o src/libexecline/el_pushenv.o src/libexecline/el_semicolon.o src/libexecline/el_spawn0.o src/libexecline/el_gspawn0.o src/libexecline/el_substandrun.o src/libexecline/el_substandrun_str.o src/libexecline/el_substitute.o src/libexecline/el_transform.o src/libexecline/el_vardupl.o src/libexecline/exlsn_define.o src/libexecline/exlsn_elglob.o src/libexecline/exlsn_importas.o src/libexecline/exlsn_multidefine.o src/libexecline/exlsn_exlp.o src/libexecline/exlsn_main.o src/libexecline/exlsn_free.o src/libexecline/exlp.o
+libexecline.a.xyzzy: src/libexecline/el_execsequence.o src/libexecline/el_forx_pidinfo.o src/libexecline/el_getstrict.o src/libexecline/el_modif_and_exec.o src/libexecline/el_modif_and_spawn.o src/libexecline/el_modifs_and_exec.o src/libexecline/el_parse.o src/libexecline/el_parse_from_buffer.o src/libexecline/el_parse_from_string.o src/libexecline/el_popenv.o src/libexecline/el_pushenv.o src/libexecline/el_semicolon.o src/libexecline/el_spawn0.o src/libexecline/el_gspawn0.o src/libexecline/el_substandrun.o src/libexecline/el_substandrun_str.o src/libexecline/el_substitute.o src/libexecline/el_transform.o src/libexecline/el_trueargv.o src/libexecline/el_vardupl.o src/libexecline/exlsn_define.o src/libexecline/exlsn_elglob.o src/libexecline/exlsn_importas.o src/libexecline/exlsn_multidefine.o src/libexecline/exlsn_exlp.o src/libexecline/exlsn_main.o src/libexecline/exlsn_free.o src/libexecline/exlp.o
 else
-libexecline.a.xyzzy: src/libexecline/el_execsequence.lo src/libexecline/el_getstrict.lo src/libexecline/el_modif_and_exec.lo src/libexecline/el_modif_and_spawn.lo src/libexecline/el_modifs_and_exec.lo src/libexecline/el_parse.lo src/libexecline/el_parse_from_buffer.lo src/libexecline/el_parse_from_string.lo src/libexecline/el_popenv.lo src/libexecline/el_pushenv.lo src/libexecline/el_semicolon.lo src/libexecline/el_spawn0.lo src/libexecline/el_gspawn0.lo src/libexecline/el_substandrun.lo src/libexecline/el_substandrun_str.lo src/libexecline/el_substitute.lo src/libexecline/el_transform.lo src/libexecline/el_vardupl.lo src/libexecline/exlsn_define.lo src/libexecline/exlsn_elglob.lo src/libexecline/exlsn_importas.lo src/libexecline/exlsn_multidefine.lo src/libexecline/exlsn_exlp.lo src/libexecline/exlsn_main.lo src/libexecline/exlsn_free.lo src/libexecline/exlp.lo
+libexecline.a.xyzzy: src/libexecline/el_execsequence.lo src/libexecline/el_forx_pidinfo.lo src/libexecline/el_getstrict.lo src/libexecline/el_modif_and_exec.lo src/libexecline/el_modif_and_spawn.lo src/libexecline/el_modifs_and_exec.lo src/libexecline/el_parse.lo src/libexecline/el_parse_from_buffer.lo src/libexecline/el_parse_from_string.lo src/libexecline/el_popenv.lo src/libexecline/el_pushenv.lo src/libexecline/el_semicolon.lo src/libexecline/el_spawn0.lo src/libexecline/el_gspawn0.lo src/libexecline/el_substandrun.lo src/libexecline/el_substandrun_str.lo src/libexecline/el_substitute.lo src/libexecline/el_transform.lo src/libexecline/el_trueargv.lo src/libexecline/el_vardupl.lo src/libexecline/exlsn_define.lo src/libexecline/exlsn_elglob.lo src/libexecline/exlsn_importas.lo src/libexecline/exlsn_multidefine.lo src/libexecline/exlsn_exlp.lo src/libexecline/exlsn_main.lo src/libexecline/exlsn_free.lo src/libexecline/exlp.lo
 endif
 libexecline.so.xyzzy: EXTRA_LIBS := -lskarnet ${SPAWN_LIB}
-libexecline.so.xyzzy: src/libexecline/el_execsequence.lo src/libexecline/el_getstrict.lo src/libexecline/el_modif_and_exec.lo src/libexecline/el_modif_and_spawn.lo src/libexecline/el_modifs_and_exec.lo src/libexecline/el_parse.lo src/libexecline/el_parse_from_buffer.lo src/libexecline/el_parse_from_string.lo src/libexecline/el_popenv.lo src/libexecline/el_pushenv.lo src/libexecline/el_semicolon.lo src/libexecline/el_spawn0.lo src/libexecline/el_gspawn0.lo src/libexecline/el_substandrun.lo src/libexecline/el_substandrun_str.lo src/libexecline/el_substitute.lo src/libexecline/el_transform.lo src/libexecline/el_vardupl.lo src/libexecline/exlsn_define.lo src/libexecline/exlsn_elglob.lo src/libexecline/exlsn_importas.lo src/libexecline/exlsn_multidefine.lo src/libexecline/exlsn_exlp.lo src/libexecline/exlsn_main.lo src/libexecline/exlsn_free.lo src/libexecline/exlp.lo
+libexecline.so.xyzzy: src/libexecline/el_execsequence.lo src/libexecline/el_forx_pidinfo.lo src/libexecline/el_getstrict.lo src/libexecline/el_modif_and_exec.lo src/libexecline/el_modif_and_spawn.lo src/libexecline/el_modifs_and_exec.lo src/libexecline/el_parse.lo src/libexecline/el_parse_from_buffer.lo src/libexecline/el_parse_from_string.lo src/libexecline/el_popenv.lo src/libexecline/el_pushenv.lo src/libexecline/el_semicolon.lo src/libexecline/el_spawn0.lo src/libexecline/el_gspawn0.lo src/libexecline/el_substandrun.lo src/libexecline/el_substandrun_str.lo src/libexecline/el_substitute.lo src/libexecline/el_transform.lo src/libexecline/el_trueargv.lo src/libexecline/el_vardupl.lo src/libexecline/exlsn_define.lo src/libexecline/exlsn_elglob.lo src/libexecline/exlsn_importas.lo src/libexecline/exlsn_multidefine.lo src/libexecline/exlsn_exlp.lo src/libexecline/exlsn_main.lo src/libexecline/exlsn_free.lo src/libexecline/exlp.lo
 execline: EXTRA_LIBS := -lskarnet ${SPAWN_LIB} ${MAYBEPTHREAD_LIB}
 execline: src/multicall/execline.o ${LIBEXECLINE} ${LIBNSSS}
 INTERNAL_LIBS :=
diff --git a/package/info b/package/info
index 0ee7de4..3e3b38d 100644
--- a/package/info
+++ b/package/info
@@ -1,4 +1,4 @@
 package=execline
-version=2.9.6.1
+version=2.9.7.0
 category=admin
 package_macro_name=EXECLINE
diff --git a/src/execline/forstdin.c b/src/execline/forstdin.c
index b023640..d1e065c 100644
--- a/src/execline/forstdin.c
+++ b/src/execline/forstdin.c
@@ -2,6 +2,7 @@
 
 #include <string.h>
 #include <errno.h>
+#include <signal.h>
 
 #include <skalibs/types.h>
 #include <skalibs/sgetopt.h>
@@ -19,56 +20,31 @@
 #define USAGE "forstdin [ -E | -e ] [ -p | -o okcode,okcode,... | -x breakcode,breakcode,... ] [ -N | -n ] [ -C | -c ] [ -0 | -d delim ] var command..."
 #define dieusage() strerr_dieusage(100, USAGE)
 
-static genalloc *forstdin_pids_p = 0 ;  /* minimize bss/data */
-
-static int fs_isok (unsigned short *tab, unsigned int n, int code)
-{
-  unsigned int i = 0 ;
-  for (; i < n ; i++) if ((unsigned short)code == tab[i]) break ;
-  return i < n ;
-}
-
-static void parallel_sigchld_handler (int sig)
-{
-  pid_t *tab = genalloc_s(pid_t, forstdin_pids_p) ;
-  size_t len = genalloc_len(pid_t, forstdin_pids_p) ;
-  int wstat ;
-  for (;;)
-  {
-    ssize_t r = wait_pids_nohang(tab, len, &wstat) ;
-    if (r <= 0) break ;
-    tab[r-1] = tab[--len] ;
-  }
-  genalloc_setlen(pid_t, forstdin_pids_p, len) ;
-  (void)sig ;
-}
-
 int main (int argc, char const **argv)
 {
-  genalloc pids = GENALLOC_ZERO ;
+  el_forx_pidinfo_t pidinfo = EL_FORX_PIDINFO_ZERO ;
   stralloc value = STRALLOC_ZERO ;
+  sigset_t emptyset ;
   char const *delim = "\n" ;
   size_t delimlen = 1 ;
   size_t nbc = 0 ;
+  unsigned int maxpar = 1 ;
   unsigned short okcodes[256] ;
   int crunch = 0, chomp = 1, not = 1, eofcode = 1, doimport = 0 ;
   PROG = "forstdin" ;
-  forstdin_pids_p = &pids ;
+  el_forx_pidinfo = &pidinfo ;
+  sigemptyset(&emptyset) ;
 
   {
     subgetopt l = SUBGETOPT_ZERO ;
     for (;;)
     {
-      int opt = subgetopt_r(argc, argv, "pNnCc0d:o:x:Ee", &l) ;
+      int opt = subgetopt_r(argc, argv, "pP:NnCc0d:o:x:Ee", &l) ;
       if (opt == -1) break ;
       switch (opt)
       {
-        case 'p' :
-        {
-          if (!genalloc_ready(pid_t, &pids, 1))
-            strerr_diefu1sys(111, "genalloc_ready") ;
-          break ;
-        }
+        case 'p' : maxpar = 10000 ; break ;
+        case 'P' : if (!uint0_scan(l.arg, &maxpar)) dieusage() ; break ;
         case 'N' : chomp = 0 ; break ;
         case 'n' : chomp = 1 ; break ;
         case 'C' : crunch = 1 ; break ;
@@ -92,15 +68,16 @@ int main (int argc, char const **argv)
   }
   if (argc < 2) dieusage() ;
   if (!argv[0][0] || strchr(argv[0], '=')) strerr_dief1x(100, "invalid variable name") ;
+  if (maxpar < 1) maxpar = 1 ;
+  if (maxpar >= 10000) maxpar = 10000 ;
+  if (!sig_catch(SIGCHLD, &el_forx_sigchld_handler))
+    strerr_diefu1sys(111, "install SIGCHLD handler") ;
 
-  if (pids.s)
-  {
-    if (!sig_catch(SIGCHLD, &parallel_sigchld_handler))
-      strerr_diefu1sys(111, "install SIGCHLD handler") ;
-  }
+  pid_t pidtab[maxpar] ;
+  pidinfo.tab = pidtab ;
+  sig_block(SIGCHLD) ;
   for (;;)
   {
-    pid_t pid ;
     value.len = 0 ;
     if (delimlen)
     {
@@ -125,34 +102,16 @@ int main (int argc, char const **argv)
     }
     eofcode = 0 ;
     if (!stralloc_0(&value)) strerr_diefu1sys(111, "stralloc_0") ;
-    if (pids.s) sig_block(SIGCHLD) ;
-    pid = el_modif_and_spawn(argv + 1, argv[0], value.s, doimport) ;
-    if (!pid) strerr_diefu2sys(111, "spawn ", argv[1]) ;
-    if (pids.s)
-    {
-      if (!genalloc_append(pid_t, &pids, &pid))
-        strerr_diefu1sys(111, "genalloc_append") ;
-      sig_unblock(SIGCHLD) ;
-    }
-    else
-    {
-      int wstat ;
-      if (wait_pid(pid, &wstat) < 0)
-        strerr_diefu2sys(111, "wait for ", argv[1]) ;
-      if (not == fs_isok(okcodes, nbc, wait_estatus(wstat)))
-        return wait_estatus(wstat) ;
-    }
-  }
-  if (pids.s)
-  {
-    sigset_t empty ;
-    sigemptyset(&empty) ;
-    sig_block(SIGCHLD) ;
-    for (;;)
+    pidtab[pidinfo.len] = el_modif_and_spawn(argv + 1, argv[0], value.s, doimport) ;
+    if (!pidtab[pidinfo.len]) strerr_diefu2sys(111, "spawn ", argv[1]) ;
+    pidinfo.len++ ;
+    while (pidinfo.len >= maxpar)
     {
-      if (!pids.len) break ;
-      sigsuspend(&empty) ;
+      sigsuspend(&emptyset) ;
+      if (maxpar == 1 && not == el_forx_isok(okcodes, nbc, wait_estatus(pidinfo.wstat)))
+        return wait_estatus(pidinfo.wstat) ;
     }
   }
+  while (pidinfo.len) sigsuspend(&emptyset) ;
   return eofcode ;
 }
diff --git a/src/execline/forx.c b/src/execline/forx.c
index 1695c00..07ac4e6 100644
--- a/src/execline/forx.c
+++ b/src/execline/forx.c
@@ -1,11 +1,14 @@
 /* ISC license. */
 
 #include <string.h>
+#include <signal.h>
+#include <limits.h>
 
 #include <skalibs/sgetopt.h>
 #include <skalibs/bytestr.h>
 #include <skalibs/strerr.h>
 #include <skalibs/env.h>
+#include <skalibs/sig.h>
 #include <skalibs/djbunix.h>
 #include <skalibs/skamisc.h>
 #include <skalibs/types.h>
@@ -13,52 +16,32 @@
 #include <execline/config.h>
 #include <execline/execline.h>
 
-#define USAGE "forx [ -E | -e ] [ -p ] [ -o okcode,okcode,... | -x breakcode,breakcode,... ] var { values... } command..."
+#define USAGE "forx [ -E | -e ] [ -p | -P maxpar ] [ -o okcode,okcode,... | -x breakcode,breakcode,... ] var { values... } command..."
 #define dieusage() strerr_dieusage(100, USAGE)
 
-static int fx_isok (unsigned short const *tab, unsigned int n, int code)
-{
-  unsigned int i = 0 ;
-  for (; i < n ; i++) if ((unsigned short)code == tab[i]) break ;
-  return i < n ;
-}
-
-static int waitn_code (unsigned short const *tab, unsigned int nbc, pid_t *pids, unsigned int n, int not)
-{
-  int ok = 1 ;
-  while (n)
-  {
-    int wstat ;
-    unsigned int i = 0 ;
-    pid_t pid = wait_nointr(&wstat) ;
-    if (pid < 0) return -1 ;
-    for (; i < n ; i++) if (pid == pids[i]) break ;
-    if (i < n)
-    {
-      if (not == fx_isok(tab, nbc, wait_estatus(wstat))) ok = 0 ;
-      pids[i] = pids[--n] ;
-    }
-  }
-  return ok ;
-}
-
 int main (int argc, char const **argv)
 {
   char const *var ;
+  el_forx_pidinfo_t pidinfo = EL_FORX_PIDINFO_ZERO ;
+  sigset_t emptyset ;
   unsigned short okcodes[256] ;
   size_t nbc = 0 ;
-  int flagpar = 0, not = 1, doimport = 0 ;
-  unsigned int argc1 ;
+  int not = 1, doimport = 0 ;
+  unsigned int maxpar = 1, argc1 ;
   PROG = "forx" ;
+  el_forx_pidinfo = &pidinfo ;
+  sigemptyset(&emptyset) ;
+
   {
     subgetopt l = SUBGETOPT_ZERO ;
     for (;;)
     {
-      int opt = subgetopt_r(argc, argv, "po:x:Ee", &l) ;
+      int opt = subgetopt_r(argc, argv, "pP:o:x:Ee", &l) ;
       if (opt == -1) break ;
       switch (opt)
       {
-        case 'p' : flagpar = 1 ; break ;
+        case 'p' : maxpar = UINT_MAX ; break ;
+        case 'P' : if (!uint0_scan(l.arg, &maxpar)) dieusage() ; break ;
         case 'o' :
           not = 0 ;
           if (!ushort_scanlist(okcodes, 256, l.arg, &nbc)) dieusage() ;
@@ -81,30 +64,29 @@ int main (int argc, char const **argv)
   argc1 = el_semicolon(argv) ;
   if (argc1 >= argc) strerr_dief1x(100, "unterminated block") ;
   if (!argc1 || (argc1 + 1 == argc)) return 0 ;
+  if (maxpar < 1) maxpar = 1 ;
+  if (maxpar > argc1) maxpar = argc1 ;
+  if (!sig_catch(SIGCHLD, &el_forx_sigchld_handler))
+    strerr_diefu1sys(111, "install SIGCHLD handler") ;
 
   {
-    pid_t pids[flagpar ? argc1 : 1] ;
+    pid_t pidtab[maxpar] ;
+    pidinfo.tab = pidtab ;
+    sig_block(SIGCHLD) ;
+
     for (unsigned int i = 0 ; i < argc1 ; i++)
     {
-      pid_t pid = el_modif_and_spawn(argv + argc1 + 1, var, argv[i], doimport) ;
-      if (!pid) strerr_diefu2sys(111, "spawn ", argv[argc1+1]) ;
-      if (flagpar) pids[i] = pid ;
-      else
+      pidtab[pidinfo.len] = el_modif_and_spawn(argv + argc1 + 1, var, argv[i], doimport) ;
+      if (!pidtab[pidinfo.len]) strerr_diefu2sys(111, "spawn ", argv[argc1+1]) ;
+      pidinfo.len++ ;
+      while (pidinfo.len >= maxpar)
       {
-        int wstat ;
-        if (wait_pid(pid, &wstat) == -1)
-          strerr_diefu2sys(111, "wait for ", argv[argc1+1]) ;
-        if (not == fx_isok(okcodes, nbc, wait_estatus(wstat)))
-          return wait_estatus(wstat) ;
+        sigsuspend(&emptyset) ;
+        if (maxpar == 1 && not == el_forx_isok(okcodes, nbc, wait_estatus(pidinfo.wstat)))
+          return wait_estatus(pidinfo.wstat) ;
       }
     }
-
-    if (flagpar)
-    {
-      int r = waitn_code(okcodes, nbc, pids, argc1, not) ;
-      if (r < 0) strerr_diefu1sys(111, "waitn") ;
-      else if (!r) return 1 ;
-    }
+    while (pidinfo.len) sigsuspend(&emptyset) ;
   }
   return 0 ;
 }
diff --git a/src/include/execline/execline.h b/src/include/execline/execline.h
index 8a204d8..7fdef51 100644
--- a/src/include/execline/execline.h
+++ b/src/include/execline/execline.h
@@ -80,8 +80,25 @@ extern int el_substitute (stralloc *, char const *, size_t, char const *, char c
 
 /* Execution with or without substitution */
 
+extern char const *const *el_trueargv ;
 extern void el_modif_and_exec (char const *const *, char const *, char const *, int) gccattr_noreturn ;
 extern pid_t el_modif_and_spawn (char const *const *, char const *, char const *, int) ;
 extern void el_modifs_and_exec (char const *const *, char const *const *, char const *const *, size_t, int) gccattr_noreturn ;
 
+
+ /* Spawning and waiting for several children at once */
+
+typedef struct el_forx_pidinfo_s el_forx_pidinfo_t, *el_forx_pidinfo_t_ref ;
+struct el_forx_pidinfo_s
+{
+  pid_t *tab ;
+  unsigned int len ;
+  int wstat ;
+} ;
+#define EL_FORX_PIDINFO_ZERO { .tab = 0, .len = 0, .wstat = 0 }
+
+extern el_forx_pidinfo_t *el_forx_pidinfo ;
+extern void el_forx_sigchld_handler (int) ;
+extern int el_forx_isok (unsigned short const *, unsigned int, unsigned short) ;
+
 #endif
diff --git a/src/libexecline/deps-lib/execline b/src/libexecline/deps-lib/execline
index 608fa7f..d5888c0 100644
--- a/src/libexecline/deps-lib/execline
+++ b/src/libexecline/deps-lib/execline
@@ -1,4 +1,5 @@
 el_execsequence.o
+el_forx_pidinfo.o
 el_getstrict.o
 el_modif_and_exec.o
 el_modif_and_spawn.o
@@ -15,6 +16,7 @@ el_substandrun.o
 el_substandrun_str.o
 el_substitute.o
 el_transform.o
+el_trueargv.o
 el_vardupl.o
 exlsn_define.o
 exlsn_elglob.o
diff --git a/src/libexecline/el_forx_pidinfo.c b/src/libexecline/el_forx_pidinfo.c
new file mode 100644
index 0000000..61c5a26
--- /dev/null
+++ b/src/libexecline/el_forx_pidinfo.c
@@ -0,0 +1,25 @@
+/* ISC license. */
+
+#include <skalibs/djbunix.h>
+
+#include <execline/execline.h>
+
+el_forx_pidinfo_t *el_forx_pidinfo = 0 ;
+
+int el_forx_isok (unsigned short const *tab, unsigned int n, unsigned short code)
+{
+  unsigned int i = 0 ;
+  for (; i < n ; i++) if (code == tab[i]) break ;
+  return i < n ;
+}
+
+void el_forx_sigchld_handler (int sig)
+{
+  for (;;)
+  {
+    ssize_t r = wait_pids_nohang(el_forx_pidinfo->tab, el_forx_pidinfo->len, &el_forx_pidinfo->wstat) ;
+    if (r <= 0) break ;
+    el_forx_pidinfo->tab[r-1] = el_forx_pidinfo->tab[--el_forx_pidinfo->len] ;
+  }
+  (void)sig ;
+}
diff --git a/src/libexecline/el_gspawn0.c b/src/libexecline/el_gspawn0.c
index d6e0d3a..2a5cb97 100644
--- a/src/libexecline/el_gspawn0.c
+++ b/src/libexecline/el_gspawn0.c
@@ -7,10 +7,6 @@
 
 pid_t el_gspawn0 (char const *prog, char const *const *argv, char const *const *envp)
 {
-  if (!argv[0])
-  {
-    static char const *const newargv[3] = { EXECLINE_BINPREFIX "exit", "0", 0 } ;
-    return gcspawn(newargv[0], newargv, envp, 0, 0, 0) ;
-  }
-  else return gcspawn(prog, argv, envp, 0, 0, 0) ;
+  if (!argv[0]) argv = el_trueargv ;
+  return gcspawn(prog, argv, envp, 0, 0, 0) ;
 }
diff --git a/src/libexecline/el_modif_and_exec.c b/src/libexecline/el_modif_and_exec.c
index e2a3618..c94d301 100644
--- a/src/libexecline/el_modif_and_exec.c
+++ b/src/libexecline/el_modif_and_exec.c
@@ -22,12 +22,11 @@ void el_modif_and_exec (char const *const *argv, char const *var, char const *va
   if (doimport)
   {
     size_t m = 0 ;
-    char const *newargv[env_len(argv) + 6] ;
+    char const *newargv[env_len(argv) + 5] ;
     newargv[m++] = EXECLINE_BINPREFIX "importas" ;
-    newargv[m++] = "-ui" ;
+    newargv[m++] = "-uSi" ;
     newargv[m++] = "--" ;
     newargv[m++] = var ;
-    newargv[m++] = var ;
     while (*argv) newargv[m++] = *argv++ ;
     newargv[m++] = 0 ;
     xmexec0_n(newargv, value ? modifs : var, value ? modiflen : varlen + 1, 1) ;
diff --git a/src/libexecline/el_modif_and_spawn.c b/src/libexecline/el_modif_and_spawn.c
index 863ca92..7f36a37 100644
--- a/src/libexecline/el_modif_and_spawn.c
+++ b/src/libexecline/el_modif_and_spawn.c
@@ -4,6 +4,7 @@
 
 #include <skalibs/posixplz.h>
 #include <skalibs/env.h>
+#include <skalibs/cspawn.h>
 
 #include <execline/config.h>
 #include <execline/execline.h>
@@ -22,18 +23,18 @@ pid_t el_modif_and_spawn (char const *const *argv, char const *var, char const *
     memcpy(modifs + varlen + 1, value, modiflen - varlen - 1) ;
   }
   env_mergen(newenv, envlen + 2, (char const *const *)environ, envlen, value ? modifs : var, value ? modiflen : varlen + 1, 1) ;
-  if (doimport)
+  if (doimport && argv[0])
   {
     size_t m = 0 ;
-    char const *newargv[env_len(argv) + 6] ;
+    char const *newargv[env_len(argv) + 5] ;
     newargv[m++] = EXECLINE_BINPREFIX "importas" ;
-    newargv[m++] = "-ui" ;
+    newargv[m++] = "-uSi" ;
     newargv[m++] = "--" ;
     newargv[m++] = var ;
-    newargv[m++] = var ;
     while (*argv) newargv[m++] = *argv++ ;
     newargv[m++] = 0 ;
-    return el_spawn0(newargv[0], newargv, newenv) ;
+    return cspawn(newargv[0], newargv, newenv, CSPAWN_FLAGS_SIGBLOCKNONE, 0, 0) ;
   }
-  else return el_spawn0(argv[0], argv, newenv) ;
+  if (!argv[0]) argv = el_trueargv ;
+  return cspawn(argv[0], argv, newenv, CSPAWN_FLAGS_SIGBLOCKNONE, 0, 0) ;
 }
diff --git a/src/libexecline/el_modifs_and_exec.c b/src/libexecline/el_modifs_and_exec.c
index ca72291..675e284 100644
--- a/src/libexecline/el_modifs_and_exec.c
+++ b/src/libexecline/el_modifs_and_exec.c
@@ -47,7 +47,7 @@ void el_modifs_and_exec (char const *const *argv, char const *const *vars, char
   {
     size_t m = 0 ;
     size_t ypos = 0 ;
-    char const *newargv[env_len(argv) + 3 + 5 * yesn] ;
+    char const *newargv[env_len(argv) + 3 + 4 * yesn] ;
     char yesvars[yeslen ? yeslen : 1] ;
     newargv[m++] = EXECLINE_BINPREFIX "multisubstitute" ;
     for (size_t i = 0 ; i < n ; i++) if (values[i])
@@ -55,10 +55,9 @@ void el_modifs_and_exec (char const *const *argv, char const *const *vars, char
       size_t len = strlen(vars[i]) + 1 ;
       char *p = yesvars + ypos ;
       newargv[m++] = " importas" ;
-      newargv[m++] = " -ui" ;
+      newargv[m++] = " -uSi" ;
       newargv[m++] = " --" ;
       newargv[m++] = p ;
-      newargv[m++] = p ;
       yesvars[ypos++] = ' ' ;
       memcpy(yesvars + ypos, vars[i], len) ;
       ypos += len ;
diff --git a/src/libexecline/el_spawn0.c b/src/libexecline/el_spawn0.c
index d397557..a05ed5d 100644
--- a/src/libexecline/el_spawn0.c
+++ b/src/libexecline/el_spawn0.c
@@ -7,10 +7,6 @@
 
 pid_t el_spawn0 (char const *prog, char const *const *argv, char const *const *envp)
 {
-  if (!argv[0])
-  {
-    static char const *const newargv[3] = { EXECLINE_BINPREFIX "exit", "0", 0 } ;
-    return cspawn(newargv[0], newargv, envp, 0, 0, 0) ;
-  }
-  else return cspawn(prog, argv, envp, 0, 0, 0) ;
+  if (!argv[0]) argv = el_trueargv ;
+  return cspawn(prog, argv, envp, 0, 0, 0) ;
 }
diff --git a/src/libexecline/el_trueargv.c b/src/libexecline/el_trueargv.c
new file mode 100644
index 0000000..c08e127
--- /dev/null
+++ b/src/libexecline/el_trueargv.c
@@ -0,0 +1,7 @@
+/* ISC license. */
+
+#include <execline/config.h>
+#include <execline/execline.h>
+
+static char const *const el_trueargv_[3] = { EXECLINE_BINPREFIX "exit", "0", 0 } ;
+char const *const *el_trueargv = el_trueargv_ ;