aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorElliott Hughes <enh@google.com>2021-04-26 19:01:22 +0000
committerAutomerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>2021-04-26 19:01:22 +0000
commit799cfb69ce51a495a86a8649efbc7f7b79258cef (patch)
tree930c099bd2c1b11596b8d11bdc88076fe77883d8
parent5beec43a2c41e4c73c7c54161f009f6fa4f1ebec (diff)
parentb7cb33d2d49e30d2e2b6051b22df69023a5c94bc (diff)
downloadtoybox-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.h4
-rw-r--r--android/device/generated/globals.h36
-rw-r--r--android/device/generated/help.h4
-rw-r--r--android/device/generated/newtoys.h2
-rw-r--r--android/linux/generated/flags.h4
-rw-r--r--android/linux/generated/globals.h36
-rw-r--r--android/linux/generated/help.h4
-rw-r--r--android/linux/generated/newtoys.h2
-rw-r--r--android/mac/generated/flags.h4
-rw-r--r--android/mac/generated/globals.h36
-rw-r--r--android/mac/generated/help.h4
-rw-r--r--android/mac/generated/newtoys.h2
-rwxr-xr-xtests/cpio.test19
-rwxr-xr-xtests/sed.test1
-rw-r--r--toys/pending/sh.c393
-rw-r--r--toys/pending/telnet.c416
-rw-r--r--toys/pending/telnetd.c54
-rw-r--r--toys/posix/cpio.c10
-rw-r--r--toys/posix/sed.c2
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;