Merge remote-tracking branch 'toybox/master' into update am: fd67c867d2 am: 9ca15f063b am: b6ece7f09c am: 99dd3dcebe

Original change: undetermined

Change-Id: I17233d316206e42988c04b4dd2883807ab68068f
diff --git a/android/device/generated/flags.h b/android/device/generated/flags.h
index 459170a..6a71e69 100644
--- a/android/device/generated/flags.h
+++ b/android/device/generated/flags.h
@@ -1524,6 +1524,19 @@
 #undef FLAG_a
 #endif
 
+// jobs   lnprs
+#undef OPTSTR_jobs
+#define OPTSTR_jobs "lnprs"
+#ifdef CLEANUP_jobs
+#undef CLEANUP_jobs
+#undef FOR_jobs
+#undef FLAG_s
+#undef FLAG_r
+#undef FLAG_p
+#undef FLAG_n
+#undef FLAG_l
+#endif
+
 // kill ?ls:  ?ls: 
 #undef OPTSTR_kill
 #define OPTSTR_kill "?ls: "
@@ -3469,6 +3482,15 @@
 #undef FOR_w
 #endif
 
+// wait   n
+#undef OPTSTR_wait
+#define OPTSTR_wait "n"
+#ifdef CLEANUP_wait
+#undef CLEANUP_wait
+#undef FOR_wait
+#undef FLAG_n
+#endif
+
 // watch ^<1n%<100=2000tebx ^<1n%<100=2000tebx
 #undef OPTSTR_watch
 #define OPTSTR_watch "^<1n%<100=2000tebx"
@@ -4881,6 +4903,17 @@
 #define FLAG_a (FORCED_FLAG<<9)
 #endif
 
+#ifdef FOR_jobs
+#ifndef TT
+#define TT this.jobs
+#endif
+#define FLAG_s (FORCED_FLAG<<0)
+#define FLAG_r (FORCED_FLAG<<1)
+#define FLAG_p (FORCED_FLAG<<2)
+#define FLAG_n (FORCED_FLAG<<3)
+#define FLAG_l (FORCED_FLAG<<4)
+#endif
+
 #ifdef FOR_kill
 #ifndef TT
 #define TT this.kill
@@ -6516,6 +6549,13 @@
 #endif
 #endif
 
+#ifdef FOR_wait
+#ifndef TT
+#define TT this.wait
+#endif
+#define FLAG_n (FORCED_FLAG<<0)
+#endif
+
 #ifdef FOR_watch
 #ifndef TT
 #define TT this.watch
diff --git a/android/device/generated/globals.h b/android/device/generated/globals.h
index 61744c4..7d5e3be 100644
--- a/android/device/generated/globals.h
+++ b/android/device/generated/globals.h
@@ -908,7 +908,7 @@
     struct sh_process *next, *prev; // | && ||
     struct arg_list *delete;   // expanded strings
     // undo redirects, a=b at start, child PID, exit status, has !, job #
-    int *urd, envlen, pid, exit, not, job;
+    int *urd, envlen, pid, exit, not, job, dash;
     long long when; // when job backgrounded/suspended
     struct sh_arg *raw, arg;
   } *pp; // currently running process
