summary refs log tree commit diff
diff options
context:
space:
mode:
authorLaurent Bercot <ska-skaware@skarnet.org>2015-02-19 04:13:20 +0000
committerLaurent Bercot <ska-skaware@skarnet.org>2015-02-19 04:13:20 +0000
commite1fe79a9e705e3cab8f632cdbe8e1774cdef2761 (patch)
treefcf1e195e4025ca14b7caea13c0da41fca15ea97
parentc73a41da14650d93801a59b85757a73741be986f (diff)
downloadexecline-e1fe79a9e705e3cab8f632cdbe8e1774cdef2761.tar.gz
execline-e1fe79a9e705e3cab8f632cdbe8e1774cdef2761.tar.xz
execline-e1fe79a9e705e3cab8f632cdbe8e1774cdef2761.zip
- exit code overhaul: forx, forbacktickx, loopwhilex, if, ifelse, ifte, v2.1.0.0
ifthenelse
 - new -o option to forx, forbacktickx, loopwhilex
 - documentation updated
 - version: rc for 2.1.0.0
-rw-r--r--INSTALL2
-rw-r--r--doc/exitcodes.html97
-rw-r--r--doc/forbacktickx.html18
-rw-r--r--doc/foreground.html9
-rw-r--r--doc/forx.html16
-rw-r--r--doc/if.html4
-rw-r--r--doc/ifelse.html3
-rw-r--r--doc/ifte.html3
-rw-r--r--doc/ifthenelse.html3
-rw-r--r--doc/index.html4
-rw-r--r--doc/loopwhilex.html25
-rw-r--r--doc/upgrade.html12
-rw-r--r--package/info2
-rw-r--r--src/execline/forbacktickx.c128
-rw-r--r--src/execline/forx.c21
-rw-r--r--src/execline/if.c8
-rw-r--r--src/execline/ifelse.c6
-rw-r--r--src/execline/ifte.c6
-rw-r--r--src/execline/ifthenelse.c4
-rw-r--r--src/execline/loopwhilex.c25
20 files changed, 273 insertions, 123 deletions
diff --git a/INSTALL b/INSTALL
index 4f04ae1..9a8f893 100644
--- a/INSTALL
+++ b/INSTALL
@@ -6,7 +6,7 @@ Build Instructions
 
   - A POSIX-compliant C development environment
   - GNU make version 4.0 or later
-  - skalibs version 2.3.0.0 or later: http://skarnet.org/software/skalibs/
+  - skalibs version 2.3.1.0 or later: http://skarnet.org/software/skalibs/
 
  This software will run on any operating system that implements
 POSIX.1-2008, available at:
diff --git a/doc/exitcodes.html b/doc/exitcodes.html
new file mode 100644
index 0000000..4b8afda
--- /dev/null
+++ b/doc/exitcodes.html
@@ -0,0 +1,97 @@
+<html>
+  <head>
+    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+    <meta http-equiv="Content-Language" content="en" />
+    <title>execline: exit codes</title>
+    <meta name="Description" content="execline: exit codes" />
+    <meta name="Keywords" content="execline exit codes" />
+    <!-- <link rel="stylesheet" type="text/css" href="http://skarnet.org/default.css" /> -->
+  </head>
+<body>
+
+<p>
+<a href="index.html">execline</a><br />
+<a href="http://skarnet.org/software/">Software</a><br />
+<a href="http://skarnet.org/">skarnet.org</a>
+</p>
+
+<h1> How to propagate exit codes up a process dynasty </h1>
+
+<p>
+ Say we have a parent process <em>P</em>, child of a grandparent process
+<em>G</em>, spawning a child process <em>C</em> and waiting for it.
+Either <em>C</em> dies normally with an exit code from 0 to 255, or it is
+killed by a signal.
+ How can we make sure that <em>P</em> reports to <em>G</em> what happened
+to <em>C</em>, with as much precision as possible&nbsp;?
+</p>
+
+<p>
+ The problem is, there's more information in a wstat (the
+structure filled in by
+<a href="http://pubs.opengroup.org/onlinepubs/9699919799/functions/waitpid.html">waitpid()</a>)
+than a process can report by
+simply exiting. <em>P</em> could exit with the same exit code as <em>C</em>,
+but then what should it do if C has been killed by a signal&nbsp;?
+</p>
+
+<p>
+ An idea is to have <em>P</em> kill itself with the same signal that killed
+<em>C</em>.
+But that's actually not right, because <em>P</em> itself could be killed by a
+signal from another source, and G needs that information. "<em>P</em> has been
+killed by a signal" and "<em>C</em> has been killed by a signal" are two
+different informations, so they should not be reported in the same way.
+</p>
+
+<p>
+ So, any way you look at it, there is always more information than we
+can report.
+</p>
+
+<p>
+Shells have their own
+<a href="http://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_08_02">convention</a>
+for reporting crashes, but since any exit code greater than 127 is reported
+as is, the information given by the shell is unreliable: "child exited 129"
+and "child was killed by SIGHUP" are indistinguishable. When shells get
+nested, all bets are off - the information conveyed by exit codes becomes
+devoid of meaning pretty fast. We need something better.
+</p>
+
+<h2> execline's solution </h2>
+
+<p>
+execline commands such as <a href="forx.html">forx</a>, that can report
+a child's exit code, proceed that way when they're in the position of
+<em>P</em>:
+</p>
+
+<ul>
+ <li> If <em>C</em> was killed by a signal: <em>P</em> exits 128 plus the
+signal number. </li>
+ <li> If <em>C</em> exited 128 or more: <em>P</em> exits 128. </li>
+ <li> Else, <em>P</em> exits with the same code as <em>C</em>. </li>
+</ul>
+
+<p>
+ Rationale:
+</p>
+
+<ul>
+ <li> 128+ exit codes are extremely rare and should report really
+problematic conditions; commands usually exit 127 or less. If <em>C</em>
+exits 128+, it's more important to convey the information
+"something really bad happened, but the <em>C</em> process itself was not
+killed by a signal" than the exact nature of the event. </li>
+ <li> Commands following that convention can be nested. If <em>P</em> exits
+129+, <em>G</em> knows that <em>C</em> was
+killed by a signal. If <em>G</em> also needs to report that to its parent,
+it will exit 128: <em>G</em>'s parent will not know the signal number, but
+it will know that <em>P</em> reported 128 or more, so either <em>C</em> or
+a scion of <em>C</em> had problems. </li>
+ <li> Exact information is reported in the common case. </li>
+</ul>
+
+</body>
+</html>
diff --git a/doc/forbacktickx.html b/doc/forbacktickx.html
index d40799a..0780f3d 100644
--- a/doc/forbacktickx.html
+++ b/doc/forbacktickx.html
@@ -29,7 +29,7 @@ run another program.
 </p>
 
 <pre>
