aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorElliott Hughes <enh@google.com>2021-04-02 14:26:24 -0700
committerElliott Hughes <enh@google.com>2021-04-02 14:36:19 -0700
commit29ae04250e7f0af8eaa7a4e70a65d1ca9cdaf373 (patch)
tree5eace784c9d65d1ca432ac3c12156f2c3812df6c
parent7b76a4f414babb51fc32002a1d63a0990bffe18e (diff)
parentaa16e0e2ccb366835c2aec0cb2f6e0e52497ede5 (diff)
downloadtoybox-29ae04250e7f0af8eaa7a4e70a65d1ca9cdaf373.tar.gz
Merge remote-tracking branch 'toybox/master' into HEAD
Change-Id: I14eea373d1242bc16afafc4ddc6ca305ef6b044e
-rw-r--r--.config-device1
-rw-r--r--.config-linux1
-rw-r--r--.config-mac1
-rw-r--r--android/device/generated/config.h2
-rw-r--r--android/device/generated/flags.h20
-rw-r--r--android/device/generated/globals.h50
-rw-r--r--android/device/generated/help.h4
-rw-r--r--android/device/generated/newtoys.h3
-rw-r--r--android/linux/generated/config.h2
-rw-r--r--android/linux/generated/flags.h20
-rw-r--r--android/linux/generated/globals.h50
-rw-r--r--android/linux/generated/help.h4
-rw-r--r--android/linux/generated/newtoys.h3
-rw-r--r--android/mac/generated/config.h2
-rw-r--r--android/mac/generated/flags.h20
-rw-r--r--android/mac/generated/globals.h50
-rw-r--r--android/mac/generated/help.h4
-rw-r--r--android/mac/generated/newtoys.h3
-rw-r--r--scripts/runtest.sh13
-rwxr-xr-xtests/install.test4
-rwxr-xr-xtests/rmdir.test4
-rw-r--r--tests/sh.test36
-rw-r--r--toys/pending/chsh.c77
-rw-r--r--toys/pending/init.c6
-rw-r--r--toys/pending/sh.c1523
-rw-r--r--toys/pending/telnetd.c56
-rw-r--r--toys/pending/vi.c147
-rw-r--r--toys/posix/cp.c10
-rw-r--r--toys/posix/rmdir.c4
29 files changed, 1256 insertions, 864 deletions
diff --git a/.config-device b/.config-device
index 0e4d691e..c871d24a 100644
--- a/.config-device
+++ b/.config-device
@@ -67,6 +67,7 @@ CONFIG_CHMOD=y
CONFIG_CHOWN=y
CONFIG_CHROOT=y
CONFIG_CHRT=y
+# CONFIG_CHSH is not set
# CONFIG_CHVT is not set
CONFIG_CKSUM=y
CONFIG_CLEAR=y
diff --git a/.config-linux b/.config-linux
index 076742be..a2aab086 100644
--- a/.config-linux
+++ b/.config-linux
@@ -68,6 +68,7 @@ CONFIG_CHMOD=y
# CONFIG_CHOWN is not set
# CONFIG_CHROOT is not set
# CONFIG_CHRT is not set
+# CONFIG_CHSH is not set
# CONFIG_CHVT is not set
# CONFIG_CKSUM is not set
# CONFIG_CLEAR is not set
diff --git a/.config-mac b/.config-mac
index c88396b9..db2d134b 100644
--- a/.config-mac
+++ b/.config-mac
@@ -68,6 +68,7 @@ CONFIG_CHMOD=y
# CONFIG_CHOWN is not set
# CONFIG_CHROOT is not set
# CONFIG_CHRT is not set
+# CONFIG_CHSH is not set
# CONFIG_CHVT is not set
# CONFIG_CKSUM is not set
# CONFIG_CLEAR is not set
diff --git a/android/device/generated/config.h b/android/device/generated/config.h
index 24afbf4a..d7d0b831 100644
--- a/android/device/generated/config.h
+++ b/android/device/generated/config.h
@@ -108,6 +108,8 @@
#define USE_CHROOT(...) __VA_ARGS__
#define CFG_CHRT 1
#define USE_CHRT(...) __VA_ARGS__
+#define CFG_CHSH 0
+#define USE_CHSH(...)
#define CFG_CHVT 0
#define USE_CHVT(...)
#define CFG_CKSUM 1
diff --git a/android/device/generated/flags.h b/android/device/generated/flags.h
index c7151866..d0fa452b 100644
--- a/android/device/generated/flags.h
+++ b/android/device/generated/flags.h
@@ -314,6 +314,15 @@
#undef FLAG_m
#endif
+// chsh s:
+#undef OPTSTR_chsh
+#define OPTSTR_chsh "s:"
+#ifdef CLEANUP_chsh
+#undef CLEANUP_chsh
+#undef FOR_chsh
+#undef FLAG_s
+#endif
+
// chvt <1
#undef OPTSTR_chvt
#define OPTSTR_chvt "<1"
@@ -2774,9 +2783,9 @@
#undef FLAG_g
#endif
-// source 0<1
+// source <1
#undef OPTSTR_source
-#define OPTSTR_source "0<1"
+#define OPTSTR_source "<1"
#ifdef CLEANUP_source
#undef CLEANUP_source
#undef FOR_source
@@ -3847,6 +3856,13 @@
#define FLAG_m (1<<7)
#endif
+#ifdef FOR_chsh
+#ifndef TT
+#define TT this.chsh
+#endif
+#define FLAG_s (FORCED_FLAG<<0)
+#endif
+
#ifdef FOR_chvt
#ifndef TT
#define TT this.chvt
diff --git a/android/device/generated/globals.h b/android/device/generated/globals.h
index 19c636e6..2211732f 100644
--- a/android/device/generated/globals.h
+++ b/android/device/generated/globals.h
@@ -541,6 +541,12 @@ struct brctl_data {
int sockfd;
};
+// toys/pending/chsh.c
+
+struct chsh_data {
+ char *s;
+};
+
// toys/pending/crond.c
struct crond_data {
@@ -846,43 +852,49 @@ struct sh_data {
// keep ifs here: used to work around compiler limitation in run_command()
char *ifs, *isexec, *wcpat;
unsigned options, jobcnt, LINENO;
- int hfd, pid, bangpid, varslen, cdcount;
+ int hfd, pid, bangpid, varslen, cdcount, srclvl, recursion;
long long SECONDS;
- // global and local variables
- struct sh_vars {
- long flags;
- char *str;
- } *vars;
+// FUNCTION transplant pipelines from place to place?
+// function keyword can have pointer to function struct? Still refcnt?
+// is function body like HERE document? Lifetime rules
- // Parsed functions
+ // Callable functions
struct sh_function {
char *name;
struct sh_pipeline { // pipeline segments: linked list of arg w/metadata
struct sh_pipeline *next, *prev, *end;
- unsigned lineno;
- int count, here, type; // TODO abuse type to replace count during parsing
+ int count, here, type, lineno;
struct sh_arg {
char **v;
int c;
} arg[1];
} *pipeline;
- struct double_list *expect; // should be zero at end of parsing
} *functions;
// runtime function call stack
struct sh_fcall {
- struct sh_fcall *next;
- struct sh_function *func;
+ struct sh_fcall *next, *prev;
+
+ // This dlist in reverse order: TT.ff current function, TT.ff->prev globals
+ struct sh_vars {
+ long flags;
+ char *str;
+ } *vars;
+
+// struct sh_function *func;
struct sh_pipeline *pl;
- int *urd, pout;
+ int varslen;
+ struct sh_arg arg;
+ struct arg_list *delete;
+ long shift;
// Runtime stack of nested if/else/fi and for/do/done contexts.
struct sh_blockstack {
struct sh_blockstack *next;
struct sh_pipeline *start, *middle;
struct sh_process *pp; // list of processes piping in to us
- int run, loop, *urd, pout;
+ int run, loop, *urd, pout, pipe;
struct sh_arg farg; // for/select arg stack, case wildcard deck
struct arg_list *fdelete; // farg's cleanup list
char *fvar; // for/select's iteration variable name
@@ -899,15 +911,6 @@ struct sh_data {
struct sh_arg *raw, arg;
} *pp; // currently running process
- struct sh_callstack {
- struct sh_callstack *next;
- struct sh_function scratch;
- struct sh_arg arg;
- struct arg_list *delete;
- long shift;
- unsigned lineno;
- } *cc;
-
// job list, command line for $*, scratch space for do_wildcard_files()
struct sh_arg jobs, *wcdeck;
};
@@ -1635,6 +1638,7 @@ extern union global_union {
struct bc_data bc;
struct bootchartd_data bootchartd;
struct brctl_data brctl;
+ struct chsh_data chsh;
struct crond_data crond;
struct crontab_data crontab;
struct dd_data dd;
diff --git a/android/device/generated/help.h b/android/device/generated/help.h
index 5467f97e..6a403233 100644
--- a/android/device/generated/help.h
+++ b/android/device/generated/help.h
@@ -362,6 +362,8 @@
#define HELP_shift "usage: shift [N]\n\nSkip N (default 1) positional parameters, moving $1 and friends along the list.\nDoes not affect $0."
+#define HELP_local "usage: local [NAME[=VALUE]...]\n\nCreate a local variable that lasts until return from this function.\nWith no arguments lists local variables in current function context.\nTODO: implement \"declare\" options."
+
#define HELP_jobs "usage: jobs [-lnprs] [%JOB | -x COMMAND...]\n\nList running/stopped background jobs.\n\n-l Include process ID in list\n-n Show only new/changed processes\n-p Show process IDs only\n-r Show running processes\n-s Show stopped processes"
#define HELP_export "usage: export [-n] [NAME[=VALUE]...]\n\nMake variables available to child processes. NAME exports existing local\nvariable(s), NAME=VALUE sets and exports.\n\n-n Unexport. Turn listed variable(s) into local variables.\n\nWith no arguments list exported variables/attributes as \"declare\" statements."
@@ -458,6 +460,8 @@
#define HELP_crond "usage: crond [-fbS] [-l N] [-d N] [-L LOGFILE] [-c DIR]\n\nA daemon to execute scheduled commands.\n\n-b Background (default)\n-c crontab dir\n-d Set log level, log to stderr\n-f Foreground\n-l Set log level. 0 is the most verbose, default 8\n-S Log to syslog (default)\n-L Log to file"
+#define HELP_chsh "usage: chsh [-s SHELL] [USER]\n\nChange user's login shell.\n\n-s Use SHELL instead of prompting\n\nNon-root users can only change their own shell to one listed in /etc/shells."
+
#define HELP_brctl "usage: brctl COMMAND [BRIDGE [INTERFACE]]\n\nManage ethernet bridges\n\nCommands:\nshow Show a list of bridges\naddbr BRIDGE Create BRIDGE\ndelbr BRIDGE Delete BRIDGE\naddif BRIDGE IFACE Add IFACE to BRIDGE\ndelif BRIDGE IFACE Delete IFACE from BRIDGE\nsetageing BRIDGE TIME Set ageing time\nsetfd BRIDGE TIME Set bridge forward delay\nsethello BRIDGE TIME Set hello time\nsetmaxage BRIDGE TIME Set max message age\nsetpathcost BRIDGE PORT COST Set path cost\nsetportprio BRIDGE PORT PRIO Set port priority\nsetbridgeprio BRIDGE PRIO Set bridge priority\nstp BRIDGE [1/yes/on|0/no/off] STP on/off"
#define HELP_bootchartd "usage: bootchartd {start [PROG ARGS]}|stop|init\n\nCreate /var/log/bootlog.tgz with boot chart data\n\nstart: start background logging; with PROG, run PROG,\n then kill logging with USR1\nstop: send USR1 to all bootchartd processes\ninit: start background logging; stop when getty/xdm is seen\n (for init scripts)\n\nUnder PID 1: as init, then exec $bootchart_init, /init, /sbin/init"
diff --git a/android/device/generated/newtoys.h b/android/device/generated/newtoys.h
index 75a5d5ea..8513181b 100644
--- a/android/device/generated/newtoys.h
+++ b/android/device/generated/newtoys.h
@@ -35,6 +35,7 @@ USE_CHMOD(NEWTOY(chmod, "<2?vfR[-vf]", TOYFLAG_BIN))
USE_CHOWN(OLDTOY(chown, chgrp, TOYFLAG_BIN))
USE_CHROOT(NEWTOY(chroot, "^<1", TOYFLAG_USR|TOYFLAG_SBIN|TOYFLAG_ARGFAIL(125)))
USE_CHRT(NEWTOY(chrt, "^mp#<0iRbrfo[!ibrfo]", TOYFLAG_USR|TOYFLAG_BIN))
+USE_CHSH(NEWTOY(chsh, "s:", TOYFLAG_USR|TOYFLAG_BIN|TOYFLAG_STAYROOT))
USE_CHVT(NEWTOY(chvt, "<1", TOYFLAG_USR|TOYFLAG_BIN))
USE_CKSUM(NEWTOY(cksum, "HIPLN", TOYFLAG_BIN))
USE_CLEAR(NEWTOY(clear, NULL, TOYFLAG_USR|TOYFLAG_BIN))
@@ -247,7 +248,7 @@ USE_SKELETON_ALIAS(NEWTOY(skeleton_alias, "b#dq", TOYFLAG_USR|TOYFLAG_BIN))
USE_SLEEP(NEWTOY(sleep, "<1", TOYFLAG_BIN))
USE_SNTP(NEWTOY(sntp, ">1M :m :Sp:t#<0=1>16asdDqr#<4>17=10[!as]", TOYFLAG_USR|TOYFLAG_BIN))
USE_SORT(NEWTOY(sort, USE_SORT_FLOAT("g")"S:T:m" "o:k*t:" "xVbMcszdfirun", TOYFLAG_USR|TOYFLAG_BIN|TOYFLAG_ARGFAIL(2)))
-USE_SH(NEWTOY(source, "0<1", TOYFLAG_NOFORK))
+USE_SH(NEWTOY(source, "<1", TOYFLAG_NOFORK))
USE_SPLIT(NEWTOY(split, ">2a#<1=2>9b#<1l#<1[!bl]", TOYFLAG_USR|TOYFLAG_BIN))
USE_STAT(NEWTOY(stat, "<1c:(format)fLt", TOYFLAG_BIN))
USE_STRINGS(NEWTOY(strings, "t:an#=4<1fo", TOYFLAG_USR|TOYFLAG_BIN))
diff --git a/android/linux/generated/config.h b/android/linux/generated/config.h
index c0a9e81f..79502c00 100644
--- a/android/linux/generated/config.h
+++ b/android/linux/generated/config.h
@@ -110,6 +110,8 @@
#define USE_CHROOT(...)
#define CFG_CHRT 0
#define USE_CHRT(...)
+#define CFG_CHSH 0
+#define USE_CHSH(...)
#define CFG_CHVT 0
#define USE_CHVT(...)
#define CFG_CKSUM 0
diff --git a/android/linux/generated/flags.h b/android/linux/generated/flags.h
index e9bffbf5..84944ce9 100644
--- a/android/linux/generated/flags.h
+++ b/android/linux/generated/flags.h
@@ -314,6 +314,15 @@
#undef FLAG_m
#endif
+// chsh s:
+#undef OPTSTR_chsh
+#define OPTSTR_chsh "s:"
+#ifdef CLEANUP_chsh
+#undef CLEANUP_chsh
+#undef FOR_chsh
+#undef FLAG_s
+#endif
+
// chvt <1
#undef OPTSTR_chvt
#define OPTSTR_chvt "<1"
@@ -2774,9 +2783,9 @@
#undef FLAG_g
#endif
-// source 0<1
+// source <1
#undef OPTSTR_source
-#define OPTSTR_source "0<1"
+#define OPTSTR_source "<1"
#ifdef CLEANUP_source
#undef CLEANUP_source
#undef FOR_source
@@ -3847,6 +3856,13 @@
#define FLAG_m (FORCED_FLAG<<7)
#endif
+#ifdef FOR_chsh
+#ifndef TT
+#define TT this.chsh
+#endif
+#define FLAG_s (FORCED_FLAG<<0)
+#endif
+
#ifdef FOR_chvt
#ifndef TT
#define TT this.chvt
diff --git a/android/linux/generated/globals.h b/android/linux/generated/globals.h
index 19c636e6..2211732f 100644
--- a/android/linux/generated/globals.h
+++ b/android/linux/generated/globals.h
@@ -541,6 +541,12 @@ struct brctl_data {
int sockfd;
};
+// toys/pending/chsh.c
+
+struct chsh_data {
+ char *s;
+};
+
// toys/pending/crond.c
struct crond_data {
@@ -846,43 +852,49 @@ struct sh_data {
// keep ifs here: used to work around compiler limitation in run_command()
char *ifs, *isexec, *wcpat;
unsigned options, jobcnt, LINENO;
- int hfd, pid, bangpid, varslen, cdcount;
+ int hfd, pid, bangpid, varslen, cdcount, srclvl, recursion;
long long SECONDS;
- // global and local variables
- struct sh_vars {
- long flags;
- char *str;
- } *vars;
+// FUNCTION transplant pipelines from place to place?
+// function keyword can have pointer to function struct? Still refcnt?
+// is function body like HERE document? Lifetime rules
- // Parsed functions
+ // Callable functions
struct sh_function {
char *name;
struct sh_pipeline { // pipeline segments: linked list of arg w/metadata
struct sh_pipeline *next, *prev, *end;
- unsigned lineno;
- int count, here, type; // TODO abuse type to replace count during parsing
+ int count, here, type, lineno;
struct sh_arg {
char **v;
int c;
} arg[1];
} *pipeline;
- struct double_list *expect; // should be zero at end of parsing
} *functions;
// runtime function call stack
struct sh_fcall {
- struct sh_fcall *next;
- struct sh_function *func;
+ struct sh_fcall *next, *prev;
+
+ // This dlist in reverse order: TT.ff current function, TT.ff->prev globals
+ struct sh_vars {
+ long flags;
+ char *str;
+ } *vars;
+
+// struct sh_function *func;
struct sh_pipeline *pl;
- int *urd, pout;
+ int varslen;
+ struct sh_arg arg;
+ struct arg_list *delete;
+ long shift;
// Runtime stack of nested if/else/fi and for/do/done contexts.
struct sh_blockstack {
struct sh_blockstack *next;
struct sh_pipeline *start, *middle;
struct sh_process *pp; // list of processes piping in to us
- int run, loop, *urd, pout;
+ int run, loop, *urd, pout, pipe;
struct sh_arg farg; // for/select arg stack, case wildcard deck
struct arg_list *fdelete; // farg's cleanup list
char *fvar; // for/select's iteration variable name
@@ -899,15 +911,6 @@ struct sh_data {
struct sh_arg *raw, arg;
} *pp; // currently running process
- struct sh_callstack {
- struct sh_callstack *next;
- struct sh_function scratch;
- struct sh_arg arg;
- struct arg_list *delete;
- long shift;
- unsigned lineno;
- } *cc;
-
// job list, command line for $*, scratch space for do_wildcard_files()
struct sh_arg jobs, *wcdeck;
};
@@ -1635,6 +1638,7 @@ extern union global_union {
struct bc_data bc;
struct bootchartd_data bootchartd;
struct brctl_data brctl;
+ struct chsh_data chsh;
struct crond_data crond;
struct crontab_data crontab;
struct dd_data dd;
diff --git a/android/linux/generated/help.h b/android/linux/generated/help.h
index b740726f..eebbf545 100644
--- a/android/linux/generated/help.h
+++ b/android/linux/generated/help.h
@@ -364,6 +364,8 @@
#define HELP_shift "usage: shift [N]\n\nSkip N (default 1) positional parameters, moving $1 and friends along the list.\nDoes not affect $0."
+#define HELP_local "usage: local [NAME[=VALUE]...]\n\nCreate a local variable that lasts until return from this function.\nWith no arguments lists local variables in current function context.\nTODO: implement \"declare\" options."
+
#define HELP_jobs "usage: jobs [-lnprs] [%JOB | -x COMMAND...]\n\nList running/stopped background jobs.\n\n-l Include process ID in list\n-n Show only new/changed processes\n-p Show process IDs only\n-r Show running processes\n-s Show stopped processes"
#define HELP_export "usage: export [-n] [NAME[=VALUE]...]\n\nMake variables available to child processes. NAME exports existing local\nvariable(s), NAME=VALUE sets and exports.\n\n-n Unexport. Turn listed variable(s) into local variables.\n\nWith no arguments list exported variables/attributes as \"declare\" statements."
@@ -460,6 +462,8 @@
#define HELP_crond "usage: crond [-fbS] [-l N] [-d N] [-L LOGFILE] [-c DIR]\n\nA daemon to execute scheduled commands.\n\n-b Background (default)\n-c crontab dir\n-d Set log level, log to stderr\n-f Foreground\n-l Set log level. 0 is the most verbose, default 8\n-S Log to syslog (default)\n-L Log to file"
+#define HELP_chsh "usage: chsh [-s SHELL] [USER]\n\nChange user's login shell.\n\n-s Use SHELL instead of prompting\n\nNon-root users can only change their own shell to one listed in /etc/shells."
+
#define HELP_brctl "usage: brctl COMMAND [BRIDGE [INTERFACE]]\n\nManage ethernet bridges\n\nCommands:\nshow Show a list of bridges\naddbr BRIDGE Create BRIDGE\ndelbr BRIDGE Delete BRIDGE\naddif BRIDGE IFACE Add IFACE to BRIDGE\ndelif BRIDGE IFACE Delete IFACE from BRIDGE\nsetageing BRIDGE TIME Set ageing time\nsetfd BRIDGE TIME Set bridge forward delay\nsethello BRIDGE TIME Set hello time\nsetmaxage BRIDGE TIME Set max message age\nsetpathcost BRIDGE PORT COST Set path cost\nsetportprio BRIDGE PORT PRIO Set port priority\nsetbridgeprio BRIDGE PRIO Set bridge priority\nstp BRIDGE [1/yes/on|0/no/off] STP on/off"
#define HELP_bootchartd "usage: bootchartd {start [PROG ARGS]}|stop|init\n\nCreate /var/log/bootlog.tgz with boot chart data\n\nstart: start background logging; with PROG, run PROG,\n then kill logging with USR1\nstop: send USR1 to all bootchartd processes\ninit: start background logging; stop when getty/xdm is seen\n (for init scripts)\n\nUnder PID 1: as init, then exec $bootchart_init, /init, /sbin/init"
diff --git a/android/linux/generated/newtoys.h b/android/linux/generated/newtoys.h
index 75a5d5ea..8513181b 100644
--- a/android/linux/generated/newtoys.h
+++ b/android/linux/generated/newtoys.h
@@ -35,6 +35,7 @@ USE_CHMOD(NEWTOY(chmod, "<2?vfR[-vf]", TOYFLAG_BIN))
USE_CHOWN(OLDTOY(chown, chgrp, TOYFLAG_BIN))
USE_CHROOT(NEWTOY(chroot, "^<1", TOYFLAG_USR|TOYFLAG_SBIN|TOYFLAG_ARGFAIL(125)))
USE_CHRT(NEWTOY(chrt, "^mp#<0iRbrfo[!ibrfo]", TOYFLAG_USR|TOYFLAG_BIN))
+USE_CHSH(NEWTOY(chsh, "s:", TOYFLAG_USR|TOYFLAG_BIN|TOYFLAG_STAYROOT))
USE_CHVT(NEWTOY(chvt, "<1", TOYFLAG_USR|TOYFLAG_BIN))
USE_CKSUM(NEWTOY(cksum, "HIPLN", TOYFLAG_BIN))
USE_CLEAR(NEWTOY(clear, NULL, TOYFLAG_USR|TOYFLAG_BIN))
@@ -247,7 +248,7 @@ USE_SKELETON_ALIAS(NEWTOY(skeleton_alias, "b#dq", TOYFLAG_USR|TOYFLAG_BIN))
USE_SLEEP(NEWTOY(sleep, "<1", TOYFLAG_BIN))
USE_SNTP(NEWTOY(sntp, ">1M :m :Sp:t#<0=1>16asdDqr#<4>17=10[!as]", TOYFLAG_USR|TOYFLAG_BIN))
USE_SORT(NEWTOY(sort, USE_SORT_FLOAT("g")"S:T:m" "o:k*t:" "xVbMcszdfirun", TOYFLAG_USR|TOYFLAG_BIN|TOYFLAG_ARGFAIL(2)))
-USE_SH(NEWTOY(source, "0<1", TOYFLAG_NOFORK))
+USE_SH(NEWTOY(source, "<1", TOYFLAG_NOFORK))
USE_SPLIT(NEWTOY(split, ">2a#<1=2>9b#<1l#<1[!bl]", TOYFLAG_USR|TOYFLAG_BIN))
USE_STAT(NEWTOY(stat, "<1c:(format)fLt", TOYFLAG_BIN))
USE_STRINGS(NEWTOY(strings, "t:an#=4<1fo", TOYFLAG_USR|TOYFLAG_BIN))
diff --git a/android/mac/generated/config.h b/android/mac/generated/config.h
index c7901845..a3c54d84 100644
--- a/android/mac/generated/config.h
+++ b/android/mac/generated/config.h
@@ -110,6 +110,8 @@
#define USE_CHROOT(...)
#define CFG_CHRT 0
#define USE_CHRT(...)
+#define CFG_CHSH 0
+#define USE_CHSH(...)
#define CFG_CHVT 0
#define USE_CHVT(...)
#define CFG_CKSUM 0
diff --git a/android/mac/generated/flags.h b/android/mac/generated/flags.h
index 432f20da..d3a16fe3 100644
--- a/android/mac/generated/flags.h
+++ b/android/mac/generated/flags.h
@@ -314,6 +314,15 @@
#undef FLAG_m
#endif
+// chsh s:
+#undef OPTSTR_chsh
+#define OPTSTR_chsh "s:"
+#ifdef CLEANUP_chsh
+#undef CLEANUP_chsh
+#undef FOR_chsh
+#undef FLAG_s
+#endif
+
// chvt <1
#undef OPTSTR_chvt
#define OPTSTR_chvt "<1"
@@ -2774,9 +2783,9 @@
#undef FLAG_g
#endif
-// source 0<1
+// source <1
#undef OPTSTR_source
-#define OPTSTR_source "0<1"
+#define OPTSTR_source "<1"
#ifdef CLEANUP_source
#undef CLEANUP_source
#undef FOR_source
@@ -3847,6 +3856,13 @@
#define FLAG_m (FORCED_FLAG<<7)
#endif
+#ifdef FOR_chsh
+#ifndef TT
+#define TT this.chsh
+#endif
+#define FLAG_s (FORCED_FLAG<<0)
+#endif
+
#ifdef FOR_chvt
#ifndef TT
#define TT this.chvt
diff --git a/android/mac/generated/globals.h b/android/mac/generated/globals.h
index 19c636e6..2211732f 100644
--- a/android/mac/generated/globals.h
+++ b/android/mac/generated/globals.h
@@ -541,6 +541,12 @@ struct brctl_data {
int sockfd;
};
+// toys/pending/chsh.c
+
+struct chsh_data {
+ char *s;
+};
+
// toys/pending/crond.c
struct crond_data {
@@ -846,43 +852,49 @@ struct sh_data {
// keep ifs here: used to work around compiler limitation in run_command()
char *ifs, *isexec, *wcpat;
unsigned options, jobcnt, LINENO;
- int hfd, pid, bangpid, varslen, cdcount;
+ int hfd, pid, bangpid, varslen, cdcount, srclvl, recursion;
long long SECONDS;
- // global and local variables
- struct sh_vars {
- long flags;
- char *str;
- } *vars;
+// FUNCTION transplant pipelines from place to place?
+// function keyword can have pointer to function struct? Still refcnt?
+// is function body like HERE document? Lifetime rules
- // Parsed functions
+ // Callable functions
struct sh_function {
char *name;
struct sh_pipeline { // pipeline segments: linked list of arg w/metadata
struct sh_pipeline *next, *prev, *end;
- unsigned lineno;
- int count, here, type; // TODO abuse type to replace count during parsing
+ int count, here, type, lineno;
struct sh_arg {
char **v;
int c;
} arg[1];
} *pipeline;
- struct double_list *expect; // should be zero at end of parsing
} *functions;
// runtime function call stack
struct sh_fcall {
- struct sh_fcall *next;
- struct sh_function *func;
+ struct sh_fcall *next, *prev;
+
+ // This dlist in reverse order: TT.ff current function, TT.ff->prev globals
+ struct sh_vars {
+ long flags;
+ char *str;
+ } *vars;
+
+// struct sh_function *func;
struct sh_pipeline *pl;
- int *urd, pout;
+ int varslen;
+ struct sh_arg arg;
+ struct arg_list *delete;
+ long shift;
// Runtime stack of nested if/else/fi and for/do/done contexts.
struct sh_blockstack {
struct sh_blockstack *next;
struct sh_pipeline *start, *middle;
struct sh_process *pp; // list of processes piping in to us
- int run, loop, *urd, pout;
+ int run, loop, *urd, pout, pipe;
struct sh_arg farg; // for/select arg stack, case wildcard deck
struct arg_list *fdelete; // farg's cleanup list
char *fvar; // for/select's iteration variable name
@@ -899,15 +911,6 @@ struct sh_data {
struct sh_arg *raw, arg;
} *pp; // currently running process
- struct sh_callstack {
- struct sh_callstack *next;
- struct sh_function scratch;
- struct sh_arg arg;
- struct arg_list *delete;
- long shift;
- unsigned lineno;
- } *cc;
-
// job list, command line for $*, scratch space for do_wildcard_files()
struct sh_arg jobs, *wcdeck;
};
@@ -1635,6 +1638,7 @@ extern union global_union {
struct bc_data bc;
struct bootchartd_data bootchartd;
struct brctl_data brctl;
+ struct chsh_data chsh;
struct crond_data crond;
struct crontab_data crontab;
struct dd_data dd;
diff --git a/android/mac/generated/help.h b/android/mac/generated/help.h
index b740726f..eebbf545 100644
--- a/android/mac/generated/help.h
+++ b/android/mac/generated/help.h
@@ -364,6 +364,8 @@
#define HELP_shift "usage: shift [N]\n\nSkip N (default 1) positional parameters, moving $1 and friends along the list.\nDoes not affect $0."
+#define HELP_local "usage: local [NAME[=VALUE]...]\n\nCreate a local variable that lasts until return from this function.\nWith no arguments lists local variables in current function context.\nTODO: implement \"declare\" options."
+
#define HELP_jobs "usage: jobs [-lnprs] [%JOB | -x COMMAND...]\n\nList running/stopped background jobs.\n\n-l Include process ID in list\n-n Show only new/changed processes\n-p Show process IDs only\n-r Show running processes\n-s Show stopped processes"
#define HELP_export "usage: export [-n] [NAME[=VALUE]...]\n\nMake variables available to child processes. NAME exports existing local\nvariable(s), NAME=VALUE sets and exports.\n\n-n Unexport. Turn listed variable(s) into local variables.\n\nWith no arguments list exported variables/attributes as \"declare\" statements."
@@ -460,6 +462,8 @@
#define HELP_crond "usage: crond [-fbS] [-l N] [-d N] [-L LOGFILE] [-c DIR]\n\nA daemon to execute scheduled commands.\n\n-b Background (default)\n-c crontab dir\n-d Set log level, log to stderr\n-f Foreground\n-l Set log level. 0 is the most verbose, default 8\n-S Log to syslog (default)\n-L Log to file"
+#define HELP_chsh "usage: chsh [-s SHELL] [USER]\n\nChange user's login shell.\n\n-s Use SHELL instead of prompting\n\nNon-root users can only change their own shell to one listed in /etc/shells."
+
#define HELP_brctl "usage: brctl COMMAND [BRIDGE [INTERFACE]]\n\nManage ethernet bridges\n\nCommands:\nshow Show a list of bridges\naddbr BRIDGE Create BRIDGE\ndelbr BRIDGE Delete BRIDGE\naddif BRIDGE IFACE Add IFACE to BRIDGE\ndelif BRIDGE IFACE Delete IFACE from BRIDGE\nsetageing BRIDGE TIME Set ageing time\nsetfd BRIDGE TIME Set bridge forward delay\nsethello BRIDGE TIME Set hello time\nsetmaxage BRIDGE TIME Set max message age\nsetpathcost BRIDGE PORT COST Set path cost\nsetportprio BRIDGE PORT PRIO Set port priority\nsetbridgeprio BRIDGE PRIO Set bridge priority\nstp BRIDGE [1/yes/on|0/no/off] STP on/off"
#define HELP_bootchartd "usage: bootchartd {start [PROG ARGS]}|stop|init\n\nCreate /var/log/bootlog.tgz with boot chart data\n\nstart: start background logging; with PROG, run PROG,\n then kill logging with USR1\nstop: send USR1 to all bootchartd processes\ninit: start background logging; stop when getty/xdm is seen\n (for init scripts)\n\nUnder PID 1: as init, then exec $bootchart_init, /init, /sbin/init"
diff --git a/android/mac/generated/newtoys.h b/android/mac/generated/newtoys.h
index 75a5d5ea..8513181b 100644
--- a/android/mac/generated/newtoys.h
+++ b/android/mac/generated/newtoys.h
@@ -35,6 +35,7 @@ USE_CHMOD(NEWTOY(chmod, "<2?vfR[-vf]", TOYFLAG_BIN))
USE_CHOWN(OLDTOY(chown, chgrp, TOYFLAG_BIN))
USE_CHROOT(NEWTOY(chroot, "^<1", TOYFLAG_USR|TOYFLAG_SBIN|TOYFLAG_ARGFAIL(125)))
USE_CHRT(NEWTOY(chrt, "^mp#<0iRbrfo[!ibrfo]", TOYFLAG_USR|TOYFLAG_BIN))
+USE_CHSH(NEWTOY(chsh, "s:", TOYFLAG_USR|TOYFLAG_BIN|TOYFLAG_STAYROOT))
USE_CHVT(NEWTOY(chvt, "<1", TOYFLAG_USR|TOYFLAG_BIN))
USE_CKSUM(NEWTOY(cksum, "HIPLN", TOYFLAG_BIN))
USE_CLEAR(NEWTOY(clear, NULL, TOYFLAG_USR|TOYFLAG_BIN))
@@ -247,7 +248,7 @@ USE_SKELETON_ALIAS(NEWTOY(skeleton_alias, "b#dq", TOYFLAG_USR|TOYFLAG_BIN))
USE_SLEEP(NEWTOY(sleep, "<1", TOYFLAG_BIN))
USE_SNTP(NEWTOY(sntp, ">1M :m :Sp:t#<0=1>16asdDqr#<4>17=10[!as]", TOYFLAG_USR|TOYFLAG_BIN))
USE_SORT(NEWTOY(sort, USE_SORT_FLOAT("g")"S:T:m" "o:k*t:" "xVbMcszdfirun", TOYFLAG_USR|TOYFLAG_BIN|TOYFLAG_ARGFAIL(2)))
-USE_SH(NEWTOY(source, "0<1", TOYFLAG_NOFORK))
+USE_SH(NEWTOY(source, "<1", TOYFLAG_NOFORK))
USE_SPLIT(NEWTOY(split, ">2a#<1=2>9b#<1l#<1[!bl]", TOYFLAG_USR|TOYFLAG_BIN))
USE_STAT(NEWTOY(stat, "<1c:(format)fLt", TOYFLAG_BIN))
USE_STRINGS(NEWTOY(strings, "t:an#=4<1fo", TOYFLAG_USR|TOYFLAG_BIN))
diff --git a/scripts/runtest.sh b/scripts/runtest.sh
index 6aad9ff1..158ed7fa 100644
--- a/scripts/runtest.sh
+++ b/scripts/runtest.sh
@@ -61,7 +61,7 @@ optional()
# Not set?
if [ -z "$1" ] || [ -z "$OPTIONFLAGS" ] || [ ${#option} -ne 0 ]
then
- SKIP=""
+ unset SKIP
return
fi
SKIP=1
@@ -183,6 +183,8 @@ do_fail()
# X means close stdin/stdout/stderr and match return code (blank means nonzero)
txpect()
{
+ local NAME CASE VERBOSITY LEN A B
+
# Run command with redirection through fifos
NAME="$CMDNAME $1"
CASE=
@@ -206,16 +208,18 @@ txpect()
LEN=$((${#1}-1))
CASE="$1"
A=
+ B=
case ${1::1} in
# send input to child
I) printf %s "${1:1}" >&$IN || { do_fail;break;} ;;
+ R) LEN=0; B=1; ;&
# check output from child
[OE])
[ $LEN == 0 ] && LARG="" || LARG="-rN $LEN"
O=$OUT
- [ ${1::1} == 'E' ] && O=$ERR
+ [ "${1:$B:1}" == 'E' ] && O=$ERR
A=
read -t2 $LARG A <&$O
VERBOSITY="$VERBOSITY"$'\n'"$A"
@@ -223,8 +227,9 @@ txpect()
then
[ -z "$A" ] && { do_fail;break;}
else
- if [ "$A" != "${1:1}" ]
- then
+ if [ ${1::1} == 'R' ] && [[ "$A" =~ "${1:2}" ]]; then true
+ elif [ ${1::1} != 'R' ] && [ "$A" == "${1:1}" ]; then true
+ else
# Append the rest of the output if there is any.
read -t.1 B <&$O
A="$A$B"
diff --git a/tests/install.test b/tests/install.test
index 228d2470..b1113c0f 100755
--- a/tests/install.test
+++ b/tests/install.test
@@ -13,3 +13,7 @@ testing "install -D exists" \
"mkdir -p a; touch a/b; install -D random a/b && cmp random a/b && echo yes" \
"yes\n" "" ""
rm -rf a random
+testing "install -D -t creates directory" \
+ "touch a; install -Dt b a && echo yes" \
+ "yes\n" "" ""
+rm -rf a b
diff --git a/tests/rmdir.test b/tests/rmdir.test
index 5b36bbc1..1731ecc2 100755
--- a/tests/rmdir.test
+++ b/tests/rmdir.test
@@ -40,6 +40,10 @@ testing "-p part of path" \
"yes\n" "" ""
rm -rf temp
+skipnot [ $UID -eq 0 ]
+testing '-p abspath' \
+ 'mkdir -p /test/2/3 && rmdir -p /test/2/3 && [ ! -e /test ] && echo yes' \
+ 'yes\n' '' ''
mkdir -p one/two/three
testing "-p one/two/three" \
diff --git a/tests/sh.test b/tests/sh.test
index 0fed3d06..84f8dab3 100644
--- a/tests/sh.test
+++ b/tests/sh.test
@@ -85,6 +85,22 @@ ln -s $(which $SH) bash
testing 'non-absolute $_' "./bash -c 'echo \$_'" './bash\n' '' ''
rm bash
+shxpect '$_ preserved on assignment error' I$'true hello; a=1 b=2 c=${}\n' \
+ E E"$P" I$'echo $_\n' O$'hello\n'
+shxpect '$_ preserved on prefix error' I$'true hello; a=1 b=2 c=${} true\n' \
+ E E"$P" I$'echo $_\n' O$'hello\n'
+shxpect '$_ preserved on exec error' I$'true hello; ${}\n' \
+ E E"$P" I$'echo $_\n' O$'hello\n'
+shxpect '$_ abspath on exec' I$'env | grep ^_=\n' O$'_=/usr/bin/env\n'
+testing '$_ literal after exec' 'env >/dev/null; echo $_' 'env\n' '' ''
+shxpect '$_ no path for builtin' I$'true; echo $_\n' O$'true\n'
+testing 'prefix is local for builtins' 'abc=123; abc=def unset abc; echo $abc' \
+ '123\n' '' ''
+testing 'prefix localizes magic vars' \
+ 'SECONDS=123; SECONDS=345 true; echo $SECONDS' '123\n' '' ''
+shxpect 'body evaluated before variable exports' I$'a=x${} y${}\n' RE'y${}'
+testing '$NOTHING clears $_' 'true; $NOTHING; echo $_' '\n' '' ''
+
testing 'exec exitval' "$SH -c 'exec echo hello' && echo \$?" "hello\n0\n" "" ""
testing 'simple script' '$SH input' 'input\n' 'echo $0' ''
testing 'simple script2' '$SH ./input two;echo $?' './input+two\n42\n' \
@@ -102,20 +118,15 @@ testing 'simple script in $PATH' "PATH='$PWD/sub:$PATH' $SH script" \
rm -rf sub
testing "script file" "chmod +x input; ./input" "hello\n" "#!$C\necho hello" ""
-
testing 'IFS $*' "$SH -c 'IFS=xy; echo \"\$*\"' one two tyree" "twoxtyree\n" \
"" ""
-
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" '' '' ''
-
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" "" ""
#########################################################################
@@ -515,11 +526,26 @@ testing 'source is live in functions' \
testing 'subshell inheritance' \
'func() { source input; cat <(echo $xx; xx=456; echo $xx); echo $xx;}; echo local xx=123 > input; func; echo $xx' \
'123\n456\n123\n\n' 'x' ''
+testing 'semicolon vs newline' \
+ 'source input 2>/dev/null || echo yes' 'one\nyes\n' \
+ 'echo one\necho two; echo |' ''
+testing 'syntax err pops to source but encapsulating function continues' \
+ 'func() { echo one; source <(echo -e "echo hello\necho |") 2>/dev/null; echo three;}; func; echo four' \
+ 'one\nhello\nthree\nfour\n' '' ''
+testing '"exit shell" means exit eval but encapsulating function continues' \
+ 'func() { eval "echo one; echo \${?potato}; echo and" 2>/dev/null; echo plus;}; func; echo then' \
+ 'one\nplus\nthen\n' '' ''
testing 'functions() {} in same PID' \
'{ echo $BASHPID; chicken() { echo $BASHPID;}; chicken;} | sort -u | wc -l' '1\n' '' ''
testing 'functions() () different PID' \
'{ echo $BASHPID; chicken() ( echo $BASHPID;); chicken;} | sort -u | wc -l' '2\n' '' ''
+testing 'function() just wants any block span' \
+ 'func() if true; then echo hello; fi; echo one; func; echo two' \
+ 'one\nhello\ntwo\n' '' ''
+shxpect 'local creates a whiteout' \
+ I$'func() { local potato; echo ${potato?bang}; }; potato=123; func\n' \
+ E E"$P" I$'echo $?\n' O$'1\n'
# TODO finish variable list from shell init
diff --git a/toys/pending/chsh.c b/toys/pending/chsh.c
new file mode 100644
index 00000000..bf409783
--- /dev/null
+++ b/toys/pending/chsh.c
@@ -0,0 +1,77 @@
+/* chsh.c - Change login shell.
+ *
+ * Copyright 2021 Michael Christensen
+ *
+ * See http://refspecs.linuxfoundation.org/LSB_4.1.0/LSB-Core-generic/LSB-Core-generic/chsh.html
+
+USE_CHSH(NEWTOY(chsh, "s:", TOYFLAG_USR|TOYFLAG_BIN|TOYFLAG_STAYROOT))
+
+config CHSH
+ bool "chsh"
+ default y
+ help
+ usage: chsh [-s SHELL] [USER]
+
+ Change user's login shell.
+
+ -s Use SHELL instead of prompting
+
+ Non-root users can only change their own shell to one listed in /etc/shells.
+*/
+
+#define FOR_chsh
+#include "toys.h"
+
+GLOBALS(
+ char *s;
+)
+
+void chsh_main()
+{
+ FILE *file;
+ char *user, *line, *shell, *encrypted;
+ struct passwd *passwd_info;
+ struct spwd *shadow_info;
+
+ // Get uid user information, may be discarded later
+
+ if ((user = *toys.optargs)) {
+ passwd_info = xgetpwnam(user);
+ if (geteuid() && strcmp(passwd_info->pw_name, user))
+ error_exit("Permission denied\n");
+ } else {
+ passwd_info = xgetpwuid(getuid());
+ user = passwd_info->pw_name;
+ }
+
+ // Get a password, encrypt it, wipe it, and check it
+ if (mlock(toybuf, sizeof(toybuf))) perror_exit("mlock");
+ if (!(shadow_info = getspnam(passwd_info->pw_name))) perror_exit("getspnam");
+ if (read_password(toybuf, sizeof(toybuf), "Password: ")) perror_exit("woaj"); //xexit();
+ if (!(encrypted = crypt(toybuf, shadow_info->sp_pwdp))) perror_exit("crypt");
+ memset(toybuf, 0, sizeof(toybuf));
+ munlock(toybuf, sizeof(toybuf)); // prevents memset from "optimizing" away.
+ if (strcmp(encrypted, shadow_info->sp_pwdp)) perror_exit("Bad password");
+
+ // Get new shell (either -s or interactive)
+ file = xfopen("/etc/shells", "r");
+ if (toys.optflags) shell = TT.s;
+ else {
+ xprintf("Changing the login shell for %s\n"
+ "Enter the new value, or press ENTER for default\n"
+ " Login shell [%s]: ", user, passwd_info->pw_shell);
+ if (!(shell = xgetline(stdin))) xexit();
+ }
+
+ // Verify supplied shell in /etc/shells, or get default shell
+ if (strlen(shell))
+ while ((line = xgetline(file)) && strcmp(shell, line)) free(line);
+ else do line = xgetline(file); while (line && *line != '/');
+ if (!line) error_exit("Shell not found in '/etc/shells'");
+
+ // Update /etc/passwd
+ passwd_info->pw_shell = line;
+ if (-1 == update_password("/etc/passwd", user, NULL)) perror_exit("Failed to remove passwd entry");
+ file = xfopen("/etc/passwd", "a");
+ if (putpwent(passwd_info, file)) perror_exit("putwent");
+}
diff --git a/toys/pending/init.c b/toys/pending/init.c
index b147911f..afc9a3ed 100644
--- a/toys/pending/init.c
+++ b/toys/pending/init.c
@@ -308,11 +308,7 @@ static void waitforpid(pid_t pid)
{
if (pid <= 0) return;
- for(;;) {
- pid_t y = wait(NULL);
- mark_as_terminated_process(y);
- if (kill(y, 0)) break;
- }
+ while (!kill(pid, 0)) mark_as_terminated_process(wait(NULL));
}
static void run_action_from_list(int action)
diff --git a/toys/pending/sh.c b/toys/pending/sh.c
index 51b3cc70..7096d135 100644
--- a/toys/pending/sh.c
+++ b/toys/pending/sh.c
@@ -47,7 +47,7 @@ USE_SH(NEWTOY(exit, 0, TOYFLAG_NOFORK))
USE_SH(NEWTOY(export, "np", TOYFLAG_NOFORK))
USE_SH(NEWTOY(set, 0, TOYFLAG_NOFORK))
USE_SH(NEWTOY(shift, ">1", TOYFLAG_NOFORK))
-USE_SH(NEWTOY(source, "0<1", TOYFLAG_NOFORK))
+USE_SH(NEWTOY(source, "<1", TOYFLAG_NOFORK))
USE_SH(OLDTOY(., source, TOYFLAG_NOFORK))
USE_SH(NEWTOY(unset, "fvn", TOYFLAG_NOFORK))
@@ -172,6 +172,17 @@ config JOBS
-r Show running processes
-s Show stopped processes
+config LOCAL
+ bool
+ default n
+ depends on SH
+ help
+ usage: local [NAME[=VALUE]...]
+
+ Create a local variable that lasts until return from this function.
+ With no arguments lists local variables in current function context.
+ TODO: implement "declare" options.
+
config SHIFT
bool
default n
@@ -208,43 +219,49 @@ GLOBALS(
// keep ifs here: used to work around compiler limitation in run_command()
char *ifs, *isexec, *wcpat;
unsigned options, jobcnt, LINENO;
- int hfd, pid, bangpid, varslen, cdcount;
+ int hfd, pid, bangpid, varslen, cdcount, srclvl, recursion;
long long SECONDS;
- // global and local variables
- struct sh_vars {
- long flags;
- char *str;
- } *vars;
+// FUNCTION transplant pipelines from place to place?
+// function keyword can have pointer to function struct? Still refcnt?
+// is function body like HERE document? Lifetime rules
- // Parsed functions
+ // Callable functions
struct sh_function {
char *name;
struct sh_pipeline { // pipeline segments: linked list of arg w/metadata
struct sh_pipeline *next, *prev, *end;
- unsigned lineno;
- int count, here, type; // TODO abuse type to replace count during parsing
+ int count, here, type, lineno;
struct sh_arg {
char **v;
int c;
} arg[1];
} *pipeline;
- struct double_list *expect; // should be zero at end of parsing
} *functions;
// runtime function call stack
struct sh_fcall {
- struct sh_fcall *next;
- struct sh_function *func;
+ struct sh_fcall *next, *prev;
+
+ // This dlist in reverse order: TT.ff current function, TT.ff->prev globals
+ struct sh_vars {
+ long flags;
+ char *str;
+ } *vars;
+
+// struct sh_function *func;
struct sh_pipeline *pl;
- int *urd, pout;
+ int varslen;
+ struct sh_arg arg;
+ struct arg_list *delete;
+ long shift;
// Runtime stack of nested if/else/fi and for/do/done contexts.
struct sh_blockstack {
struct sh_blockstack *next;
struct sh_pipeline *start, *middle;
struct sh_process *pp; // list of processes piping in to us
- int run, loop, *urd, pout;
+ int run, loop, *urd, pout, pipe;
struct sh_arg farg; // for/select arg stack, case wildcard deck
struct arg_list *fdelete; // farg's cleanup list
char *fvar; // for/select's iteration variable name
@@ -261,22 +278,12 @@ GLOBALS(
struct sh_arg *raw, arg;
} *pp; // currently running process
- struct sh_callstack {
- struct sh_callstack *next;
- struct sh_function scratch;
- struct sh_arg arg;
- struct arg_list *delete;
- long shift;
- unsigned lineno;
- } *cc;
-
// job list, command line for $*, scratch space for do_wildcard_files()
struct sh_arg jobs, *wcdeck;
)
-// Can't yet avoid this prototype. Fundamental problem is $($($(blah))) nests,
-// leading to function loop with run->parse->run
-static int sh_run(char *new);
+// Prototype because $($($(blah))) nests, leading to run->parse->run loop
+int do_source(char *name, FILE *ff);
// ordered for greedy matching, so >&; becomes >& ; not > &;
// making these const means I need to typecast the const away later to
@@ -296,6 +303,24 @@ static void syntax_err(char *s)
if (!(TT.options&FLAG_i)) xexit();
}
+void debug_show_fds()
+{
+ int x = 0, fd = open("/proc/self/fd", O_RDONLY);
+ DIR *X = fdopendir(fd);
+ struct dirent *DE;
+ char *s, *ss = 0, buf[4096], *sss = buf;
+
+ for (; (DE = readdir(X));) {
+ if (atoi(DE->d_name) == fd) continue;
+ s = xreadlink(ss = xmprintf("/proc/self/fd/%s", DE->d_name));
+ if (s && *s != '.') sss += sprintf(sss, ", %s=%s"+2*!x++, DE->d_name, s);
+ free(s); free(ss);
+ }
+ *sss = 0;
+ dprintf(2, "%d fd:%s\n", getpid(), buf);
+ closedir(X);
+}
+
// append to array with null terminator and realloc as necessary
static void arg_add(struct sh_arg *arg, char *data)
{
@@ -326,6 +351,20 @@ static void arg_add_del(struct sh_arg *arg, char *data,struct arg_list **delete)
arg_add(arg, push_arg(delete, data));
}
+// Assign one variable from malloced key=val string, returns var struct
+// TODO implement remaining types
+#define VAR_NOFREE (1<<10)
+#define VAR_WHITEOUT (1<<9)
+#define VAR_DICT (1<<8)
+#define VAR_ARRAY (1<<7)
+#define VAR_INT (1<<6)
+#define VAR_TOLOWER (1<<5)
+#define VAR_TOUPPER (1<<4)
+#define VAR_NAMEREF (1<<3)
+#define VAR_GLOBAL (1<<2)
+#define VAR_READONLY (1<<1)
+#define VAR_MAGIC (1<<0)
+
// return length of valid variable name
static char *varend(char *s)
{
@@ -336,26 +375,34 @@ static char *varend(char *s)
}
// Return index of variable within this list
-static struct sh_vars *findvar(char *name)
+static struct sh_vars *findvar(char *name, struct sh_fcall **pff)
{
int len = varend(name)-name;
- struct sh_vars *var = TT.vars+TT.varslen;
+ struct sh_fcall *ff = TT.ff;
- if (len) while (var-- != TT.vars)
- if (!strncmp(var->str, name, len) && var->str[len] == '=') return var;
+ // advance through locals to global context, ignoring whiteouts
+ if (len) do {
+ struct sh_vars *var = ff->vars+ff->varslen;
+
+ if (!var) continue;
+ if (pff) *pff = ff;
+ while (var-- != ff->vars)
+ if (pff || !(var->flags&VAR_WHITEOUT))
+ if (!strncmp(var->str, name, len) && var->str[len] == '=') return var;
+ } while ((ff = ff->next)!=TT.ff);
return 0;
}
-// Append variable to TT.vars, returning *struct. Does not check duplicates.
-static struct sh_vars *addvar(char *s)
+// Append variable to ff->vars, returning *struct. Does not check duplicates.
+static struct sh_vars *addvar(char *s, struct sh_fcall *ff)
{
- if (!(TT.varslen&31))
- TT.vars = xrealloc(TT.vars, (TT.varslen+32)*sizeof(*TT.vars));
- TT.vars[TT.varslen].flags = 0;
- TT.vars[TT.varslen].str = s;
+ if (!(ff->varslen&31))
+ ff->vars = xrealloc(ff->vars, (ff->varslen+32)*sizeof(*ff->vars));
+ ff->vars[ff->varslen].flags = 0;
+ ff->vars[ff->varslen].str = s;
- return TT.vars+TT.varslen++;
+ return ff->vars+ff->varslen++;
}
// TODO function to resolve a string into a number for $((1+2)) etc
@@ -370,18 +417,6 @@ long long do_math(char **s)
return ll;
}
-// Assign one variable from malloced key=val string, returns var struct
-// TODO implement remaining types
-#define VAR_DICT 256
-#define VAR_ARRAY 128
-#define VAR_INT 64
-#define VAR_TOLOWER 32
-#define VAR_TOUPPER 16
-#define VAR_NAMEREF 8
-#define VAR_GLOBAL 4
-#define VAR_READONLY 2
-#define VAR_MAGIC 1
-
// declare -aAilnrux
// ft
static struct sh_vars *setvar(char *s)
@@ -397,8 +432,8 @@ static struct sh_vars *setvar(char *s)
return 0;
}
if (!strncmp(s, "IFS=", 4)) TT.ifs = s+4;
- if (!(var = findvar(s))) return addvar(s);
- flags = var->flags;
+ if (!(var = findvar(s, 0))) return addvar(s, TT.ff->prev);
+ flags = (var->flags &= ~VAR_WHITEOUT);
if (flags&VAR_READONLY) {
error_msg("%.*s: read only", len, s);
@@ -419,9 +454,9 @@ static struct sh_vars *setvar(char *s)
// TODO: trailing garbage after do_math()?
if (*s == 'S') TT.SECONDS = millitime() - 1000*do_math(&ss);
else if (*s == 'R') srandom(do_math(&ss));
- } else if (flags&VAR_GLOBAL) xsetenv(var->str = s, 0);
- else {
- free(var->str);
+ } else {
+ if (!(flags&VAR_NOFREE)) free(var->str);
+ else var->flags ^= VAR_NOFREE;
var->str = s;
}
@@ -430,15 +465,24 @@ static struct sh_vars *setvar(char *s)
static void unsetvar(char *name)
{
- struct sh_vars *var = findvar(name);
- int ii = var-TT.vars;
+ struct sh_fcall *ff;
+ struct sh_vars *var = findvar(name, &ff);
+ int ii = var-ff->vars, len = varend(name)-name;
- if (!var) return;
- if (var->flags&VAR_GLOBAL) xunsetenv(name);
- else free(var->str);
+ // Is this freeable?
+ if (name[len]) return error_msg("bad %s", name);
+ if (!var || (var->flags&VAR_WHITEOUT)) return;
+ if (var->flags&VAR_READONLY) return error_msg("readonly %s", name);
- memmove(TT.vars+ii, TT.vars+ii+1, TT.varslen-ii);
- TT.varslen--;
+ // turn local into whiteout or free from global context
+ if (ff != TT.ff->prev) {
+ var->flags = VAR_WHITEOUT;
+ if (!(var->flags&VAR_NOFREE))
+ (var->str = xrealloc(var->str, len+2))[len+1] = 0;
+ } else {
+ if (!(var->flags&VAR_NOFREE)) free(var->str);
+ memmove(ff->vars+ii, ff->vars+ii+1, (ff->varslen--)-ii);
+ }
}
static struct sh_vars *setvarval(char *name, char *val)
@@ -449,7 +493,7 @@ static struct sh_vars *setvarval(char *name, char *val)
// get value of variable starting at s.
static char *getvar(char *s)
{
- struct sh_vars *var = findvar(s);
+ struct sh_vars *var = findvar(s, 0);
if (!var) return 0;
@@ -458,7 +502,7 @@ static char *getvar(char *s)
if (c == 'S') sprintf(toybuf, "%lld", (millitime()-TT.SECONDS)/1000);
else if (c == 'R') sprintf(toybuf, "%ld", random()&((1<<16)-1));
- else if (c == 'L') sprintf(toybuf, "%u", TT.LINENO);
+ else if (c == 'L') sprintf(toybuf, "%u", TT.ff->pl->lineno);
else if (c == 'G') sprintf(toybuf, "TODO: GROUPS");
return toybuf;
@@ -467,6 +511,37 @@ static char *getvar(char *s)
return varend(var->str)+1;
}
+// TODO: keep variable arrays sorted for binary search
+
+// create array of variables visible in current function.
+static struct sh_vars **visible_vars(void)
+{
+ struct sh_arg arg;
+ struct sh_fcall *ff;
+ struct sh_vars *vv;
+ unsigned ii, jj, len;
+
+ arg.c = 0;
+ arg.v = 0;
+
+ // Find non-duplicate entries: TODO, sort and binary search
+ for (ff = TT.ff; ; ff = ff->next) {
+ if (ff->vars) for (ii = ff->varslen; ii--;) {
+ vv = ff->vars+ii;
+ len = 1+(varend(vv->str)-vv->str);
+ for (jj = 0; ;jj++) {
+ if (jj == arg.c) arg_add(&arg, (void *)vv);
+ else if (strncmp(arg.v[jj], vv->str, len)) continue;
+
+ break;
+ }
+ }
+ if (ff->next == TT.ff) break;
+ }
+
+ return (void *)arg.v;
+}
+
// malloc declare -x "escaped string"
static char *declarep(struct sh_vars *var)
{
@@ -529,95 +604,71 @@ static int redir_prefix(char *word)
// quote is depth of existing quote stack in toybuf (usually 0)
static char *parse_word(char *start, int early, int quote)
{
- int i, q, qc = 0;
- char *end = start, *s;
-
- // Things we should only return at the _start_ of a word
+ int ii, qq, qc = 0;
+ char *end = start, *ss;
- // Redirections. 123<<file- parses as 2 args: "123<<" "file-".
- s = end + redir_prefix(end);
-
- if (strstart(&s, "<(") || strstart(&s, ">(")) {
+ // Handle redirections, <(), (( )) that only count at the start of word
+ ss = end + redir_prefix(end); // 123<<file- parses as 2 args: "123<<" "file-"
+ if (strstart(&ss, "<(") || strstart(&ss, ">(")) {
toybuf[quote++]=')';
- end = s;
- } else if ((i = anystart(s, (void *)redirectors))) return s+i;
-
- if (strstart(&s, "<(") || strstart(&s, ">(")) {
- toybuf[quote++]=')';
- end = s;
- }
-
- // (( is a special quote at the start of a word
+ end = ss;
+ } else if ((ii = anystart(ss, (void *)redirectors))) return ss+ii;
if (strstart(&end, "((")) toybuf[quote++] = 254;
- // find end of this word
+ // Loop to find end of this word
while (*end) {
- i = 0;
+ // If we're stopping early and already handled a symbol...
+ if (early && end!=start && !quote) break;
// barf if we're near overloading quote stack (nesting ridiculously deep)
if (quote>4000) {
- syntax_err("tilt");
+ syntax_err("bad quote depth");
return (void *)1;
}
- // Handle quote contexts
- if ((q = quote ? toybuf[quote-1] : 0)) {
- // when waiting for parentheses, they nest
- if ((q == ')' || q >= 254) && (*end == '(' || *end == ')')) {
- if (*end == '(') qc++;
+ // Are we in a quote context?
+ if ((qq = quote ? toybuf[quote-1] : 0)) {
+ ii = *end++;
+ if ((qq==')' || qq>=254) && (ii=='(' || ii==')')) { // parentheses nest
+ if (ii=='(') qc++;
else if (qc) qc--;
- else if (q >= 254) {
+ else if (qq>=254) {
// (( can end with )) or retroactively become two (( if we hit one )
- if (*end == ')' && end[1] == ')') quote--, end++;
- else if (q == 254) return start+1;
- else if (q == 255) toybuf[quote-1] = ')';
- } else if (*end == ')') quote--;
- end++;
-
- // end quote?
- } else if (*end == q) quote--, end++;
-
- // single quote claims everything
- else if (q == '\'') end++;
- else i++;
-
- // loop if we already handled a symbol and aren't stopping early
- if (early && !quote) return end;
- if (!i) continue;
+ if (ii==')' && *end==')') quote--, end++;
+ else if (qq==254) return start+1;
+ else if (qq==255) toybuf[quote-1] = ')';
+ } else if (ii==')') quote--;
+ } else if (ii==qq) quote--; // matching end quote
+ else if (qq!='\'') end--, ii = 0; // single quote claims everything
+ if (ii) continue; // fall through for other quote types
+
+ // space and flow control chars only end word when not quoted in any way
} else {
- // Things that only matter when unquoted
-
if (isspace(*end)) break;
-
- // Flow control characters that end pipeline segments
- s = end + anystart(end, (char *[]){";;&", ";;", ";&", ";", "||",
+ ss = end + anystart(end, (char *[]){";;&", ";;", ";&", ";", "||",
"|&", "|", "&&", "&", "(", ")", 0});
- if (s != end) return (end == start) ? s : end;
+ if (ss!=end) return (end==start) ? ss : end;
}
- // Things the same unquoted or in most non-single-quote contexts
-
- // start new quote context?
- if (strchr("'\"`"+(q == '"'), *end)) toybuf[quote++] = *end;
+ // start new quote context? (' not special within ")
+ if (strchr("'\"`"+(qq=='"'), ii = *end++)) toybuf[quote++] = ii;
- // backslash escapes
- else if (*end == '\\') {
- if (!end[1] || (end[1]=='\n' && !end[2])) return early ? end+1 : 0;
- end += 2;
- } else if (*end == '$' && -1 != (i = stridx("({[", end[1]))) {
- end++;
- if (strstart(&end, "((")) toybuf[quote++] = 255;
+ // \? $() ${} $[] ?() *() +() @() !()
+ else {
+ if (ii=='\\') { // TODO why end[1] here? sh -c $'abc\\\ndef' Add test.
+ if (!*end || (*end=='\n' && !end[1])) return early ? end : 0;
+ } else if (ii=='$' && -1!=(qq = stridx("({[", *end))) {
+ if (strstart(&end, "((")) {
+ toybuf[quote++] = 255;
+ end++;
+ } else toybuf[quote++] = ")}]"[qq];
+ } else if (*end=='(' && strchr("?*+@!", ii)) toybuf[quote++] = ')';
else {
- toybuf[quote++] = ")}]"[i];
- end++;
+ end--;
+ if (early && !quote) return end;
}
- } else if (end[1]=='(' && strchr("?*+@!", *end)) {
- toybuf[quote++] = ')';
- end += 2;
+ end++;
}
-
- if (early && !quote) return end;
- end++;
}
return (quote && !early) ? 0 : end;
@@ -646,6 +697,7 @@ static int save_redirect(int **rd, int from, int to)
{
int cnt, hfd, *rr;
+//dprintf(2, "%d redir %d to %d\n", getpid(), from, to);
if (from == to) return 0;
// save displaced to, copying to high (>=10) file descriptor to undo later
// except if we're saving to environment variable instead (don't undo that)
@@ -677,85 +729,195 @@ static int save_redirect(int **rd, int from, int to)
// TODO: waitpid(WNOHANG) to clean up zombies and catch background& ending
static void subshell_callback(char **argv)
{
- char *s;
-
- xsetenv(s = xmprintf("@%d,%d=", getpid(), getppid()), 0);
- s[strlen(s)-1] = 0;
- xsetenv(xmprintf("$=%d", TT.pid), 0);
+ // This depends on environ having been replaced by caller
+ environ[1] = xmprintf("@%d,%d", getpid(), getppid());
+ environ[2] = xmprintf("$=%d", TT.pid);
// TODO: test $$ in (nommu)
}
+// turn a parsed pipeline back into a string.
+static char *pl2str(struct sh_pipeline *pl, int one)
+{
+ struct sh_pipeline *end = 0, *pp;
+ int len = len, i;
+ char *s, *ss;
+
+ // Find end of block (or one argument)
+ if (one) end = pl->next;
+ else for (end = pl, len = 0; end; end = end->next)
+ if (end->type == 1) len++;
+ else if (end->type == 3 && --len<0) break;
+
+ // measure, then allocate
+ for (ss = 0;; ss = xmalloc(len+1)) {
+ for (pp = pl; pp != end; pp = pp->next) {
+ for (i = len = 0; i<pp->arg->c; i++)
+ len += snprintf(ss+len, ss ? INT_MAX : 0, "%s ", pp->arg->v[i]);
+ if (!(s = pp->arg->v[pp->arg->c])) s = ";"+(pp->next==end);
+ len += snprintf(ss+len, ss ? INT_MAX : 0, s);
+ }
+
+ if (ss) return ss;
+ }
+
+// TODO test output with case and function
+// TODO add HERE documents back in
+}
+
+// restore displaced filehandles, closing high filehandles they were copied to
+static void unredirect(int *urd)
+{
+ int *rr = urd+1, i;
+
+ if (!urd) return;
+
+ for (i = 0; i<*urd; i++, rr += 2) if (rr[0] != -1) {
+ // No idea what to do about fd exhaustion here, so Steinbach's Guideline.
+ dup2(rr[0], rr[1]);
+ close(rr[0]);
+ }
+ free(urd);
+}
+
+static struct sh_blockstack *clear_block(struct sh_blockstack *blk)
+{
+ memset(blk, 0, sizeof(*blk));
+ blk->start = TT.ff->pl;
+ blk->run = 1;
+ blk->pout = -1;
+
+ return blk;
+}
+
+// when ending a block, free, cleanup redirects and pop stack.
+static struct sh_pipeline *pop_block(void)
+{
+ struct sh_pipeline *pl = 0;
+ struct sh_blockstack *blk = TT.ff->blk;
+
+ // when ending a block, free, cleanup redirects and pop stack.
+ if (blk->pout != -1) close(blk->pout);
+ unredirect(blk->urd);
+ llist_traverse(blk->fdelete, llist_free_arg);
+ free(blk->farg.v);
+ if (TT.ff->blk->next) {
+ pl = blk->start->end;
+ free(llist_pop(&TT.ff->blk));
+ } else clear_block(blk);
+
+ return pl;
+}
+
+// Push a new empty block to the stack
+static void add_block(void)
+{
+ struct sh_blockstack *blk = clear_block(xmalloc(sizeof(*blk)));
+
+ blk->next = TT.ff->blk;
+ TT.ff->blk = blk;
+}
+
+// Add entry to runtime function call stack
+static void call_function(void)
+{
+ // dlist in reverse order: TT.ff = current function, TT.ff->prev = globals
+ dlist_add_nomalloc((void *)&TT.ff, xzalloc(sizeof(struct sh_fcall)));
+ TT.ff = TT.ff->prev;
+ add_block();
+
+// TODO caller needs to set pl, vars, func
+ // default $* is to copy previous
+ TT.ff->arg.v = TT.ff->next->arg.v;
+ TT.ff->arg.c = TT.ff->next->arg.c;
+}
+
+// returns 0 if source popped, nonzero if function popped
+static int end_function(int funconly)
+{
+ struct sh_fcall *ff = TT.ff;
+ int func = ff->next!=ff && ff->vars;
+
+ if (!func && funconly) return 0;
+
+ llist_traverse(ff->delete, llist_free_arg);
+ ff->delete = 0;
+ while (TT.ff->blk->next) pop_block();
+ pop_block();
+
+ // for a function, free variables and pop context
+ if (!func) return 0;
+ while (ff->varslen)
+ if (!(ff->vars[--ff->varslen].flags&VAR_NOFREE))
+ free(ff->vars[ff->varslen].str);
+ free(ff->vars);
+ free(TT.ff->blk);
+ free(dlist_pop(&TT.ff));
+
+ return 1;
+}
+
// TODO check every caller of run_subshell for error, or syntax_error() here
// from pipe() failure
+// TODO need CLOFORK? CLOEXEC doesn't help if we don't exec...
+
// Pass environment and command string to child shell, return PID of child
static int run_subshell(char *str, int len)
{
pid_t pid;
-
+//dprintf(2, "%d run_subshell %.*s\n", getpid(), len, str);
// The with-mmu path is significantly faster.
if (CFG_TOYBOX_FORK) {
- char *s;
-
if ((pid = fork())<0) perror_msg("fork");
else if (!pid) {
- s = xstrndup(str, len);
- sh_run(s);
- free(s);
- _exit(toys.exitval);
+ call_function();
+ if (str) {
+ do_source(0, fmemopen(str, len, "r"));
+ _exit(toys.exitval);
+ }
}
// On nommu vfork, exec /proc/self/exe, and pipe state data to ourselves.
} else {
- int pipes[2], i, c;
+ int pipes[2];
+ unsigned len, i;
+ char **oldenv = environ, *s, *ss = str ? : pl2str(TT.ff->pl->next, 0);
+ struct sh_vars **vv;
// open pipe to child
if (pipe(pipes) || 254 != dup2(pipes[0], 254)) return 1;
close(pipes[0]);
fcntl(pipes[1], F_SETFD, FD_CLOEXEC);
- // vfork child
+ // vfork child with clean environment
+ environ = xzalloc(4*sizeof(char *));
+ *environ = getvar("PATH") ? : "PATH=";
pid = xpopen_setup(0, 0, subshell_callback);
-
+// TODO what if pid -1? Handle process exhaustion.
// free entries added to end of environment by callback (shared heap)
- for (i = 0; environ[i]; i++) {
- c = environ[i][0];
- if (c == '_' || !ispunct(c)) continue;
- free(environ[i]);
- environ[i] = 0;
- }
+ free(environ[1]);
+ free(environ[2]);
+ free(environ);
+ environ = oldenv;
- // marshall data to child
+ // marshall context to child
close(254);
- for (i = 0; i<TT.varslen; i++) {
- char *s;
-
- if (TT.vars[i].flags&VAR_GLOBAL) continue;
- dprintf(pipes[1], "%s\n", s = declarep(TT.vars+i));
+ for (i = 0, vv = visible_vars(); vv[i]; i++) {
+ if (vv[i]->flags&VAR_WHITEOUT) continue;
+ dprintf(pipes[1], "%s\n", s = declarep(vv[i]));
free(s);
}
- dprintf(pipes[1], "%.*s\n", len, str);
+ free(vv);
+
+ // send command
+ dprintf(pipes[1], "%.*s\n", len, ss);
+ if (!str) free(ss);
close(pipes[1]);
}
return pid;
}
-// restore displaced filehandles, closing high filehandles they were copied to
-static void unredirect(int *urd)
-{
- int *rr = urd+1, i;
-
- if (!urd) return;
-
- for (i = 0; i<*urd; i++, rr += 2) if (rr[0] != -1) {
- // No idea what to do about fd exhaustion here, so Steinbach's Guideline.
- dup2(rr[0], rr[1]);
- close(rr[0]);
- }
- free(urd);
-}
-
// Call subshell with either stdin/stdout redirected, return other end of pipe
static int pipe_subshell(char *s, int len, int out)
{
@@ -771,7 +933,9 @@ static int pipe_subshell(char *s, int len, int out)
// Perform input or output redirect and launch process (ignoring errors)
save_redirect(&uu, pipes[in], in);
close(pipes[in]);
+ fcntl(pipes[!in], F_SETFD, FD_CLOEXEC);
run_subshell(s, len);
+ fcntl(pipes[!in], F_SETFD, 0);
unredirect(uu);
return pipes[out];
@@ -817,15 +981,15 @@ char *getvar_special(char *str, int len, int *used, struct arg_list **delete)
*ss = 0;
} else if (cc == '?') s = xmprintf("%d", toys.exitval);
else if (cc == '$') s = xmprintf("%d", TT.pid);
- else if (cc == '#') s = xmprintf("%d", TT.cc->arg.c?TT.cc->arg.c-1:0);
+ else if (cc == '#') s = xmprintf("%d", TT.ff->arg.c ? TT.ff->arg.c-1 : 0);
else if (cc == '!') s = xmprintf("%d"+2*!TT.bangpid, TT.bangpid);
else {
delete = 0;
for (*used = uu = 0; *used<len && isdigit(str[*used]); ++*used)
uu = (10*uu)+str[*used]-'0';
if (*used) {
- if (uu) uu += TT.cc->shift;
- if (uu<TT.cc->arg.c) s = TT.cc->arg.v[uu];
+ if (uu) uu += TT.ff->shift;
+ if (uu<TT.ff->arg.c) s = TT.ff->arg.v[uu];
} else if ((*used = varend(str)-str)) return getvar(str);
}
if (s) push_arg(delete, s);
@@ -1240,7 +1404,7 @@ static int expand_arg_nobrace(struct sh_arg *arg, char *str, unsigned flags,
// both types of subshell work the same, so do $( here not in '$' below
// TODO $((echo hello) | cat) ala $(( becomes $( ( retroactively
- } else if (cc == '`' || (cc == '$' && strchr("([", str[ii]))) {
+ } else if (cc == '`' || (cc == '$' && str[ii] && strchr("([", str[ii]))) {
off_t pp = 0;
s = str+ii-1;
@@ -1273,6 +1437,7 @@ dprintf(2, "TODO: do math for %.*s\n", kk, s);
}
// TODO what does \ in `` mean? What is echo `printf %s \$x` supposed to do?
+ // This has to be async so pipe buffer doesn't fill up
if (!ss) jj = pipe_subshell(s, kk, 0);
if ((ifs = readfd(jj, 0, &pp)))
for (kk = strlen(ifs); kk && ifs[kk-1]=='\n'; ifs[--kk] = 0);
@@ -1321,10 +1486,15 @@ dprintf(2, "TODO: do math for %.*s\n", kk, s);
// special case: normal varname followed by @} or *} = prefix list
if (ss[jj] == '*' || (ss[jj] == '@' && !isalpha(ss[jj+1]))) {
- for (slice++, kk = 0; kk<TT.varslen; kk++)
- if (!strncmp(s = TT.vars[kk].str, ss, jj))
+ struct sh_vars **vv = visible_vars();
+
+ for (slice++, kk = 0; vv[kk]; kk++) {
+ if (vv[kk]->flags&VAR_WHITEOUT) continue;
+ if (!strncmp(s = vv[kk]->str, ss, jj))
arg_add(&aa, push_arg(delete, s = xstrndup(s, stridx(s, '='))));
+ }
if (aa.c) push_arg(delete, aa.v);
+ free(vv);
// else dereference to get new varname, discarding if none, check err
} else {
@@ -1342,8 +1512,8 @@ dprintf(2, "TODO: do math for %.*s\n", kk, s);
if (!jj) ifs = (void *)1;
else if (ifs && *(ss = ifs)) {
if (strchr("@*", cc)) {
- aa.c = TT.cc->arg.c-1;
- aa.v = TT.cc->arg.v+1;
+ aa.c = TT.ff->arg.c-1;
+ aa.v = TT.ff->arg.v+1;
jj = 1;
} else ifs = getvar_special(ifs, strlen(ifs), &jj, delete);
if (ss && ss[jj]) {
@@ -1366,8 +1536,8 @@ barf:
// Resolve unprefixed variables
if (strchr("{$", ss[-1])) {
if (strchr("@*", cc)) {
- aa.c = TT.cc->arg.c-1;
- aa.v = TT.cc->arg.v+1;
+ aa.c = TT.ff->arg.c-1;
+ aa.v = TT.ff->arg.v+1;
} else {
ifs = getvar_special(ss, jj, &jj, delete);
if (!jj) {
@@ -1400,7 +1570,6 @@ barf:
// when aa proceed through entries until NULL, else process ifs once
mm = yy = 0;
do {
-
// get next argument
if (aa.c) ifs = aa.v[mm++] ? : "";
@@ -1412,7 +1581,7 @@ barf:
push_arg(delete, ifs = slashcopy(slice+xx+1, "}", 0));
if (dd == '?' || (dd == '=' &&
!(setvar(s = xmprintf("%.*s=%s", (int)(slice-ss), ss, ifs)))))
- goto barf;
+ goto barf; // TODO ? exits past "source" boundary
}
} else if (dd == '-'); // NOP when ifs not empty
// use alternate value
@@ -1831,35 +2000,6 @@ static char *expand_one_arg(char *new, unsigned flags, struct arg_list **del)
// TODO |&
-// turn a parsed pipeline back into a string.
-static char *pl2str(struct sh_pipeline *pl, int one)
-{
- struct sh_pipeline *end = 0, *pp;
- int len = len, i;
- char *s, *ss;
-
- // Find end of block (or one argument)
- if (one) end = pl->next;
- else for (end = pl, len = 0; end; end = end->next)
- if (end->type == 1) len++;
- else if (end->type == 3 && --len<0) break;
-
- // measure, then allocate
- for (ss = 0;; ss = xmalloc(len+1)) {
- for (pp = pl; pp != end; pp = pp->next) {
- for (i = len = 0; i<pp->arg->c; i++)
- len += snprintf(ss+len, ss ? INT_MAX : 0, "%s ", pp->arg->v[i]);
- if (!(s = pp->arg->v[pp->arg->c])) s = ";"+(pp->next==end);
- len += snprintf(ss+len, ss ? INT_MAX : 0, s);
- }
-
- if (ss) return ss;
- }
-
-// TODO test output with case and function
-// TODO add HERE documents back in
-}
-
// Expand arguments and perform redirections. Return new process object with
// expanded args. This can be called from command or block context.
static struct sh_process *expand_redir(struct sh_arg *arg, int skip, int *urd)
@@ -1869,7 +2009,6 @@ static struct sh_process *expand_redir(struct sh_arg *arg, int skip, int *urd)
int j, to, from, here = 0;
TT.hfd = 10;
-
pp = xzalloc(sizeof(struct sh_process));
pp->urd = urd;
pp->raw = arg;
@@ -2076,128 +2215,148 @@ notfd:
return pp;
}
-static void shexec(char *cmd, char **argv)
-{
- xsetenv(xmprintf("_=%s", cmd), 0);
- execve(cmd, argv, environ);
-// TODO: why?
- if (errno == ENOEXEC) run_subshell("source \"$_\"", 11);
-}
-
-// Call binary, or run via child shell
+// Call binary, or run script via xexec("sh --")
static void sh_exec(char **argv)
{
- char *pp = getvar("PATH" ? : _PATH_DEFPATH), *cc = TT.isexec ? : *argv;
+ char *pp = getvar("PATH" ? : _PATH_DEFPATH), *cc = TT.isexec ? : *argv, *ss,
+ **sss = 0, **oldenv = environ, **argv2;
struct string_list *sl;
if (getpid() != TT.pid) signal(SIGINT, SIG_DFL); // TODO: restore all?
- if (strchr(cc, '/')) shexec(cc, argv);
- else for (sl = find_in_path(pp, cc); sl; free(llist_pop(&sl)))
- shexec(sl->str, argv);
+ errno = ENOENT;
+ if (strchr(ss = cc, '/')) {
+ if (access(ss, X_OK)) ss = 0;
+ } else for (sl = find_in_path(pp, cc); sl || (ss = 0); free(llist_pop(&sl)))
+ if (!access(ss = sl->str, X_OK)) break;
+
+ if (ss) {
+ struct sh_vars **vv = visible_vars();
+ struct sh_arg aa;
+ unsigned uu, argc;
+
+ // convert vars in-place and use original sh_arg alloc to add one more
+ aa.v = environ = (void *)vv;
+ for (aa.c = uu = 0; vv[uu]; uu++) {
+ if ((vv[uu]->flags&(VAR_WHITEOUT|VAR_GLOBAL))==VAR_GLOBAL) {
+ if (*(pp = vv[uu]->str)=='_' && pp[1] == '=') sss = aa.v+aa.c;
+ aa.v[aa.c++] = pp;
+ }
+ }
+ aa.v[aa.c] = 0;
+ if (!sss) {
+ arg_add(&aa, 0);
+ sss = aa.v+aa.c-1;
+ }
+ *sss = xmprintf("_=%s", ss);
+
+ // exec or source
+ execve(ss, argv, environ);
+ if (errno == ENOEXEC) {
+ for (argc = 0; argv[argc]; argc++);
+ argv2 = xmalloc((argc+3)*sizeof(char *));
+ memcpy(argv2+3, argv+1, argc*sizeof(char *));
+ argv2[0] = "sh";
+ argv2[1] = "--";
+ argv2[2] = ss;
+ xexec(argv2);
+ free(argv2);
+ }
+ environ = oldenv;
+ free(*sss);
+ free(aa.v);
+ }
perror_msg("%s", *argv);
if (!TT.isexec) _exit(127);
+ llist_traverse(sl, free);
}
-// Execute a single command
-static struct sh_process *run_command(struct sh_arg *arg)
+// Execute a single command at TT.ff->pl
+static struct sh_process *run_command(void)
{
- char *s, *ss = 0, *sss, **old = environ;
- struct sh_arg env = {0};
- int envlen, jj = 0, ll;
- struct sh_process *pp;
+ char *s, *sss;
+ struct sh_arg *arg = TT.ff->pl->arg;
+ int envlen, jj = 0, persist = 1;
+ struct sh_process *pp = 0;
struct arg_list *delete = 0;
- struct toy_list *tl;
- // Count leading variable assignments
+ // Count leading variable assignments and perform any assignment(s)
for (envlen = 0; envlen<arg->c; envlen++) {
s = varend(arg->v[envlen]);
if (s == arg->v[envlen] || *s != '=') break;
}
-
- // perform assignments locally if there's no command
- if (envlen == arg->c) {
- while (jj<envlen) {
- if (!(s = expand_one_arg(arg->v[jj], SEMI_IFS, 0))) break;
- setvar((s == arg->v[jj++]) ? xstrdup(s) : s);
- }
- if (jj == envlen) setvarval("_", "");
-
- // assign leading environment variables (if any) in temp environ copy
- } else if (envlen) {
- while (environ[env.c]) env.c++;
- memcpy(env.v = xmalloc(sizeof(char *)*(env.c+33)), environ,
- sizeof(char *)*(env.c+1));
- for (; jj<envlen; jj++) {
- if (!(sss = expand_one_arg(arg->v[jj], SEMI_IFS, &delete))) break;
- for (ll = 0; ll<env.c; ll++) {
- for (s = sss, ss = env.v[ll]; *s == *ss && *s != '='; s++, ss++);
- if (*s != '=') continue;
- env.v[ll] = sss;
- break;
+ if (envlen) {
+ struct sh_fcall *ff;
+ struct sh_vars *vv;
+
+ // If prefix assignment, create temp function context to hold vars
+ if (!(persist = envlen==arg->c || TT.ff->blk->pipe)) call_function();
+ for (; jj<envlen && !pp; jj++) {
+// TODO this is localize(), merge with export() and local_main
+ s = arg->v[jj];
+ if (!persist && (!(vv = findvar(s, &ff)) || ff != TT.ff)) {
+ if (vv && (vv->flags&VAR_READONLY)) {
+ error_msg("%.*s: readonly variable", (int)(varend(s)-s), s);
+ continue;
+ }
+ addvar(s, TT.ff)->flags = VAR_NOFREE|VAR_GLOBAL;
}
- if (ll == env.c) arg_add(&env, sss);
+ if (!(sss = expand_one_arg(s, SEMI_IFS, persist ? 0 : &delete))) {
+ if (!pp) pp = xzalloc(sizeof(struct sh_process));
+ pp->exit = 1;
+ } else setvar((!persist || sss != s) ? sss : xstrdup(sss));
}
- environ = env.v;
- }
-
- // return early if error or assignment only
- if (envlen == arg->c || jj != envlen) {
- pp = xzalloc(sizeof(struct sh_process));
- pp->exit = jj != envlen;
-
- goto out;
}
- // expand arguments and perform redirects
- pp = expand_redir(arg, envlen, 0);
-
- // Do nothing if nothing to do
- if (pp->exit || !pp->arg.v);
-// else if (!strcmp(*pp->arg.v, "(("))
-// TODO: handle ((math)) currently totally broken
-// TODO: call functions()
- // Is this command a builtin that should run in this process?
- else if ((tl = toy_find(*pp->arg.v))
- && ((tl->flags&TOYFLAG_NOFORK)
- || (TT.ff->pout == -1 && (tl->flags&TOYFLAG_MAYFORK))))
- {
- sigjmp_buf rebound;
- char temp[jj = offsetof(struct toy_context, rebound)];
-
- // This fakes lots of what toybox_main() does.
- memcpy(&temp, &toys, jj);
- memset(&toys, 0, jj);
-
- // The compiler complains "declaration does not declare anything" if we
- // name the union in TT, only works WITHOUT name. So we can't sizeof(union)
- // instead offsetof() first thing after the union to get the size.
- memset(&TT, 0, offsetof(struct sh_data, ifs));
+ // Expand command line and do what it says
+ if (!pp) pp = expand_redir(arg, envlen, 0);
+ if (pp->exit || envlen==arg->c) s = 0; // leave $_ alone
+ else if (!pp->arg.v) s = ""; // nothing to do but blank $_
+ else {
+ struct toy_list *tl = toy_find(pp->arg.v[envlen]);
+ jj = tl ? tl->flags : 0;
TT.pp = pp;
- if (!sigsetjmp(rebound, 1)) {
- toys.rebound = &rebound;
- toy_singleinit(tl, pp->arg.v); // arg.v must be null terminated
- tl->toy_main();
- xflush(0);
- }
- TT.pp = 0;
- toys.rebound = 0;
- pp->exit = toys.exitval;
- if (toys.optargs != toys.argv+1) free(toys.optargs);
- if (toys.old_umask) umask(toys.old_umask);
- memcpy(&toys, &temp, jj);
- } else if (-1==(pp->pid = xpopen_setup(pp->arg.v, 0, sh_exec)))
- perror_msg("%s: vfork", *pp->arg.v);
-
- // Restore environment variables
- environ = old;
- free(env.v);
+ s = pp->arg.v[pp->arg.c-1];
+ sss = pp->arg.v[pp->arg.c];
+//dprintf(2, "%d run command %s\n", getpid(), pp->arg.v[envlen]);
+// TODO handle ((math)): else if (!strcmp(*pp->arg.v, "(("))
+// TODO: call functions() FUNCTION
+// TODO what about "echo | x=1 | export fruit", must subshell? Test this.
+// TODO: figure out when can exec instead of forking, ala sh -c blah
+
+ // Is this command a builtin that should run in this process?
+ if ((jj&TOYFLAG_NOFORK) || ((jj&TOYFLAG_MAYFORK) && (!sss || *sss!='|'))) {
+ sigjmp_buf rebound;
+ char temp[jj = offsetof(struct toy_context, rebound)];
+
+ // This fakes lots of what toybox_main() does.
+ memcpy(&temp, &toys, jj);
+ memset(&toys, 0, jj);
+
+ // The compiler complains "declaration does not declare anything" if we
+ // name the union in TT, only works WITHOUT name. So we can't
+ // sizeof(union) instead offsetof() first thing after union to get size.
+ memset(&TT, 0, offsetof(struct sh_data, ifs));
+ if (!sigsetjmp(rebound, 1)) {
+ toys.rebound = &rebound;
+ toy_singleinit(tl, pp->arg.v);
+ tl->toy_main();
+ xflush(0);
+ }
+ toys.rebound = 0;
+ pp->exit = toys.exitval;
+ if (toys.optargs != toys.argv+1) free(toys.optargs);
+ if (toys.old_umask) umask(toys.old_umask);
+ memcpy(&toys, &temp, jj);
+ } else if (-1==(pp->pid = xpopen_setup(pp->arg.v, 0, sh_exec)))
+ perror_msg("%s: vfork", *pp->arg.v);
+ }
- if (pp->arg.c) setvarval("_", pp->arg.v[pp->arg.c-1]);
// cleanup process
unredirect(pp->urd);
-out:
+ if (!persist) end_function(0);
+ if (s) setvarval("_", s);
llist_traverse(delete, llist_free_arg);
return pp;
@@ -2227,13 +2386,6 @@ static void free_pipeline(void *pipeline)
free(pl);
}
-static void free_function(struct sh_function *sp)
-{
- llist_traverse(sp->pipeline, free_pipeline);
- llist_traverse(sp->expect, free);
- memset(sp, 0, sizeof(struct sh_function));
-}
-
// TODO this has to add to a namespace context. Functions within functions...
static struct sh_pipeline *add_function(char *name, struct sh_pipeline *pl)
{
@@ -2243,24 +2395,25 @@ dprintf(2, "stub add_function");
}
// Append a new pipeline to function, returning pipeline and pipeline's arg
-static struct sh_pipeline *add_pl(struct sh_function *sp, struct sh_arg **arg)
+static struct sh_pipeline *add_pl(struct sh_pipeline **ppl, struct sh_arg **arg)
{
struct sh_pipeline *pl = xzalloc(sizeof(struct sh_pipeline));
*arg = pl->arg;
- if (TT.cc) pl->lineno = TT.cc->lineno;
- dlist_add_nomalloc((void *)&sp->pipeline, (void *)pl);
+ pl->lineno = TT.LINENO;
+ dlist_add_nomalloc((void *)ppl, (void *)pl);
return pl->end = pl;
}
// Add a line of shell script to a shell function. Returns 0 if finished,
// 1 to request another line of input (> prompt), -1 for syntax err
-static int parse_line(char *line, struct sh_function *sp)
+static int parse_line(char *line, struct sh_pipeline **ppl,
+ struct double_list **expect)
{
char *start = line, *delete = 0, *end, *s, *ex, done = 0,
*tails[] = {"fi", "done", "esac", "}", "]]", ")", 0};
- struct sh_pipeline *pl = sp->pipeline ? sp->pipeline->prev : 0, *pl2, *pl3;
+ struct sh_pipeline *pl = *ppl ? (*ppl)->prev : 0, *pl2, *pl3;
struct sh_arg *arg = 0;
long i;
@@ -2300,7 +2453,7 @@ static int parse_line(char *line, struct sh_function *sp)
// Parse words, assemble argv[] pipelines, check flow control and HERE docs
if (start) for (;;) {
- ex = sp->expect ? sp->expect->prev->data : 0;
+ ex = *expect ? (*expect)->prev->data : 0;
// Look for << HERE redirections in completed pipeline segment
if (pl && pl->count == -1) {
@@ -2317,9 +2470,9 @@ static int parse_line(char *line, struct sh_function *sp)
// Add another arg[] to the pipeline segment (removing/readding to list
// because realloc can move pointer)
- dlist_lpop(&sp->pipeline);
+ dlist_lpop(ppl);
pl = xrealloc(pl, sizeof(*pl) + ++pl->count*sizeof(struct sh_arg));
- dlist_add_nomalloc((void *)&sp->pipeline, (void *)pl);
+ dlist_add_nomalloc((void *)ppl, (void *)pl);
// queue up HERE EOF so input loop asks for more lines.
arg[pl->count].v = xzalloc(2*sizeof(void *));
@@ -2339,9 +2492,9 @@ static int parse_line(char *line, struct sh_function *sp)
// Parse next word and detect overflow (too many nested quotes).
if ((end = parse_word(start, 0, 0)) == (void *)1) goto flush;
-
+//dprintf(2, "%d %p %s word=%.*s\n", getpid(), pl, ex, (int)(end-start), end ? start : "");
// Is this a new pipeline segment?
- if (!pl) pl = add_pl(sp, &arg);
+ if (!pl) pl = add_pl(ppl, &arg);
// Do we need to request another line to finish word (find ending quote)?
if (!end) {
@@ -2376,7 +2529,7 @@ static int parse_line(char *line, struct sh_function *sp)
if (pl->prev->type == 2) {
// Add a call to "true" between empty ) ;;
arg_add(arg, xstrdup(":"));
- pl = add_pl(sp, &arg);
+ pl = add_pl(ppl, &arg);
}
pl->type = 129;
} else {
@@ -2401,7 +2554,7 @@ static int parse_line(char *line, struct sh_function *sp)
}
// don't save blank pipeline segments
- if (!arg->c) free_pipeline(dlist_lpop(&sp->pipeline));
+ if (!arg->c) free_pipeline(dlist_lpop(ppl));
// stop at EOL, else continue with new pipeline segment for )
if (end == start) done++;
@@ -2422,7 +2575,7 @@ static int parse_line(char *line, struct sh_function *sp)
if (arg->c==3) {
if (strcmp(s, "in")) goto flush;
pl->type = 1;
- (pl = add_pl(sp, &arg))->type = 129;
+ (pl = add_pl(ppl, &arg))->type = 129;
}
continue;
@@ -2439,7 +2592,7 @@ static int parse_line(char *line, struct sh_function *sp)
// esac right after "in" or ";;" ends block, fall through
if (arg->c>1) {
arg->v[1] = 0;
- pl = add_pl(sp, &arg);
+ pl = add_pl(ppl, &arg);
arg_add(arg, s);
} else pl->type = 0;
} else {
@@ -2447,7 +2600,7 @@ static int parse_line(char *line, struct sh_function *sp)
if (i>0 && ((i&1)==!!strchr("|)", *s) || strchr(";(", *s)))
goto flush;
if (*s=='&' || !strcmp(s, "||")) goto flush;
- if (*s==')') pl = add_pl(sp, &arg);
+ if (*s==')') pl = add_pl(ppl, &arg);
continue;
}
@@ -2485,9 +2638,9 @@ static int parse_line(char *line, struct sh_function *sp)
// end function segment, expect function body
pl->count = -1;
- dlist_add(&sp->expect, "}");
- dlist_add(&sp->expect, 0);
- dlist_add(&sp->expect, "{");
+ dlist_add(expect, "}");
+ dlist_add(expect, 0);
+ dlist_add(expect, "{");
continue;
}
@@ -2498,7 +2651,7 @@ static int parse_line(char *line, struct sh_function *sp)
// Sanity check and break the segment
if (strncmp(s, "((", 2) && *varend(s)) goto flush;
pl->count = -1;
- sp->expect->prev->data = "do\0C";
+ (*expect)->prev->data = "do\0C";
continue;
@@ -2511,7 +2664,7 @@ static int parse_line(char *line, struct sh_function *sp)
if (!strcmp(ex, "{")) {
if (strcmp(s, "{")) goto flush;
free(arg->v[--arg->c]); // don't save the {, function starts the block
- free(dlist_lpop(&sp->expect));
+ free(dlist_lpop(expect));
continue;
@@ -2536,7 +2689,7 @@ static int parse_line(char *line, struct sh_function *sp)
if (!strcmp(s, "for") || !strcmp(s, "select") || !strcmp(s, "case")) {
// TODO why !pl->type here
if (!pl->type) pl->type = (*s == 'c') ? 128 : 1;
- dlist_add(&sp->expect, (*s == 'c') ? "esac" : "do\0A");
+ dlist_add(expect, (*s == 'c') ? "esac" : "do\0A");
continue;
}
@@ -2549,14 +2702,14 @@ static int parse_line(char *line, struct sh_function *sp)
else if (!strcmp(s, "(")) end = ")";
// Expecting NULL means a statement: I.E. any otherwise unrecognized word
- if (!ex && sp->expect) free(dlist_lpop(&sp->expect));
+ if (!ex && *expect) free(dlist_lpop(expect));
// Did we start a new statement
if (end) {
pl->type = 1;
// Only innermost statement needed in { { { echo ;} ;} ;} and such
- if (sp->expect && !sp->expect->prev->data) free(dlist_lpop(&sp->expect));
+ if (*expect && !(*expect)->prev->data) free(dlist_lpop(expect));
// if can't end a statement here skip next few tests
} else if (!ex);
@@ -2569,7 +2722,7 @@ static int parse_line(char *line, struct sh_function *sp)
if (aa->v[aa->c] && strcmp(aa->v[aa->c], "&")) goto flush;
// consume word, record block end location in earlier !0 type blocks
- free(dlist_lpop(&sp->expect));
+ free(dlist_lpop(expect));
if (3 == (pl->type = anystr(s, tails) ? 3 : 2)) {
for (i = 0, pl2 = pl3 = pl; (pl2 = pl2->prev);) {
if (pl2->type == 3) i++;
@@ -2592,7 +2745,7 @@ static int parse_line(char *line, struct sh_function *sp)
// fi could have elif, which queues a then.
} else if (!strcmp(ex, "fi")) {
if (!strcmp(s, "elif")) {
- free(dlist_lpop(&sp->expect));
+ free(dlist_lpop(expect));
end = "then";
// catch duplicate else while we're here
} else if (!strcmp(s, "else")) {
@@ -2600,7 +2753,7 @@ static int parse_line(char *line, struct sh_function *sp)
s = "2 else";
goto flush;
}
- free(dlist_lpop(&sp->expect));
+ free(dlist_lpop(expect));
end = "fi\0B";
}
}
@@ -2609,8 +2762,8 @@ static int parse_line(char *line, struct sh_function *sp)
if (end) {
if (!pl->type) pl->type = 2;
- dlist_add(&sp->expect, end);
- if (!anystr(end, tails)) dlist_add(&sp->expect, 0);
+ dlist_add(expect, end);
+ if (!anystr(end, tails)) dlist_add(expect, 0);
pl->count = -1;
}
@@ -2621,27 +2774,28 @@ static int parse_line(char *line, struct sh_function *sp)
free(delete);
// ignore blank and comment lines
- if (!sp->pipeline) return 0;
+ if (!*ppl) return 0;
// TODO <<< has no parsing impact, why play with it here at all?
// advance past <<< arguments (stored as here documents, but no new input)
- pl = sp->pipeline->prev;
+ pl = (*ppl)->prev;
while (pl->count<pl->here && pl->arg[pl->count].c<0)
pl->arg[pl->count++].c = 0;
// return if HERE document pending or more flow control needed to complete
- if (sp->expect) return 1;
- if (sp->pipeline && pl->count != pl->here) return 1;
+ if (*expect) return 1;
+ if (*ppl && pl->count != pl->here) return 1;
if (pl->arg->v[pl->arg->c] && strcmp(pl->arg->v[pl->arg->c], "&")) return 1;
// Don't need more input, can start executing.
- dlist_terminate(sp->pipeline);
+ dlist_terminate(*ppl);
return 0;
flush:
if (s) syntax_err(s);
- free_function(sp);
+ llist_traverse(*ppl, free_pipeline);
+ llist_traverse(*expect, free);
return 0-!!s;
}
@@ -2664,24 +2818,6 @@ static int wait_pipeline(struct sh_process *pp)
return rc;
}
-// when ending a block, free, cleanup redirects and pop stack.
-static struct sh_pipeline *pop_block(struct sh_blockstack **cached)
-{
- struct sh_blockstack *blk = TT.ff->blk;
- struct sh_pipeline *pl = blk->start->end;
-
- // when ending a block, free, cleanup redirects and pop stack.
- if (TT.ff->pout != -1) close(TT.ff->pout);
- TT.ff->pout = blk->pout;
- unredirect(blk->urd);
- llist_traverse(blk->fdelete, llist_free_arg);
- free(blk->farg.v);
- free(llist_pop(&TT.ff->blk));
- if (cached) *cached = TT.ff->blk;
-
- return pl;
-}
-
// Print prompt to stderr, parsing escapes
// Truncated to 4k at the moment, waiting for somebody to complain.
static void do_prompt(char *prompt)
@@ -2696,7 +2832,7 @@ static void do_prompt(char *prompt)
if (c=='!') {
if (*prompt=='!') prompt++;
else {
- pp += snprintf(pp, len, "%u", TT.cc->lineno);
+ pp += snprintf(pp, len, "%u", TT.LINENO);
continue;
}
} else if (c=='\\') {
@@ -2742,6 +2878,46 @@ static void do_prompt(char *prompt)
writeall(2, toybuf, len);
}
+// returns NULL for EOF, 1 for invalid, else null terminated string.
+static char *get_next_line(FILE *ff, int prompt)
+{
+ char *new;
+ int len, cc;
+
+ if (!ff) {
+ char ps[16];
+
+ sprintf(ps, "PS%d", prompt);
+ do_prompt(getvar(ps));
+ }
+
+// TODO what should ctrl-C do? (also in "select")
+// TODO line editing/history, should set $COLUMNS $LINES and sigwinch update
+// TODO: after first EINTR returns closed?
+// TODO: ctrl-z during script read having already read partial line,
+// SIGSTOP and SIGTSTP need SA_RESTART, but child proc should stop
+// TODO if (!isspace(*new)) add_to_history(line);
+
+ for (new = 0, len = 0;;) {
+ errno = 0;
+ if (!(cc = getc(ff ? : stdin))) {
+ if (TT.LINENO) continue;
+ free(new);
+ return (char *)1;
+ }
+ if (cc<0) {
+ if (errno == EINTR) continue;
+ break;
+ }
+ if (!(len&63)) new = xrealloc(new, len+65);
+ if (cc == '\n') break;
+ new[len++] = cc;
+ }
+ if (new) new[len] = 0;
+
+ return new;
+}
+
/*
TODO: "echo | read i" is backgroundable with ctrl-Z despite read = builtin.
probably have to inline run_command here to do that? Implicit ()
@@ -2757,52 +2933,19 @@ static void do_prompt(char *prompt)
when to auto-exec? ps vs sh -c 'ps' vs sh -c '(ps)'
*/
-// Add entry to runtime function call stack
-static void call_function(struct sh_function *func)
-{
- struct sh_fcall *ff = xzalloc(sizeof(struct sh_fcall));
-
- ff->next = TT.ff;
- ff->func = func;
- ff->pl = func->pipeline;
- ff->pout = -1;
- TT.ff = ff;
-}
-
-static struct sh_fcall *pop_function(struct sh_blockstack **blk)
-{
- struct sh_fcall *ff = TT.ff;
-
- if (!ff) return 0;
-
- if (ff->pout != -1) close(ff->pout);
- unredirect(ff->urd);
-
- while (ff->blk) pop_block(0);
- TT.ff = ff->next;
- free(ff);
- if (blk) *blk = TT.ff ? TT.ff->blk : 0;
-
- return TT.ff;
-}
-
// run a parsed shell function. Handle flow control blocks and characters,
// setup pipes and block redirection, break/continue, call builtins, functions,
// vfork/exec external commands. Return when out of input.
static void run_lines(void)
{
char *ctl, *s, *ss, **vv;
- struct sh_blockstack *blk = TT.ff->blk;
struct sh_process *pplist = 0; // processes piping into current level
long i, j, k;
// iterate through pipeline segments
for (;;) {
-
- // return from function
if (!TT.ff->pl) {
- if (!TT.ff->next) break;
- pop_function(&blk);
+ if (!end_function(1)) break;
continue;
}
@@ -2810,30 +2953,32 @@ static void run_lines(void)
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];
-
- TT.LINENO = TT.ff->pl->lineno;
+//dprintf(2, "%d s=%s ss=%s ctl=%s type=%d\n", getpid(), s, ss, ctl, TT.ff->pl->type);
if (!pplist) TT.hfd = 10;
// Skip disabled blocks, handle pipes and backgrounding
if (TT.ff->pl->type<2) {
- if (blk && !blk->run) {
+ if (!TT.ff->blk->run) {
TT.ff->pl = TT.ff->pl->end->next;
continue;
}
if (TT.options&OPT_x) {
- struct sh_callstack *sc;
+ unsigned lineno;
char *ss, *ps4 = getvar("PS4");
// duplicate first char of ps4 call depth times
if (ps4 && *ps4) {
- for (i = 0, sc = TT.cc; sc; sc = sc->next) i++;
j = getutf8(ps4, k = strlen(ps4), 0);
- ss = xmalloc(i*j+k);
- for (k = 0; k<i; k++) memcpy(ss+k*j, ps4, j);
+ ss = xmalloc(TT.srclvl*j+k+1);
+ for (k = 0; k<TT.srclvl; k++) memcpy(ss+k*j, ps4, j);
strcpy(ss+k*j, ps4+j);
+ // show saved line number from function, not next to read
+ lineno = TT.LINENO;
+ TT.LINENO = TT.ff->pl->lineno;
do_prompt(ss);
+ TT.LINENO = lineno;
free(ss);
// TODO resolve variables
@@ -2844,41 +2989,42 @@ static void run_lines(void)
}
// pipe data into and out of this segment, I.E. leading/trailing |
- unredirect(TT.ff->urd);
- TT.ff->urd = 0;
-
- // Pipe from previous segment becomes our stdin.
- if (TT.ff->pout != -1) {
- if (save_redirect(&TT.ff->urd, TT.ff->pout, 0)) break;
- close(TT.ff->pout);
- TT.ff->pout = -1;
+ unredirect(TT.ff->blk->urd);
+ TT.ff->blk->urd = 0;
+ TT.ff->blk->pipe = 0;
+
+ // Consume pipe from previous segment as stdin.
+ if (TT.ff->blk->pout != -1) {
+ TT.ff->blk->pipe++;
+ if (save_redirect(&TT.ff->blk->urd, TT.ff->blk->pout, 0)) break;
+ close(TT.ff->blk->pout);
+ TT.ff->blk->pout = -1;
}
- // are we piping output to the next segment?
+ // Create output pipe and save next process's stdin in pout
if (ctl && *ctl == '|' && ctl[1] != '|') {
int pipes[2] = {-1, -1};
+ TT.ff->blk->pipe++;
if (pipe(pipes)) {
perror_msg("pipe");
-// TODO record pipeline rc
-// TODO check did not reach end of pipeline after loop
+
break;
}
- if (save_redirect(&TT.ff->urd, pipes[1], 1)) {
+ if (save_redirect(&TT.ff->blk->urd, pipes[1], 1)) {
close(pipes[0]);
close(pipes[1]);
break;
}
if (pipes[1] != 1) close(pipes[1]);
- fcntl(TT.ff->pout = *pipes, F_SETFD, FD_CLOEXEC);
- if (ctl[1] == '&') save_redirect(&TT.ff->urd, 1, 2);
+ fcntl(TT.ff->blk->pout = *pipes, F_SETFD, FD_CLOEXEC);
+ if (ctl[1] == '&') save_redirect(&TT.ff->blk->urd, 1, 2);
}
}
// Is this an executable segment?
if (!TT.ff->pl->type) {
-
// Is it a flow control jump? These aren't handled as normal builtins
// because they move *pl to other pipeline segments which is local here.
if (!strcmp(s, "break") || !strcmp(s, "continue")) {
@@ -2886,65 +3032,55 @@ static void run_lines(void)
// How many layers to peel off?
i = ss ? atol(ss) : 0;
if (i<1) i = 1;
- if (!blk || TT.ff->pl->arg->c>2 || (ss && ss[strspn(ss,"0123456789")])){
- syntax_err(s);
- break;
- }
-
- while (i && blk) {
- if (blk->middle && !strcmp(*blk->middle->arg->v, "do")
- && !--i && *s=='c') TT.ff->pl = blk->start;
- else TT.ff->pl = pop_block(&blk);
+ if (TT.ff->blk->next && TT.ff->pl->arg->c<3
+ && (!ss || !ss[strspn(ss,"0123456789")]))
+ {
+ while (i && TT.ff->blk->next)
+ if (TT.ff->blk->middle && !strcmp(*TT.ff->blk->middle->arg->v, "do")
+ && !--i && *s=='c') TT.ff->pl = TT.ff->blk->start;
+ else TT.ff->pl = pop_block();
}
if (i) {
- syntax_err("break");
+ syntax_err(s);
break;
}
// Parse and run next command, saving resulting process
- } else dlist_add_nomalloc((void *)&pplist,
- (void *)run_command(TT.ff->pl->arg));
+ } else dlist_add_nomalloc((void *)&pplist, (void *)run_command());
// Start of flow control block?
} else if (TT.ff->pl->type == 1) {
struct sh_process *pp = 0;
- int rc;
-
- // Save new block and add it to the stack.
- blk = xzalloc(sizeof(*blk));
- blk->next = TT.ff->blk;
- blk->start = TT.ff->pl;
- blk->run = 1;
- TT.ff->blk = blk;
-
- // push pipe and redirect context into block
- blk->pout = TT.ff->pout;
- TT.ff->pout = -1;
- pp = expand_redir(TT.ff->pl->end->arg, 1, blk->urd = TT.ff->urd);
- TT.ff->urd = 0;
- rc = pp->exit;
- if (pp->arg.c) {
- syntax_err(*pp->arg.v);
- rc = 1;
+
+// TODO test cat | {thingy} is new PID: { is ( for |
+
+ // perform/save trailing redirects
+ pp = expand_redir(TT.ff->pl->end->arg, 1, TT.ff->blk->urd);
+ TT.ff->blk->urd = pp->urd;
+ pp->urd = 0;
+ if (pp->arg.c) syntax_err(*pp->arg.v);
+ llist_traverse(pp->delete, llist_free_arg);
+ pp->delete = 0;
+ if (pp->exit || pp->arg.c) {
+ free(pp);
+ toys.exitval = 1;
+
+ break;
}
+ add_block();
// TODO test background a block: { abc; } &
// If we spawn a subshell, pass data off to child process
- i = ctl && !strcmp(ctl, "&");
- if (!rc && (blk->pout!=-1 || !strcmp(s, "(") || i)) {
- // Create new process
- if (!CFG_TOYBOX_FORK) {
- ss = pl2str(TT.ff->pl->next, 0);
- pp->pid = run_subshell(ss, strlen(ss));
- free(ss);
- } else if (!(pp->pid = fork())) {
- // Clear the board in child process: run pl->next in root sh_fcall
- // just leak rather than freeing, it's copy-on-write with parent
- TT.ff->pl = TT.ff->pl->next;
- TT.ff->next = 0;
- blk = TT.ff->blk = 0;
+ if (TT.ff->blk->pipe || !strcmp(s, "(") || (ctl && !strcmp(ctl, "&"))) {
+ if (!(pp->pid = run_subshell(0, -1))) {
+
+ // zap forked child's cleanup context and advance to next statement
pplist = 0;
+ while (TT.ff->blk->next) TT.ff->blk = TT.ff->blk->next;
+ TT.ff->blk->pout = -1;
+ TT.ff->blk->urd = 0;
+ TT.ff->pl = TT.ff->next->pl->next;
continue;
}
@@ -2953,13 +3089,7 @@ static void run_lines(void)
// handle start of block in this process
} else {
- // clean up after redirects (if any)
- llist_traverse(pp->delete, llist_free_arg);
free(pp);
- if (rc) {
- toys.exitval = rc;
- break;
- }
// What flow control statement is this?
@@ -2967,27 +3097,31 @@ static void run_lines(void)
// for/select/do/done: populate blk->farg with expanded args (if any)
if (!strcmp(s, "for") || !strcmp(s, "select")) {
- if (blk->loop); // TODO: still needed?
- else if (!strncmp(blk->fvar = ss, "((", 2)) {
- blk->loop = 1;
+ if (TT.ff->blk->loop);
+ else if (!strncmp(TT.ff->blk->fvar = ss, "((", 2)) {
+ TT.ff->blk->loop = 1;
dprintf(2, "TODO skipped init for((;;)), need math parser\n");
// in LIST
} else if (TT.ff->pl->next->type == 's') {
for (i = 1; i<TT.ff->pl->next->arg->c; i++)
- if (expand_arg(&blk->farg, TT.ff->pl->next->arg->v[i],
- 0, &blk->fdelete)) break;
- if (i != TT.ff->pl->next->arg->c) TT.ff->pl = pop_block(&blk);
+ if (expand_arg(&TT.ff->blk->farg, TT.ff->pl->next->arg->v[i],
+ 0, &TT.ff->blk->fdelete)) break;
+ if (i != TT.ff->pl->next->arg->c) TT.ff->pl = pop_block();
+
// in without LIST. (This expansion can't return error.)
- } else expand_arg(&blk->farg, "\"$@\"", 0, &blk->fdelete);
+ } else expand_arg(&TT.ff->blk->farg, "\"$@\"", 0,
+ &TT.ff->blk->fdelete);
// TODO: ls -C style output
- if (*s == 's') for (i = 0; i<blk->farg.c; i++)
- dprintf(2, "%ld) %s\n", i+1, blk->farg.v[i]);
+ if (*s == 's') for (i = 0; i<TT.ff->blk->farg.c; i++)
+ dprintf(2, "%ld) %s\n", i+1, TT.ff->blk->farg.v[i]);
// TODO: bash man page says it performs <(process substituion) here?!?
- } else if (!strcmp(s, "case"))
- if (!(blk->fvar = expand_one_arg(ss, NO_NULL, &blk->fdelete))) break;
+ } else if (!strcmp(s, "case")) {
+ TT.ff->blk->fvar = expand_one_arg(ss, NO_NULL, &TT.ff->blk->fdelete);
+ if (!TT.ff->blk->fvar) break;
+ }
// TODO [[/]] ((/)) function/}
}
@@ -2996,10 +3130,10 @@ dprintf(2, "TODO skipped init for((;;)), need math parser\n");
} else if (TT.ff->pl->type == 2) {
int match, err;
- blk->middle = TT.ff->pl;
+ TT.ff->blk->middle = TT.ff->pl;
// ;; end, ;& continue through next block, ;;& test next block
- if (!strcmp(*blk->start->arg->v, "case")) {
+ if (!strcmp(*TT.ff->blk->start->arg->v, "case")) {
if (!strcmp(s, ";;")) {
while (TT.ff->pl->type!=3) TT.ff->pl = TT.ff->pl->end;
continue;
@@ -3016,10 +3150,10 @@ dprintf(2, "TODO skipped init for((;;)), need math parser\n");
} else vv += **vv == '(';
}
arg.c = arg2.c = 0;
- if ((err = expand_arg_nobrace(&arg, *vv++, NO_SPLIT, &blk->fdelete,
- &arg2))) break;
+ if ((err = expand_arg_nobrace(&arg, *vv++, NO_SPLIT,
+ &TT.ff->blk->fdelete, &arg2))) break;
s = arg.c ? *arg.v : "";
- match = wildcard_match(blk->fvar, s, &arg2, 0);
+ match = wildcard_match(TT.ff->blk->fvar, s, &arg2, 0);
if (match>=0 && !s[match]) break;
else if (**vv++ == ')') {
vv = 0;
@@ -3033,29 +3167,32 @@ dprintf(2, "TODO skipped init for((;;)), need math parser\n");
}
// Handle if/else/elif statement
- } else if (!strcmp(s, "then")) blk->run = blk->run && !toys.exitval;
- else if (!strcmp(s, "else") || !strcmp(s, "elif")) blk->run = !blk->run;
+ } else if (!strcmp(s, "then"))
+ TT.ff->blk->run = TT.ff->blk->run && !toys.exitval;
+ else if (!strcmp(s, "else") || !strcmp(s, "elif"))
+ TT.ff->blk->run = !TT.ff->blk->run;
// Loop
else if (!strcmp(s, "do")) {
+ struct sh_blockstack *blk = TT.ff->blk;
+
ss = *blk->start->arg->v;
if (!strcmp(ss, "while")) blk->run = blk->run && !toys.exitval;
else if (!strcmp(ss, "until")) blk->run = blk->run && toys.exitval;
else if (!strcmp(ss, "select")) {
- do_prompt(getvar("PS3"));
-// TODO: ctrl-c not breaking out of this?
- if (!(ss = xgetline(stdin))) {
- TT.ff->pl = pop_block(&blk);
+ if (!(ss = get_next_line(0, 3)) || ss==(void *)1) {
+ TT.ff->pl = pop_block();
printf("\n");
- } else if (!*ss) {
- TT.ff->pl = blk->start;
- continue;
} else {
match = atoi(ss);
- setvarval(blk->fvar, (match<1 || match>blk->farg.c)
- ? "" : blk->farg.v[match-1]);
+ free(ss);
+ if (!*ss) {
+ TT.ff->pl = blk->start;
+ continue;
+ } else setvarval(blk->fvar, (match<1 || match>blk->farg.c)
+ ? "" : blk->farg.v[match-1]);
}
- } else if (blk->loop >= blk->farg.c) TT.ff->pl = pop_block(&blk);
+ } else if (blk->loop >= blk->farg.c) TT.ff->pl = pop_block();
else if (!strncmp(blk->fvar, "((", 2)) {
dprintf(2, "TODO skipped running for((;;)), need math parser\n");
} else setvarval(blk->fvar, blk->farg.v[blk->loop++]);
@@ -3064,27 +3201,24 @@ dprintf(2, "TODO skipped running for((;;)), need math parser\n");
// end of block
} else if (TT.ff->pl->type == 3) {
- // If we end a block we're not in, pop function context.
- if (!blk) {
+ // If we end a block we're not in, exit subshell
+ if (!TT.ff->blk->next) xexit();
- // Exit subshell if no function context left
- if (!pop_function(&blk)) xexit();
- } else {
+ // repeating block?
+ if (TT.ff->blk->run && !strcmp(s, "done")) {
+ TT.ff->pl = TT.ff->blk->middle;
+ continue;
+ }
- // repeating block?
- if (blk->run && !strcmp(s, "done")) {
- TT.ff->pl = blk->middle;
- continue;
- }
+ // cleans up after trailing redirections/pipe
+ pop_block();
- // cleans up after trailing redirections/pipe
- pop_block(&blk);
- }
+// FUNCTION this!
} else if (TT.ff->pl->type == 'f') TT.ff->pl = add_function(s, TT.ff->pl);
- // Three cases: background & pipeline | last process in pipeline ;
+ // Three cases: 1) background & 2) pipeline | 3) last process in pipeline ;
// If we ran a process and didn't pipe output, background or wait for exit
- if (pplist && TT.ff->pout == -1) {
+ if (pplist && TT.ff->blk->pout == -1) {
if (ctl && !strcmp(ctl, "&")) {
pplist->job = ++TT.jobcnt;
arg_add(&TT.jobs, (void *)pplist);
@@ -3110,40 +3244,20 @@ dprintf(2, "TODO skipped running for((;;)), need math parser\n");
toys.exitval = wait_pipeline(pplist);
llist_traverse(pplist, free_process);
}
- while (pop_function(0));
-}
-// Parse and run a self-contained command line with no prompt/continuation
-static int sh_run(char *new)
-{
- struct sh_function scratch;
- void *ff = TT.ff;
-
-// TODO switch the fmemopen for -c to use this? Error checking? $(blah)
-// TODO Merge this with do_source()
-
- memset(&scratch, 0, sizeof(struct sh_function));
- if (!parse_line(new, &scratch)) {
- TT.ff = 0;
- call_function(&scratch);
- run_lines();
- TT.ff = ff;
- }
-// TODO else error?
- free_function(&scratch);
-
- return toys.exitval;
+ // exit source context (and function calls on syntax err)
+ while (end_function(0));
}
// set variable
-static struct sh_vars *initlocal(char *name, char *val)
+static struct sh_vars *initvar(char *name, char *val)
{
- return addvar(xmprintf("%s=%s", name, val ? val : ""));
+ return addvar(xmprintf("%s=%s", name, val ? val : ""), TT.ff);
}
-static struct sh_vars *initlocaldef(char *name, char *val, char *def)
+static struct sh_vars *initvardef(char *name, char *val, char *def)
{
- return initlocal(name, (!val || !*val) ? def : val);
+ return initvar(name, (!val || !*val) ? def : val);
}
// export existing "name" or assign/export name=value string (making new copy)
@@ -3154,28 +3268,26 @@ static void export(char *str)
// Make sure variable exists and is updated
if (strchr(str, '=')) shv = setvar(xstrdup(str));
- else if (!(shv = findvar(str))) shv = addvar(str = xmprintf("%s=", str));
+ else if (!(shv = findvar(str, 0))) {
+ shv = addvar(str = xmprintf("%s=", str), TT.ff->prev);
+ shv->flags = VAR_WHITEOUT;
+ } else if (shv->flags&VAR_WHITEOUT) shv->flags |= VAR_GLOBAL;
if (!shv || (shv->flags&VAR_GLOBAL)) return;
- // Resolve local magic for export
+ // Resolve magic for export (bash bug compatibility, really should be dynamic)
if (shv->flags&VAR_MAGIC) {
s = shv->str;
shv->str = xmprintf("%.*s=%s", (int)(varend(str)-str), str, getvar(str));
free(s);
}
-
- xsetenv(shv->str, 0);
shv->flags |= VAR_GLOBAL;
}
static void unexport(char *str)
{
- struct sh_vars *shv = findvar(str);
+ struct sh_vars *shv = findvar(str, 0);
- if (shv) {
- if (shv->flags&VAR_GLOBAL) shv->str = xpop_env(str);
- shv->flags &=~VAR_GLOBAL;
- }
+ if (shv) shv->flags &=~VAR_GLOBAL;
if (strchr(str, '=')) setvar(str);
}
@@ -3194,71 +3306,70 @@ FILE *fpathopen(char *name)
return f;
}
-// get line with command history
-char *prompt_getline(FILE *ff, int prompt)
-{
- char *new, ps[16];
-
-// TODO line editing/history, should set $COLUMNS $LINES and sigwinch update
- errno = 0;
- if (!ff && prompt) {
- sprintf(ps, "PS%d", prompt);
- do_prompt(getvar(ps));
- }
- do if ((new = xgetline(ff ? : stdin))) return new;
- while (errno == EINTR);
-// TODO: after first EINTR returns closed?
-// TODO: ctrl-z during script read having already read partial line,
-// SIGSTOP and SIGTSTP need need SA_RESTART, but child proc should stop
-// TODO if (!isspace(*new)) add_to_history(line);
-
- return 0;
-}
-
// Read script input and execute lines, with or without prompts
int do_source(char *name, FILE *ff)
{
- struct sh_callstack *cc = xzalloc(sizeof(struct sh_callstack));
- int more = 0;
+ struct sh_pipeline *pl = 0;
+ struct double_list *expect = 0;
+ unsigned lineno = TT.LINENO, more = 0;
+ int cc, ii;
char *new;
- cc->next = TT.cc;
- cc->arg.v = toys.optargs;
- cc->arg.c = toys.optc;
- TT.cc = cc;
+ if (++TT.recursion>(50+200*CFG_TOYBOX_FORK)) {
+ error_msg("recursive occlusion");
+
+ goto end;
+ }
+
+// TODO fix/catch NONBLOCK on input?
+// TODO when DO we reset lineno? (!LINENO means \0 returns 1)
+// when do we NOT reset lineno? Inherit but preserve perhaps? newline in $()?
+ if (!name) TT.LINENO = 0;
do {
- new = prompt_getline(ff, more+1);
- if (!(TT.LINENO = TT.cc->lineno++) && new && !memcmp(new, "\177ELF", 4)) {
- error_msg("'%s' is ELF", name);
- free(new);
+ if ((void *)1 == (new = get_next_line(ff, more+1))) goto is_binary;
+//dprintf(2, "%d getline from %p %s\n", getpid(), ff, new);
+ // did we exec an ELF file or something?
+ if (!TT.LINENO++ && name && new) {
+ wchar_t wc;
- break;
+ // A shell script's first line has no high bytes that aren't valid utf-8.
+ for (ii = 0; new[ii] && 0<(cc = utf8towc(&wc, new+ii, 4)); ii += cc);
+ if (new[ii]) {
+is_binary:
+ if (name) error_msg("'%s' is binary", name); // TODO syntax_err() exit?
+ free(new);
+ new = 0;
+ }
}
// TODO: source <(echo 'echo hello\') vs source <(echo -n 'echo hello\')
// prints "hello" vs "hello\"
// returns 0 if line consumed, command if it needs more data
- more = parse_line(new ? : " ", &cc->scratch);
- if (more==1) {
- if (!new && !ff) syntax_err("unexpected end of file");
- } else {
- if (!more) {
- call_function(&cc->scratch);
- run_lines();
- }
- free_function(&cc->scratch);
- more = 0;
- }
+ more = parse_line(new ? : " ", &pl, &expect);
free(new);
- } while(new);
+ if (more==1) {
+ if (!new) {
+ if (!ff) syntax_err("unexpected end of file");
+ } else continue;
+ } else if (!more && pl) {
+ TT.ff->pl = pl;
+ run_lines();
+ } else more = 0;
+
+ llist_traverse(pl, free_pipeline);
+ pl = 0;
+ llist_traverse(expect, free);
+ expect = 0;
+ } while (new);
if (ff) fclose(ff);
- TT.cc = TT.cc->next;
- free_function(&cc->scratch);
- llist_traverse(cc->delete, llist_free_arg);
- free(cc);
+
+ if (!name) TT.LINENO = lineno;
+
+end:
+ TT.recursion--;
return more;
}
@@ -3266,49 +3377,55 @@ int do_source(char *name, FILE *ff)
// init locals, sanitize environment, handle nommu subshell handoff
static void subshell_setup(void)
{
- int ii, to, from, pid, ppid, zpid, myppid = getppid(), len, uid = getuid();
+ int ii, from, pid, ppid, zpid, myppid = getppid(), len, uid = getuid();
struct passwd *pw = getpwuid(uid);
- char *s, *ss, *magic[] = {"SECONDS","RANDOM","LINENO","GROUPS"},
+ char *s, *ss, *magic[] = {"SECONDS", "RANDOM", "LINENO", "GROUPS"},
*readonly[] = {xmprintf("EUID=%d", geteuid()), xmprintf("UID=%d", uid),
xmprintf("PPID=%d", myppid)};
struct stat st;
+ struct sh_vars *shv;
struct utsname uu;
+ // Create initial function context
+ call_function();
+ TT.ff->arg.v = toys.optargs;
+ TT.ff->arg.c = toys.optc;
+
// Initialize magic and read only local variables
srandom(TT.SECONDS = millitime());
for (ii = 0; ii<ARRAY_LEN(magic); ii++)
- initlocal(magic[ii], "")->flags = VAR_MAGIC|(VAR_INT*('G'!=*magic[ii]));
+ initvar(magic[ii], "")->flags = VAR_MAGIC|(VAR_INT*('G'!=*magic[ii]));
for (ii = 0; ii<ARRAY_LEN(readonly); ii++)
- addvar(readonly[ii])->flags = VAR_READONLY|VAR_INT;
+ addvar(readonly[ii], TT.ff)->flags = VAR_READONLY|VAR_INT;
// Add local variables that can be overwritten
- initlocal("PATH", _PATH_DEFPATH);
+ initvar("PATH", _PATH_DEFPATH);
if (!pw) pw = (void *)toybuf; // first use, so still zeroed
sprintf(toybuf+1024, "%u", uid);
- initlocaldef("HOME", pw->pw_dir, "/");
- initlocaldef("SHELL", pw->pw_shell, "/bin/sh");
- initlocaldef("USER", pw->pw_name, toybuf+1024);
- initlocaldef("LOGNAME", pw->pw_name, toybuf+1024);
+ initvardef("HOME", pw->pw_dir, "/");
+ initvardef("SHELL", pw->pw_shell, "/bin/sh");
+ initvardef("USER", pw->pw_name, toybuf+1024);
+ initvardef("LOGNAME", pw->pw_name, toybuf+1024);
gethostname(toybuf, sizeof(toybuf)-1);
- initlocal("HOSTNAME", toybuf);
+ initvar("HOSTNAME", toybuf);
uname(&uu);
- initlocal("HOSTTYPE", uu.machine);
+ initvar("HOSTTYPE", uu.machine);
sprintf(toybuf, "%s-unknown-linux", uu.machine);
- initlocal("MACHTYPE", toybuf);
- initlocal("OSTYPE", uu.sysname);
+ initvar("MACHTYPE", toybuf);
+ initvar("OSTYPE", uu.sysname);
// sprintf(toybuf, "%s-toybox", TOYBOX_VERSION);
- // initlocal("BASH_VERSION", toybuf);
- initlocal("OPTERR", "1"); // TODO: test if already exported?
+ // initvar("BASH_VERSION", toybuf); TODO
+ initvar("OPTERR", "1"); // TODO: test if already exported?
if (readlink0("/proc/self/exe", s = toybuf, sizeof(toybuf))||(s=getenv("_")))
- initlocal("BASH", s);
- initlocal("PS2", "> ");
- initlocal("PS3", "#? ");
- initlocal("PS4", "+ ");
+ initvar("BASH", s);
+ initvar("PS2", "> ");
+ initvar("PS3", "#? ");
+ initvar("PS4", "+ ");
// Ensure environ copied and toys.envc set, and clean out illegal entries
TT.ifs = " \t\n";
- xsetenv("", 0);
- for (to = from = pid = ppid = zpid = 0; (s = environ[from]); from++) {
+
+ for (from = pid = ppid = zpid = 0; (s = environ[from]); from++) {
// If nommu subshell gets handoff
if (!CFG_TOYBOX_FORK && !toys.stacktop) {
@@ -3320,27 +3437,23 @@ static void subshell_setup(void)
}
// Filter out non-shell variable names from inherited environ.
- // (haven't xsetenv() yet so no need to free() or adjust toys.envc)
- ss = varend(s);
- if (*ss == '=') {
- struct sh_vars *shv = findvar(s);
+ if (*varend(s) != '=') continue;
- if (!shv) addvar(environ[from])->flags = VAR_GLOBAL;
- else if (shv->flags&VAR_READONLY) continue;
- else {
- shv->flags |= VAR_GLOBAL;
+ if (!(shv = findvar(s, 0))) addvar(s, TT.ff)->flags = VAR_GLOBAL|VAR_NOFREE;
+ else if (shv->flags&VAR_READONLY) continue;
+ else {
+ if (!(shv->flags&VAR_NOFREE)) {
free(shv->str);
- shv->str = s;
+ shv->flags ^= VAR_NOFREE;
}
- environ[to++] = s;
+ shv->flags |= VAR_GLOBAL;
+ shv->str = s;
}
if (!memcmp(s, "IFS=", 4)) TT.ifs = s+4;
}
- environ[to++] = 0;
- toys.envc = to;
// set/update PWD
- sh_run("cd .");
+ do_source(0, fmemopen("cd .", 4, "r"));
// set _ to path to this shell
s = toys.argv[0];
@@ -3350,31 +3463,30 @@ static void subshell_setup(void)
s = xmprintf("%s/%s", ss, s);
free(ss);
ss = s;
- } else if (*toybuf) s = toybuf;
+ } else if (*toybuf) s = toybuf; // from /proc/self/exe
}
- s = xsetenv("_", s);
- if (!findvar(s)) addvar(s)->flags = VAR_GLOBAL;
+ setvarval("_", s)->flags |= VAR_GLOBAL;
free(ss);
if (!(ss = getvar("SHLVL"))) export("SHLVL=1");
else {
char buf[16];
sprintf(buf, "%u", atoi(ss+6)+1);
- xsetenv("SHLVL", buf);
- export("SHLVL");
+ setvarval("SHLVL", buf)->flags |= VAR_GLOBAL;
}
//TODO indexed array,associative array,integer,local,nameref,readonly,uppercase
// if (s+1<ss && strchr("aAilnru", *s)) {
- // sanity check: magic env variable, pipe status
- if (CFG_TOYBOX_FORK || toys.stacktop || pid!=getpid() || ppid!=myppid) return;
- if (fstat(254, &st) || !S_ISFIFO(st.st_mode)) error_exit(0);
- TT.pid = zpid;
- fcntl(254, F_SETFD, FD_CLOEXEC);
- do_source("", fdopen(254, "r"));
+ // Are we a nofork subshell? (check magic env variable and pipe status)
+ if (!CFG_TOYBOX_FORK && !toys.stacktop && pid==getpid() && ppid==myppid) {
+ if (fstat(254, &st) || !S_ISFIFO(st.st_mode)) error_exit(0);
+ TT.pid = zpid;
+ fcntl(254, F_SETFD, FD_CLOEXEC);
+ do_source(0, fdopen(254, "r"));
- xexit();
+ xexit();
+ }
}
void sh_main(void)
@@ -3384,7 +3496,6 @@ void sh_main(void)
signal(SIGPIPE, SIG_IGN);
TT.options = OPT_B;
-
TT.pid = getpid();
TT.SECONDS = time(0);
@@ -3421,14 +3532,13 @@ void sh_main(void)
xsignal(SIGINT, SIG_IGN);
}
-// TODO unify fmemopen() here with sh_run
if (cc) ff = fmemopen(cc, strlen(cc), "r");
else if (TT.options&FLAG_s) ff = (TT.options&FLAG_i) ? 0 : stdin;
else if (!(ff = fpathopen(*toys.optargs))) perror_exit_raw(*toys.optargs);
// Read and execute lines from file
if (do_source(cc ? : *toys.optargs, ff))
- error_exit("%u:unfinished line"+3*!TT.cc->lineno, TT.cc->lineno);
+ error_exit("%u:unfinished line"+3*!TT.LINENO, TT.LINENO);
}
// TODO: ./blah.sh one two three: put one two three in scratch.arg
@@ -3515,9 +3625,14 @@ void set_main(void)
char *cc, *ostr[] = {"braceexpand", "noclobber", "xtrace"};
int ii, jj, kk, oo = 0, dd = 0;
+ // display visible variables
if (!*toys.optargs) {
+ struct sh_vars **vv = visible_vars();
+
// TODO escape properly
- for (ii = 0; ii<TT.varslen; ii++) printf("%s\n", TT.vars[ii].str);
+ for (ii = 0; vv[ii]; ii++)
+ if (!(vv[ii]->flags&VAR_WHITEOUT)) printf("%s\n", vv[ii]->str);
+ free(vv);
return;
}
@@ -3550,9 +3665,10 @@ void set_main(void)
// handle positional parameters
if (cc) {
struct arg_list *al, **head;
- struct sh_arg *arg = &TT.cc->arg;
+ struct sh_arg *arg = &TT.ff->arg;
- for (al = *(head = &TT.cc->delete); al; al = *(head = &al->next))
+ // don't free memory that's already scheduled for deletion
+ for (al = *(head = &TT.ff->delete); al; al = *(head = &al->next))
if (al->arg == (void *)arg->v) break;
// free last set's memory (if any) so it doesn't accumulate in loop
@@ -3563,8 +3679,8 @@ void set_main(void)
}
while (toys.optargs[ii])
- arg_add(arg, push_arg(&TT.cc->delete, strdup(toys.optargs[ii++])));
- push_arg(&TT.cc->delete, arg->v);
+ arg_add(arg, push_arg(&TT.ff->delete, strdup(toys.optargs[ii++])));
+ push_arg(&TT.ff->delete, arg->v);
}
}
@@ -3595,7 +3711,16 @@ void export_main(void)
// list existing variables?
if (!toys.optc) {
- for (arg = environ; *arg; arg++) xprintf("declare -x %s\n", *arg);
+ struct sh_vars **vv = visible_vars();
+ unsigned uu;
+
+ for (uu = 0; vv[uu]; uu++) {
+ if ((vv[uu]->flags&(VAR_WHITEOUT|VAR_GLOBAL))==VAR_GLOBAL) {
+ xputs(eq = declarep(vv[uu]));
+ free(eq);
+ }
+ }
+
return;
}
@@ -3614,19 +3739,17 @@ void export_main(void)
void eval_main(void)
{
- struct sh_arg old = TT.cc->arg, *volatile arg = &TT.cc->arg;
char *s;
- // borrow the $* expand infrastructure (avoiding $* from trap handler race).
- arg->c = 0;
- arg->v = toys.argv;
- arg->c = toys.optc+1;
+ // borrow the $* expand infrastructure
+ call_function();
+ TT.ff->arg.v = toys.argv;
+ TT.ff->arg.c = toys.optc+1;
s = expand_one_arg("\"$*\"", SEMI_IFS, 0);
- arg->c = 0;
- arg->v = old.v;
- arg->c = old.c;
-
- sh_run(s);
+ TT.ff->arg.v = TT.ff->next->arg.v;
+ TT.ff->arg.c = TT.ff->next->arg.c;
+ do_source(0, fmemopen(s, strlen(s), "r"));
+ free(dlist_pop(&TT.ff));
free(s);
}
@@ -3746,26 +3869,78 @@ void jobs_main(void)
}
}
+#define CLEANUP_exec
+#define FOR_local
+#include "generated/flags.h"
+
+void local_main(void)
+{
+ struct sh_fcall *ff, *ff2;
+ struct sh_vars *var;
+ char **arg, *eq;
+
+ // find local variable context
+ for (ff = TT.ff;; ff = ff->next) {
+ if (ff == TT.ff->prev) return error_msg("not in function");
+ if (ff->vars) break;
+ }
+
+ // list existing vars (todo:
+ if (!toys.optc) {
+ for (var = ff->vars; var; var++) xputs(var->str); // TODO escape
+ return;
+ }
+
+ // set/move variables
+ for (arg = toys.optargs; *arg; arg++) {
+ if ((eq = varend(*arg)) == *arg || (*eq && *eq != '=')) {
+ error_msg("bad %s", *arg);
+ continue;
+ }
+
+ if ((var = findvar(*arg, &ff2)) && ff == ff2 && !*eq) continue;
+ if (var && (var->flags&VAR_READONLY)) {
+ error_msg("%.*s: readonly variable", (int)(varend(*arg)-*arg), *arg);
+ continue;
+ }
+
+ // Add local inheriting global status and setting whiteout if blank.
+ if (!var || ff!=ff2) {
+ int flags = var ? var->flags&VAR_GLOBAL : 0;
+
+ var = addvar(xmprintf("%s%s", *arg, *eq ? "" : "="), ff);
+ var->flags = flags|(VAR_WHITEOUT*!*eq);
+ }
+
+ // TODO accept declare options to set more flags
+ // TODO, integer, uppercase take effect. Setvar?
+ }
+}
+
void shift_main(void)
{
long long by = 1;
if (toys.optc) by = atolx(*toys.optargs);
- by += TT.cc->shift;
- if (by<0 || by>=TT.cc->arg.c) toys.exitval++;
- else TT.cc->shift = by;
+ by += TT.ff->shift;
+ if (by<0 || by>=TT.ff->arg.c) toys.exitval++;
+ else TT.ff->shift = by;
}
void source_main(void)
{
- char *name = toys.optargs[1];
+ char *name = *toys.optargs;
FILE *ff = fpathopen(name);
if (!ff) return perror_msg_raw(name);
-
// $0 is shell name, not source file name while running this
// TODO add tests: sh -c "source input four five" one two three
*toys.optargs = *toys.argv;
-
+ ++TT.srclvl;
+ call_function();
+ TT.ff->arg.v = toys.optargs;
+ TT.ff->arg.c = toys.optc;
do_source(name, ff);
+ free(dlist_pop(&TT.ff));
+ --TT.srclvl;
}
diff --git a/toys/pending/telnetd.c b/toys/pending/telnetd.c
index bb08e785..c82ff61e 100644
--- a/toys/pending/telnetd.c
+++ b/toys/pending/telnetd.c
@@ -24,7 +24,9 @@ config TELNETD
#define FOR_telnetd
#include "toys.h"
+#include <arpa/telnet.h>
#include <utmp.h>
+
GLOBALS(
char *login_path;
char *issue_path;
@@ -36,20 +38,6 @@ GLOBALS(
pid_t fork_pid;
)
-
-# define IAC 255 /* interpret as command: */
-# define DONT 254 /* you are not to use option */
-# define DO 253 /* please, you use option */
-# define WONT 252 /* I won't use option */
-# define WILL 251 /* I will use option */
-# define SB 250 /* interpret as subnegotiation */
-# define SE 240 /* end sub negotiation */
-# define NOP 241 /* No Operation */
-# define TELOPT_ECHO 1 /* echo */
-# define TELOPT_SGA 3 /* suppress go ahead */
-# define TELOPT_TTYPE 24 /* terminal type */
-# define TELOPT_NAWS 31 /* window size */
-
#define BUFSIZE 4*1024
struct term_session {
int new_fd, pty_fd;
@@ -132,7 +120,7 @@ static int listen_socket(void)
char buf[sizeof(struct sockaddr_storage)];
memset(buf, 0, sizeof(buf));
- if (toys.optflags & FLAG_b) {
+ if (FLAG(b)) {
get_sockaddr(TT.host_addr, buf);
af = ((struct sockaddr *)buf)->sa_family;
} else {
@@ -140,8 +128,7 @@ static int listen_socket(void)
((struct sockaddr_in*)buf)->sin_family = af;
}
s = xsocket(af, SOCK_STREAM, 0);
- if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (char *)&yes, sizeof(yes)) == -1)
- perror_exit("setsockopt");
+ xsetsockopt(s, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes));
xbind(s, (struct sockaddr *)buf, ((af == AF_INET)?
(sizeof(struct sockaddr_in)):(sizeof(struct sockaddr_in6))));
@@ -187,9 +174,9 @@ static int new_session(int sockfd)
setsockopt(sockfd, SOL_SOCKET, SO_KEEPALIVE, &i, sizeof(i));
flags = fcntl(sockfd, F_GETFL);
fcntl(sockfd, F_SETFL, flags | O_NONBLOCK);
- if (toys.optflags & FLAG_i) fcntl((sockfd + 1), F_SETFL, flags | O_NONBLOCK);
+ if (FLAG(i)) fcntl((sockfd + 1), F_SETFL, flags | O_NONBLOCK);
- writeall((toys.optflags & FLAG_i)?1:sockfd, intial_iacs, sizeof(intial_iacs));
+ writeall(FLAG(i)?1:sockfd, intial_iacs, sizeof(intial_iacs));
if ((TT.fork_pid = forkpty(&fd, tty_name, NULL, NULL)) > 0) {
flags = fcntl(fd, F_GETFL);
fcntl(fd, F_SETFL, flags | O_NONBLOCK);
@@ -300,21 +287,19 @@ static int dup_iacs(char *start, int fd, int len)
void telnetd_main(void)
{
- errno = 0;
fd_set rd, wr;
struct term_session *tm = NULL;
struct timeval tv, *tv_ptr = NULL;
int pty_fd, new_fd, c = 0, w, master_fd = 0;
- int inetd_m = toys.optflags & FLAG_i;
- if (!(toys.optflags & FLAG_l)) TT.login_path = "/bin/login";
- if (!(toys.optflags & FLAG_f)) TT.issue_path = "/etc/issue.net";
- if (toys.optflags & FLAG_w) toys.optflags |= FLAG_F;
- if (!inetd_m) {
+ if (!FLAG(l)) TT.login_path = "/bin/login";
+ if (!FLAG(f)) TT.issue_path = "/etc/issue.net";
+ if (FLAG(w)) toys.optflags |= FLAG_F;
+ if (!FLAG(i)) {
master_fd = listen_socket();
fcntl(master_fd, F_SETFD, FD_CLOEXEC);
if (master_fd > TT.gmax_fd) TT.gmax_fd = master_fd;
- if (!(toys.optflags & FLAG_F)) daemon(0, 0);
+ if (!FLAG(F)) daemon(0, 0);
} else {
pty_fd = new_session(master_fd); //master_fd = 0
if (pty_fd > TT.gmax_fd) TT.gmax_fd = pty_fd;
@@ -328,7 +313,7 @@ void telnetd_main(void)
} else session_list = tm;
}
- if ((toys.optflags & FLAG_w) && !session_list) {
+ if (FLAG(w) && !session_list) {
tv.tv_sec = TT.w_sec;
tv.tv_usec = 0;
tv_ptr = &tv;
@@ -338,7 +323,7 @@ void telnetd_main(void)
for (;;) {
FD_ZERO(&rd);
FD_ZERO(&wr);
- if (!inetd_m) FD_SET(master_fd, &rd);
+ if (!FLAG(i)) FD_SET(master_fd, &rd);
tm = session_list;
while (tm) {
@@ -354,10 +339,10 @@ void telnetd_main(void)
int r = select(TT.gmax_fd + 1, &rd, &wr, NULL, tv_ptr);
- if (!r) return; //timeout
+ if (!r) error_exit("select timed out");
if (r < -1) continue;
- if (!inetd_m && FD_ISSET(master_fd, &rd)) { //accept new connection
+ if (!FLAG(i) && FD_ISSET(master_fd, &rd)) { //accept new connection
new_fd = accept(master_fd, NULL, NULL);
if (new_fd < 0) continue;
tv_ptr = NULL;
@@ -382,7 +367,7 @@ void telnetd_main(void)
if ((c = read(tm->pty_fd, tm->buff1 + tm->buff1_avail,
BUFSIZE-tm->buff1_avail)) <= 0) break;
tm->buff1_avail += c;
- if ((w = dup_iacs(tm->buff1 + tm->buff1_written, tm->new_fd + inetd_m,
+ if ((w = dup_iacs(tm->buff1 + tm->buff1_written, tm->new_fd + FLAG(i),
tm->buff1_avail - tm->buff1_written)) < 0) break;
tm->buff1_written += w;
}
@@ -401,7 +386,7 @@ void telnetd_main(void)
tm->buff2_written += w;
}
if (FD_ISSET(tm->new_fd, &wr)) {
- if ((w = dup_iacs(tm->buff1 + tm->buff1_written, tm->new_fd + inetd_m,
+ if ((w = dup_iacs(tm->buff1 + tm->buff1_written, tm->new_fd + FLAG(i),
tm->buff1_avail - tm->buff1_written)) < 0) break;
tm->buff1_written += w;
}
@@ -421,17 +406,16 @@ void telnetd_main(void)
// funny little dance to avoid race conditions.
toys.signal = 0;
pid = waitpid(-1, &status, WNOHANG);
- if (pid < 0) break;
+ if (pid <= 0) break;
toys.signal++;
-
for (tm = session_list; tm; tm = tm->next) {
if (tm->child_pid == pid) break;
prev = tm;
}
- if (!tm) return; // reparented child we don't care about
+ if (!tm) error_exit("unexpected reparenting of %d", pid);
- if (toys.optflags & FLAG_i) exit(EXIT_SUCCESS);
+ if (FLAG(i)) exit(EXIT_SUCCESS);
if (!prev) session_list = session_list->next;
else prev->next = tm->next;
utmp_entry();
diff --git a/toys/pending/vi.c b/toys/pending/vi.c
index 18ce8e6a..da43d5d5 100644
--- a/toys/pending/vi.c
+++ b/toys/pending/vi.c
@@ -949,13 +949,57 @@ static int vi_zero(int count0, int count1, char *unused)
return 1;
}
-static int vi_eol(int count0, int count1, char *unused)
+static int vi_dollar(int count0, int count1, char *unused)
+{
+ size_t new = text_strchr(TT.cursor, '\n');
+
+ if (new != TT.cursor) {
+ TT.cursor = new - 1;
+ TT.vi_mov_flag |= 2;
+ check_cursor_bounds();
+ }
+ return 1;
+}
+
+static void vi_eol()
{
- //forward find /n
TT.cursor = text_strchr(TT.cursor, '\n');
- TT.vi_mov_flag |= 2;
check_cursor_bounds();
- return 1;
+}
+
+static void ctrl_b()
+{
+ int i;
+
+ for (i=0; i<TT.screen_height-2; ++i) {
+ TT.screen = text_psol(TT.screen);
+ // TODO: retain x offset.
+ TT.cursor = text_psol(TT.screen);
+ }
+}
+
+static void ctrl_f()
+{
+ int i;
+
+ for (i=0; i<TT.screen_height-2; ++i) TT.screen = text_nsol(TT.screen);
+ // TODO: real vi keeps the x position.
+ if (TT.screen > TT.cursor) TT.cursor = TT.screen;
+}
+
+static void ctrl_e()
+{
+ TT.screen = text_nsol(TT.screen);
+ // TODO: real vi keeps the x position.
+ if (TT.screen > TT.cursor) TT.cursor = TT.screen;
+}
+
+static void ctrl_y()
+{
+ TT.screen = text_psol(TT.screen);
+ // TODO: only if we're on the bottom line
+ TT.cursor = text_psol(TT.cursor);
+ // TODO: real vi keeps the x position.
}
//TODO check register where to push from
@@ -1040,7 +1084,7 @@ static int vi_D(char reg, int count0, int count1)
{
size_t pos = TT.cursor;
if (!count0) return 1;
- vi_eol(1, 1, 0);
+ vi_eol();
vi_delete(reg, pos, 0);
if (--count0) vi_dd(reg, count0, 1);
@@ -1133,7 +1177,7 @@ struct vi_mov_param vi_movs[] =
{"l", 0, &cur_right},
{"M", 0, &vi_M},
{"w", 0, &vi_movw},
- {"$", 0, &vi_eol},
+ {"$", 0, &vi_dollar},
{"f", 1, &vi_find_c},
{"F", 1, &vi_find_cb},
};
@@ -1346,14 +1390,14 @@ static void draw_page()
tty_jump(0, y);
tty_esc("2K");
//find cursor position
- aw = crunch_nstr(&end, 1024, bytes, 0, "\t\n", vi_crunch);
+ aw = crunch_nstr(&end, INT_MAX, bytes, 0, "\t\n", vi_crunch);
//if we need to render text that is not inserted to buffer yet
if (TT.vi_mode == 2 && TT.il->len) {
char* iend = TT.il->data; //input end
x = 0;
//find insert end position
- iw = crunch_str(&iend, 1024, 0, "\t\n", vi_crunch);
+ iw = crunch_str(&iend, INT_MAX, 0, "\t\n", vi_crunch);
clip = (aw+iw) - TT.screen_width+margin;
//if clipped area is bigger than text before insert
@@ -1411,17 +1455,12 @@ static void draw_page()
tty_jump(0, y);
if (draw_line) {
-
tty_esc("2K");
- if (line) {
- if (draw_line && line && strlen(line)) {
-
- aw = crunch_nstr(&line, clip, bytes, 0, "\t\n", vi_crunch);
- crunch_str(&line, TT.screen_width-1, stdout, "\t\n", vi_crunch);
- if ( *line ) printf("@");
-
- }
- } else if (draw_line) printf("~");
+ if (line && strlen(line)) {
+ aw = crunch_nstr(&line, clip, bytes, 0, "\t\n", vi_crunch);
+ crunch_str(&line, TT.screen_width-1, stdout, "\t\n", vi_crunch);
+ if ( *line ) printf("@");
+ } else printf("\033[2m~\033[m");
}
if (SSOL+bytes < TT.filesize) {
line = toybuf;
@@ -1432,20 +1471,21 @@ static void draw_page()
TT.drawn_row = TT.scr_row, TT.drawn_col = clip;
- //finished updating visual area
+ // Finished updating visual area, show status line.
tty_jump(0, TT.screen_height);
tty_esc("2K");
- if (TT.vi_mode == 2) printf("\x1b[1m-- INSERT --\x1b[m");
+ if (TT.vi_mode == 2) printf("\033[1m-- INSERT --\033[m");
if (!TT.vi_mode) {
- cx_scr = printf("%s",TT.il->data);
+ cx_scr = printf("%s", TT.il->data);
cy_scr = TT.screen_height;
+ *toybuf = 0;
+ } else {
+ // TODO: the row,col display doesn't show the cursor column
+ // TODO: real vi shows the percentage by lines, not bytes
+ sprintf(toybuf, "%zu/%zuC %zu%% %d,%d", TT.cursor, TT.filesize,
+ (100*TT.cursor)/TT.filesize, TT.cur_row+1, TT.cur_col+1);
+ if (TT.cur_col != cx_scr) sprintf(toybuf+strlen(toybuf),"-%d", cx_scr+1);
}
-
- sprintf(toybuf, "%zu / %zu,%d,%d", TT.cursor, TT.filesize,
- TT.cur_row+1, TT.cur_col+1);
-
- if (TT.cur_col != cx_scr) sprintf(toybuf+strlen(toybuf),"-%d", cx_scr+1);
-
tty_jump(TT.screen_width-strlen(toybuf), TT.screen_height);
printf("%s", toybuf);
@@ -1455,12 +1495,12 @@ static void draw_page()
void vi_main(void)
{
+ char stdout_buf[BUFSIZ];
char keybuf[16] = {0};
char vi_buf[16] = {0};
char utf8_code[8] = {0};
- int utf8_dec_p = 0, vi_buf_pos = 0, i;
- FILE *script = 0;
- if (FLAG(s)) script = fopen(TT.s, "r");
+ int utf8_dec_p = 0, vi_buf_pos = 0;
+ FILE *script = FLAG(s) ? xfopen(TT.s, "r") : 0;
TT.il = xzalloc(sizeof(struct str_line));
TT.il->data = xzalloc(80);
@@ -1478,17 +1518,19 @@ void vi_main(void)
terminal_size(&TT.screen_width, &TT.screen_height);
TT.screen_height -= 1;
+ // Avoid flicker.
+ setbuf(stdout, stdout_buf);
+
+ xsignal(SIGWINCH, generic_signal);
set_terminal(0, 1, 0, 0);
//writes stdout into different xterm buffer so when we exit
//we dont get scroll log full of junk
tty_esc("?1049h");
- tty_esc("H");
- xflush(1);
-
- draw_page();
for (;;) {
int key = 0;
+
+ draw_page();
if (script) {
key = fgetc(script);
if (key == EOF) {
@@ -1499,9 +1541,12 @@ void vi_main(void)
} else key = scan_key(keybuf, -1);
if (key == -1) goto cleanup_vi;
-
- terminal_size(&TT.screen_width, &TT.screen_height);
- TT.screen_height -= 1; //TODO this is hack fix visual alignment
+ else if (key == -3) {
+ toys.signal = 0;
+ terminal_size(&TT.screen_width, &TT.screen_height);
+ TT.screen_height -= 1; //TODO this is hack fix visual alignment
+ continue;
+ }
// TODO: support cursor keys in ex mode too.
if (TT.vi_mode && key>=256) {
@@ -1510,7 +1555,10 @@ void vi_main(void)
else if (key==KEY_DOWN) cur_down(1, 1, 0);
else if (key==KEY_LEFT) cur_left(1, 1, 0);
else if (key==KEY_RIGHT) cur_right(1, 1, 0);
- draw_page();
+ else if (key==KEY_HOME) vi_zero(1, 1, 0);
+ else if (key==KEY_END) vi_dollar(1, 1, 0);
+ else if (key==KEY_PGDN) ctrl_f();
+ else if (key==KEY_PGUP) ctrl_b();
continue;
}
@@ -1524,7 +1572,7 @@ void vi_main(void)
TT.il->len++;
break;
case 'A':
- vi_eol(1, 1, 0);
+ vi_eol();
TT.vi_mode = 2;
break;
case 'a':
@@ -1534,23 +1582,16 @@ void vi_main(void)
TT.vi_mode = 2;
break;
case 'B'-'@':
- for (i=0; i<TT.screen_height-2; ++i) TT.screen = text_psol(TT.screen);
- // TODO: if we're on the bottom visible line, move the cursor up.
- if (TT.screen > TT.cursor) TT.cursor = TT.screen;
+ ctrl_b();
break;
case 'E'-'@':
- TT.screen = text_nsol(TT.screen);
- // TODO: real vi keeps the x position.
- if (TT.screen > TT.cursor) TT.cursor = TT.screen;
+ ctrl_e();
break;
case 'F'-'@':
- for (i=0; i<TT.screen_height-2; ++i) TT.screen = text_nsol(TT.screen);
- // TODO: real vi keeps the x position.
- if (TT.screen > TT.cursor) TT.cursor = TT.screen;
+ ctrl_f();
break;
- case 'U'-'@':
- TT.screen = text_psol(TT.screen);
- // TODO: if we're on the bottom visible line, move the cursor up.
+ case 'Y'-'@':
+ ctrl_y();
break;
case 27:
vi_buf[0] = 0;
@@ -1650,9 +1691,6 @@ void vi_main(void)
break;
}
}
-
- draw_page();
-
}
cleanup_vi:
linelist_unload();
@@ -1660,4 +1698,3 @@ cleanup_vi:
tty_reset();
tty_esc("?1049l");
}
-
diff --git a/toys/posix/cp.c b/toys/posix/cp.c
index d98c2ae7..a27c200a 100644
--- a/toys/posix/cp.c
+++ b/toys/posix/cp.c
@@ -520,11 +520,11 @@ void install_main(void)
return;
}
- if (FLAG(D) && !FLAG(t)) {
- TT.destname = toys.optargs[toys.optc-1];
- if (mkpathat(AT_FDCWD, TT.destname, 0, MKPATHAT_MAKE))
- perror_exit("-D '%s'", TT.destname);
- if (toys.optc == 1) return;
+ if (FLAG(D)) {
+ char *destname = FLAG(t) ? TT.i.t : (TT.destname = toys.optargs[toys.optc-1]);
+ if (mkpathat(AT_FDCWD, destname, 0777, MKPATHAT_MAKE | (FLAG(t) ? MKPATHAT_MKLAST : 0)))
+ perror_exit("-D '%s'", destname);
+ if (toys.optc == !FLAG(t)) return;
}
// Translate flags from install to cp
diff --git a/toys/posix/rmdir.c b/toys/posix/rmdir.c
index c25fd839..3714f334 100644
--- a/toys/posix/rmdir.c
+++ b/toys/posix/rmdir.c
@@ -25,7 +25,7 @@ static void do_rmdir(char *name)
{
char *temp;
- for (;;) {
+ do {
if (rmdir(name)) {
if (!FLAG(ignore_fail_on_non_empty) || errno != ENOTEMPTY)
perror_msg_raw(name);
@@ -39,7 +39,7 @@ static void do_rmdir(char *name)
if (!(temp = strrchr(name, '/'))) return;
*temp = 0;
} while (!temp[1]);
- }
+ } while (*name);
}
void rmdir_main(void)