@@ -970,8 +970,6 @@
 struct telnet_data {
   int sock;
   char buf[2048]; // Half sizeof(toybuf) allows a buffer full of IACs.
-  char iac[128];
-  int iac_len;
   struct termios old_term;
   struct termios raw_term;
   uint8_t mode;
diff --git a/android/device/generated/help.h b/android/device/generated/help.h
index 4286e01..ebddd3b 100644
--- a/android/device/generated/help.h
+++ b/android/device/generated/help.h
@@ -358,6 +358,8 @@
 
 #define HELP_stty "usage: stty [-ag] [-F device] SETTING...\n\nGet/set terminal configuration.\n\n-F	Open device instead of stdin\n-a	Show all current settings (default differences from \"sane\")\n-g	Show all current settings usable as input to stty\n\nSpecial characters (syntax ^c or undef): intr quit erase kill eof eol eol2\nswtch start stop susp rprnt werase lnext discard\n\nControl/input/output/local settings as shown by -a, '-' prefix to disable\n\nCombo settings: cooked/raw, evenp/oddp/parity, nl, ek, sane\n\nN	set input and output speed (ispeed N or ospeed N for just one)\ncols N	set number of columns\nrows N	set number of rows\nline N	set line discipline\nmin N	set minimum chars per read\ntime N	set read timeout\nspeed	show speed only\nsize	show size only"
 
+#define HELP_wait "usage: wait [-n] [ID...]\n\nWait for background processes to exit, returning its exit code.\nID can be PID or job, with no IDs waits for all backgrounded processes.\n\n-n	Wait for next process to exit"
+
 #define HELP_source "usage: source FILE [ARGS...]\n\nRead FILE and execute commands. Any ARGS become positional parameters."
 
 #define HELP_shift "usage: shift [N]\n\nSkip N (default 1) positional parameters, moving $1 and friends along the list.\nDoes not affect $0."
diff --git a/android/device/generated/newtoys.h b/android/device/generated/newtoys.h
index 04b4b8e..40ac3d4 100644
--- a/android/device/generated/newtoys.h
+++ b/android/device/generated/newtoys.h
@@ -139,6 +139,7 @@
 USE_IP(OLDTOY(iproute, ip, TOYFLAG_SBIN))
 USE_IP(OLDTOY(iprule, ip, TOYFLAG_SBIN))
 USE_IP(OLDTOY(iptunnel, ip, TOYFLAG_SBIN))
+USE_SH(NEWTOY(jobs, "lnprs", TOYFLAG_NOFORK))
 USE_KILL(NEWTOY(kill, "?ls: ", TOYFLAG_BIN|TOYFLAG_MAYFORK))
 USE_KILLALL(NEWTOY(killall, "?s:ilqvw", TOYFLAG_USR|TOYFLAG_BIN))
 USE_KILLALL5(NEWTOY(killall5, "?o*ls: [!lo][!ls]", TOYFLAG_SBIN))
@@ -305,6 +306,7 @@
 USE_VI(NEWTOY(vi, ">1s:", TOYFLAG_USR|TOYFLAG_BIN))
 USE_VMSTAT(NEWTOY(vmstat, ">2n", TOYFLAG_BIN))
 USE_W(NEWTOY(w, NULL, TOYFLAG_USR|TOYFLAG_BIN))
+USE_SH(NEWTOY(wait, "n", TOYFLAG_NOFORK))
 USE_WATCH(NEWTOY(watch, "^<1n%<100=2000tebx", TOYFLAG_USR|TOYFLAG_BIN|TOYFLAG_LOCALE))
 USE_WATCHDOG(NEWTOY(watchdog, "<1>1Ft#=4<1T#=60<1", TOYFLAG_NEEDROOT|TOYFLAG_BIN))
 USE_WC(NEWTOY(wc, "mcwl", TOYFLAG_USR|TOYFLAG_BIN|TOYFLAG_LOCALE))
diff --git a/android/linux/generated/flags.h b/android/linux/generated/flags.h
index fadedb3..7f37168 100644
--- a/android/linux/generated/flags.h
+++ b/android/linux/generated/flags.h
@@ -1524,6 +1524,19 @@
 #undef FLAG_a
 #endif
 
+// jobs   lnprs
+#undef OPTSTR_jobs
+#define OPTSTR_jobs "lnprs"
+#ifdef CLEANUP_jobs
+#undef CLEANUP_jobs
+#undef FOR_jobs
+#undef FLAG_s
+#undef FLAG_r
+#undef FLAG_p
+#undef FLAG_n
+#undef FLAG_l
+#endif
+
 // kill   ?ls: 
 #undef OPTSTR_kill
 #define OPTSTR_kill "?ls: "
@@ -3469,6 +3482,15 @@
 #undef FOR_w
 #endif
 
+// wait   n
+#undef OPTSTR_wait
+#define OPTSTR_wait "n"
+#ifdef CLEANUP_wait
+#undef CLEANUP_wait
+#undef FOR_wait
+#undef FLAG_n
+#endif
+
 // watch   ^<1n%<100=2000tebx
 #undef OPTSTR_watch
 #define OPTSTR_watch "^<1n%<100=2000tebx"
@@ -4881,6 +4903,17 @@
 #define FLAG_a (FORCED_FLAG<<9)
 #endif
 
+#ifdef FOR_jobs
+#ifndef TT
+#define TT this.jobs
+#endif
+#define FLAG_s (FORCED_FLAG<<0)
+#define FLAG_r (FORCED_FLAG<<1)
+#define FLAG_p (FORCED_FLAG<<2)
+#define FLAG_n (FORCED_FLAG<<3)
+#define FLAG_l (FORCED_FLAG<<4)
+#endif
+
 #ifdef FOR_kill
 #ifndef TT
 #define TT this.kill
@@ -6516,6 +6549,13 @@
 #endif
 #endif
 
+#ifdef FOR_wait
+#ifndef TT
+#define TT this.wait
+#endif
+#define FLAG_n (FORCED_FLAG<<0)
+#endif
+
 #ifdef FOR_watch
 #ifndef TT
 #define TT this.watch
diff --git a/android/linux/generated/globals.h b/android/linux/generated/globals.h
index 61744c4..7d5e3be 100644
--- a/android/linux/generated/globals.h
+++ b/android/linux/generated/globals.h
@@ -908,7 +908,7 @@
     struct sh_process *next, *prev; // | && ||
     struct arg_list *delete;   // expanded strings
     // undo redirects, a=b at start, child PID, exit status, has !, job #
-    int *urd, envlen, pid, exit, not, job;
+    int *urd, envlen, pid, exit, not, job, dash;
     long long when; // when job backgrounded/suspended
     struct sh_arg *raw, arg;
   } *pp; // currently running process