-     forbacktickx [ -p | -x breakcode ] [ -n ] [ -C | -c ] [ -0 | -d <em>delim</em> ] <em>variable</em> { <em>gen...</em> } <em>loop...</em>
+     forbacktickx [ -p | -o <em>okcodes</em> | -x <em>breakcodes</em> ] [ -n ] [ -C | -c ] [ -0 | -d <em>delim</em> ] <em>variable</em> { <em>gen...</em> } <em>loop...</em>
 </pre>
 
 <ul>
@@ -56,11 +56,17 @@ exiting, though. </li>
  <li> <tt>-0</tt>&nbsp;: accept null characters from <em>gen</em>'s output,
 using them as delimiters. If this option and a <tt>-d</tt> option are
 used simultaneously, the rightmost one wins. </li>
- <li> <tt>-x</tt>&nbsp;<em>breakcodes</em>&nbsp;: <em>breakcodes</em> must
-be a comma-separated list of exit codes. If at some point <em>loop...</em>
-exits with a code listed in <em>breakcodes</em>, forbacktickx will not keep
-looping, but will exit immediately with the same exit code. This doesn't apply
-if the <tt>-p</tt> option has been given. </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
+<em>okcodes</em>,
+forbacktickx will run the following instances of the loop, but if the exit code is
+not listed in <em>okcodes</em>, forbacktickx will exit immediately with an
+<a href="exitcodes.html">approximation</a> of the same exit code. </li>
+ <li> <tt>-x</tt>&nbsp;<em>breakcodes</em>&nbsp;: like the previous
+option, but with inverted meaning - the listed exit codes are codes
+that will make forbacktickx break the loop and exit, and the unlisted exit
+codes will make it keep looping. </li>
  <li> Other options are used to <a href="el_transform.html">control
 the substitution mechanism</a> for every <em>x</em>. Of course, you can't
 split <em>x</em>. </li>
diff --git a/doc/foreground.html b/doc/foreground.html
index a2a465f..c0928e9 100644
--- a/doc/foreground.html
+++ b/doc/foreground.html
@@ -37,7 +37,8 @@
 executes it, then waits for it to complete. </li>
  <li> <tt>foreground</tt> sets the <tt>?</tt> environment
 variable to the exit code of <em>prog1</em>. If <em>prog1...</em>
-did not exit normally, the <tt>?</tt> value is 111. </li>
+was killed by a signal, the <tt>?</tt> value is 256 plus the signal
+number. </li>
  <li> <tt>foreground</tt> then execs into <em>prog2...</em>. </li>
 </ul>
 
@@ -51,6 +52,12 @@ wrap external commands that exit instead of natively supporting the
  <li> <tt>foreground <em>prog1...</em> "" <em>prog2...</em></tt> is
 equivalent to <tt>sh -c '<em>prog1...</em> ; exec <em>prog2...</em>'</tt>.
  </li>
+ <li> 256 and above are not valid exit codes for commands, so when the
+<tt>?</tt> environment variable contains 256 or more, it means that the
+previous command was killed by a signal. There is no ambiguity here, and
+<tt>?</tt> reports exactly what happened to the previous command;
+<tt>foreground</tt> does not exit, so there is no need for
+<a href="exitcodes.html">exit code approximation</a>. </li>
 </ul>
 
 </body>
