diff options
author | Elliott Hughes <enh@google.com> | 2021-04-26 19:01:22 +0000 |
---|---|---|
committer | Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com> | 2021-04-26 19:01:22 +0000 |
commit | 799cfb69ce51a495a86a8649efbc7f7b79258cef (patch) | |
tree | 930c099bd2c1b11596b8d11bdc88076fe77883d8 | |
parent | 5beec43a2c41e4c73c7c54161f009f6fa4f1ebec (diff) | |
parent | b7cb33d2d49e30d2e2b6051b22df69023a5c94bc (diff) | |
download | toybox-799cfb69ce51a495a86a8649efbc7f7b79258cef.tar.gz |
Merge remote-tracking branch 'toybox/master' into HEAD am: b7cb33d2d4
Original change: https://android-review.googlesource.com/c/platform/external/toybox/+/1686746
Change-Id: I8365ed6e93bc92c53832eb33aaa504149b81a9c4
-rw-r--r-- | android/device/generated/flags.h | 4 | ||||
-rw-r--r-- | android/device/generated/globals.h | 36 | ||||
-rw-r--r-- | android/device/generated/help.h | 4 | ||||
-rw-r--r-- | android/device/generated/newtoys.h | 2 | ||||
-rw-r--r-- | android/linux/generated/flags.h | 4 | ||||
-rw-r--r-- | android/linux/generated/globals.h | 36 | ||||
-rw-r--r-- | android/linux/generated/help.h | 4 | ||||
-rw-r--r-- | android/linux/generated/newtoys.h | 2 | ||||
-rw-r--r-- | android/mac/generated/flags.h | 4 | ||||
-rw-r--r-- | android/mac/generated/globals.h | 36 | ||||
-rw-r--r-- | android/mac/generated/help.h | 4 | ||||
-rw-r--r-- | android/mac/generated/newtoys.h | 2 | ||||
-rwxr-xr-x | tests/cpio.test | 19 | ||||
-rwxr-xr-x | tests/sed.test | 1 | ||||
-rw-r--r-- | toys/pending/sh.c | 393 | ||||
-rw-r--r-- | toys/pending/telnet.c | 416 | ||||
-rw-r--r-- | toys/pending/telnetd.c | 54 | ||||
-rw-r--r-- | toys/posix/cpio.c | 10 | ||||
-rw-r--r-- | toys/posix/sed.c | 2 |
19 files changed, 536 insertions, 497 deletions
diff --git a/android/device/generated/flags.h b/android/device/generated/flags.h index 9c544272..459170a3 100644 --- a/android/device/generated/flags.h +++ b/android/device/generated/flags.h @@ -3338,9 +3338,9 @@ #undef FOR_unlink #endif -// unset fvn +// unset fvn[!fv] #undef OPTSTR_unset -#define OPTSTR_unset "fvn" +#define OPTSTR_unset "fvn[!fv]" #ifdef CLEANUP_unset #undef CLEANUP_unset #undef FOR_unset diff --git a/android/device/generated/globals.h b/android/device/generated/globals.h index 006c3377..61744c4c 100644 --- a/android/device/generated/globals.h +++ b/android/device/generated/globals.h @@ -859,11 +859,7 @@ struct sh_data { unsigned options, jobcnt, LINENO; int hfd, pid, bangpid, varslen, cdcount, srclvl, recursion; -// 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 - - // Callable functions + // Callable function array struct sh_function { char *name; struct sh_pipeline { // pipeline segments: linked list of arg w/metadata @@ -874,7 +870,9 @@ struct sh_data { int c; } arg[1]; } *pipeline; - } *functions; + unsigned long refcount; + } **functions; + long funcslen; // runtime function call stack struct sh_fcall { @@ -885,14 +883,13 @@ struct sh_data { long flags; char *str; } *vars; + long varslen, shift; -// struct sh_function *func; + struct sh_function *func; // TODO wire this up struct sh_pipeline *pl; char *ifs; - 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 { @@ -971,20 +968,15 @@ struct tcpsvd_data { // toys/pending/telnet.c struct telnet_data { - int port; - int sfd; - char buff[128]; - int pbuff; - char iac[256]; - int piac; - char *ttype; - struct termios def_term; + int sock; + char buf[2048]; // Half sizeof(toybuf) allows a buffer full of IACs. + char iac[128]; + int iac_len; + struct termios old_term; struct termios raw_term; - uint8_t term_ok; - uint8_t term_mode; - uint8_t flags; - unsigned win_width; - unsigned win_height; + uint8_t mode; + int echo, sga; + int state, request; }; // toys/pending/telnetd.c diff --git a/android/device/generated/help.h b/android/device/generated/help.h index 0e3af90d..4286e01d 100644 --- a/android/device/generated/help.h +++ b/android/device/generated/help.h @@ -348,7 +348,7 @@ #define HELP_telnetd "Handle incoming telnet connections\n\n-l LOGIN Exec LOGIN on connect\n-f ISSUE_FILE Display ISSUE_FILE instead of /etc/issue\n-K Close connection as soon as login exits\n-p PORT Port to listen on\n-b ADDR[:PORT] Address to bind to\n-F Run in foreground\n-i Inetd mode\n-w SEC Inetd 'wait' mode, linger time SEC\n-S Log to syslog (implied by -i or without -F and -w)" -#define HELP_telnet "usage: telnet HOST [PORT]\n\nConnect to telnet server" +#define HELP_telnet "usage: telnet HOST [PORT]\n\nConnect to telnet server." #define HELP_tcpsvd "usage: tcpsvd [-hEv] [-c N] [-C N[:MSG]] [-b N] [-u User] [-l Name] IP Port Prog\nusage: udpsvd [-hEv] [-c N] [-u User] [-l Name] IP Port Prog\n\nCreate TCP/UDP socket, bind to IP:PORT and listen for incoming connection.\nRun PROG for each connection.\n\nIP IP to listen on, 0 = all\nPORT Port to listen on\nPROG ARGS Program to run\n-l NAME Local hostname (else looks up local hostname in DNS)\n-u USER[:GRP] Change to user/group after bind\n-c N Handle up to N (> 0) connections simultaneously\n-b N (TCP Only) Allow a backlog of approximately N TCP SYNs\n-C N[:MSG] (TCP Only) Allow only up to N (> 0) connections from the same IP\n New connections from this IP address are closed\n immediately. MSG is written to the peer before close\n-h Look up peer's hostname\n-E Don't set up environment variables\n-v Verbose" @@ -374,7 +374,7 @@ #define HELP_unset "usage: unset [-fvn] NAME...\n\n-f NAME is a function\n-v NAME is a variable\n-n dereference NAME and unset that" -#define HELP_set "usage: set [+a] [+o OPTION] [VAR...]\n\nSet variables and shell attributes. Use + to disable and - to enable.\nNAME=VALUE arguments assign to the variable, any leftovers set $1, $2...\nWith no arguments, prints current variables.\n\n-f NAME is a function\n-v NAME is a variable\n-n dereference NAME and unset that\n\nOPTIONs:\n history - enable command history" +#define HELP_set "usage: set [+a] [+o OPTION] [VAR...]\n\nSet variables and shell attributes. Use + to disable and - to enable.\nNAME=VALUE arguments assign to the variable, any leftovers set $1, $2...\nWith no arguments, prints current variables.\n\n-f NAME is a function\n-v NAME is a variable\n-n don't follow name reference\n\nOPTIONs:\n history - enable command history" #define HELP_exit "usage: exit [status]\n\nExit shell. If no return value supplied on command line, use value\nof most recent command, or 0 if none." diff --git a/android/device/generated/newtoys.h b/android/device/generated/newtoys.h index 6a39d804..e97ac2f5 100644 --- a/android/device/generated/newtoys.h +++ b/android/device/generated/newtoys.h @@ -292,7 +292,7 @@ USE_UNICODE(NEWTOY(unicode, "<1", TOYFLAG_USR|TOYFLAG_BIN)) USE_UNIQ(NEWTOY(uniq, "f#s#w#zicdu", TOYFLAG_USR|TOYFLAG_BIN)) USE_UNIX2DOS(NEWTOY(unix2dos, 0, TOYFLAG_BIN)) USE_UNLINK(NEWTOY(unlink, "<1>1", TOYFLAG_USR|TOYFLAG_BIN)) -USE_SH(NEWTOY(unset, "fvn", TOYFLAG_NOFORK)) +USE_SH(NEWTOY(unset, "fvn[!fv]", TOYFLAG_NOFORK)) USE_UNSHARE(NEWTOY(unshare, "<1^f(fork);r(map-root-user);i:(ipc);m:(mount);n:(net);p:(pid);u:(uts);U:(user);", TOYFLAG_USR|TOYFLAG_BIN)) USE_UPTIME(NEWTOY(uptime, ">0ps", TOYFLAG_USR|TOYFLAG_BIN)) USE_USERADD(NEWTOY(useradd, "<1>2u#<0G:s:g:h:SDH", TOYFLAG_NEEDROOT|TOYFLAG_UMASK|TOYFLAG_SBIN)) diff --git a/android/linux/generated/flags.h b/android/linux/generated/flags.h index 6dbebfee..fadedb31 100644 --- a/android/linux/generated/flags.h +++ b/android/linux/generated/flags.h @@ -3338,9 +3338,9 @@ #undef FOR_unlink #endif -// unset fvn +// unset fvn[!fv] #undef OPTSTR_unset -#define OPTSTR_unset "fvn" +#define OPTSTR_unset "fvn[!fv]" #ifdef CLEANUP_unset #undef CLEANUP_unset #undef FOR_unset diff --git a/android/linux/generated/globals.h b/android/linux/generated/globals.h index 006c3377..61744c4c 100644 --- a/android/linux/generated/globals.h +++ b/android/linux/generated/globals.h @@ -859,11 +859,7 @@ struct sh_data { unsigned options, jobcnt, LINENO; int hfd, pid, bangpid, varslen, cdcount, srclvl, recursion; -// 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 - - // Callable functions + // Callable function array struct sh_function { char *name; struct sh_pipeline { // pipeline segments: linked list of arg w/metadata @@ -874,7 +870,9 @@ struct sh_data { int c; } arg[1]; } *pipeline; - } *functions; + unsigned long refcount; + } **functions; + long funcslen; // runtime function call stack struct sh_fcall { @@ -885,14 +883,13 @@ struct sh_data { long flags; char *str; } *vars; + long varslen, shift; -// struct sh_function *func; + struct sh_function *func; // TODO wire this up struct sh_pipeline *pl; char *ifs; - 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 { @@ -971,20 +968,15 @@ struct tcpsvd_data { // toys/pending/telnet.c struct telnet_data { - int port; - int sfd; - char buff[128]; - int pbuff; - char iac[256]; - int piac; - char *ttype; - struct termios def_term; + int sock; + char buf[2048]; // Half sizeof(toybuf) allows a buffer full of IACs. + char iac[128]; + int iac_len; + struct termios old_term; struct termios raw_term; - uint8_t term_ok; - uint8_t term_mode; - uint8_t flags; - unsigned win_width; - unsigned win_height; + uint8_t mode; + int echo, sga; + int state, request; }; // toys/pending/telnetd.c diff --git a/android/linux/generated/help.h b/android/linux/generated/help.h index a0c75204..c99ddf1d 100644 --- a/android/linux/generated/help.h +++ b/android/linux/generated/help.h @@ -350,7 +350,7 @@ #define HELP_telnetd "Handle incoming telnet connections\n\n-l LOGIN Exec LOGIN on connect\n-f ISSUE_FILE Display ISSUE_FILE instead of /etc/issue\n-K Close connection as soon as login exits\n-p PORT Port to listen on\n-b ADDR[:PORT] Address to bind to\n-F Run in foreground\n-i Inetd mode\n-w SEC Inetd 'wait' mode, linger time SEC\n-S Log to syslog (implied by -i or without -F and -w)" -#define HELP_telnet "usage: telnet HOST [PORT]\n\nConnect to telnet server" +#define HELP_telnet "usage: telnet HOST [PORT]\n\nConnect to telnet server." #define HELP_tcpsvd "usage: tcpsvd [-hEv] [-c N] [-C N[:MSG]] [-b N] [-u User] [-l Name] IP Port Prog\nusage: udpsvd [-hEv] [-c N] [-u User] [-l Name] IP Port Prog\n\nCreate TCP/UDP socket, bind to IP:PORT and listen for incoming connection.\nRun PROG for each connection.\n\nIP IP to listen on, 0 = all\nPORT Port to listen on\nPROG ARGS Program to run\n-l NAME Local hostname (else looks up local hostname in DNS)\n-u USER[:GRP] Change to user/group after bind\n-c N Handle up to N (> 0) connections simultaneously\n-b N (TCP Only) Allow a backlog of approximately N TCP SYNs\n-C N[:MSG] (TCP Only) Allow only up to N (> 0) connections from the same IP\n New connections from this IP address are closed\n immediately. MSG is written to the peer before close\n-h Look up peer's hostname\n-E Don't set up environment variables\n-v Verbose" @@ -376,7 +376,7 @@ #define HELP_unset "usage: unset [-fvn] NAME...\n\n-f NAME is a function\n-v NAME is a variable\n-n dereference NAME and unset that" -#define HELP_set "usage: set [+a] [+o OPTION] [VAR...]\n\nSet variables and shell attributes. Use + to disable and - to enable.\nNAME=VALUE arguments assign to the variable, any leftovers set $1, $2...\nWith no arguments, prints current variables.\n\n-f NAME is a function\n-v NAME is a variable\n-n dereference NAME and unset that\n\nOPTIONs:\n history - enable command history" +#define HELP_set "usage: set [+a] [+o OPTION] [VAR...]\n\nSet variables and shell attributes. Use + to disable and - to enable.\nNAME=VALUE arguments assign to the variable, any leftovers set $1, $2...\nWith no arguments, prints current variables.\n\n-f NAME is a function\n-v NAME is a variable\n-n don't follow name reference\n\nOPTIONs:\n history - enable command history" #define HELP_exit "usage: exit [status]\n\nExit shell. If no return value supplied on command line, use value\nof most recent command, or 0 if none." diff --git a/android/linux/generated/newtoys.h b/android/linux/generated/newtoys.h index 6a39d804..e97ac2f5 100644 --- a/android/linux/generated/newtoys.h +++ b/android/linux/generated/newtoys.h @@ -292,7 +292,7 @@ USE_UNICODE(NEWTOY(unicode, "<1", TOYFLAG_USR|TOYFLAG_BIN)) USE_UNIQ(NEWTOY(uniq, "f#s#w#zicdu", TOYFLAG_USR|TOYFLAG_BIN)) USE_UNIX2DOS(NEWTOY(unix2dos, 0, TOYFLAG_BIN)) USE_UNLINK(NEWTOY(unlink, "<1>1", TOYFLAG_USR|TOYFLAG_BIN)) -USE_SH(NEWTOY(unset, "fvn", TOYFLAG_NOFORK)) +USE_SH(NEWTOY(unset, "fvn[!fv]", TOYFLAG_NOFORK)) USE_UNSHARE(NEWTOY(unshare, "<1^f(fork);r(map-root-user);i:(ipc);m:(mount);n:(net);p:(pid);u:(uts);U:(user);", TOYFLAG_USR|TOYFLAG_BIN)) USE_UPTIME(NEWTOY(uptime, ">0ps", TOYFLAG_USR|TOYFLAG_BIN)) USE_USERADD(NEWTOY(useradd, "<1>2u#<0G:s:g:h:SDH", TOYFLAG_NEEDROOT|TOYFLAG_UMASK|TOYFLAG_SBIN)) diff --git a/android/mac/generated/flags.h b/android/mac/generated/flags.h index 741e1413..b9d6485b 100644 --- a/android/mac/generated/flags.h +++ b/android/mac/generated/flags.h @@ -3338,9 +3338,9 @@ #undef FOR_unlink #endif -// unset fvn +// unset fvn[!fv] #undef OPTSTR_unset -#define OPTSTR_unset "fvn" +#define OPTSTR_unset "fvn[!fv]" #ifdef CLEANUP_unset #undef CLEANUP_unset #undef FOR_unset diff --git a/android/mac/generated/globals.h b/android/mac/generated/globals.h index 006c3377..61744c4c 100644 --- a/android/mac/generated/globals.h +++ b/android/mac/generated/globals.h @@ -859,11 +859,7 @@ struct sh_data { unsigned options, jobcnt, LINENO; int hfd, pid, bangpid, varslen, cdcount, srclvl, recursion; -// 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 - - // Callable functions + // Callable function array struct sh_function { char *name; struct sh_pipeline { // pipeline segments: linked list of arg w/metadata @@ -874,7 +870,9 @@ struct sh_data { int c; } arg[1]; } *pipeline; - } *functions; + unsigned long refcount; + } **functions; + long funcslen; // runtime function call stack struct sh_fcall { @@ -885,14 +883,13 @@ struct sh_data { long flags; char *str; } *vars; + long varslen, shift; -// struct sh_function *func; + struct sh_function *func; // TODO wire this up struct sh_pipeline *pl; char *ifs; - 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 { @@ -971,20 +968,15 @@ struct tcpsvd_data { // toys/pending/telnet.c struct telnet_data { - int port; - int sfd; - char buff[128]; - int pbuff; - char iac[256]; - int piac; - char *ttype; - struct termios def_term; + int sock; + char buf[2048]; // Half sizeof(toybuf) allows a buffer full of IACs. + char iac[128]; + int iac_len; + struct termios old_term; struct termios raw_term; - uint8_t term_ok; - uint8_t term_mode; - uint8_t flags; - unsigned win_width; - unsigned win_height; + uint8_t mode; + int echo, sga; + int state, request; }; // toys/pending/telnetd.c diff --git a/android/mac/generated/help.h b/android/mac/generated/help.h index a0c75204..c99ddf1d 100644 --- a/android/mac/generated/help.h +++ b/android/mac/generated/help.h @@ -350,7 +350,7 @@ #define HELP_telnetd "Handle incoming telnet connections\n\n-l LOGIN Exec LOGIN on connect\n-f ISSUE_FILE Display ISSUE_FILE instead of /etc/issue\n-K Close connection as soon as login exits\n-p PORT Port to listen on\n-b ADDR[:PORT] Address to bind to\n-F Run in foreground\n-i Inetd mode\n-w SEC Inetd 'wait' mode, linger time SEC\n-S Log to syslog (implied by -i or without -F and -w)" -#define HELP_telnet "usage: telnet HOST [PORT]\n\nConnect to telnet server" +#define HELP_telnet "usage: telnet HOST [PORT]\n\nConnect to telnet server." #define HELP_tcpsvd "usage: tcpsvd [-hEv] [-c N] [-C N[:MSG]] [-b N] [-u User] [-l Name] IP Port Prog\nusage: udpsvd [-hEv] [-c N] [-u User] [-l Name] IP Port Prog\n\nCreate TCP/UDP socket, bind to IP:PORT and listen for incoming connection.\nRun PROG for each connection.\n\nIP IP to listen on, 0 = all\nPORT Port to listen on\nPROG ARGS Program to run\n-l NAME Local hostname (else looks up local hostname in DNS)\n-u USER[:GRP] Change to user/group after bind\n-c N Handle up to N (> 0) connections simultaneously\n-b N (TCP Only) Allow a backlog of approximately N TCP SYNs\n-C N[:MSG] (TCP Only) Allow only up to N (> 0) connections from the same IP\n New connections from this IP address are closed\n immediately. MSG is written to the peer before close\n-h Look up peer's hostname\n-E Don't set up environment variables\n-v Verbose" @@ -376,7 +376,7 @@ #define HELP_unset "usage: unset [-fvn] NAME...\n\n-f NAME is a function\n-v NAME is a variable\n-n dereference NAME and unset that" -#define HELP_set "usage: set [+a] [+o OPTION] [VAR...]\n\nSet variables and shell attributes. Use + to disable and - to enable.\nNAME=VALUE arguments assign to the variable, any leftovers set $1, $2...\nWith no arguments, prints current variables.\n\n-f NAME is a function\n-v NAME is a variable\n-n dereference NAME and unset that\n\nOPTIONs:\n history - enable command history" +#define HELP_set "usage: set [+a] [+o OPTION] [VAR...]\n\nSet variables and shell attributes. Use + to disable and - to enable.\nNAME=VALUE arguments assign to the variable, any leftovers set $1, $2...\nWith no arguments, prints current variables.\n\n-f NAME is a function\n-v NAME is a variable\n-n don't follow name reference\n\nOPTIONs:\n history - enable command history" #define HELP_exit "usage: exit [status]\n\nExit shell. If no return value supplied on command line, use value\nof most recent command, or 0 if none." diff --git a/android/mac/generated/newtoys.h b/android/mac/generated/newtoys.h index 6a39d804..e97ac2f5 100644 --- a/android/mac/generated/newtoys.h +++ b/android/mac/generated/newtoys.h @@ -292,7 +292,7 @@ USE_UNICODE(NEWTOY(unicode, "<1", TOYFLAG_USR|TOYFLAG_BIN)) USE_UNIQ(NEWTOY(uniq, "f#s#w#zicdu", TOYFLAG_USR|TOYFLAG_BIN)) USE_UNIX2DOS(NEWTOY(unix2dos, 0, TOYFLAG_BIN)) USE_UNLINK(NEWTOY(unlink, "<1>1", TOYFLAG_USR|TOYFLAG_BIN)) -USE_SH(NEWTOY(unset, "fvn", TOYFLAG_NOFORK)) +USE_SH(NEWTOY(unset, "fvn[!fv]", TOYFLAG_NOFORK)) USE_UNSHARE(NEWTOY(unshare, "<1^f(fork);r(map-root-user);i:(ipc);m:(mount);n:(net);p:(pid);u:(uts);U:(user);", TOYFLAG_USR|TOYFLAG_BIN)) USE_UPTIME(NEWTOY(uptime, ">0ps", TOYFLAG_USR|TOYFLAG_BIN)) USE_USERADD(NEWTOY(useradd, "<1>2u#<0G:s:g:h:SDH", TOYFLAG_NEEDROOT|TOYFLAG_UMASK|TOYFLAG_SBIN)) diff --git a/tests/cpio.test b/tests/cpio.test index 34794841..183dadde 100755 --- a/tests/cpio.test +++ b/tests/cpio.test @@ -63,3 +63,22 @@ testing "skip NUL" "for i in a b; do dd if=/dev/zero bs=512 count=1 2>/dev/null; rm -rf a a.cpio testing "error on empty file" "cpio -i 2>/dev/null || echo err" "err\n" "" "" + +mkdir a +touch a/file +ln -s a/symlink a/symlink +mkdir a/dir +find a | cpio -o -H newc >a.cpio +if [ "$(id -u)" -eq 0 ]; then + # We chown between user "root" and the last user in /etc/passwd, + # and group "root" and the last group in /etc/group. + USR="$(sed -n '$s/:.*//p' /etc/passwd)" + GRP="$(sed -n '$s/:.*//p' /etc/group)" + # Or if that fails, we assume we're on Android... + : "${USR:=shell}" + : "${GRP:=shell}" + chown -h "${USR}:${GRP}" a/file a/symlink a/dir +fi +skipnot [ $(id -u) -eq 0 ] +testing "-t preserve ownership" "cpio -t <a.cpio >/dev/null && stat -c '%U:%G' a/file a/symlink a/dir" "${USR}:${GRP}\n${USR}:${GRP}\n${USR}:${GRP}\n" "" "" +rm -rf a a.cpio diff --git a/tests/sed.test b/tests/sed.test index c3928e58..fd3b2057 100755 --- a/tests/sed.test +++ b/tests/sed.test @@ -115,6 +115,7 @@ testing 'empty match after nonempty match' "sed -e 's/a*/c/g'" 'cbcncgc' \ testing 'empty match' "sed -e 's/[^ac]*/A/g'" 'AaAcA' '' 'abcde' testing 's///#comment' "sed -e 's/TWO/four/i#comment'" "one\nfour\nthree" \ "" "one\ntwo\nthree" +testing 's///num off end' 'sed -e s/e//2' 'e\n' '' 'e\n' testing 'N flushes pending a and advances match counter' \ "sed -e 'a woo' -e 'N;\$p'" 'woo\none\ntwo\none\ntwo' "" 'one\ntwo' diff --git a/toys/pending/sh.c b/toys/pending/sh.c index 02277fe0..fd72037a 100644 --- a/toys/pending/sh.c +++ b/toys/pending/sh.c @@ -49,7 +49,7 @@ USE_SH(NEWTOY(set, 0, TOYFLAG_NOFORK)) USE_SH(NEWTOY(shift, ">1", TOYFLAG_NOFORK)) USE_SH(NEWTOY(source, "<1", TOYFLAG_NOFORK)) USE_SH(OLDTOY(., source, TOYFLAG_NOFORK)) -USE_SH(NEWTOY(unset, "fvn", TOYFLAG_NOFORK)) +USE_SH(NEWTOY(unset, "fvn[!fv]", TOYFLAG_NOFORK)) USE_SH(NEWTOY(sh, "0(noediting)(noprofile)(norc)sc:i", TOYFLAG_BIN)) USE_SH(OLDTOY(toysh, sh, TOYFLAG_BIN)) @@ -107,7 +107,7 @@ config SET -f NAME is a function -v NAME is a variable - -n dereference NAME and unset that + -n don't follow name reference OPTIONs: history - enable command history @@ -222,11 +222,7 @@ GLOBALS( unsigned options, jobcnt, LINENO; int hfd, pid, bangpid, varslen, cdcount, srclvl, recursion; -// 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 - - // Callable functions + // Callable function array struct sh_function { char *name; struct sh_pipeline { // pipeline segments: linked list of arg w/metadata @@ -237,7 +233,9 @@ GLOBALS( int c; } arg[1]; } *pipeline; - } *functions; + unsigned long refcount; + } **functions; + long funcslen; // runtime function call stack struct sh_fcall { @@ -248,14 +246,13 @@ GLOBALS( long flags; char *str; } *vars; + long varslen, shift; -// struct sh_function *func; + struct sh_function *func; // TODO wire this up struct sh_pipeline *pl; char *ifs; - 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 { @@ -421,31 +418,16 @@ long long do_math(char **s) // declare -aAilnrux // ft -static struct sh_vars *setvar(char *s) +static struct sh_vars *setvar_found(char *s, struct sh_vars *var) { - struct sh_fcall *ff; - struct sh_vars *var; long flags; - int len = varend(s)-s; - if (s[len] != '=') { - error_msg("bad setvar %s\n", s); + if ((flags = var->flags&~VAR_WHITEOUT)&VAR_READONLY) { + error_msg("%.*s: read only", (int)(strchr(s, '=')-s), s); free(s); return 0; - } - if (!(var = findvar(s, &ff))) ff = TT.ff->prev; - if (!strncmp(s, "IFS=", 4)) - do ff->ifs = s+4; while ((ff = ff->next) != TT.ff->prev); - if (!var) return addvar(s, TT.ff->prev); - flags = (var->flags &= ~VAR_WHITEOUT); - - if (flags&VAR_READONLY) { - error_msg("%.*s: read only", len, s); - free(s); - - return 0; - } + } else var->flags = flags; // TODO if (flags&(VAR_TOUPPER|VAR_TOLOWER)) // unicode _is stupid enough for upper/lower case to be different utf8 byte @@ -454,11 +436,11 @@ static struct sh_vars *setvar(char *s) // TODO VAR_ARRAY VAR_DICT if (flags&VAR_MAGIC) { - char *ss = s+len-1; + char *ss = strchr(s, '=')+1; -// TODO: trailing garbage after do_math()? if (*s == 'S') TT.SECONDS = millitime() - 1000*do_math(&ss); else if (*s == 'R') srandom(do_math(&ss)); +// TODO: trailing garbage after do_math()? } else { if (!(flags&VAR_NOFREE)) free(var->str); else var->flags ^= VAR_NOFREE; @@ -468,28 +450,56 @@ static struct sh_vars *setvar(char *s) return var; } -static void unsetvar(char *name) +// Update $IFS cache in function call stack after variable assignment +static void cache_ifs(char *s, struct sh_fcall *ff) +{ + if (!strncmp(s, "IFS=", 4)) + do ff->ifs = s+4; while ((ff = ff->next) != TT.ff->prev); +} + +static struct sh_vars *setvar(char *s) +{ + struct sh_fcall *ff; + struct sh_vars *var; + + if (s[varend(s)-s] != '=') { + error_msg("bad setvar %s\n", s); + free(s); + + return 0; + } + if (!(var = findvar(s, &ff))) ff = TT.ff->prev; + cache_ifs(s, ff); + if (!var) return addvar(s, TT.ff->prev); + + return setvar_found(s, var); +} + +// returns whether variable found (whiteout doesn't count) +static int unsetvar(char *name) { struct sh_fcall *ff; struct sh_vars *var = findvar(name, &ff); int ii = var-ff->vars, len = varend(name)-name; - // 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); - - // 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); + if (!var || (var->flags&VAR_WHITEOUT)) return 0; + if (var->flags&VAR_READONLY) error_msg("readonly %.*s", len, name); + else { + // turn local into whiteout + if (ff != TT.ff->prev) { + var->flags = VAR_WHITEOUT; + if (!(var->flags&VAR_NOFREE)) + (var->str = xrealloc(var->str, len+2))[len+1] = 0; + // free from global context + } else { + if (!(var->flags&VAR_NOFREE)) free(var->str); + memmove(ff->vars+ii, ff->vars+ii+1, (ff->varslen--)-ii); + } + if (!strcmp(name, "IFS")) + do ff->ifs = " \t\n"; while ((ff = ff->next) != TT.ff->prev); } - if (!strcmp(name, "IFS")) - do ff->ifs = " \t\n"; while ((ff = ff->next) != TT.ff->prev); + + return 1; } static struct sh_vars *setvarval(char *name, char *val) @@ -758,6 +768,7 @@ static char *pl2str(struct sh_pipeline *pl, int one) // measure, then allocate for (ss = 0;; ss = xmalloc(len+1)) { for (pp = pl; pp != end; pp = pp->next) { + if (pp->type == 'F') continue; // TODO fix this 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); @@ -769,6 +780,7 @@ static char *pl2str(struct sh_pipeline *pl, int one) // TODO test output with case and function // TODO add HERE documents back in +// TODO handle functions } // restore displaced filehandles, closing high filehandles they were copied to @@ -839,6 +851,19 @@ static void call_function(void) TT.ff->ifs = TT.ff->next->ifs; } +// functions contain pipelines contain functions: prototype because loop +static void free_pipeline(void *pipeline); + +static void free_function(struct sh_function *funky) +{ + if (--funky->refcount) return; + + free(funky->name); + llist_traverse(funky->pipeline, free_pipeline); + free(funky); +} + +// TODO: old function-vs-source definition is "has variables", but no ff->func? // returns 0 if source popped, nonzero if function popped static int end_function(int funconly) { @@ -858,6 +883,7 @@ static int end_function(int funconly) free(ff->vars[ff->varslen].str); free(ff->vars); free(TT.ff->blk); + if (ff->func) free_function(ff->func); free(dlist_pop(&TT.ff)); return 1; @@ -2285,61 +2311,63 @@ static struct sh_process *run_command(void) { 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; + int envlen, funk = TT.funcslen, jj = 0, locals = 0; + struct sh_process *pp; // Count leading variable assignments for (envlen = 0; envlen<arg->c; envlen++) if ((s = varend(arg->v[envlen])) == arg->v[envlen] || *s != '=') break; + pp = expand_redir(arg, envlen, 0); + + // Are we calling a shell function? TODO binary search + if (pp->arg.c && !strchr(*pp->arg.v, '/')) + for (funk = 0; funk<TT.funcslen; funk++) + if (!strcmp(*pp->arg.v, TT.functions[funk]->name)) break; + + // Create new function context to hold local vars? + if (funk != TT.funcslen || (envlen && pp->arg.c) || TT.ff->blk->pipe) { + call_function(); + addvar(0, TT.ff); // function context (not source) so end_function deletes + locals = 1; + } // perform any assignments if (envlen) { struct sh_fcall *ff; struct sh_vars *vv; - // If prefix assignment, create temp function context to hold vars - if (envlen!=arg->c || TT.ff->blk->pipe) { - call_function(); - addvar(0, TT.ff); // function context (not source) so end_function deletes - persist = 0; - } else ff = TT.ff->prev; - for (; jj<envlen && !pp; jj++) { - if (!(vv = findvar(s = arg->v[jj], &ff))) ff = persist?TT.ff->prev:TT.ff; - if (vv && (vv->flags&VAR_READONLY)) { - error_msg("%.*s: readonly variable", (int)(varend(s)-s), s); - continue; - } - if (!vv || (!persist && ff != TT.ff && (ff = TT.ff))) - (vv = addvar(s, ff))->flags = VAR_NOFREE|(VAR_GLOBAL*!persist); - if (!(sss = expand_one_arg(s, SEMI_IFS, persist ? 0 : &delete))) { - if (!pp) pp = xzalloc(sizeof(*pp)); - pp->exit = 1; - } else { - if (persist || sss != s) { - vv->flags &= ~VAR_NOFREE; - vv->str = sss==s ? xstrdup(sss) : sss; + for (; jj<envlen && !pp->exit; jj++) { + if (!(vv = findvar(s = arg->v[jj], &ff))) ff = locals?TT.ff:TT.ff->prev; + else if (vv->flags&VAR_READONLY) ff = 0; + else if (locals && ff!=TT.ff) vv = 0, ff = TT.ff; + + if (!vv&&ff) (vv = addvar(s, ff))->flags = VAR_NOFREE|(VAR_GLOBAL*locals); + if (!(sss = expand_one_arg(s, SEMI_IFS, 0))) pp->exit = 1; + else { + if (!setvar_found(sss, vv)) continue; + if (sss==s) { + if (!locals) vv->str = xstrdup(sss); + else vv->flags |= VAR_NOFREE; } - if (!strncmp(vv->str, "IFS=", 4)) - do ff->ifs = vv->str+4; while ((ff = ff->next) != TT.ff->prev); + cache_ifs(vv->str, ff ? : TT.ff); } } } - // expand cmdline with _old_ var context, matching bash's order of operations - if (!pp) { - sss = persist ? 0 : dlist_pop(&TT.ff); - pp = expand_redir(arg, envlen, 0); - if (!persist) { - dlist_add_nomalloc((void *)&TT.ff, (void *)sss); - TT.ff = TT.ff->prev; - } - } - // Do the thing if (pp->exit || envlen==arg->c) s = 0; // leave $_ alone - else if (!pp->arg.v) s = ""; // nothing to do but blank $_ - else { + else if (!pp->arg.c) s = ""; // nothing to do but blank $_ + +// TODO: call functions() FUNCTION +// TODO what about "echo | x=1 | export fruit", must subshell? Test this. +// Several NOFORK can just NOP in a pipeline? Except ${a?b} still errors + + // call shell function + else if (funk != TT.funcslen) { + (TT.ff->func = TT.functions[funk])->refcount++; + TT.ff->pl = TT.ff->func->pipeline; + TT.ff->arg = pp->arg; + } else { struct toy_list *tl = toy_find(*pp->arg.v); jj = tl ? tl->flags : 0; @@ -2348,12 +2376,10 @@ static struct sh_process *run_command(void) sss = pp->arg.v[pp->arg.c]; //dprintf(2, "%d run command %p %s\n", getpid(), TT.ff, *pp->arg.v); debug_show_fds(); // 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!='|'))) { + if ((jj&TOYFLAG_NOFORK) || ((jj&TOYFLAG_MAYFORK) && !locals)) { sigjmp_buf rebound; char temp[jj = offsetof(struct toy_context, rebound)]; @@ -2383,9 +2409,8 @@ static struct sh_process *run_command(void) // cleanup process unredirect(pp->urd); pp->urd = 0; - if (!persist) end_function(0); + if (locals && funk == TT.funcslen) end_function(0); if (s) setvarval("_", s); - llist_traverse(delete, llist_free_arg); return pp; } @@ -2405,8 +2430,14 @@ static void free_pipeline(void *pipeline) struct sh_pipeline *pl = pipeline; int i, j; - // free arguments and HERE doc contents - if (pl) for (j=0; j<=pl->count; j++) { + if (!pl) return; + + // free either function or arguments and HERE doc contents + if (pl->type == 'F') { + free_function((void *)*pl->arg->v); + *pl->arg->v = 0; + } + for (j=0; j<=pl->count; j++) { if (!pl->arg[j].v) continue; for (i = 0; i<=pl->arg[j].c; i++) free(pl->arg[j].v[i]); free(pl->arg[j].v); @@ -2414,14 +2445,6 @@ static void free_pipeline(void *pipeline) free(pl); } -// TODO this has to add to a namespace context. Functions within functions... -static struct sh_pipeline *add_function(char *name, struct sh_pipeline *pl) -{ -dprintf(2, "stub add_function"); - - return pl->end; -} - // Append a new pipeline to function, returning pipeline and pipeline's arg static struct sh_pipeline *add_pl(struct sh_pipeline **ppl, struct sh_arg **arg) { @@ -2514,6 +2537,38 @@ static int parse_line(char *line, struct sh_pipeline **ppl, if (done) break; s = 0; + // Did we just end a function? + if (ex == (void *)1) { + struct sh_function *funky; + + // function must be followed by a compound statement for some reason + if ((*ppl)->prev->type != 3) { + s = *(*ppl)->prev->arg->v; + goto flush; + } + + // Back up to saved function() statement and create sh_function + free(dlist_lpop(expect)); + pl = (void *)(*expect)->data; + funky = xmalloc(sizeof(struct sh_function)); + funky->refcount = 1; + funky->name = *pl->arg->v; + *pl->arg->v = (void *)funky; + + // Chop out pipeline segments added since saved function + funky->pipeline = pl->next; + pl->next->prev = (*ppl)->prev; + (*ppl)->prev->next = pl->next; + pl->next = *ppl; + (*ppl)->prev = pl; + + // Immature function has matured (meaning cleanup is different) + pl->type = 'F'; + pl = 0; + free(dlist_lpop(expect)); + ex = *expect ? (*expect)->prev->data : 0; + } + // skip leading whitespace/comment here to know where next word starts while (isspace(*start)) ++start; if (*start=='#') while (*start && *start != '\n') ++start; @@ -2521,6 +2576,18 @@ static int parse_line(char *line, struct sh_pipeline **ppl, // 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 : ""); + + if (pl && pl->type == 'f' && arg->c == 1 && (end-start!=1 || *start!='(')) { +funky: + // end function segment, expect function body + dlist_add(expect, (void *)pl); + pl = 0; + dlist_add(expect, (void *)1); + dlist_add(expect, 0); + + continue; + } + // Is this a new pipeline segment? if (!pl) pl = add_pl(ppl, &arg); @@ -2570,8 +2637,9 @@ static int parse_line(char *line, struct sh_pipeline **ppl, // Did we hit end of line or ) outside a function declaration? // ) is only saved at start of a statement, ends current statement } else if (end == start || (arg->c && *start == ')' && pl->type!='f')) { - if (pl->type == 'f' && arg->c<3) { - s = "function()"; + // function () needs both parentheses or neither + if (pl->type == 'f' && arg->c != 1 && arg->c != 3) { + s = "function("; goto flush; } @@ -2581,12 +2649,10 @@ static int parse_line(char *line, struct sh_pipeline **ppl, goto flush; } - // don't save blank pipeline segments - if (!arg->c) free_pipeline(dlist_lpop(ppl)); - - // stop at EOL, else continue with new pipeline segment for ) + // Stop at EOL. Discard blank pipeline segment, else end segment if (end == start) done++; - pl->count = -1; + if (!pl->type && !arg->c) free_pipeline(dlist_lpop(ppl)); + else pl->count = -1; continue; } @@ -2635,8 +2701,32 @@ static int parse_line(char *line, struct sh_pipeline **ppl, } } + // Are we starting a new [function] name [()] definition + if (!pl->type || pl->type=='f') { + if (!pl->type && arg->c==1 && !strcmp(s, "function")) { + free(arg->v[--arg->c]); + arg->v[arg->c] = 0; + pl->type = 'f'; + continue; + } else if (arg->c==2 && !strcmp(s, "(")) pl->type = 'f'; + } + + // one or both of [function] name[()] + if (pl->type=='f') { + if (arg->v[0][strcspn(*arg->v, "\"'`><;|&$")]) { + s = *arg->v; + goto flush; + } + if (arg->c == 2 && strcmp(s, "(")) goto flush; + if (arg->c == 3) { + if (strcmp(s, ")")) goto flush; + goto funky; + } + + continue; + // is it a line break token? - if (strchr(";|&", *s) && strncmp(s, "&>", 2)) { + } else if (strchr(";|&", *s) && strncmp(s, "&>", 2)) { arg->c--; // treat ; as newline so we don't have to check both elsewhere. @@ -2644,7 +2734,7 @@ static int parse_line(char *line, struct sh_pipeline **ppl, arg->v[arg->c] = 0; free(s); s = 0; -// TODO enforce only one ; allowed between "for i" and in or do. +// TODO can't have ; between "for i" and in or do. (Newline yes, ; no. Why?) if (!arg->c && ex && !memcmp(ex, "do\0C", 4)) continue; // ;; and friends only allowed in case statements @@ -2655,23 +2745,6 @@ static int parse_line(char *line, struct sh_pipeline **ppl, pl->count = -1; continue; - } - - // is a function() in progress? - if (arg->c>1 && !strcmp(s, "(")) pl->type = 'f'; - if (pl->type=='f') { - if (arg->c == 2 && strcmp(s, "(")) goto flush; - if (arg->c == 3) { - if (strcmp(s, ")")) goto flush; - - // end function segment, expect function body - pl->count = -1; - dlist_add(expect, "}"); - dlist_add(expect, 0); - dlist_add(expect, "{"); - - continue; - } // a for/select must have at least one additional argument on same line } else if (ex && !memcmp(ex, "do\0A", 4)) { @@ -2688,17 +2761,9 @@ static int parse_line(char *line, struct sh_pipeline **ppl, // Do we expect something that _must_ come next? (no multiple statements) if (ex) { - // When waiting for { it must be next symbol, but can be on a new line. - if (!strcmp(ex, "{")) { - if (strcmp(s, "{")) goto flush; - free(arg->v[--arg->c]); // don't save the {, function starts the block - free(dlist_lpop(expect)); - - continue; - // The "test" part of for/select loops can have (at most) one "in" line, // for {((;;))|name [in...]} do - } else if (!memcmp(ex, "do\0C", 4)) { + if (!memcmp(ex, "do\0C", 4)) { if (strcmp(s, "do")) { // can only have one "in" line between for/do, but not with for(()) if (pl->prev->type == 's') goto flush; @@ -2744,10 +2809,8 @@ static int parse_line(char *line, struct sh_pipeline **ppl, // If we got here we expect a specific word to end this block: is this it? else if (!strcmp(s, ex)) { - struct sh_arg *aa = pl->prev->arg; - // can't "if | then" or "while && do", only ; & or newline works - if (aa->v[aa->c] && strcmp(aa->v[aa->c], "&")) goto flush; + if (strcmp(pl->prev->arg->v[pl->prev->arg->c] ? : "&", "&")) goto flush; // consume word, record block end location in earlier !0 type blocks free(dlist_lpop(expect)); @@ -2761,7 +2824,7 @@ static int parse_line(char *line, struct sh_pipeline **ppl, pl3 = pl2; } else pl2->end = pl; } - if ((pl2->type == 1 || pl2->type == 'f') && --i<0) break; + if (pl2->type == 1 && --i<0) break; } } } @@ -2969,7 +3032,7 @@ static char *get_next_line(FILE *ff, int prompt) static void run_lines(void) { char *ctl, *s, *ss, **vv; - struct sh_process *pplist = 0; // processes piping into current level + struct sh_process *pp, *pplist = 0; // processes piping into current level long i, j, k; // iterate through pipeline segments @@ -3074,13 +3137,12 @@ static void run_lines(void) syntax_err(s); break; } - // Parse and run next command, saving resulting process - } else dlist_add_nomalloc((void *)&pplist, (void *)run_command()); + } else if ((pp = run_command())) + dlist_add_nomalloc((void *)&pplist, (void *)pp); // Start of flow control block? } else if (TT.ff->pl->type == 1) { - struct sh_process *pp = 0; // TODO test cat | {thingy} is new PID: { is ( for | @@ -3244,8 +3306,25 @@ dprintf(2, "TODO skipped running for((;;)), need math parser\n"); // cleans up after trailing redirections/pipe pop_block(); -// FUNCTION this! - } else if (TT.ff->pl->type == 'f') TT.ff->pl = add_function(s, TT.ff->pl); + // declare a shell function + } else if (TT.ff->pl->type == 'F') { + struct sh_function *funky = (void *)*TT.ff->pl->arg->v; + +// TODO binary search + for (i = 0; i<TT.funcslen; i++) + if (!strcmp(TT.functions[i]->name, funky->name)) break; + if (i == TT.funcslen) { + struct sh_arg arg = {(void *)TT.functions, TT.funcslen}; + + arg_add(&arg, (void *)funky); // TODO possibly an expand@31 function? + TT.functions = (void *)arg.v; + TT.funcslen++; + } else { + free_function(TT.functions[i]); + TT.functions[i] = funky; + } + TT.functions[i]->refcount++; + } // 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 @@ -3480,7 +3559,7 @@ static void subshell_setup(void) shv->flags |= VAR_GLOBAL; shv->str = s; } - if (!memcmp(s, "IFS=", 4)) TT.ff->ifs = s+4; + cache_ifs(s, TT.ff); } // set/update PWD @@ -3715,9 +3794,15 @@ void set_main(void) } } +// TODO need test: unset clears var first and stops, function only if no var. +#define CLEANUP_cd +#define FOR_unset +#include "generated/flags.h" + void unset_main(void) { char **arg, *s; + int ii; for (arg = toys.optargs; *arg; arg++) { s = varend(*arg); @@ -3726,12 +3811,20 @@ void unset_main(void) continue; } - // unset magic variable? - unsetvar(*arg); + // TODO -n and name reference support + // unset variable + if (!FLAG(f) && unsetvar(*arg)) continue; + // unset function TODO binary search + for (ii = 0; ii<TT.funcslen; ii++) + if (!strcmp(*arg, TT.functions[ii]->name)) break; + if (ii != TT.funcslen) { + free_function(TT.functions[ii]); + memmove(TT.functions+ii, TT.functions+ii+1, TT.funcslen+1-ii); + } } } -#define CLEANUP_cd +#define CLEANUP_unset #define FOR_export #include "generated/flags.h" diff --git a/toys/pending/telnet.c b/toys/pending/telnet.c index b4d5c72f..a7ea91e2 100644 --- a/toys/pending/telnet.c +++ b/toys/pending/telnet.c @@ -14,329 +14,287 @@ config TELNET help usage: telnet HOST [PORT] - Connect to telnet server + Connect to telnet server. */ #define FOR_telnet #include "toys.h" #include <arpa/telnet.h> -#include <netinet/in.h> GLOBALS( - int port; - int sfd; - char buff[128]; - int pbuff; - char iac[256]; - int piac; - char *ttype; - struct termios def_term; + int sock; + char buf[2048]; // Half sizeof(toybuf) allows a buffer full of IACs. + char iac[128]; + int iac_len; + struct termios old_term; struct termios raw_term; - uint8_t term_ok; - uint8_t term_mode; - uint8_t flags; - unsigned win_width; - unsigned win_height; + uint8_t mode; + int echo, sga; + int state, request; ) -#define DATABUFSIZE 128 -#define IACBUFSIZE 256 +#define NORMAL 0 +#define SAW_IAC 1 +#define SAW_WWDD 2 +#define SAW_SB 3 +#define SAW_SB_TTYPE 4 +#define WANT_IAC 5 +#define WANT_SE 6 +#define SAW_CR 10 + #define CM_TRY 0 #define CM_ON 1 #define CM_OFF 2 -#define UF_ECHO 0x01 -#define UF_SGA 0x02 -// sets terminal mode: LINE or CHARACTER based om internal stat. -static char const es[] = "\r\nEscape character is "; -static void set_mode(void) +static void raw(int raw) { - if (TT.flags & UF_ECHO) { - if (TT.term_mode == CM_TRY) { - TT.term_mode = CM_ON; - printf("\r\nEntering character mode%s'^]'.\r\n", es); - if (TT.term_ok) tcsetattr(0, TCSADRAIN, &TT.raw_term); - } - } else { - if (TT.term_mode != CM_OFF) { - TT.term_mode = CM_OFF; - printf("\r\nEntering line mode%s'^C'.\r\n", es); - if (TT.term_ok) tcsetattr(0, TCSADRAIN, &TT.def_term); - } - } + tcsetattr(0, TCSADRAIN, raw ? &TT.raw_term : &TT.old_term); } -// flushes all data in IAC buff to server. static void flush_iac(void) { - int wlen = write(TT.sfd, TT.iac, TT.piac); - - if(wlen <= 0) error_msg("IAC : send failed."); - TT.piac = 0; + xwrite(TT.sock, TT.iac, TT.iac_len); + TT.iac_len = 0; } -// puts DATA in iac buff of length LEN and updates iac buff pointer. -static void put_iac(int len, ...) +static void iac(int n, ...) { va_list va; - if(TT.piac + len >= IACBUFSIZE) flush_iac(); - va_start(va, len); - for(;len > 0; TT.iac[TT.piac++] = (uint8_t)va_arg(va, int), len--); + if (TT.iac_len + n >= sizeof(TT.iac)) flush_iac(); + va_start(va, n); + while (n--) TT.iac[TT.iac_len++] = va_arg(va, int); va_end(va); } -// puts string STR in iac buff and updates iac buff pointer. -static void str_iac(char *str) +static void iacstr(char *str) { - int len = strlen(str); + if (TT.iac_len) flush_iac(); + xwrite(TT.sock, str, strlen(str)); + TT.iac_len = 0; +} - if(TT.piac + len + 1 >= IACBUFSIZE) flush_iac(); - strcpy(&TT.iac[TT.piac], str); - TT.piac += len+1; +static void slc(int line) +{ + TT.mode = line ? CM_OFF : CM_ON; + xprintf("Entering %s mode\r\nEscape character is '^%c'.\r\n", + line ? "line" : "character", line ? 'C' : ']'); + raw(!line); +} + +static void set_mode(void) +{ + if (TT.echo) { + if (TT.mode == CM_TRY) slc(0); + } else if (TT.mode != CM_OFF) slc(1); } static void handle_esc(void) { char input; - if(toys.signal && TT.term_ok) tcsetattr(0, TCSADRAIN, &TT.raw_term); - xwrite(1,"\r\nConsole escape. Commands are:\r\n\n" + if (toys.signal) raw(1); + + // This matches busybox telnet, not BSD telnet. + xputsn("\r\n" + "Console escape. Commands are:\r\n" + "\r\n" " l go to line mode\r\n" " c go to character mode\r\n" " z suspend telnet\r\n" - " e exit telnet\r\n", 114); - - if (read(0, &input, 1) <= 0) { - if(TT.term_ok) tcsetattr(0, TCSADRAIN, &TT.def_term); - exit(0); + " e exit telnet\r\n" + "\r\n" + "telnet> "); + // In particular, the boxes only read a single character, not a line. + if (read(0, &input, 1) <= 0 || input == 4 || input == 'e') { + xprintf("Connection closed.\r\n"); + xexit(); } - switch (input) { - case 'l': + if (input == 'l') { if (!toys.signal) { - TT.term_mode = CM_TRY; - TT.flags &= ~(UF_ECHO | UF_SGA); + TT.mode = CM_TRY; + TT.echo = TT.sga = 0; set_mode(); - put_iac(6, IAC,DONT,TELOPT_ECHO,IAC,DONT, TELOPT_SGA); + iac(6, IAC, DONT, TELOPT_ECHO, IAC, DONT, TELOPT_SGA); flush_iac(); goto ret; } - break; - case 'c': + } else if (input == 'c') { if (toys.signal) { - TT.term_mode = CM_TRY; - TT.flags |= (UF_ECHO | UF_SGA); + TT.mode = CM_TRY; + TT.echo = TT.sga = 1; set_mode(); - put_iac(6, IAC,DO,TELOPT_ECHO,IAC,DO,TELOPT_SGA); + iac(6, IAC, DO, TELOPT_ECHO, IAC, DO, TELOPT_SGA); flush_iac(); goto ret; } - break; - case 'z': - if(TT.term_ok) tcsetattr(0, TCSADRAIN, &TT.def_term); + } else if (input == 'z') { + raw(0); kill(0, SIGTSTP); - if(TT.term_ok) tcsetattr(0, TCSADRAIN, &TT.raw_term); - break; - case 'e': - if(TT.term_ok) tcsetattr(0, TCSADRAIN, &TT.def_term); - exit(0); - default: break; + raw(1); } - xwrite(1, "continuing...\r\n", 15); - if (toys.signal && TT.term_ok) tcsetattr(0, TCSADRAIN, &TT.def_term); + xprintf("telnet %s %s\r\n", toys.optargs[0], toys.optargs[1] ?: "23"); + if (toys.signal) raw(0); ret: toys.signal = 0; } -/* - * handles telnet SUB NEGOTIATIONS - * only terminal type is supported. - */ -static void handle_negotiations(void) +// Handle WILL WONT DO DONT requests from the server. +static void handle_wwdd(char opt) { - char opt = TT.buff[TT.pbuff++]; - - switch(opt) { - case TELOPT_TTYPE: - opt = TT.buff[TT.pbuff++]; - if(opt == TELQUAL_SEND) { - put_iac(4, IAC,SB,TELOPT_TTYPE,TELQUAL_IS); - str_iac(TT.ttype); - put_iac(2, IAC,SE); - } - break; - default: break; - } -} - -/* - * handles server's DO DONT WILL WONT requests. - * supports ECHO, SGA, TTYPE, NAWS - */ -static void handle_ddww(char ddww) -{ - char opt = TT.buff[TT.pbuff++]; - - switch (opt) { - case TELOPT_ECHO: /* ECHO */ - if (ddww == DO) put_iac(3, IAC,WONT,TELOPT_ECHO); - if(ddww == DONT) break; - if (TT.flags & UF_ECHO) { - if (ddww == WILL) return; - } else if (ddww == WONT) return; - if (TT.term_mode != CM_OFF) TT.flags ^= UF_ECHO; - (TT.flags & UF_ECHO)? put_iac(3, IAC,DO,TELOPT_ECHO) : - put_iac(3, IAC,DONT,TELOPT_ECHO); + if (opt == TELOPT_ECHO) { + if (TT.request == DO) iac(3, IAC, WONT, TELOPT_ECHO); + if (TT.request == DONT) return; + if (TT.echo) { + if (TT.request == WILL) return; + } else if (TT.request == WONT) return; + if (TT.mode != CM_OFF) TT.echo ^= 1; + iac(3, IAC, TT.echo ? DO : DONT, TELOPT_ECHO); set_mode(); - printf("\r\n"); - break; - - case TELOPT_SGA: /* Supress GO Ahead */ - if (TT.flags & UF_SGA){ if (ddww == WILL) return; - } else if (ddww == WONT) return; - - TT.flags ^= UF_SGA; - (TT.flags & UF_SGA)? put_iac(3, IAC,DO,TELOPT_SGA) : - put_iac(3, IAC,DONT,TELOPT_SGA); - break; - - case TELOPT_TTYPE: /* Terminal Type */ - (TT.ttype)? put_iac(3, IAC,WILL,TELOPT_TTYPE): - put_iac(3, IAC,WONT,TELOPT_TTYPE); - break; - - case TELOPT_NAWS: /* Window Size */ - put_iac(3, IAC,WILL,TELOPT_NAWS); - put_iac(9, IAC,SB,TELOPT_NAWS,(TT.win_width >> 8) & 0xff, - TT.win_width & 0xff,(TT.win_height >> 8) & 0xff, - TT.win_height & 0xff,IAC,SE); - break; - - default: /* Default behaviour is to say NO */ - if(ddww == WILL) put_iac(3, IAC,DONT,opt); - if(ddww == DO) put_iac(3, IAC,WONT,opt); - break; + } else if (opt == TELOPT_SGA) { // Suppress Go Ahead + if (TT.sga) { + if (TT.request == WILL) return; + } else if (TT.request == WONT) return; + TT.sga ^= 1; + iac(3, IAC, TT.sga ? DO : DONT, TELOPT_SGA); + } else if (opt == TELOPT_TTYPE) { // Terminal TYPE + iac(3, IAC, WILL, TELOPT_TTYPE); + } else if (opt == TELOPT_NAWS) { // Negotiate About Window Size + unsigned cols = 80, rows = 24; + + terminal_size(&cols, &rows); + iac(3, IAC, WILL, TELOPT_NAWS); + iac(7, IAC, SB, TELOPT_NAWS, cols>>8, cols, rows>>8, rows); + iac(2, IAC, SE); + } else { + // Say "no" to anything we don't understand. + iac(3, IAC, (TT.request == WILL) ? DONT : WONT, opt); } } -/* - * parses data which is read from server of length LEN. - * and passes it to console. - */ -static int read_server(int len) +static void handle_server_output(int n) { + char *p = TT.buf, *end = TT.buf + n, ch; int i = 0; - char curr; - TT.pbuff = 0; - - do { - curr = TT.buff[TT.pbuff++]; - if (curr == IAC) { - curr = TT.buff[TT.pbuff++]; - switch (curr) { - case DO: /* FALLTHROUGH */ - case DONT: /* FALLTHROUGH */ - case WILL: /* FALLTHROUGH */ - case WONT: - handle_ddww(curr); - break; - case SB: - handle_negotiations(); - break; - case SE: - break; - default: break; + + // Possibilities: + // + // 1. Regular character + // 2. IAC [WILL|WONT|DO|DONT] option + // 3. IAC SB option arg... IAC SE + // + // The only subnegotiation we support is IAC SB TTYPE SEND IAC SE, so we just + // hard-code that into our state machine rather than having a more general + // "collect the subnegotation into a buffer and handle it after we've seen + // the IAC SE at the end". It's 2021, so we're unlikely to need more. + + while (p < end) { + ch = *p++; + if (TT.state == SAW_IAC) { + if (ch >= WILL && ch <= DONT) { + TT.state = SAW_WWDD; + TT.request = ch; + } else if (ch == SB) { + TT.state = SAW_SB; + } else { + TT.state = NORMAL; } - } else { - toybuf[i++] = curr; - if (curr == '\r') { curr = TT.buff[TT.pbuff++]; - if (curr != '\0') TT.pbuff--; + } else if (TT.state == SAW_WWDD) { + handle_wwdd(ch); + TT.state = NORMAL; + } else if (TT.state == SAW_SB) { + if (ch == TELOPT_TTYPE) TT.state = SAW_SB_TTYPE; + else TT.state = WANT_IAC; + } else if (TT.state == SAW_SB_TTYPE) { + if (ch == TELQUAL_SEND) { + iac(4, IAC, SB, TELOPT_TTYPE, TELQUAL_IS); + iacstr(getenv("TERM") ?: "NVT"); + iac(2, IAC, SE); } + TT.state = WANT_IAC; + } else if (TT.state == WANT_IAC) { + if (ch == IAC) TT.state = WANT_SE; + } else if (TT.state == WANT_SE) { + if (ch == SE) TT.state = NORMAL; + } else if (ch == IAC) { + TT.state = SAW_IAC; + } else { + if (TT.state == SAW_CR && ch == '\0') { + // CR NUL -> CR + } else toybuf[i++] = ch; + if (ch == '\r') TT.state = SAW_CR; + TT.state = NORMAL; } - } while (TT.pbuff < len); - + } if (i) xwrite(0, toybuf, i); - return 0; } -/* - * parses data which is read from console of length LEN - * and passes it to server. - */ -static void write_server(int len) +static void handle_user_input(int n) { - char *c = (char*)TT.buff; + char *p = TT.buf, ch; int i = 0; - for (; len > 0; len--, c++) { - if (*c == 0x1d) { + while (n--) { + ch = *p++; + if (ch == 0x1d) { handle_esc(); return; } - toybuf[i++] = *c; - if (*c == IAC) toybuf[i++] = *c; /* IAC -> IAC IAC */ - else if (*c == '\r') toybuf[i++] = '\0'; /* CR -> CR NUL */ + toybuf[i++] = ch; + if (ch == IAC) toybuf[i++] = IAC; // IAC -> IAC IAC + else if (ch == '\r') toybuf[i++] = '\n'; // CR -> CR LF + else if (ch == '\n') { // LF -> CR LF + toybuf[i-1] = '\r'; + toybuf[i++] = '\n'; + } } - if(i) xwrite(TT.sfd, toybuf, i); + if (i) xwrite(TT.sock, toybuf, i); +} + +static void reset_terminal(void) +{ + raw(0); } void telnet_main(void) { - char *port = "23"; - int set = 1, len; struct pollfd pfds[2]; + int n = 1; - TT.win_width = 80; //columns - TT.win_height = 24; //rows + tcgetattr(0, &TT.old_term); + TT.raw_term = TT.old_term; + cfmakeraw(&TT.raw_term); - if (toys.optc == 2) port = toys.optargs[1]; + TT.sock = xconnectany(xgetaddrinfo(*toys.optargs, toys.optargs[1] ?: "23", 0, + SOCK_STREAM, IPPROTO_TCP, 0)); + xsetsockopt(TT.sock, SOL_SOCKET, SO_KEEPALIVE, &n, sizeof(n)); - TT.ttype = getenv("TERM"); - if(!TT.ttype) TT.ttype = ""; - if(strlen(TT.ttype) > IACBUFSIZE-1) TT.ttype[IACBUFSIZE - 1] = '\0'; + xprintf("Connected to %s.\r\n", *toys.optargs); - if (!tcgetattr(0, &TT.def_term)) { - TT.term_ok = 1; - TT.raw_term = TT.def_term; - cfmakeraw(&TT.raw_term); - } - terminal_size(&TT.win_width, &TT.win_height); - - TT.sfd = xconnectany(xgetaddrinfo(*toys.optargs, port, 0, SOCK_STREAM, - IPPROTO_TCP, 0)); - setsockopt(TT.sfd, SOL_SOCKET, SO_REUSEADDR, &set, sizeof(set)); - setsockopt(TT.sfd, SOL_SOCKET, SO_KEEPALIVE, &set, sizeof(set)); + sigatexit(reset_terminal); + signal(SIGINT, generic_signal); pfds[0].fd = 0; pfds[0].events = POLLIN; - pfds[1].fd = TT.sfd; + pfds[1].fd = TT.sock; pfds[1].events = POLLIN; - - signal(SIGINT, generic_signal); - while(1) { - if(TT.piac) flush_iac(); - if(poll(pfds, 2, -1) < 0) { + for (;;) { + if (poll(pfds, 2, -1) < 0) { if (toys.signal) handle_esc(); - else sleep(1); - - continue; + else perror_exit("poll"); } - if(pfds[0].revents) { - len = read(0, TT.buff, DATABUFSIZE); - if(len > 0) write_server(len); - else return; + if (pfds[0].revents) { + if ((n = read(0, TT.buf, sizeof(TT.buf))) <= 0) xexit(); + handle_user_input(n); } - if(pfds[1].revents) { - len = read(TT.sfd, TT.buff, DATABUFSIZE); - if(len > 0) read_server(len); - else { - printf("Connection closed by foreign host\r\n"); - if(TT.term_ok) tcsetattr(0, TCSADRAIN, &TT.def_term); - exit(1); - } + if (pfds[1].revents) { + if ((n = read(TT.sock, TT.buf, sizeof(TT.buf))) <= 0) + error_exit("Connection closed by foreign host\r"); + handle_server_output(n); } + if (TT.iac_len) flush_iac(); } } diff --git a/toys/pending/telnetd.c b/toys/pending/telnetd.c index c82ff61e..032104f2 100644 --- a/toys/pending/telnetd.c +++ b/toys/pending/telnetd.c @@ -25,7 +25,6 @@ config TELNETD #define FOR_telnetd #include "toys.h" #include <arpa/telnet.h> -#include <utmp.h> GLOBALS( char *login_path; @@ -97,23 +96,6 @@ static void get_sockaddr(char *host, void *buf) else ((struct sockaddr_in6*)buf)->sin6_port = port_num; } -static void utmp_entry(void) -{ - struct utmp entry; - struct utmp *utp_ptr; - pid_t pid = getpid(); - - utmpname(_PATH_UTMP); - setutent(); //start from start - while ((utp_ptr = getutent()) != NULL) { - if (utp_ptr->ut_pid == pid && utp_ptr->ut_type >= INIT_PROCESS) break; - } - if (!utp_ptr) entry.ut_type = DEAD_PROCESS; - entry.ut_time = time(0); - setutent(); - pututline(&entry); -} - static int listen_socket(void) { int s, af = AF_INET, yes = 1; @@ -165,27 +147,27 @@ static void write_issue(char *tty) static int new_session(int sockfd) { - char *argv_login[2]; //arguments for execvp cmd, NULL + char *argv_login[] = {NULL, "-h", NULL, NULL}; char tty_name[30]; //tty name length. - int fd, flags, i = 1; + int fd, i = 1; char intial_iacs[] = {IAC, DO, TELOPT_ECHO, IAC, DO, TELOPT_NAWS, IAC, WILL, TELOPT_ECHO, IAC, WILL, TELOPT_SGA }; + struct sockaddr_storage sa; + socklen_t sl = sizeof(sa); setsockopt(sockfd, SOL_SOCKET, SO_KEEPALIVE, &i, sizeof(i)); - flags = fcntl(sockfd, F_GETFL); - fcntl(sockfd, F_SETFL, flags | O_NONBLOCK); - if (FLAG(i)) fcntl((sockfd + 1), F_SETFL, flags | O_NONBLOCK); 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); - return fd; - } + if ((TT.fork_pid = forkpty(&fd, tty_name, NULL, NULL)) > 0) return fd; if (TT.fork_pid < 0) perror_exit("fork"); + + if (getpeername(sockfd, (void *)&sa, &sl)) perror_exit("getpeername"); + if (getnameinfo((void *)&sa, sl, toybuf, sizeof(toybuf), NULL, 0, 0)) + perror_exit("getnameinfo"); + write_issue(tty_name); - argv_login[0] = strdup(TT.login_path); - argv_login[1] = NULL; + argv_login[0] = TT.login_path; + argv_login[2] = toybuf; execvp(argv_login[0], argv_login); exit(EXIT_FAILURE); } @@ -373,7 +355,15 @@ void telnetd_main(void) } if (FD_ISSET(tm->new_fd, &rd)) { if ((c = read(tm->new_fd, tm->buff2+tm->buff2_avail, - BUFSIZE-tm->buff2_avail)) <= 0) break; + BUFSIZE-tm->buff2_avail)) <= 0) { + // The other side went away without a proper shutdown. Happens if + // you exit telnet via ^]^D, leaving the socket in TIME_WAIT. + xclose(tm->new_fd); + tm->new_fd = -1; + xclose(tm->pty_fd); + tm->pty_fd = -1; + break; + } c = handle_iacs(tm, c, tm->pty_fd); tm->buff2_avail += c; if ((w = write(tm->pty_fd, tm->buff2+ tm->buff2_written, @@ -416,9 +406,9 @@ void telnetd_main(void) if (!tm) error_exit("unexpected reparenting of %d", pid); if (FLAG(i)) exit(EXIT_SUCCESS); + if (!prev) session_list = session_list->next; else prev->next = tm->next; - utmp_entry(); xclose(tm->pty_fd); xclose(tm->new_fd); free(tm); diff --git a/toys/posix/cpio.c b/toys/posix/cpio.c index 07c294f0..35b74b3b 100644 --- a/toys/posix/cpio.c +++ b/toys/posix/cpio.c @@ -166,11 +166,13 @@ void cpio_main(void) if (!test) err = mkdir(name, mode) && !FLAG(u); } else if (S_ISLNK(mode)) { data = strpad(afd, size, 0); - if (!test) err = symlink(data, name); + if (!test) { + err = symlink(data, name); + // Can't get a filehandle to a symlink, so do special chown + if (!err && !geteuid() && !FLAG(no_preserve_owner)) + err = lchown(name, uid, gid); + } free(data); - // Can't get a filehandle to a symlink, so do special chown - if (!err && !geteuid() && !FLAG(no_preserve_owner)) - err = lchown(name, uid, gid); } else if (S_ISREG(mode)) { int fd = test ? 0 : open(name, O_CREAT|O_WRONLY|O_EXCL|O_NOFOLLOW, mode); diff --git a/toys/posix/sed.c b/toys/posix/sed.c index 9bd05034..d5a4a833 100644 --- a/toys/posix/sed.c +++ b/toys/posix/sed.c @@ -443,7 +443,7 @@ static void sed_line(char **pline, long plen) // If we're replacing only a specific match, skip if this isn't it off = command->sflags>>4; if (off && off != ++count) { - memcpy(l2+l2used, rline, match[0].rm_eo); + if (l2) memcpy(l2+l2used, rline, match[0].rm_eo); l2used += match[0].rm_eo; rline += match[0].rm_eo; |