@@ -970,8 +970,6 @@
 struct telnet_data {
   int sock;
   char buf[2048]; // Half sizeof(toybuf) allows a buffer full of IACs.
-  char iac[128];
-  int iac_len;
   struct termios old_term;
   struct termios raw_term;
   uint8_t mode;
diff --git a/android/linux/generated/help.h b/android/linux/generated/help.h
index c99ddf1..86fbdbf 100644
--- a/android/linux/generated/help.h
+++ b/android/linux/generated/help.h
@@ -360,6 +360,8 @@
 
 #define HELP_stty "usage: stty [-ag] [-F device] SETTING...\n\nGet/set terminal configuration.\n\n-F	Open device instead of stdin\n-a	Show all current settings (default differences from \"sane\")\n-g	Show all current settings usable as input to stty\n\nSpecial characters (syntax ^c or undef): intr quit erase kill eof eol eol2\nswtch start stop susp rprnt werase lnext discard\n\nControl/input/output/local settings as shown by -a, '-' prefix to disable\n\nCombo settings: cooked/raw, evenp/oddp/parity, nl, ek, sane\n\nN	set input and output speed (ispeed N or ospeed N for just one)\ncols N	set number of columns\nrows N	set number of rows\nline N	set line discipline\nmin N	set minimum chars per read\ntime N	set read timeout\nspeed	show speed only\nsize	show size only"
 
+#define HELP_wait "usage: wait [-n] [ID...]\n\nWait for background processes to exit, returning its exit code.\nID can be PID or job, with no IDs waits for all backgrounded processes.\n\n-n	Wait for next process to exit"
+
 #define HELP_source "usage: source FILE [ARGS...]\n\nRead FILE and execute commands. Any ARGS become positional parameters."
 
 #define HELP_shift "usage: shift [N]\n\nSkip N (default 1) positional parameters, moving $1 and friends along the list.\nDoes not affect $0."
diff --git a/android/linux/generated/newtoys.h b/android/linux/generated/newtoys.h
index 04b4b8e..40ac3d4 100644
--- a/android/linux/generated/newtoys.h
+++ b/android/linux/generated/newtoys.h
@@ -139,6 +139,7 @@
 USE_IP(OLDTOY(iproute, ip, TOYFLAG_SBIN))
 USE_IP(OLDTOY(iprule, ip, TOYFLAG_SBIN))
 USE_IP(OLDTOY(iptunnel, ip, TOYFLAG_SBIN))
+USE_SH(NEWTOY(jobs, "lnprs", TOYFLAG_NOFORK))
 USE_KILL(NEWTOY(kill, "?ls: ", TOYFLAG_BIN|TOYFLAG_MAYFORK))
 USE_KILLALL(NEWTOY(killall, "?s:ilqvw", TOYFLAG_USR|TOYFLAG_BIN))
 USE_KILLALL5(NEWTOY(killall5, "?o*ls: [!lo][!ls]", TOYFLAG_SBIN))
@@ -305,6 +306,7 @@
 USE_VI(NEWTOY(vi, ">1s:", TOYFLAG_USR|TOYFLAG_BIN))
 USE_VMSTAT(NEWTOY(vmstat, ">2n", TOYFLAG_BIN))
 USE_W(NEWTOY(w, NULL, TOYFLAG_USR|TOYFLAG_BIN))
+USE_SH(NEWTOY(wait, "n", TOYFLAG_NOFORK))
 USE_WATCH(NEWTOY(watch, "^<1n%<100=2000tebx", TOYFLAG_USR|TOYFLAG_BIN|TOYFLAG_LOCALE))
 USE_WATCHDOG(NEWTOY(watchdog, "<1>1Ft#=4<1T#=60<1", TOYFLAG_NEEDROOT|TOYFLAG_BIN))
 USE_WC(NEWTOY(wc, "mcwl", TOYFLAG_USR|TOYFLAG_BIN|TOYFLAG_LOCALE))
diff --git a/android/mac/generated/flags.h b/android/mac/generated/flags.h
index b9d6485..5f4854d 100644
--- a/android/mac/generated/flags.h
+++ b/android/mac/generated/flags.h
@@ -1524,6 +1524,19 @@
 #undef FLAG_a
 #endif
 
+// jobs   lnprs
+#undef OPTSTR_jobs
+#define OPTSTR_jobs "lnprs"
+#ifdef CLEANUP_jobs
+#undef CLEANUP_jobs
+#undef FOR_jobs
+#undef FLAG_s
+#undef FLAG_r
+#undef FLAG_p
+#undef FLAG_n
+#undef FLAG_l
+#endif
+
 // kill   ?ls: 
 #undef OPTSTR_kill
 #define OPTSTR_kill "?ls: "
@@ -3469,6 +3482,15 @@
 #undef FOR_w
 #endif
 
+// wait   n
+#undef OPTSTR_wait
+#define OPTSTR_wait "n"
+#ifdef CLEANUP_wait
+#undef CLEANUP_wait
+#undef FOR_wait
+#undef FLAG_n
+#endif
+
 // watch   ^<1n%<100=2000tebx
 #undef OPTSTR_watch
 #define OPTSTR_watch "^<1n%<100=2000tebx"
@@ -4881,6 +4903,17 @@
 #define FLAG_a (FORCED_FLAG<<9)
 #endif
 
+#ifdef FOR_jobs
+#ifndef TT
+#define TT this.jobs
+#endif
+#define FLAG_s (FORCED_FLAG<<0)
+#define FLAG_r (FORCED_FLAG<<1)
+#define FLAG_p (FORCED_FLAG<<2)
+#define FLAG_n (FORCED_FLAG<<3)
+#define FLAG_l (FORCED_FLAG<<4)
+#endif
+
 #ifdef FOR_kill
 #ifndef TT
 #define TT this.kill
@@ -6516,6 +6549,13 @@
 #endif
 #endif
 
+#ifdef FOR_wait
+#ifndef TT
+#define TT this.wait
+#endif
+#define FLAG_n (FORCED_FLAG<<0)
+#endif
+
 #ifdef FOR_watch
 #ifndef TT
 #define TT this.watch
diff --git a/android/mac/generated/globals.h b/android/mac/generated/globals.h
index 61744c4..7d5e3be 100644
--- a/android/mac/generated/globals.h
+++ b/android/mac/generated/globals.h
@@ -908,7 +908,7 @@
     struct sh_process *next, *prev; // | && ||
     struct arg_list *delete;   // expanded strings
     // undo redirects, a=b at start, child PID, exit status, has !, job #
-    int *urd, envlen, pid, exit, not, job;
+    int *urd, envlen, pid, exit, not, job, dash;
     long long when; // when job backgrounded/suspended
     struct sh_arg *raw, arg;
   } *pp; // currently running process