diff --git a/doc/forx.html b/doc/forx.html
index 0349a85..0ed4799 100644
--- a/doc/forx.html
+++ b/doc/forx.html
@@ -28,7 +28,7 @@
 </p>
 
 <pre>
-     forx [ -p | -x <em>breakcodes</em> ] <em>variable</em> { <em>args...</em> } <em>loop...</em>
+     forx [ -p | -o <em>okcodes</em> | -x <em>breakcodes</em> ] <em>variable</em> { <em>args...</em> } <em>loop...</em>
 </pre>
 
 <ul>
@@ -48,11 +48,17 @@ That block contains a list of <em>args</em>. </li>
 <em>loop...</em> to exit before spawning the next one. <tt>forx</tt>
 will still wait for all instances of <em>loop</em> to terminate before
 exiting, though. </li>
- <li> <tt>-x</tt>&nbsp;<em>breakcodes</em>&nbsp;: <em>breakcodes</em> must
+ <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 <em>breakcodes</em>,
-forx will not run the following instances of the loop, but exit immediately with the
-same exit code. </li>
+hasn't been given and <em>loop</em> exits with one of the codes in
+<em>okcodes</em>,
+forx will run the following instances of the loop, but if the exit code is
+not listed in <em>okcodes</em>, forx will exit immediately with an
+<a href="exitcodes.html">approximation</a> of the same exit code. </li>
+ <li> <tt>-x</tt>&nbsp;<em>breakcodes</em>&nbsp;: like the previous
+option, but with inverted meaning - the listed exit codes are codes
+that will make forx break the loop and exit, and the unlisted exit
+codes will make it keep looping. </li>
 </ul>
 
 <h2> Notes </h2>
diff --git a/doc/if.html b/doc/if.html
index 2157a07..5723ef7 100644
--- a/doc/if.html
+++ b/doc/if.html
@@ -35,8 +35,8 @@
  <li> <tt>if</tt> reads <em>prog1...</em> in a
 <a href="el_semicolon.html">block</a>. It forks and executes it,
 then waits for it to complete. </li>
- <li> If <em>prog1</em> crashes, <tt>if</tt> exits 1 with a special
-error message. </li>
+ <li> If <em>prog1</em> crashes, <tt>if</tt> prints an error message
+then exits 128 plus the number of the signal that killed <em>prog1</em>. </li>
  <li> If <em>prog1</em> exits a non-zero status,
 <tt>if</tt> exits 1.</li>
  <li> Else <tt>if</tt> execs into <em>prog2</em>. </li>
diff --git a/doc/ifelse.html b/doc/ifelse.html
index 2298cf8..06d2e9c 100644
--- a/doc/ifelse.html
+++ b/doc/ifelse.html
@@ -34,7 +34,8 @@
  <li> <tt>ifelse</tt> reads <em>prog1...</em> in a
 <a href="el_semicolon.html">block</a>. It forks and executes it,
 then waits for it to complete. </li>
- <li> If <em>prog1</em> crashes, <tt>ifelse</tt> exits 1 with an error message. </li>
+ <li> If <em>prog1</em> crashes, <tt>ifelse</tt> prints an error message
+and exits 128 plus the number of the signal that killed <em>prog1</em>. </li>
  <li> If <em>prog1</em> exits with a return code equal to 0,
 <tt>ifelse</tt> execs into <em>prog2</em>. </li>
  <li> Else <tt>ifelse</tt> execs into <em>prog3</em>. </li>
diff --git a/doc/ifte.html b/doc/ifte.html
index 40b0ec9..9cbfdcc 100644
--- a/doc/ifte.html
+++ b/doc/ifte.html
@@ -37,7 +37,8 @@ consecutive <a href="el_semicolon.html">blocks</a>. </li>
  <li> <tt>ifte</tt> runs <em>progif...</em> as a child process
 and waits for it to complete. </li>
  <li> If <em>progif...</em> crashes (i.e. is killed by a signal), <tt>ifte</tt>
-exits 1 with an error message. </li>
+prints an error message, then exits 128 plus the number of the signal that
+killed <em>progif</em>. </li>
  <li> If <em>progif...</em> exits zero, <tt>ifte</tt> execs into
 <em>progthen...</em>, else it execs into <em>progelse...</em>. </li>
 </ul>
diff --git a/doc/ifthenelse.html b/doc/ifthenelse.html
index 3180412..97aec39 100644
--- a/doc/ifthenelse.html
+++ b/doc/ifthenelse.html
@@ -38,7 +38,8 @@ consecutive <a href="el_semicolon.html">blocks</a>. </li>
  <li> <tt>ifthenelse</tt> runs <em>progif...</em> as a child process
 and waits for it to complete. </li>
  <li> If <em>progif...</em> crashes (i.e. is killed by a signal), <tt>ifthenelse</tt>
-exits 1 with an error message. </li>
+prints an error message, then exits 128 plus the number of the signal
+that killed <em>progif</em>. </li>
  <li> If <em>progif...</em> exits zero, <tt>ifthenelse</tt> runs
 <em>progthen...</em> as a child process, else it runs <em>progelse...</em>. </li>
  <li> <tt>ifthenelse</tt> waits for its child to complete and puts the exit
diff --git a/doc/index.html b/doc/index.html
index 5fdfb46..5b19477 100644
--- a/doc/index.html
+++ b/doc/index.html
@@ -51,7 +51,7 @@ shell's syntax, and has no security issues.
  <li> GNU make, version 4.0 or later. Please be aware that execline will
 not build with an earlier version. </li>
  <li> <a href="http://skarnet.org/software/skalibs/">skalibs</a> version
-2.3.0.0 or later. It's a build-time requirement. It's also a run-time
+2.3.1.0 or later. It's a build-time requirement. It's also a run-time
 requirement if you link against the shared version of the skalibs
 library. </li>
 </ul>
@@ -66,7 +66,7 @@ library. </li>
 <h3> Download </h3>
 
 <ul>
- <li> The current released version of execline is <a href="execline-2.0.2.1.tar.gz">2.0.2.1</a>. </li>
+ <li> The current released version of execline is <a href="execline-2.1.0.0.tar.gz">2.1.0.0</a>. </li>
  <li> Alternatively, you can checkout a copy of the execline git repository:
 <pre> git clone git://git.skarnet.org/execline </pre> </li>
 </ul>
diff --git a/doc/loopwhilex.html b/doc/loopwhilex.html
index 91e9fb1..8c8d760 100644
--- a/doc/loopwhilex.html
+++ b/doc/loopwhilex.html
@@ -24,31 +24,38 @@
 <h2> Interface </h2>
 
 <pre>
-     loopwhilex [ -n ] [ -x <em>exitcodes</em> ] <em>prog...</em>
+     loopwhilex [ -n ] [ -o <em>okcodes</em> | -x <em>breakcodes</em> ] <em>prog...</em>
 </pre>
 
 <ul>
  <li> <tt>loopwhilex</tt> runs <em>prog...</em> as a child process and
 waits for it to complete. </li>
  <li> As long as <em>prog</em> exits zero, <tt>loopwhile</tt> runs it again. </li>
- <li> <tt>loopwhilex</tt> then exits 0. If <em>prog</em> was killed by a signal,
-<tt>loopwhilex</tt> exits that signal's number instead. </li>
+ <li> <tt>loopwhilex</tt> then exits with an
+<a href="exitcodes.html">approximation</a> of the last <em>prog</em>
+invocation's exit code. </li>
 </ul>
 
 <h2> Options </h2>
 
 <ul>
- <li> <tt>-x</tt>&nbsp;<em>exitcodes</em>&nbsp;: <em>exitcodes</em> must be a comma-separated
-list of valid exit codes. If this option is given, <tt>loopwhilex</tt> will run
-<em>prog...</em> as long as its exit code is <em>not</em> listed in <em>breakcodes</em>. </li>
- <li> <tt>-n</tt>&nbsp;: negate the test: run <em>prog...</em> as long as it exits non-zero
-(or exits a code that <em>is</em> listed in <em>breakcodes</em>). </li>
+ <li> <tt>-o</tt>&nbsp;<em>okcodes</em>&nbsp;: <em>okcodes</em> must
+be a comma-separated list of exit codes. <tt>loopwhilex</tt> will keep
+looping as long as <em>prog</em> exits with one of the codes in
+<em>okcodes</em>. </li>
+ <li> <tt>-x</tt>&nbsp;<em>breakcodes</em>&nbsp;: like the previous
+option, but with inverted meaning - the listed exit codes are codes
+that will break the loop and exit, and the unlisted exit codes will keep
+the loop running. </li>
+ <li> <tt>-n</tt>&nbsp;: negate the test. This option is now redundant,
+and may disappear soon. </li>
 </ul>
 
 <h2> Notes </h2>
 
 <ul>
- <li> <tt>loopwhilex <em>prog</em>...</tt> is equivalent to <tt>loopwhilex -n -x 0 <em>prog...</em></tt>. </li>
+ <li> <tt>loopwhilex <em>prog</em>...</tt> is equivalent to <tt>loopwhilex -n -x 0 <em>prog...</em></tt>
+and <tt>loopwhilex -o 0 <em>prog</em>...</tt> </li>
  <li> Be careful: execline <strong>maintains no state</strong>, in particular it
 uses <strong>no real variables</strong>, and environment will
 be of no use here since every instance of <em>prog...</em> runs as a separate
diff --git a/doc/upgrade.html b/doc/upgrade.html
index 115ed5e..b66a215 100644
--- a/doc/upgrade.html
+++ b/doc/upgrade.html
@@ -17,6 +17,18 @@
 
 <h1> What has changed in execline </h1>
 