@@ -970,8 +970,6 @@
 struct telnet_data {
   int sock;
   char buf[2048]; // Half sizeof(toybuf) allows a buffer full of IACs.
-  char iac[128];
-  int iac_len;
   struct termios old_term;
   struct termios raw_term;
   uint8_t mode;
diff --git a/android/mac/generated/help.h b/android/mac/generated/help.h
index c99ddf1..86fbdbf 100644
--- a/android/mac/generated/help.h
+++ b/android/mac/generated/help.h
@@ -360,6 +360,8 @@
 
 #define HELP_stty "usage: stty [-ag] [-F device] SETTING...\n\nGet/set terminal configuration.\n\n-F	Open device instead of stdin\n-a	Show all current settings (default differences from \"sane\")\n-g	Show all current settings usable as input to stty\n\nSpecial characters (syntax ^c or undef): intr quit erase kill eof eol eol2\nswtch start stop susp rprnt werase lnext discard\n\nControl/input/output/local settings as shown by -a, '-' prefix to disable\n\nCombo settings: cooked/raw, evenp/oddp/parity, nl, ek, sane\n\nN	set input and output speed (ispeed N or ospeed N for just one)\ncols N	set number of columns\nrows N	set number of rows\nline N	set line discipline\nmin N	set minimum chars per read\ntime N	set read timeout\nspeed	show speed only\nsize	show size only"
 
+#define HELP_wait "usage: wait [-n] [ID...]\n\nWait for background processes to exit, returning its exit code.\nID can be PID or job, with no IDs waits for all backgrounded processes.\n\n-n	Wait for next process to exit"
+
 #define HELP_source "usage: source FILE [ARGS...]\n\nRead FILE and execute commands. Any ARGS become positional parameters."
 
 #define HELP_shift "usage: shift [N]\n\nSkip N (default 1) positional parameters, moving $1 and friends along the list.\nDoes not affect $0."
diff --git a/android/mac/generated/newtoys.h b/android/mac/generated/newtoys.h
index 04b4b8e..40ac3d4 100644
--- a/android/mac/generated/newtoys.h
+++ b/android/mac/generated/newtoys.h
@@ -139,6 +139,7 @@
 USE_IP(OLDTOY(iproute, ip, TOYFLAG_SBIN))
 USE_IP(OLDTOY(iprule, ip, TOYFLAG_SBIN))
 USE_IP(OLDTOY(iptunnel, ip, TOYFLAG_SBIN))
+USE_SH(NEWTOY(jobs, "lnprs", TOYFLAG_NOFORK))
 USE_KILL(NEWTOY(kill, "?ls: ", TOYFLAG_BIN|TOYFLAG_MAYFORK))
 USE_KILLALL(NEWTOY(killall, "?s:ilqvw", TOYFLAG_USR|TOYFLAG_BIN))
 USE_KILLALL5(NEWTOY(killall5, "?o*ls: [!lo][!ls]", TOYFLAG_SBIN))
@@ -305,6 +306,7 @@
 USE_VI(NEWTOY(vi, ">1s:", TOYFLAG_USR|TOYFLAG_BIN))
 USE_VMSTAT(NEWTOY(vmstat, ">2n", TOYFLAG_BIN))
 USE_W(NEWTOY(w, NULL, TOYFLAG_USR|TOYFLAG_BIN))
+USE_SH(NEWTOY(wait, "n", TOYFLAG_NOFORK))
 USE_WATCH(NEWTOY(watch, "^<1n%<100=2000tebx", TOYFLAG_USR|TOYFLAG_BIN|TOYFLAG_LOCALE))
 USE_WATCHDOG(NEWTOY(watchdog, "<1>1Ft#=4<1T#=60<1", TOYFLAG_NEEDROOT|TOYFLAG_BIN))
 USE_WC(NEWTOY(wc, "mcwl", TOYFLAG_USR|TOYFLAG_BIN|TOYFLAG_LOCALE))
diff --git a/lib/portability.c b/lib/portability.c
index f3c3c25..6118d0f 100644
--- a/lib/portability.c
+++ b/lib/portability.c
@@ -49,7 +49,7 @@
 // Get list of mounted filesystems, including stat and statvfs info.
 // Returns a reversed list, which is good for finding overmounts and such.
 
-#if defined(__APPLE__) || defined(__FreeBSD__)
+#if defined(__APPLE__) || defined(__FreeBSD__) || defined(__OpenBSD__)
 
 #include <sys/mount.h>
 
@@ -188,7 +188,7 @@
 
 #endif
 
-#ifdef __APPLE__
+#if defined(__APPLE__) || defined(__OpenBSD__)
 
 #include <sys/event.h>
 
@@ -332,7 +332,7 @@
   return fsetxattr(fd, name, value, size, 0, flags);
 }
 