+<h2> in 2.1.0.0 </h2>
+
+<ul>
+ <li> skalibs dependency bumped to 2.3.1.0 </li>
+ <li> <a href="foreground.html">foreground</a> now sets the ? environment
+variable to 256 plus the signal number when its block was killed by a signal
+(in previous releases it used 126). </li>
+ <li> New rules for exit codes of forx, loopwhilex, forbacktickx </li>
+ <li> New <tt>-e</tt> option to <a href="loopwhilex.html">loopwhilex</a>,
+deprecating the <tt>-x</tt> option. </li>
+</ul>
+
 <h2> in 2.0.2.1 </h2>
 
 <ul>
diff --git a/package/info b/package/info
index a5b29c1..54dedf4 100644
--- a/package/info
+++ b/package/info
@@ -1,4 +1,4 @@
 package=execline
-version=2.0.2.1
+version=2.1.0.0
 category=admin
 package_macro_name=EXECLINE
diff --git a/src/execline/forbacktickx.c b/src/execline/forbacktickx.c
index f7b1460..51f308e 100644
--- a/src/execline/forbacktickx.c
+++ b/src/execline/forbacktickx.c
@@ -17,10 +17,10 @@
 #include <execline/config.h>
 #include <execline/execline.h>
 
-#define USAGE "forbacktickx [ -p | -x breakcode,breakcode,... ] [ -n ] [ -C | -c ] [ -0 | -d delim ] var { backtickcmd... } command..."
+#define USAGE "forbacktickx [ -p | -o okcode,okcode,... | -x breakcode,breakcode,... ] [ -n ] [ -C | -c ] [ -0 | -d delim ] var { backtickcmd... } command..."
 #define dieusage() strerr_dieusage(100, USAGE)
 