-#else
+#elif !defined(__OpenBSD__)
 
 ssize_t xattr_get(const char *path, const char *name, void *value, size_t size)
 {
@@ -534,6 +534,8 @@
   return ((dev&0xfff00000)>>12)|(dev&0xff);
 #elif defined(__APPLE__)
   return dev&0xffffff;
+#elif defined(__OpenBSD__)
+  return minor(dev);
 #else
 #error
 #endif
@@ -545,6 +547,8 @@
   return (dev&0xfff00)>>8;
 #elif defined(__APPLE__)
   return (dev>>24)&0xff;
+#elif defined(__OpenBSD__)
+  return major(dev);
 #else
 #error
 #endif
@@ -556,6 +560,8 @@
   return (minor&0xff)|((major&0xfff)<<8)|((minor&0xfff00)<<12);
 #elif defined(__APPLE__)
   return (minor&0xffffff)|((major&0xff)<<24);
+#elif defined(__OpenBSD__)
+  return makedev(major, minor);
 #else
 #error
 #endif
@@ -563,7 +569,7 @@
 
 char *fs_type_name(struct statfs *statfs)
 {
-#if defined(__APPLE__)
+#if defined(__APPLE__) || defined(__OpenBSD__)
   // macOS has an `f_type` field, but assigns values dynamically as filesystems
   // are registered. They do give you the name directly though, so use that.
   return statfs->f_fstypename;
@@ -606,6 +612,16 @@
 {
   return (ioctl(fd, BLKGETSIZE64, size) >= 0);
 }
+#elif defined(__OpenBSD__)
+#include <sys/dkio.h>
+#include <sys/disklabel.h>
+int get_block_device_size(int fd, unsigned long long* size)
+{
+  struct disklabel lab;
+  int status = (ioctl(fd, DIOCGDINFO, &lab) >= 0);
+  *size = lab.d_secsize * lab.d_nsectors;
+  return status;
+}
 #endif
 
 // TODO copy_file_range
diff --git a/lib/portability.h b/lib/portability.h
index 54f97af..bb792f1 100644
--- a/lib/portability.h
+++ b/lib/portability.h
@@ -133,7 +133,7 @@
 #define bswap_32(x) OSSwapInt32(x)
 #define bswap_64(x) OSSwapInt64(x)
 
-#elif defined(__FreeBSD__)
+#elif defined(__FreeBSD__) || defined(__OpenBSD__)
 
 #include <sys/endian.h>
 
@@ -184,7 +184,7 @@
 
 #ifdef __APPLE__
 #include <util.h>
-#elif !defined(__FreeBSD__)
+#elif !defined(__FreeBSD__) && !defined(__OpenBSD__)
 #include <pty.h>
 #else
 #include <termios.h>
@@ -208,13 +208,16 @@
 ssize_t xattr_fset(int, const char*, const void*, size_t, int);
 #endif
 
-#ifdef __APPLE__
+#if defined(__APPLE__)
 // macOS doesn't have these functions, but we can fake them.
 int mknodat(int, const char*, mode_t, dev_t);
 int posix_fallocate(int, off_t, off_t);
 
 // macOS keeps newlocale(3) in the non-POSIX <xlocale.h> rather than <locale.h>.
 #include <xlocale.h>
+#endif
+
+#if defined(__APPLE__) || defined(__OpenBSD__)
 static inline long statfs_bsize(struct statfs *sf) { return sf->f_iosize; }
 static inline long statfs_frsize(struct statfs *sf) { return sf->f_bsize; }
 #else
diff --git a/tests/sh.test b/tests/sh.test
index 84f8dab..947e3ae 100644
--- a/tests/sh.test
+++ b/tests/sh.test
@@ -123,8 +123,9 @@
 testing 'default exports' \
   "env -i \"$(which $SH)\" --noprofile --norc -c env | sort" \
   "PWD=$(pwd)\nSHLVL=1\n_=$(which env)\n" "" ""
-testing "leading assignment fail" \
-  "{ \$SH -c 'X=\${a?blah} > walroid';ls walroid;} 2>/dev/null" '' '' ''
+# toysh order of operations not matching bash
+#testing "leading assignment fail" \
+#  "{ \$SH -c 'X=\${a?blah} > walroid';ls walroid;} 2>/dev/null" '' '' ''
 testing "lineno" "$SH input" "5 one\n6 one\n5 two\n6 two\n" \
   '#!/bin/bash\n\nfor i in one two\ndo\n  echo $LINENO $i\n  echo $LINENO $i\ndone\n' ""
 testing "eval0" "sh -c 'eval echo \$*' one two three" "two three\n" "" ""
@@ -151,6 +152,8 @@
 testing "||" "true || echo hello" "" "" ""
 testing "||2" "false || echo hello" "hello\n" "" ""
 testing "&& ||" "true && false && potato || echo hello" "hello\n" "" ""
+testing "&& after function" "x(){ false;};x && echo yes" "" "" ""
+testing "|| after function" "x(){ false;};x || echo yes" "yes\n" "" ""
 
 # redirection
 
diff --git a/toys/pending/sh.c b/toys/pending/sh.c
index 3ae66f6..ab2408a 100644
--- a/toys/pending/sh.c
+++ b/toys/pending/sh.c
@@ -45,11 +45,13 @@
 USE_SH(NEWTOY(exec, "^cla:", TOYFLAG_NOFORK))
 USE_SH(NEWTOY(exit, 0, TOYFLAG_NOFORK))
 USE_SH(NEWTOY(export, "np", TOYFLAG_NOFORK))
+USE_SH(NEWTOY(jobs, "lnprs", TOYFLAG_NOFORK))
 USE_SH(NEWTOY(set, 0, TOYFLAG_NOFORK))
 USE_SH(NEWTOY(shift, ">1", TOYFLAG_NOFORK))
 USE_SH(NEWTOY(source, "<1", TOYFLAG_NOFORK))
 USE_SH(OLDTOY(., source, TOYFLAG_NOFORK))
 USE_SH(NEWTOY(unset, "fvn[!fv]", TOYFLAG_NOFORK))
+USE_SH(NEWTOY(wait, "n", TOYFLAG_NOFORK))
 
 USE_SH(NEWTOY(sh, "0(noediting)(noprofile)(norc)sc:i", TOYFLAG_BIN))
 USE_SH(OLDTOY(toysh, sh, TOYFLAG_BIN))
@@ -201,6 +203,18 @@
     usage: source FILE [ARGS...]
 
     Read FILE and execute commands. Any ARGS become positional parameters.
+
+config WAIT
+  bool
+  default n
+  depends on SH
+  help
+    usage: wait [-n] [ID...]
+
+    Wait for background processes to exit, returning its exit code.
+    ID can be PID or job, with no IDs waits for all backgrounded processes.
+
+    -n	Wait for next process to exit
 */
 
 #define FOR_sh
@@ -271,7 +285,7 @@
     struct sh_process *next, *prev; // | && ||
     struct arg_list *delete;   // expanded strings
     // undo redirects, a=b at start, child PID, exit status, has !, job #
-    int *urd, envlen, pid, exit, not, job;
+    int *urd, envlen, pid, exit, not, job, dash;
     long long when; // when job backgrounded/suspended
     struct sh_arg *raw, arg;
   } *pp; // currently running process
@@ -752,6 +766,7 @@
 // TODO: test $$ in (nommu)
 }
 
+// TODO what happens when you background a function?
 // turn a parsed pipeline back into a string.
 static char *pl2str(struct sh_pipeline *pl, int one)
 {
@@ -2327,6 +2342,7 @@
   // Create new function context to hold local vars?
   if (funk != TT.funcslen || (envlen && pp->arg.c) || TT.ff->blk->pipe) {
     call_function();
+// TODO function needs to run asynchronously in pipeline
     if (funk != TT.funcslen) {
       TT.ff->delete = pp->delete;
       pp->delete = 0;
@@ -2370,7 +2386,6 @@
   else if (funk != TT.funcslen) {
     (TT.ff->func = TT.functions[funk])->refcount++;
     TT.ff->pl = TT.ff->func->pipeline;
-    TT.ff->next->pl = TT.ff->next->pl->next;
     TT.ff->arg = pp->arg;
   } else {
     struct toy_list *tl = toy_find(*pp->arg.v);
@@ -2420,11 +2435,16 @@
   return pp;
 }
 
-static void free_process(void *ppp)
+static int free_process(struct sh_process *pp)
 {
-  struct sh_process *pp = ppp;
+  int rc;
+
+  if (!pp) return 127;
+  rc = pp->exit;
   llist_traverse(pp->delete, llist_free_arg);
   free(pp);
+
+  return rc;
 }
 
 // if then fi for while until select done done case esac break continue return
@@ -2563,6 +2583,7 @@
         pl->next = *ppl;
         (*ppl)->prev = pl;
         dlist_terminate(funky->pipeline = add_pl(&funky->pipeline, 0));
+        funky->pipeline->type = 'f';
 
         // Immature function has matured (meaning cleanup is different)
         pl->type = 'F';
@@ -2903,6 +2924,80 @@
   return 0-!!s;
 }
 
+// Find + and - jobs. Returns index of plus, writes minus to *minus
+int find_plus_minus(int *minus)
+{
+  long long when, then;
+  int i, plus;
+
+  if (minus) *minus = 0;
+  for (then = i = plus = 0; i<TT.jobs.c; i++) {
+    if ((when = ((struct sh_process *)TT.jobs.v[i])->when) > then) {
+      then = when;
+      if (minus) *minus = plus;
+      plus = i;
+    }
+  }
+
+  return plus;
+}
+
+char is_plus_minus(int i, int plus, int minus)
+{
+  return (i == plus) ? '+' : (i == minus) ? '-' : ' ';
+}
+
+
+// We pass in dash to avoid looping over every job each time
+char *show_job(struct sh_process *pp, char dash)
+{
+  char *s = "Run", *buf = 0;
+  int i, j, len, len2;
+
+// TODO Terminated (Exited)
+  if (pp->exit<0) s = "Stop";
+  else if (pp->exit>126) s = "Kill";
+  else if (pp->exit>0) s = "Done";
+  for (i = len = len2 = 0;; i++) {
+    len += snprintf(buf, len2, "[%d]%c  %-6s", pp->job, dash, s);
+    for (j = 0; j<pp->raw->c; j++)
+      len += snprintf(buf, len2, " %s"+!j, pp->raw->v[j]);
+    if (!i) buf = xmalloc(len2 = len+1);
+    else break;
+  }
+
+  return buf;
+}
+
+// Wait for pid to exit and remove from jobs table, returning process or 0.
+struct sh_process *wait_job(int pid, int nohang)
+{
+  struct sh_process *pp;
+  int ii, status, minus, plus;
+
+  if (TT.jobs.c<1) return 0;
+  for (;;) {
+    errno = 0;
+    if (1>(pid = waitpid(pid, &status, nohang ? WNOHANG : 0))) {
+      if (!nohang && errno==EINTR && !toys.signal) continue;
+      return 0;
+    }
+    for (ii = 0; ii<TT.jobs.c; ii++) {
+      pp = (void *)TT.jobs.v[ii];
+      if (pp->pid == pid) break;
+    }
+    if (ii == TT.jobs.c) continue;
+    if (pid<1) return 0;
+    if (!WIFSTOPPED(status) && !WIFCONTINUED(status)) break;
+  }
+  plus = find_plus_minus(&minus);
+  memmove(TT.jobs.v+ii, TT.jobs.v+ii+1, (TT.jobs.c--)-ii);
+  pp->exit = WIFEXITED(status) ? WEXITSTATUS(status) : WTERMSIG(status)+128;
+  pp->dash = is_plus_minus(ii, plus, minus);
+
+  return pp;
+}
+
 // wait for every process in a pipeline to end
 static int wait_pipeline(struct sh_process *pp)
 {
@@ -2918,6 +3013,13 @@
     rc = pp->not ? !pp->exit : pp->exit;
   }
 
+  while ((pp = wait_job(-1, 1)) && (TT.options&FLAG_i)) {
+    char *s = show_job(pp, pp->dash);
+
+    dprintf(2, "%s\n", s);
+    free(s);
+  }
+
   return rc;
 }
 
@@ -3049,14 +3151,13 @@
   for (;;) {
     if (!TT.ff->pl) {
       if (!end_function(1)) break;
-
-      continue;
+      goto advance;
     }
 
     ctl = TT.ff->pl->end->arg->v[TT.ff->pl->end->arg->c];
     s = *TT.ff->pl->arg->v;
     ss = TT.ff->pl->arg->v[1];
-//dprintf(2, "%d s=%s ss=%s ctl=%s type=%d\n", getpid(), (TT.ff->pl->type == 'F') ? ((struct sh_function *)s)->name : s, ss, ctl, TT.ff->pl->type);
+//dprintf(2, "%d s=%s ss=%s ctl=%s type=%d pl=%p ff=%p\n", getpid(), (TT.ff->pl->type == 'F') ? ((struct sh_function *)s)->name : s, ss, ctl, TT.ff->pl->type, TT.ff->pl, TT.ff);
     if (!pplist) TT.hfd = 10;
 
     // Skip disabled blocks, handle pipes and backgrounding
@@ -3303,7 +3404,6 @@
 
     // end of block
     } else if (TT.ff->pl->type == 3) {
-
       // If we end a block we're not in, exit subshell
       if (!TT.ff->blk->next) xexit();
 
@@ -3340,20 +3440,23 @@
     // If we ran a process and didn't pipe output, background or wait for exit
     if (pplist && TT.ff->blk->pout == -1) {
       if (ctl && !strcmp(ctl, "&")) {
+        if (!TT.jobs.c) TT.jobcnt = 0;
         pplist->job = ++TT.jobcnt;
         arg_add(&TT.jobs, (void *)pplist);
+        if (TT.options&FLAG_i) dprintf(2, "[%u] %u\n", pplist->job,pplist->pid);
       } else {
         toys.exitval = wait_pipeline(pplist);
-        llist_traverse(pplist, free_process);
+        llist_traverse(pplist, (void *)free_process);
       }
       pplist = 0;
     }
-
+advance:
     // for && and || skip pipeline segment(s) based on return code
     if (!TT.ff->pl->type || TT.ff->pl->type == 3) {
-      while (ctl && !strcmp(ctl, toys.exitval ? "&&" : "||")) {
-        if ((TT.ff->pl = TT.ff->pl->next)->type) TT.ff->pl = TT.ff->pl->end;
+      for (;;) {
         ctl = TT.ff->pl->arg->v[TT.ff->pl->arg->c];
+        if (!ctl || strcmp(ctl, toys.exitval ? "&&" : "||")) break;
+        if ((TT.ff->pl = TT.ff->pl->next)->type) TT.ff->pl = TT.ff->pl->end;
       }
     }
     TT.ff->pl = TT.ff->pl->next;
@@ -3362,7 +3465,7 @@
   // clean up any unfinished stuff
   if (pplist) {
     toys.exitval = wait_pipeline(pplist);
-    llist_traverse(pplist, free_process);
+    llist_traverse(pplist, (void *)free_process);
   }
 
   // exit source context (and function calls on syntax err)
@@ -3914,24 +4017,6 @@
   environ = old;
 }
 
-// Find + and - jobs. Returns index of plus, writes minus to *minus
-int find_plus_minus(int *minus)
-{
-  long long when, then;
-  int i, plus;
-
-  if (minus) *minus = 0;
-  for (then = i = plus = 0; i<TT.jobs.c; i++) {
-    if ((when = ((struct sh_process *)TT.jobs.v[i])->when) > then) {
-      then = when;
-      if (minus) *minus = plus;
-      plus = i;
-    }
-  }
-
-  return plus;
-}
-
 // Return T.jobs index or -1 from identifier
 // Note, we don't return "ambiguous job spec", we return the first hit or -1.
 // TODO %% %+ %- %?ab
@@ -3965,22 +4050,6 @@
   return -1;
 }
 
-// We pass in dash to avoid looping over every job each time
-void print_job(int i, char dash)
-{
-  struct sh_process *pp = (void *)TT.jobs.v[i];
-  char *s = "Run";
-  int j;
-
-// TODO Terminated (Exited)
-  if (pp->exit<0) s = "Stop";
-  else if (pp->exit>126) s = "Kill";
-  else if (pp->exit>0) s = "Done";
-  printf("[%d]%c  %-6s", pp->job, dash, s);
-  for (j = 0; j<pp->raw->c; j++) printf(" %s"+!j, pp->raw->v[j]);
-  printf("\n");
-}
-
 void jobs_main(void)
 {
   int i, j, minus, plus = find_plus_minus(&minus);
@@ -3998,7 +4067,9 @@
       }
     } else if ((j = i) >= TT.jobs.c) break;
 