-static int isbreak (unsigned short *tab, unsigned int n, int code)
+static int isok (unsigned short *tab, unsigned int n, int code)
 {
   register unsigned int i = 0 ;
   for (; i < n ; i++) if ((unsigned short)code == tab[i]) break ;
@@ -33,16 +33,17 @@ int main (int argc, char const **argv, char const *const *envp)
   char const *delim = " \n\r\t" ;
   unsigned int delimlen = 4 ;
   char const *x ;
-  int argc1 ;
-  unsigned short breakcodes[256] ;
+  pid_t pidw ;
+  int fd, argc1 ;
+  unsigned short okcodes[256] ;
   unsigned int nbc = 0 ;
-  int crunch = 0, chomp = 0 ;
+  int crunch = 0, chomp = 0, not = 1 ;
   PROG = "forbacktickx" ;
   {
     subgetopt_t l = SUBGETOPT_ZERO ;
     for (;;)
     {
-      register int opt = subgetopt_r(argc, argv, "epnCc0d:x:", &l) ;
+      register int opt = subgetopt_r(argc, argv, "epnCc0d:o:x:", &l) ;
       if (opt == -1) break ;
       switch (opt)
       {
@@ -58,8 +59,13 @@ int main (int argc, char const **argv, char const *const *envp)
         case 'c' : crunch = 0 ; break ;
         case '0' : delim = "" ; delimlen = 1 ; break ;
         case 'd' : delim = l.arg ; delimlen = str_len(delim) ; break ;
+        case 'o' :
+          not = 0 ;
+          if (!ushort_scanlist(okcodes, 256, l.arg, &nbc)) dieusage() ;
+          break ;
         case 'x' :
-          if (!ushort_scanlist(breakcodes, 256, l.arg, &nbc)) dieusage() ;
+          not = 1 ;
+          if (!ushort_scanlist(okcodes, 256, l.arg, &nbc)) dieusage() ;
           break ;
         default : dieusage() ;
       }
@@ -73,73 +79,69 @@ int main (int argc, char const **argv, char const *const *envp)
   if (!argc1) strerr_dief1x(100, "empty block") ;
   if (argc1 >= argc) strerr_dief1x(100, "unterminated block") ;
   argv[argc1] = 0 ;
+  pidw = el_spawn1(argv[0], argv, envp, &fd, 1) ;
+  if (!pidw) strerr_diefu2sys(111, "spawn ", argv[0]) ;
   {
-    int fd ;
-    pid_t pidw = el_spawn1(argv[0], argv, envp, &fd, 1) ;
-    if (!pidw) strerr_diefu2sys(111, "spawn ", argv[0]) ;
+    char buf[BUFFER_INSIZE] ;
+    buffer b = BUFFER_INIT(&buffer_read, fd, buf, BUFFER_INSIZE) ;
+    stralloc modif = STRALLOC_ZERO ;
+    unsigned int envlen = env_len(envp) ;
+    unsigned int modifstart = str_len(x)+1 ;
+    char const *newenv[envlen + 2] ;
+    if (!stralloc_ready(&modif, modifstart+1))
+      strerr_diefu1sys(111, "stralloc_ready") ;
+    byte_copy(modif.s, modifstart-1, x) ;
+    modif.s[modifstart-1] = '=' ;
+    for (;;)
     {
-      char buf[BUFFER_INSIZE] ;
-      buffer b = BUFFER_INIT(&buffer_read, fd, buf, BUFFER_INSIZE) ;
-      stralloc modif = STRALLOC_ZERO ;
-      unsigned int envlen = env_len(envp) ;
-      unsigned int modifstart = str_len(x)+1 ;
-      char const *newenv[envlen + 2] ;
-      if (!stralloc_ready(&modif, modifstart+1))
-        strerr_diefu1sys(111, "stralloc_ready") ;
-      byte_copy(modif.s, modifstart-1, x) ;
-      modif.s[modifstart-1] = '=' ;
-      for (;;)
+      pid_t pid ;
+      modif.len = modifstart ;
+      if (delimlen)
       {
-        pid_t pid ;
-        modif.len = modifstart ;
-        if (delimlen)
-        {
-          register int r = skagetlnsep(&b, &modif, delim, delimlen) ;
-          if (!r) break ;
-          else if (r < 0)
-          {
-            if (errno != EPIPE) strerr_diefu1sys(111, "skagetlnsep") ;
-            if (chomp) break ;
-          }
-          else modif.len-- ;
-          if ((modif.len == modifstart) && crunch) continue ;
-        }
-        else
-        {
-          unsigned int unread = 0 ;
-          if (netstring_get(&b, &modif, &unread) <= 0)
-          {
-            if (netstring_okeof(&b, unread)) break ;
-            else strerr_diefu1sys(111, "netstring_get") ;
-          }
-        }
-        if (!stralloc_0(&modif)) strerr_diefu1sys(111, "stralloc_0") ;
-        if (!env_merge(newenv, envlen+2, envp, envlen, modif.s, modif.len))
-          strerr_diefu1sys(111, "merge environment") ;
-        pid = el_spawn0(argv[argc1 + 1], argv + argc1 + 1, newenv) ;
-        if (!pid) strerr_diefu2sys(111, "spawn ", argv[argc1+1]) ;
-        if (pids.s)
+        register int r = skagetlnsep(&b, &modif, delim, delimlen) ;
+        if (!r) break ;
+        else if (r < 0)
         {
-          if (!genalloc_append(pid_t, &pids, &pid))
-            strerr_diefu1sys(111, "genalloc_append") ;
+          if (errno != EPIPE) strerr_diefu1sys(111, "skagetlnsep") ;
+          if (chomp) break ;
         }
-        else
+        else modif.len-- ;
+        if ((modif.len == modifstart) && crunch) continue ;
+      }
+      else
+      {
+        unsigned int unread = 0 ;
+        if (netstring_get(&b, &modif, &unread) <= 0)
         {
-          int wstat ;
-          if (wait_pid(pid, &wstat) < 0)
-            strerr_diefu2sys(111, "wait for ", argv[argc1 + 1]) ;
-          if (isbreak(breakcodes, nbc, wait_status(wstat)))
-            return wait_status(wstat) ;
+          if (netstring_okeof(&b, unread)) break ;
+          else strerr_diefu1sys(111, "netstring_get") ;
         }
       }
-      stralloc_free(&modif) ;
+      if (!stralloc_0(&modif)) strerr_diefu1sys(111, "stralloc_0") ;
+      if (!env_merge(newenv, envlen+2, envp, envlen, modif.s, modif.len))
+        strerr_diefu1sys(111, "merge environment") ;
+      pid = el_spawn0(argv[argc1 + 1], argv + argc1 + 1, newenv) ;
+      if (!pid) strerr_diefu2sys(111, "spawn ", argv[argc1+1]) ;
+      if (pids.s)
+      {
+        if (!genalloc_append(pid_t, &pids, &pid))
+          strerr_diefu1sys(111, "genalloc_append") ;
+      }
+      else
+      {
+        int wstat ;
+        if (wait_pid(pid, &wstat) < 0)
+          strerr_diefu2sys(111, "wait for ", argv[argc1 + 1]) ;
+        if (not == isok(okcodes, nbc, wait_estatus(wstat)))
+          return wait_estatus(wstat) ;
+      }
     }
-    fd_close(fd) ;
-    if (!genalloc_append(pid_t, &pids, &pidw))
-      strerr_diefu1sys(111, "genalloc_append") ;
+    stralloc_free(&modif) ;
   }
+  fd_close(fd) ;
+  if (!genalloc_append(pid_t, &pids, &pidw))
+    strerr_diefu1sys(111, "genalloc_append") ;
   if (!waitn(genalloc_s(pid_t, &pids), genalloc_len(pid_t, &pids)))
     strerr_diefu1sys(111, "waitn") ;
-  /* genalloc_free(pid_t, &pids) ; */
   return 0 ;
 }
diff --git a/src/execline/forx.c b/src/execline/forx.c
index 25d6d44..ef08ab2 100644
--- a/src/execline/forx.c
+++ b/src/execline/forx.c
@@ -11,10 +11,10 @@
 #include <execline/config.h>
 #include <execline/execline.h>
 
-#define USAGE "forx [ -p | -x breakcode,breakcode,... ] var { values... } command..."
+#define USAGE "forx [ -p | -o okcode,okcode,... | -x breakcode,breakcode,... ] var { values... } command..."
 #define dieusage() strerr_dieusage(100, USAGE)
 
-static int isbreak (unsigned short *tab, unsigned int n, int code)
+static int isok (unsigned short *tab, unsigned int n, int code)
 {
   register unsigned int i = 0 ;
   for (; i < n ; i++) if ((unsigned short)code == tab[i]) break ;
@@ -25,22 +25,27 @@ int main (int argc, char const **argv, char const *const *envp)
 {
   char const *x ;
   int argc1 ;
-  unsigned short breakcodes[256] ;
+  unsigned short okcodes[256] ;
   unsigned int nbc = 0 ;
-  int flagpar = 0 ;
+  int flagpar = 0, not = 1 ;
   PROG = "forx" ;
   {
     subgetopt_t l = SUBGETOPT_ZERO ;
     for (;;)
     {
-      register int opt = subgetopt_r(argc, argv, "epx:", &l) ;
+      register int opt = subgetopt_r(argc, argv, "epo:x:", &l) ;
       if (opt == -1) break ;
       switch (opt)
       {
         case 'e' : break ; /* compat */
         case 'p' : flagpar = 1 ; break ;
+        case 'o' :
+          not = 0 ;
+          if (!ushort_scanlist(okcodes, 256, l.arg, &nbc)) dieusage() ;
+          break ;
         case 'x' :
-          if (!ushort_scanlist(breakcodes, 256, l.arg, &nbc)) dieusage() ;
+          not = 1 ;
+          if (!ushort_scanlist(okcodes, 256, l.arg, &nbc)) dieusage() ;
           break ;
         default : dieusage() ;
       }
@@ -80,8 +85,8 @@ int main (int argc, char const **argv, char const *const *envp)
         int wstat ;
         if (wait_pid(pid, &wstat) == -1)
           strerr_diefu2sys(111, "wait for ", argv[argc1+1]) ;
-        if (isbreak(breakcodes, nbc, wait_status(wstat)))
-          return wait_status(wstat) ;
+        if (not == isok(okcodes, nbc, wait_estatus(wstat)))
+          return wait_estatus(wstat) ;
       }
     }
     if (flagpar)
diff --git a/src/execline/if.c b/src/execline/if.c
index 9d0b4b4..410e118 100644
--- a/src/execline/if.c
+++ b/src/execline/if.c
@@ -15,9 +15,8 @@ int main (int argc, char const **argv, char const *const *envp)
 {
   int argc1, wstat ;
   pid_t pid ;
-  unsigned int not = 0 ;
+  int not = 0, flagnormalcrash = 0 ;
   unsigned short e = 1 ;
-  int flagnormalcrash = 0 ;
   PROG = "if" ;
   {
     subgetopt_t l = SUBGETOPT_ZERO ;
@@ -36,6 +35,7 @@ int main (int argc, char const **argv, char const *const *envp)
     }
     argc -= l.ind ; argv += l.ind ;
   }
+  if (e > 255) strerr_dief1x(100, "invalid exit code") ;
   argc1 = el_semicolon(argv) ;
   if (argc1 >= argc) strerr_dief1x(100, "unterminated block") ;
   argv[argc1] = 0 ;
@@ -46,9 +46,9 @@ int main (int argc, char const **argv, char const *const *envp)
   {
     char fmt[UINT_FMT] ;
     fmt[uint_fmt(fmt, WTERMSIG(wstat))] = 0 ;
-    strerr_dief2x(1, "child crashed with signal ", fmt) ;
+    strerr_dief2x(128 + WTERMSIG(wstat), "child crashed with signal ", fmt) ;
   }
-  if (not == !wait_status(wstat)) return (int)e ;
+  if (not == !wait_estatus(wstat)) return e ;
   pathexec0_run(argv+argc1+1, envp) ;
   strerr_dieexec(111, argv[argc1+1]) ;
 }
diff --git a/src/execline/ifelse.c b/src/execline/ifelse.c
index 6d8801e..edf7cdf 100644
--- a/src/execline/ifelse.c
+++ b/src/execline/ifelse.c
@@ -45,10 +45,10 @@ int main (int argc, char const **argv, char const *const *envp)
   if (!flagnormalcrash && WIFSIGNALED(wstat))
   {
     char fmt[UINT_FMT] ;
-    fmt[uint_fmt(fmt, WSTOPSIG(wstat))] = 0 ;
-    strerr_dief2x(1, "child crashed with signal ", fmt) ;
+    fmt[uint_fmt(fmt, WTERMSIG(wstat))] = 0 ;
+    strerr_dief2x(128 + WTERMSIG(wstat), "child crashed with signal ", fmt) ;
   }
-  if (not != !wait_status(wstat)) argv[argc2] = 0 ; else argv += argc2+1 ;
+  if (not != !wait_estatus(wstat)) argv[argc2] = 0 ; else argv += argc2+1 ;
   pathexec0_run(argv, envp) ;
   strerr_dieexec(111, *argv) ;
 }
diff --git a/src/execline/ifte.c b/src/execline/ifte.c
index 3fe021e..eae19c6 100644
--- a/src/execline/ifte.c
+++ b/src/execline/ifte.c
@@ -46,14 +46,14 @@ int main (int argc, char const **argv, char const *const *envp)
   {
     char fmt[UINT_FMT] ;
     fmt[uint_fmt(fmt, WTERMSIG(wstat))] = 0 ;
-    strerr_dief2x(1, "child crashed with signal ", fmt) ;
+    strerr_dief2x(128 + WTERMSIG(wstat), "child crashed with signal ", fmt) ;
   }
-  if (not != !wait_status(wstat)) argv[argc1] = 0 ;
-  else
+  if (not == !wait_estatus(wstat))
   {
     argv += argc1 + 1 ;
     argv[argc2] = 0 ;
   }
+  else argv[argc1] = 0 ;
   pathexec0_run(argv, envp) ;
   strerr_dieexec(111, *argv) ;
 }
diff --git a/src/execline/ifthenelse.c b/src/execline/ifthenelse.c
index 9960222..2d71fec 100644
--- a/src/execline/ifthenelse.c
+++ b/src/execline/ifthenelse.c
@@ -52,9 +52,9 @@ int main (int argc, char const **argv, char const *const *envp)
     {
       char fmt[UINT_FMT] ;
       fmt[uint_fmt(fmt, WTERMSIG(wstat))] = 0 ;
-      strerr_dief2x(1, "child crashed with signal ", fmt) ;
+      strerr_dief2x(128 + WTERMSIG(wstat), "child crashed with signal ", fmt) ;
     }
-    if (wait_status(wstat))
+    if (wait_estatus(wstat))
     {
       argv += argc2 + 1 ;
       argc2 = argc3 ;
diff --git a/src/execline/loopwhilex.c b/src/execline/loopwhilex.c
index 5cb6a4e..d2955ad 100644
--- a/src/execline/loopwhilex.c
+++ b/src/execline/loopwhilex.c
@@ -8,10 +8,10 @@
 #include <skalibs/djbunix.h>
 #include <execline/execline.h>
 
-#define USAGE "loopwhilex [ -n ] [ -x exitcode,exitcode,... ] prog..."
+#define USAGE "loopwhilex [ -n ] [ -o okcode,okcode,... | -x exitcode,exitcode,... ] prog..."
 #define dieusage() strerr_dieusage(100, USAGE)
 
-static int isbreak (unsigned short *tab, unsigned int n, int code)
+static int isok (unsigned short *tab, unsigned int n, int code)
 {
   register unsigned int i = 0 ;
   for (; i < n ; i++) if ((unsigned short)code == tab[i]) break ;
@@ -21,21 +21,26 @@ static int isbreak (unsigned short *tab, unsigned int n, int code)
 int main (int argc, char const *const *argv, char const *const *envp)
 {
   int wstat ;
-  int not = 0, cont = 1 ;
-  unsigned short breakcodes[256] ;
+  int not = 0, cont = 1, rev = 0 ;
+  unsigned short okcodes[256] ;
   unsigned int nbc = 0 ;
   PROG = "loopwhilex" ;
   {
     subgetopt_t l = SUBGETOPT_ZERO ;
     for (;;)
     {
-      register int opt = subgetopt_r(argc, argv, "nx:", &l) ;
+      register int opt = subgetopt_r(argc, argv, "no:x:", &l) ;
       if (opt == -1) break ;
       switch (opt)
       {
         case 'n' : not = 1 ; break ;
+        case 'o' :
+          rev = 0 ;
+          if (!ushort_scanlist(okcodes, 256, l.arg, &nbc)) dieusage() ;
+          break ;
         case 'x' :
-          if (!ushort_scanlist(breakcodes, 256, l.arg, &nbc)) dieusage() ;
+          rev = 1 ;
+          if (!ushort_scanlist(okcodes, 256, l.arg, &nbc)) dieusage() ;
           break ;
         default : dieusage() ;
       }
@@ -46,17 +51,17 @@ int main (int argc, char const *const *argv, char const *const *envp)
 
   if (!nbc)
   {
-    breakcodes[0] = 0 ;
+    okcodes[0] = 0 ;
     nbc = 1 ;
-    not = !not ;
   }
+  else if (rev) not = !not ;
 
   while (cont)
   {
     pid_t pid = el_spawn0(argv[0], argv, envp) ;
     if (!pid) strerr_diefu2sys(111, "spawn ", argv[0]) ;
     if (wait_pid(pid, &wstat) < 0) strerr_diefu1sys(111, "wait_pid") ;
-    cont = not == isbreak(breakcodes, nbc, wait_status(wstat)) ;
+    cont = not != isok(okcodes, nbc, wait_estatus(wstat)) ;
   }
-  return WIFSIGNALED(wstat) ? WTERMSIG(wstat) : 0 ;
+  return wait_estatus(wstat) ;
 }