-    print_job(i, (i == plus) ? '+' : (i == minus) ? '-' : ' ');
+    s = show_job((void *)TT.jobs.v[i], is_plus_minus(i, plus, minus));
+    printf("%s\n", s);
+    free(s);
   }
 }
 
@@ -4077,3 +4148,35 @@
   free(dlist_pop(&TT.ff));
   --TT.srclvl;
 }
+
+#define CLEANUP_local
+#define FOR_wait
+#include "generated/flags.h"
+
+void wait_main(void)
+{
+  struct sh_process *pp;
+  int ii, jj;
+  long long ll;
+  char *s;
+
+  // TODO does -o pipefail affect return code here
+  if (FLAG(n)) toys.exitval = free_process(wait_job(-1, 0));
+  else if (!toys.optc) while (TT.jobs.c) {
+    if (!(pp = wait_job(-1, 0))) break;
+  } else for (ii = 0; ii<toys.optc; ii++) {
+    ll = estrtol(toys.optargs[ii], &s, 10);
+    if (errno || *s) {
+      if (-1 == (jj = find_job(toys.optargs[ii]))) {
+        error_msg("%s: bad pid/job", toys.optargs[ii]);
+        continue;
+      }
+      ll = ((struct sh_process *)TT.jobs.v[jj])->pid;
+    }
+    if (!(pp = wait_job(ll, 0))) {
+      if (toys.signal) toys.exitval = 128+toys.signal;
+      break;
+    }
+    toys.exitval = free_process(pp);
+  }
+}
diff --git a/toys/pending/telnet.c b/toys/pending/telnet.c
index a7ea91e..6b2c273 100644
--- a/toys/pending/telnet.c
+++ b/toys/pending/telnet.c
@@ -24,8 +24,6 @@
 GLOBALS(
   int sock;
   char buf[2048]; // Half sizeof(toybuf) allows a buffer full of IACs.
-  char iac[128];
-  int iac_len;
   struct termios old_term;
   struct termios raw_term;
   uint8_t mode;
@@ -51,29 +49,6 @@
   tcsetattr(0, TCSADRAIN, raw ? &TT.raw_term : &TT.old_term);
 }
 
-static void flush_iac(void)
-{
-  xwrite(TT.sock, TT.iac, TT.iac_len);
-  TT.iac_len = 0;
-}
-
-static void iac(int n, ...)
-{
-  va_list va; 
-
-  if (TT.iac_len + n >= sizeof(TT.iac)) flush_iac();
-  va_start(va, n);
-  while (n--) TT.iac[TT.iac_len++] = va_arg(va, int);
-  va_end(va);
-}
-
-static void iacstr(char *str)
-{
-  if (TT.iac_len) flush_iac();
-  xwrite(TT.sock, str, strlen(str));
-  TT.iac_len = 0;
-}
-
 static void slc(int line)
 {
   TT.mode = line ? CM_OFF : CM_ON;
@@ -116,8 +91,7 @@
       TT.mode = CM_TRY;
       TT.echo = TT.sga = 0;
       set_mode();
-      iac(6, IAC, DONT, TELOPT_ECHO, IAC, DONT, TELOPT_SGA);
-      flush_iac();
+      dprintf(TT.sock,"%c%c%c%c%c%c",IAC,DONT,TELOPT_ECHO,IAC,DONT,TELOPT_SGA);
       goto ret;
     }
   } else if (input == 'c') {
@@ -125,8 +99,7 @@
       TT.mode = CM_TRY;
       TT.echo = TT.sga = 1;
       set_mode();
-      iac(6, IAC, DO, TELOPT_ECHO, IAC, DO, TELOPT_SGA);
-      flush_iac();
+      dprintf(TT.sock,"%c%c%c%c%c%c",IAC,DO,TELOPT_ECHO,IAC,DO,TELOPT_SGA);
       goto ret;
     }
   } else if (input == 'z') {
@@ -146,32 +119,32 @@
 static void handle_wwdd(char opt)
 {
   if (opt == TELOPT_ECHO) {
-    if (TT.request == DO) iac(3, IAC, WONT, TELOPT_ECHO);
+    if (TT.request == DO) dprintf(TT.sock, "%c%c%c", IAC, WONT, TELOPT_ECHO);
     if (TT.request == DONT) return;
     if (TT.echo) {
         if (TT.request == WILL) return;
     } else if (TT.request == WONT) return;
     if (TT.mode != CM_OFF) TT.echo ^= 1;
-    iac(3, IAC, TT.echo ? DO : DONT, TELOPT_ECHO);
+    dprintf(TT.sock, "%c%c%c", IAC, TT.echo ? DO : DONT, TELOPT_ECHO);
     set_mode();
   } else if (opt == TELOPT_SGA) { // Suppress Go Ahead
     if (TT.sga) {
       if (TT.request == WILL) return;
     } else if (TT.request == WONT) return;
     TT.sga ^= 1;
-    iac(3, IAC, TT.sga ? DO : DONT, TELOPT_SGA);
+    dprintf(TT.sock, "%c%c%c", IAC, TT.sga ? DO : DONT, TELOPT_SGA);
   } else if (opt == TELOPT_TTYPE) { // Terminal TYPE
-    iac(3, IAC, WILL, TELOPT_TTYPE);
+    dprintf(TT.sock, "%c%c%c", IAC, WILL, TELOPT_TTYPE);
   } else if (opt == TELOPT_NAWS) { // Negotiate About Window Size
     unsigned cols = 80, rows = 24;
 
     terminal_size(&cols, &rows);
-    iac(3, IAC, WILL, TELOPT_NAWS);
-    iac(7, IAC, SB, TELOPT_NAWS, cols>>8, cols, rows>>8, rows);
-    iac(2, IAC, SE);
+    dprintf(TT.sock, "%c%c%c%c%c%c%c%c%c%c%c%c", IAC, WILL, TELOPT_NAWS,
+            IAC, SB, TELOPT_NAWS, cols>>8, cols, rows>>8, rows,
+            IAC, SE);
   } else {
     // Say "no" to anything we don't understand.
-    iac(3, IAC, (TT.request == WILL) ? DONT : WONT, opt);
+    dprintf(TT.sock, "%c%c%c", IAC, (TT.request == WILL) ? DONT : WONT, opt);
   }
 }
 
@@ -210,9 +183,8 @@
       else TT.state = WANT_IAC;
     } else if (TT.state == SAW_SB_TTYPE) {
       if (ch == TELQUAL_SEND) {
-        iac(4, IAC, SB, TELOPT_TTYPE, TELQUAL_IS);
-        iacstr(getenv("TERM") ?: "NVT");
-        iac(2, IAC, SE);
+        dprintf(TT.sock, "%c%c%c%c%s%c%c", IAC, SB, TELOPT_TTYPE, TELQUAL_IS,
+                getenv("TERM") ?: "NVT", IAC, SE);
       }
       TT.state = WANT_IAC;
     } else if (TT.state == WANT_IAC) {
@@ -295,6 +267,5 @@
         error_exit("Connection closed by foreign host\r");
       handle_server_output(n);
     }
-    if (TT.iac_len) flush_iac();
   }
 }