aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorElliott Hughes <enh@google.com>2016-04-20 18:22:50 -0700
committerElliott Hughes <enh@google.com>2016-04-20 18:22:50 -0700
commitb9cc6c64274a1b67546ff8be78dbc0290b3e0634 (patch)
tree33fdd6f69968e684c5a0ff7e72a7e5a042da402f
parent1c2326b8af500adbb5864ea87afa94a0c6cf1ff0 (diff)
parent7b6957fa1e23f1b0f614bb5102c527bee2db3002 (diff)
downloadandroid_external_toybox-b9cc6c64274a1b67546ff8be78dbc0290b3e0634.tar.gz
android_external_toybox-b9cc6c64274a1b67546ff8be78dbc0290b3e0634.tar.bz2
android_external_toybox-b9cc6c64274a1b67546ff8be78dbc0290b3e0634.zip
Merge remote-tracking branch 'toybox/master' into HEAD
-rw-r--r--Makefile3
-rw-r--r--README2
-rw-r--r--lib/interestingtimes.c2
-rw-r--r--lib/lib.c7
-rw-r--r--lib/lib.h2
-rw-r--r--lib/portability.h10
-rw-r--r--main.c2
-rwxr-xr-xscripts/genconfig.sh3
-rwxr-xr-xtests/diff.test30
-rwxr-xr-xtests/mv.test138
-rwxr-xr-xtests/sed.test1
-rwxr-xr-xtests/sh.test55
-rwxr-xr-xtests/tail.test1
-rw-r--r--toys/other/rev.c6
-rw-r--r--toys/pending/diff.c6
-rw-r--r--toys/pending/modprobe.c2
-rw-r--r--toys/pending/netstat.c2
-rw-r--r--toys/pending/sh.c212
-rw-r--r--toys/posix/cp.c17
-rw-r--r--toys/posix/ps.c15
-rw-r--r--toys/posix/sed.c307
-rw-r--r--toys/posix/tail.c6
-rwxr-xr-xwww/roadmap.html9
23 files changed, 478 insertions, 360 deletions
diff --git a/Makefile b/Makefile
index c7237405..711d6247 100644
--- a/Makefile
+++ b/Makefile
@@ -64,7 +64,8 @@ tests:
help::
@echo ' toybox - Build toybox.'
@echo ' COMMANDNAME - Build individual toybox command as a standalone binary.'
- @echo ' list - List COMMANDNAMEs (also list_working and list_pending).'
+ @echo ' list - List COMMANDNAMEs you can build standalone.'
+ @echo ' list_pending - List unfinished COMMANDNAMEs out of toys/pending.'
@echo ' change - Build each command standalone under change/.'
@echo ' baseline - Create toybox_old for use by bloatcheck.'
@echo ' bloatcheck - Report size differences between old and current versions'
diff --git a/README b/README
index 5fc9ec0b..f180768a 100644
--- a/README
+++ b/README
@@ -10,7 +10,7 @@ The special name "." indicates the current directory (just like ".." means
the parent directory), and you can run a program that isn't in the $PATH by
specifying a path to it, so this should work:
- wget http://landley.net/bin/toybox-x86_64
+ wget http://landley.net/toybox/bin/toybox-x86_64
chmod +x toybox-x86_64
./toybox-x86_64 echo hello world
diff --git a/lib/interestingtimes.c b/lib/interestingtimes.c
index c4ea2c27..8337ce8b 100644
--- a/lib/interestingtimes.c
+++ b/lib/interestingtimes.c
@@ -227,7 +227,7 @@ void tty_jump(int x, int y)
void tty_reset(void)
{
- set_terminal(1, 0, 0);
+ set_terminal(0, 0, 0);
tty_esc("?25h");
tty_esc("0m");
tty_jump(0, 999);
diff --git a/lib/lib.c b/lib/lib.c
index 3ca7052a..0df45bf9 100644
--- a/lib/lib.c
+++ b/lib/lib.c
@@ -919,11 +919,14 @@ void mode_to_string(mode_t mode, char *buf)
*buf = c;
}
-char *basename_r(char *name)
+// basename() can modify its argument or return a pointer to a constant string
+// This just gives after the last '/' or the whole stirng if no /
+char *getbasename(char *name)
{
char *s = strrchr(name, '/');
if (s) return s+1;
+
return name;
}
@@ -945,7 +948,7 @@ void names_to_pid(char **names, int (*callback)(pid_t pid, char *name))
for (curname = names; *curname; curname++)
if (**curname == '/' ? !strcmp(cmd, *curname)
- : !strcmp(basename_r(cmd), basename_r(*curname)))
+ : !strcmp(getbasename(cmd), getbasename(*curname)))
if (callback(u, *curname)) break;
if (*curname) break;
}
diff --git a/lib/lib.h b/lib/lib.h
index 52ddc28e..23a3b2b3 100644
--- a/lib/lib.h
+++ b/lib/lib.h
@@ -286,7 +286,7 @@ char *num_to_sig(int sig);
mode_t string_to_mode(char *mode_str, mode_t base);
void mode_to_string(mode_t mode, char *buf);
-char *basename_r(char *name);
+char *getbasename(char *name);
void names_to_pid(char **names, int (*callback)(pid_t pid, char *name));
pid_t xvforkwrap(pid_t pid);
diff --git a/lib/portability.h b/lib/portability.h
index d11cc9c6..fdee5fcf 100644
--- a/lib/portability.h
+++ b/lib/portability.h
@@ -157,19 +157,11 @@ int utimensat(int fd, const char *path, const struct timespec times[2], int flag
#endif // glibc in general
-#if !defined(__GLIBC__) && !defined(__BIONIC__)
+#if !defined(__GLIBC__)
// POSIX basename.
#include <libgen.h>
#endif
-// glibc was handled above; for 32-bit bionic we need to avoid a collision
-// with toybox's basename_r so we can't include <libgen.h> even though that
-// would give us a POSIX basename(3).
-#if defined(__BIONIC__)
-char *basename(char *path);
-char *dirname(char *path);
-#endif
-
// Work out how to do endianness
#ifndef __APPLE__
diff --git a/main.c b/main.c
index cf82872d..9c70c961 100644
--- a/main.c
+++ b/main.c
@@ -202,7 +202,7 @@ int main(int argc, char *argv[])
toys.stacktop = &stack;
}
- *argv = basename_r(*argv);
+ *argv = getbasename(*argv);
// If nommu can't fork, special reentry path.
// Use !stacktop to signal "vfork happened", both before and after xexec()
diff --git a/scripts/genconfig.sh b/scripts/genconfig.sh
index e9bb5141..e4c2aad7 100755
--- a/scripts/genconfig.sh
+++ b/scripts/genconfig.sh
@@ -148,8 +148,7 @@ do
WORKING="$WORKING $NAME"
done &&
echo -e "clean::\n\trm -f $WORKING $PENDING" &&
-echo -e "list:\n\t@echo $(echo $WORKING $PENDING | tr ' ' '\n' | sort | xargs)" &&
-echo -e "list_working:\n\t@echo $(echo $WORKING | tr ' ' '\n' | sort | xargs)" &&
+echo -e "list:\n\t@echo $(echo $WORKING | tr ' ' '\n' | sort | xargs)" &&
echo -e "list_pending:\n\t@echo $(echo $PENDING | tr ' ' '\n' | sort | xargs)" &&
echo -e ".PHONY: $WORKING $PENDING" | sed 's/ \([^ ]\)/ test_\1/g'
) > .singlemake
diff --git a/tests/diff.test b/tests/diff.test
new file mode 100755
index 00000000..ca0b682b
--- /dev/null
+++ b/tests/diff.test
@@ -0,0 +1,30 @@
+#!/bin/bash
+
+#testing "name" "command" "result" "infile" "stdin"
+
+seq 10 > left
+seq 11 > right
+
+expected='--- left
++++ right
+@@ -8,3 +8,4 @@
+ 8
+ 9
+ 10
++11
+'
+# Hm this only gives unified diffs?
+testing "simple" "diff left right" "$expected" "" ""
+
+
+expected='--- tree1/file
++++ tree2/file
+@@ -1 +1 @@
+-foo
++food
+'
+mkdir -p tree1 tree2
+echo foo > tree1/file
+echo food > tree2/file
+
+testing "simple" "diff -r tree1 tree2 |tee out" "$expected" "" ""
diff --git a/tests/mv.test b/tests/mv.test
index ab2ca5ed..036fd48e 100755
--- a/tests/mv.test
+++ b/tests/mv.test
@@ -1,5 +1,10 @@
#!/bin/bash
+# TODO: needs root to mount tmpfs to test moving across filesystems.
+# check handling of chattr +i immutable bit
+# "touch two; chmod -w two; mv one two" shouldn't prompt to delete two if
+# one doesn't exist.
+
# Copyright 2013 Robin Mittal <robinmittal.it@gmail.com>
# Copyright 2013 Divya Kothari <divya.s.kothari@gmail.com>
@@ -8,98 +13,145 @@
#testing "name" "command" "result" "infile" "stdin"
touch file
-testing "old_file to new_file" "mv file file1 && [ ! -e file -a -f file1 ] &&
- echo 'yes'" "yes\n" "" ""
+testing "file to file" \
+ "mv file file1 && [ ! -e file -a -f file1 ] && echo yes" \
+ "yes\n" "" ""
rm -f file*
touch file
mkdir dir
-testing "file to a dir" "mv file dir && [ ! -e file -a -f dir/file ] &&
- echo 'yes'" "yes\n" "" ""
+testing "file to dir" \
+ "mv file dir && [ ! -e file -a -f dir/file ] && echo yes" \
+ "yes\n" "" ""
rm -rf file* dir*
mkdir dir
-testing "old_dir to new_dir" "mv dir dir1 && [ ! -e dir -a -d dir1 ] &&
- echo 'yes'" "yes\n" "" ""
+testing "dir to dir" \
+ "mv dir dir1 && [ ! -e dir -a -d dir1 ] && echo yes" \
+ "yes\n" "" ""
rm -rf dir*
mkdir dir1 dir2
touch file1 file2 dir1/file3
ln -s file1 link1
-testing "multiple files/dir to a dir" "mv file1 file2 link1 dir1 dir2 &&
- [ ! -e file1 -a ! -e file2 -a ! -e link1 -a ! -e dir1 ] &&
- [ -f dir2/file1 -a -f dir2/file2 -a -L dir2/link1 -a -d dir2/dir1 ] &&
- [ -f dir2/dir1/file3 ] && readlink dir2/link1" "file1\n" "" ""
+testing "multiple files/dirs to a dir" \
+ "mv file1 file2 link1 dir1 dir2 &&
+ [ ! -e file1 -a ! -e file2 -a ! -e link1 -a ! -e dir1 ] &&
+ [ -f dir2/file1 -a -f dir2/file2 -a -L dir2/link1 -a -d dir2/dir1 ] &&
+ [ -f dir2/dir1/file3 ] && readlink dir2/link1" \
+ "file1\n" "" ""
rm -rf file* link* dir*
-touch file1
-testing "a empty file to new_file" "mv file1 file2 &&
- [ ! -e file1 -a -f file2 ] && stat -c %s file2" "0\n" "" ""
-rm -rf file*
-
-mkdir dir1
-testing "enpty dir to new_dir" "mv dir1 dir2 &&
- [ ! -d dir1 -a -d dir2 ] && echo 'yes'" "yes\n" "" ""
-rm -rf dir*
-
dd if=/dev/zero of=file1 seek=10k count=1 >/dev/null 2>&1
-testing "file new_file (random file)" "mv file1 file2 &&
- [ ! -e file1 -a -f file2 ] && stat -c %s file2" "5243392\n" "" ""
+testing "random file to new file" \
+ "mv file1 file2 && [ ! -e file1 -a -f file2 ] && stat -c %s file2" \
+ "5243392\n" "" ""
rm -f file*
touch file1
ln -s file1 link1
-testing "link new_link (softlink)" "mv link1 link2 &&
- [ ! -e link1 -a -L link2 ] && readlink link2" "file1\n" "" ""
+testing "symlink to new symlink" \
+ "mv link1 link2 && [ ! -e link1 -a -L link2 ] && readlink link2" \
+ "file1\n" "" ""
unlink tLink2 &>/dev/null
rm -f file* link*
touch file1
ln file1 link1
-testing "link new_link (hardlink)" "mv link1 link2 &&
- [ ! -e link1 -a -f link2 -a file1 -ef link2 ] && echo 'yes'" "yes\n" "" ""
+testing "hard link to new hardlink" \
+ "mv link1 link2 && [ ! -e link1 -a -f link2 -a file1 -ef link2 ] && echo yes" \
+ "yes\n" "" ""
unlink link2 &>/dev/null
rm -f file* link*
touch file1
chmod a-r file1
-testing "file new_file (unreadable)" "mv file1 file2 &&
- [ ! -e file1 -a -f file2 ] && echo 'yes'" "yes\n" "" ""
+testing "file to unreadable file" \
+ "mv file1 file2 && [ ! -e file1 -a -f file2 ] && echo yes" \
+ "yes\n" "" ""
rm -f file*
touch file1
ln file1 link1
mkdir dir1
-testing "file link dir (hardlink)" "mv file1 link1 dir1 &&
- [ ! -e file1 -a ! -e link1 -a -f dir1/file1 -a -f dir1/link1 ] &&
- [ dir1/file1 -ef dir1/link1 ] && echo 'yes'" "yes\n" "" ""
+testing "file hardlink dir" \
+ "mv file1 link1 dir1 &&
+ [ ! -e file1 -a ! -e link1 -a -f dir1/file1 -a -f dir1/link1 ] &&
+ [ dir1/file1 -ef dir1/link1 ] && echo yes" \
+ "yes\n" "" ""
rm -rf file* link* dir*
mkdir -p dir1/dir2 dir3
touch dir1/dir2/file1 dir1/dir2/file2
-testing "dir1/dir2 dir3/new_dir" "mv dir1/dir2 dir3/dir4 &&
- [ ! -e dir1/dir2 -a -d dir3/dir4 -a -f dir3/dir4/file1 ] &&
- [ -f dir3/dir4/file2 ] && echo 'yes'" "yes\n" "" ""
+testing "dir to new dir" \
+ "mv dir1/dir2 dir3/new &&
+ [ ! -e dir1/dir2 -a -d dir3/new -a -f dir3/new/file1 ] &&
+ [ -f dir3/new/file2 ] && echo yes" \
+ "yes\n" "" ""
rm -rf file* dir*
mkdir dir1 dir2
-testing "dir new_dir (already exist)" "mv dir1 dir2 &&
- [ ! -e dir1 -a -d dir2/dir1 ] && echo 'yes'" "yes\n" "" ""
+testing "dir to existing dir" \
+ "mv dir1 dir2 && [ ! -e dir1 -a -d dir2/dir1 ] && echo yes" \
+ "yes\n" "" ""
rm -rf dir*
touch file1 file2
-testing "-f file new_file (exist)" "mv -f file1 file2 &&
- [ ! -e file1 -a -e file2 ] && echo 'yes'" "yes\n" "" ""
+chmod 400 file1 file2
+testing "force over unwritable" \
+ "mv -f file1 file2 && [ ! -e file1 -a -e file2 ] && echo yes" \
+ "yes\n" "" ""
+rm -f file*
+
+touch file1 file2
+testing "no clobber (dest exists)" \
+ "mv -n file1 file2 && [ -e file1 -a -e file2 ] && echo yes"\
+ "yes\n" "" ""
rm -f file*
+touch file1
+testing "no clobber (dest doesn't exist)" \
+ "mv -n file1 new-dest && [ ! -e file1 -a -e new-dest ] && echo yes"\
+ "yes\n" "" ""
+rm -f file*
+
+# If there is stdin, it prompts. If no stdin, it moves anyway and file2 won't
+# exist.
touch file1 file2
-testing "-n file new_file (exist)" "mv -n file1 file2 &&
- [ -e file1 -a -e file2 ] && echo 'yes'" "yes\n" "" ""
+chmod 400 file1 file2
+testing "mv over unwritable file: no stdin" \
+ "mv file2 file1 && [ -e file1 -a ! -e file2 ] && echo yes" \
+ "yes\n" "" ""
rm -f file*
touch file1 file2
chmod 400 file1 file2
-testing "file over unwritable file with no stdin" \
- "</dev/null mv file2 file1 && [ -e file -a ! -e file2 ] && echo 'yes'" \
- "yes\n" "" ""
+testing "mv over unwritable file: answered YES" \
+ "mv file2 file1 && [ -e file1 -a ! -e file2 ] && echo yes" \
+ "yes\n" "" "y\n"
+rm -f file*
+
+touch file1 file2
+chmod 400 file1 file2
+testing "mv over unwritable file: answered NO" \
+ "mv file2 file1 && [ -e file1 -a -e file2 ] && echo yes" \
+ "yes\n" "" "n\n"
+rm -f file*
+
+touch file1 file2
+testing "interactive: no stdin" \
+ "mv -i file2 file1 && [ -e file1 -a ! -e file2 ] && echo yes" \
+ "yes\n" "" ""
+rm -f file*
+
+touch file1 file2
+testing "interactive: answered YES" \
+ "mv -i file2 file1 && [ -e file1 -a ! -e file2 ] && echo yes" \
+ "yes\n" "" "y\n"
+rm -f file*
+
+touch file1 file2
+testing "interactive: answered NO" \
+ "mv -i file2 file1 && [ -e file1 -a -e file2 ] && echo yes" \
+ "yes\n" "" "n\n"
rm -f file*
diff --git a/tests/sed.test b/tests/sed.test
index 7a4a8cda..822cfa37 100755
--- a/tests/sed.test
+++ b/tests/sed.test
@@ -127,6 +127,7 @@ testing "blank pattern repeats last pattern" \
testing "" "sed -e '1a\' -e 'huh'" "meep\nhuh\n" "" "meep"
testing "" "sed -f input" "blah\nboom\n" '1a\\\nboom' 'blah'
+testing "" "sed -f - input" "blah\nboom\n" 'blah' '1a\\\nboom'
testing "" "sed '1a\
hello'" "merp\nhello\n" "" "merp"
diff --git a/tests/sh.test b/tests/sh.test
new file mode 100755
index 00000000..4f1ecb2c
--- /dev/null
+++ b/tests/sh.test
@@ -0,0 +1,55 @@
+#!/bin/bash
+
+[ -f testing.sh ] && . testing.sh
+
+#testing "name" "command" "result" "infile" "stdin"
+
+shellit()
+{
+ EVAL="bash -c" testing "$2" "$1 printf %s $2" "$3" "$4" "$5"
+}
+
+# $'' expands special chars but doesn't do so inside double quotes.
+
+shellit "" "\$'a\\tb'" "a\tb" "" ""
+shellit "" "\"\$'a\\tb'\"" '$'"'"'a\\tb'"'" "" ""
+
+# $(( )) tests
+
+shellit 'x=1;' '$((-x))' '-1' '' ''
+shellit 'x=0;' '$((x++)); echo $x' '01\n' '' ''
+shellit 'x=0;' '$((++x))' '1' '' ''
+shellit 'x=0;' '$((~x))' '-1' '' ''
+shellit 'x=1;' '$((!x))' '0' '' ''
+shellit 'x=0;' '$((!x))' '1' '' ''
+shellit 'x=2;' '$((2*x))' '4' '' ''
+shellit 'x=9;' '$((x/4))' '2' '' ''
+shellit 'x=9;' '$((x%4))' '1' '' ''
+shellit 'x=4;' '$((x+2))' '6' '' ''
+shellit 'x=4;' '$((x-2))' '2' '' ''
+shellit 'x=4;' '$((1<<x))' '16' '' ''
+shellit 'x=4;' '$((x>>1))' '2' '' ''
+shellit '' '$((3**4))' '81' '' ''
+shellit '' '$((3<=4))' '1' '' ''
+shellit '' '$((3>=4))' '0' '' ''
+shellit '' '$((3<4))' '1' '' ''
+shellit '' '$((3>4))' '0' '' ''
+shellit '' '$((3==4))' '0' '' ''
+shellit '' '$((3!=4))' '1' '' ''
+shellit '' '$((6&4))' '4' '' ''
+shellit '' '$((4|2))' '6' '' ''
+shellit '' '$((6&&2))' '1' '' ''
+shellit '' '$((6||4))' '1' '' ''
+shellit '' '$((1?2:3))' '2' '' ''
+shellit 'x=2;' '$((x=3)); echo $x' '33\n' '' ''
+shellit 'x=2;' '$((x*=3)); echo $x' '66\n' '' ''
+shellit 'x=5;' '$((x/=2)); echo $x' '22\n' '' ''
+shellit 'x=9;' '$((x%=5)); echo $x' '44\n' '' ''
+shellit 'x=9;' '$((x-=3)); echo $x' '66\n' '' ''
+shellit 'x=3;' '$((x+=2)); echo $x' '55\n' '' ''
+shellit 'x=7;' '$((x&=13)); echo $x' '55\n' '' ''
+shellit 'x=5;' '$((x|=12)); echo $x' '1313\n' '' ''
+shellit 'x=5;' '$((x^=12)); echo $x' '99\n' '' ''
+shellit 'x=2;' '$((x<<=2)); echo $x' '88\n' '' ''
+shellit 'x=12;' '$((x>>=2)); echo $x' '33\n' '' ''
+shellit 'x=2;' '$((x++,5)); echo $x' '53\n' '' ''
diff --git a/tests/tail.test b/tests/tail.test
index c1c44c6a..81dc871f 100755
--- a/tests/tail.test
+++ b/tests/tail.test
@@ -20,6 +20,7 @@ testing "-c out of bounds" "tail -c 999 file1" "$BIGTEST" "" ""
testing "-c+ in bounds" "tail -c +27 file1" \
"x\nseven\neight\nnine\nten\neleven\n" "" ""
testing "-c+ out of bonds" "tail -c +999 file1" "" "" ""
+testing "-N" "tail -1 file1" "eleven\n" "" ""
rm file1
testing "stdin no trailing newline" "tail -n 1 - " "c" "" "a\nb\nc"
diff --git a/toys/other/rev.c b/toys/other/rev.c
index 4cf7214f..15066310 100644
--- a/toys/other/rev.c
+++ b/toys/other/rev.c
@@ -20,11 +20,11 @@ static void do_rev(int fd, char *name)
char *c;
for (;;) {
- int len, i;
+ unsigned len, i;
if (!(c = get_line(fd))) break;
- len = strlen(c) - 1;
- for (i = 0; i <= len/2; i++) {
+ len = strlen(c);
+ if (len--) for (i = 0; i <= len/2; i++) {
char tmp = c[i];
c[i] = c[len-i];
diff --git a/toys/pending/diff.c b/toys/pending/diff.c
index da6c13a0..53bdbce3 100644
--- a/toys/pending/diff.c
+++ b/toys/pending/diff.c
@@ -59,7 +59,7 @@ struct diff {
long a, b, c, d, prev, suff;
};
-static struct dir {
+static struct dir_t {
char **list;
int nr_elm;
} dir[2];
@@ -69,7 +69,7 @@ struct candidate {
struct candidate *prev, *next;
};
-static struct file {
+static struct file_t {
FILE *fp;
int len;
} file[2];
@@ -797,7 +797,7 @@ void diff_main(void)
if (S_ISDIR(st[0].st_mode) && S_ISDIR(st[1].st_mode)) {
for (j = 0; j < 2; j++) {
- memset(&dir[j], 0, sizeof(dir));
+ memset(&dir[j], 0, sizeof(struct dir_t));
dirtree_flagread(files[j], DIRTREE_SYMFOLLOW, list_dir);
dir[j].nr_elm = TT.size; //size updated in list_dir
qsort(&(dir[j].list[1]), (TT.size - 1), sizeof(char*), cmp);
diff --git a/toys/pending/modprobe.c b/toys/pending/modprobe.c
index 6813dec3..7a35c186 100644
--- a/toys/pending/modprobe.c
+++ b/toys/pending/modprobe.c
@@ -70,7 +70,7 @@ static char *path2mod(char *file, char *mod)
if (!file) return NULL;
if (!mod) mod = xmalloc(MODNAME_LEN);
- from = basename_r(file);
+ from = getbasename(file);
for (i = 0; i < (MODNAME_LEN-1) && from[i] && from[i] != '.'; i++)
mod[i] = (from[i] == '-') ? '_' : from[i];
diff --git a/toys/pending/netstat.c b/toys/pending/netstat.c
index d6acd7a3..02ab4fc2 100644
--- a/toys/pending/netstat.c
+++ b/toys/pending/netstat.c
@@ -440,7 +440,7 @@ static void scan_pid(int pid)
if ((p = strchr(line, ' '))) *p = 0; // "/bin/netstat -ntp" -> "/bin/netstat"
snprintf(TT.current_name, sizeof(TT.current_name), "%d/%s",
- pid, basename_r(line)); // "584/netstat"
+ pid, getbasename(line)); // "584/netstat"
free(line);
fd_dir = xmprintf("/proc/%d/fd", pid);
diff --git a/toys/pending/sh.c b/toys/pending/sh.c
index e221960a..76b4e13b 100644
--- a/toys/pending/sh.c
+++ b/toys/pending/sh.c
@@ -19,7 +19,23 @@
*
* Things like the bash man page are good to read too.
*
+ * TODO: "make sh" doesn't work (nofork builtins need to be included)
+ * TODO: test that $PS1 color changes work without stupid \[ \] hack
+ * TODO: make fake pty wrapper for test infrastructure
* TODO: // Handle embedded NUL bytes in the command line.
+ * TODO: var=val command
+ * existing but considered builtins: false kill pwd true
+ * buitins: alias bg command fc fg getopts jobs newgrp read umask unalias wait
+ * "special" builtins: break continue : . eval exec export readonly return set
+ * shift times trap unset
+ * | & ; < > ( ) $ ` \ " ' <space> <tab> <newline>
+ * * ? [ # ~ = %
+ * ! { } case do done elif else esac fi for if in then until while
+ * [[ ]] function select
+ * $@ $* $# $? $- $$ $! $0
+ * ENV HOME IFS LANG LC_ALL LINENO PATH PPID PS1 PS2 PS4 PWD
+ * label:
+ * TODO: test exit from "trap EXIT" doesn't recurse
USE_SH(NEWTOY(cd, NULL, TOYFLAG_NOFORK))
USE_SH(NEWTOY(exit, NULL, TOYFLAG_NOFORK))
@@ -42,16 +58,6 @@ config SH
-c command line to execute
-i interactive mode (default when STDIN is a tty)
-config EXIT
- bool
- default n
- depends on SH
- help
- usage: exit [status]
-
- Exit shell. If no return value supplied on command line, use value
- of most recent command, or 0 if none.
-
config CD
bool
default n
@@ -63,112 +69,16 @@ config CD
-P Physical path: resolve symlinks in path.
-L Local path: .. trims directories off $PWD (default).
-*/
-
-/*
-This level of micromanagement is silly, it adds more complexity than it's
-worth. (Not just to the code, but decision fatigue configuring it.)
-
-That said, the following list is kept for the moment as a todo list of
-features I need to implement.
-
-config SH_PROFILE
- bool "Profile support"
- default n
- depends on SH_TTY
- help
- Read /etc/profile and ~/.profile when running interactively.
-
- Also enables the built-in command "source".
-
-config SH_JOBCTL
- bool "Job Control (fg, bg, jobs)"
- default n
- depends on SH_TTY
- help
- Add job control to toysh. This lets toysh handle CTRL-Z, and enables
- the built-in commands "fg", "bg", and "jobs".
-
- With pipe support, enable use of "&" to run background processes.
-
-config SH_FLOWCTL
- bool "Flow control (if, while, for, functions)"
- default n
- depends on SH
- help
- Add flow control to toysh. This enables the if/then/else/fi,
- while/do/done, and for/do/done constructs.
-
- With pipe support, this enables the ability to define functions
- using the "function name" or "name()" syntax, plus curly brackets
- "{ }" to group commands.
-
-config SH_QUOTES
- bool "Smarter argument parsing (quotes)"
- default n
- depends on SH
- help
- Add support for parsing "" and '' style quotes to the toysh command
- parser, with lets arguments have spaces in them.
-
-config SH_WILDCARDS
- bool "Wildcards ( ?*{,} )"
- default n
- depends on SH_QUOTES
- help
- Expand wildcards in argument names, ala "ls -l *.t?z" and
- "rm subdir/{one,two,three}.txt".
-
-config SH_PROCARGS
- bool "Executable arguments ( `` and $() )"
- default n
- depends on SH_QUOTES
- help
- Add support for executing arguments contianing $() and ``, using
- the output of the command as the new argument value(s).
-
- (Bash calls this "command substitution".)
-
-config SH_ENVVARS
- bool "Environment variable support"
- default n
- depends on SH_QUOTES
- help
- Substitute environment variable values for $VARNAME or ${VARNAME},
- and enable the built-in command "export".
-
-config SH_LOCALS
- bool "Local variables"
- default n
- depends on SH_ENVVARS
- help
- Support for local variables, fancy prompts ($PS1), the "set" command,
- and $?.
-
-config SH_ARRAYS
- bool "Array variables"
- default n
- depends on SH_LOCALS
- help
- Support for ${blah[blah]} style array variables.
-config SH_PIPES
- bool "Pipes and redirects ( | > >> < << & && | || () ; )"
+config EXIT
+ bool
default n
depends on SH
help
- Support multiple commands on the same command line. This includes
- | pipes, > >> < redirects, << here documents, || && conditional
- execution, () subshells, ; sequential execution, and (with job
- control) & background processes.
+ usage: exit [status]
-config SH_BUILTINS
- bool "Builtin commands"
- default n
- depends on SH
- help
- Adds the commands exec, fg, bg, help, jobs, pwd, export, source, set,
- unset, read, alias.
+ Exit shell. If no return value supplied on command line, use value
+ of most recent command, or 0 if none.
*/
#define FOR_sh
@@ -176,17 +86,9 @@ config SH_BUILTINS
GLOBALS(
char *command;
-)
-// A single executable, its arguments, and other information we know about it.
-#define SH_FLAG_EXIT 1
-#define SH_FLAG_SUSPEND 2
-#define SH_FLAG_PIPE 4
-#define SH_FLAG_AND 8
-#define SH_FLAG_OR 16
-#define SH_FLAG_AMP 32
-#define SH_FLAG_SEMI 64
-#define SH_FLAG_PAREN 128
+ long lineno;
+)
// What we know about a single process.
struct command {
@@ -206,6 +108,18 @@ struct pipeline {
int cmdlinelen; // How long is cmdline?
};
+void cd_main(void)
+{
+ char *dest = *toys.optargs ? *toys.optargs : getenv("HOME");
+
+ xchdir(dest ? dest : "/");
+}
+
+void exit_main(void)
+{
+ exit(*toys.optargs ? atoi(*toys.optargs) : 0);
+}
+
// Parse one word from the command line, appending one or more argv[] entries
// to struct command. Handles environment variable substitution and
// substrings. Returns pointer to next used byte, or NULL if it
@@ -287,6 +201,7 @@ static void run_pipeline(struct pipeline *line)
if (!cmd || !cmd->argc) return;
tl = toy_find(cmd->argv[0]);
+
// Is this command a builtin that should run in this process?
if (tl && (tl->flags & TOYFLAG_NOFORK)) {
struct toy_context temp;
@@ -351,34 +266,65 @@ static void handle(char *command)
}
}
-void cd_main(void)
+static void do_prompt(void)
{
- char *dest = *toys.optargs ? *toys.optargs : getenv("HOME");
+ char *prompt = getenv("PS1"), *s, c, cc;
- xchdir(dest ? dest : "/");
-}
+ if (!prompt) prompt = "\\$ ";
+ while (*prompt) {
+ c = *(prompt++);
-void exit_main(void)
-{
- exit(*toys.optargs ? atoi(*toys.optargs) : 0);
+ if (c=='!') {
+ if (*prompt=='!') prompt++;
+ else {
+ printf("%ld", TT.lineno);
+ continue;
+ }
+ } else if (c=='\\') {
+ cc = *(prompt++);
+ if (!cc) goto down;
+
+ // \nnn \dD{}hHjlstT@AuvVwW!#$
+ // Ignore bash's "nonprintable" hack; query our cursor position instead.
+ if (cc=='[' || cc==']') continue;
+ else if (cc=='$') putchar(getuid() ? '$' : '#');
+ else if (cc=='h' || cc=='H') {
+ *toybuf = 0;
+ gethostname(toybuf, sizeof(toybuf)-1);
+ if (cc=='h' && (s = strchr(toybuf, '.'))) *s = 0;
+ fputs(toybuf, stdout);
+ } else if (cc=='s') fputs(getbasename(*toys.argv), stdout);
+ else {
+ if (!(c = unescape(cc))) {
+ c = '\\';
+ prompt--;
+ }
+
+ goto down;
+ }
+ continue;
+ }
+down:
+ putchar(c);
+ }
}
void sh_main(void)
{
- FILE *f;
+ FILE *f = 0;
// Set up signal handlers and grab control of this tty.
if (isatty(0)) toys.optflags |= FLAG_i;
- f = *toys.optargs ? xfopen(*toys.optargs, "r") : NULL;
- if (TT.command) handle(TT.command);
+ if (*toys.optargs) f = xfopen(*toys.optargs, "r");
+ if (TT.command) handle(xstrdup(TT.command));
else {
size_t cmdlen = 0;
for (;;) {
- char *prompt = getenv("PS1"), *command = 0;
+ char *command = 0;
// TODO: parse escapes in prompt
- if (!f) printf("%s", prompt ? prompt : "$ ");
+ if (!f) do_prompt();
if (1 > getline(&command, &cmdlen, f ? f : stdin)) break;
handle(command);
free(command);
diff --git a/toys/posix/cp.c b/toys/posix/cp.c
index d822b1e2..ea1ef6f4 100644
--- a/toys/posix/cp.c
+++ b/toys/posix/cp.c
@@ -395,20 +395,23 @@ void cp_main(void)
errno = EXDEV;
if (CFG_MV && toys.which->name[0] == 'm') {
- if (!(toys.optflags & FLAG_f)) {
+ int force = toys.optflags & FLAG_f, no_clobber = toys.optflags & FLAG_n;
+
+ if (!force || no_clobber) {
struct stat st;
+ int exists = !stat(TT.destname, &st);
- // Technically "is writeable" is more complicated (022 is not writeable
- // by the owner, just everybody _else_) but I don't care.
- if (!stat(TT.destname, &st)
- && ((toys.optflags & FLAG_i) || !(st.st_mode & 0222)))
- {
+ // Prompt if -i or file isn't writable. Technically "is writable" is
+ // more complicated (022 is not writeable by the owner, just everybody
+ // _else_) but I don't care.
+ if (exists && ((toys.optflags & FLAG_i) || !(st.st_mode & 0222))) {
fprintf(stderr, "%s: overwrite '%s'", toys.which->name, TT.destname);
if (!yesno(1)) rc = 0;
else unlink(TT.destname);
}
+ // if -n and dest exists, don't try to rename() or copy
+ if (exists && no_clobber) rc = 0;
}
-
if (rc) rc = rename(src, TT.destname);
}
diff --git a/toys/posix/ps.c b/toys/posix/ps.c
index 26b4a4ea..5235a64a 100644
--- a/toys/posix/ps.c
+++ b/toys/posix/ps.c
@@ -1144,6 +1144,8 @@ static int header_line(int line, int rev)
{
if (!line) return 0;
+ if (toys.optflags&FLAG_b) rev = 0;
+
printf("%s%*.*s%s\r\n", rev ? "\033[7m" : "",
(toys.optflags&FLAG_b) ? 0 : -TT.width, TT.width, toybuf,
rev ? "\033[0m" : "");
@@ -1339,7 +1341,8 @@ static void top_common(
*pos = 0;
lines = header_line(lines, 1);
}
- if (!recalc) printf("\033[%dH\033[J", 1+TT.height-lines);
+ if (!recalc && !(toys.optflags&FLAG_b))
+ printf("\033[%dH\033[J", 1+TT.height-lines);
recalc = 1;
for (i = 0; i<lines && i+topoff<mix.count; i++) {
@@ -1357,6 +1360,14 @@ static void top_common(
if (timeout<=now) timeout = new.whence+TT.top.d;
if (timeout<=now || timeout>now+TT.top.d) timeout = now+TT.top.d;
+ // In batch mode, we ignore the keyboard.
+ if (toys.optflags&FLAG_b) {
+ msleep(timeout-now);
+ // Make an obvious gap between datasets.
+ xputs("\n\n\n");
+ continue;
+ }
+
i = scan_key_getsize(scratch, timeout-now, &TT.width, &TT.height);
if (i==-1 || i==3 || toupper(i)=='Q') {
done++;
@@ -1406,7 +1417,7 @@ static void top_setup(char *defo, char *defk)
TT.top.d *= 1000;
if (toys.optflags&FLAG_b) TT.width = TT.height = 99999;
else {
- xset_terminal(0, 1, 0);
+ set_terminal(0, 1, 0);
sigatexit(tty_sigreset);
xsignal(SIGWINCH, generic_signal);
printf("\033[?25l\033[0m");
diff --git a/toys/posix/sed.c b/toys/posix/sed.c
index e1c00bab..7da519d0 100644
--- a/toys/posix/sed.c
+++ b/toys/posix/sed.c
@@ -182,13 +182,20 @@ GLOBALS(
unsigned xx;
)
-struct step {
- struct step *next, *prev;
+// Linked list of parsed sed commands. Offset fields indicate location where
+// regex or string starts, ala offset+(char *)struct, because we remalloc()
+// these to expand them for multiline inputs, and pointers would have to be
+// individually adjusted.
+
+struct sedcmd {
+ struct sedcmd *next, *prev;
// Begin and end of each match
- long lmatch[2];
- int rmatch[2], arg1, arg2, w; // offsets because remalloc()
- unsigned not, hit, sflags;
+ long lmatch[2]; // line number of match
+ int rmatch[2]; // offset of regex struct for prefix matches (/abc/,/def/p)
+ int arg1, arg2, w; // offset of two arguments per command, plus s//w filename
+ unsigned not, hit;
+ unsigned sflags; // s///flag bits: i=1, g=2, p=4
char c; // action
};
@@ -214,8 +221,9 @@ static int emit(char *line, long len, int eol)
// Do regex matching handling embedded NUL bytes in string. Note that
// neither the pattern nor the match can currently include NUL bytes
-// (even with wildcards) and string must be null terminated.
-static int ghostwheel(regex_t *preg, char *string, long len, int nmatch,
+// (even with wildcards) and string must be null terminated at string[len].
+// But this can find a match after the first NUL.
+static int regex_null(regex_t *preg, char *string, long len, int nmatch,
regmatch_t pmatch[], int eflags)
{
char *s = string;
@@ -274,7 +282,7 @@ static void *get_regex(void *trump, int offset)
}
// Apply pattern to line from input file
-static void walk_pattern(char **pline, long plen)
+static void process_line(char **pline, long plen)
{
struct append {
struct append *next, *prev;
@@ -283,7 +291,7 @@ static void walk_pattern(char **pline, long plen)
} *append = 0;
char *line = TT.nextline;
long len = TT.nextlen;
- struct step *logrus;
+ struct sedcmd *command;
int eol = 0, tea = 0;
// Grab next line for deferred processing (EOF detection: we get a NULL
@@ -303,45 +311,45 @@ static void walk_pattern(char **pline, long plen)
// The restart-1 is because we added one to make sure it wasn't NULL,
// otherwise N as last command would restart script
- logrus = TT.restart ? ((struct step *)TT.restart)-1 : (void *)TT.pattern;
+ command = TT.restart ? ((struct sedcmd *)TT.restart)-1 : (void *)TT.pattern;
TT.restart = 0;
- while (logrus) {
- char *str, c = logrus->c;
+ while (command) {
+ char *str, c = command->c;
// Have we got a line or regex matching range for this rule?
- if (*logrus->lmatch || *logrus->rmatch) {
+ if (*command->lmatch || *command->rmatch) {
int miss = 0;
long lm;
// In a match that might end?
- if (logrus->hit) {
- if (!(lm = logrus->lmatch[1])) {
- if (!logrus->rmatch[1]) logrus->hit = 0;
+ if (command->hit) {
+ if (!(lm = command->lmatch[1])) {
+ if (!command->rmatch[1]) command->hit = 0;
else {
- void *rm = get_regex(logrus, logrus->rmatch[1]);
+ void *rm = get_regex(command, command->rmatch[1]);
// regex match end includes matching line, so defer deactivation
- if (line && !ghostwheel(rm, line, len, 0, 0, 0)) miss = 1;
+ if (line && !regex_null(rm, line, len, 0, 0, 0)) miss = 1;
}
- } else if (lm > 0 && lm < TT.count) logrus->hit = 0;
+ } else if (lm > 0 && lm < TT.count) command->hit = 0;
// Start a new match?
} else {
- if (!(lm = *logrus->lmatch)) {
- void *rm = get_regex(logrus, *logrus->rmatch);
+ if (!(lm = *command->lmatch)) {
+ void *rm = get_regex(command, *command->rmatch);
- if (line && !ghostwheel(rm, line, len, 0, 0, 0)) logrus->hit++;
- } else if (lm == TT.count || (lm == -1 && !pline)) logrus->hit++;
+ if (line && !regex_null(rm, line, len, 0, 0, 0)) command->hit++;
+ } else if (lm == TT.count || (lm == -1 && !pline)) command->hit++;
- if (!logrus->lmatch[1] && !logrus->rmatch[1]) miss = 1;
+ if (!command->lmatch[1] && !command->rmatch[1]) miss = 1;
}
// Didn't match?
- lm = !(logrus->hit ^ logrus->not);
+ lm = !(command->hit ^ command->not);
// Deferred disable from regex end match
- if (miss || logrus->lmatch[1] == TT.count) logrus->hit = 0;
+ if (miss || command->lmatch[1] == TT.count) command->hit = 0;
if (lm) {
// Handle skipping curly bracket command group
@@ -349,19 +357,19 @@ static void walk_pattern(char **pline, long plen)
int curly = 1;
while (curly) {
- logrus = logrus->next;
- if (logrus->c == '{') curly++;
- if (logrus->c == '}') curly--;
+ command = command->next;
+ if (command->c == '{') curly++;
+ if (command->c == '}') curly--;
}
}
- logrus = logrus->next;
+ command = command->next;
continue;
}
}
// A deleted line can still update line match state for later commands
if (!line) {
- logrus = logrus->next;
+ command = command->next;
continue;
}
@@ -369,7 +377,7 @@ static void walk_pattern(char **pline, long plen)
if (c=='a' || c=='r') {
struct append *a = xzalloc(sizeof(struct append));
- if (logrus->arg1) a->str = logrus->arg1+(char *)logrus;
+ if (command->arg1) a->str = command->arg1+(char *)command;
a->file = c=='r';
dlist_add_nomalloc((void *)&append, (void *)a);
} else if (c=='b' || c=='t' || c=='T') {
@@ -377,16 +385,16 @@ static void walk_pattern(char **pline, long plen)
if (c != 'b') tea = 0;
if (c=='b' || t^(c=='T')) {
- if (!logrus->arg1) break;
- str = logrus->arg1+(char *)logrus;
- for (logrus = (void *)TT.pattern; logrus; logrus = logrus->next)
- if (logrus->c == ':' && !strcmp(logrus->arg1+(char *)logrus, str))
+ if (!command->arg1) break;
+ str = command->arg1+(char *)command;
+ for (command = (void *)TT.pattern; command; command = command->next)
+ if (command->c == ':' && !strcmp(command->arg1+(char *)command, str))
break;
- if (!logrus) error_exit("no :%s", str);
+ if (!command) error_exit("no :%s", str);
}
} else if (c=='c') {
- str = logrus->arg1+(char *)logrus;
- if (!logrus->hit) emit(str, strlen(str), 1);
+ str = command->arg1+(char *)command;
+ if (!command->hit) emit(str, strlen(str), 1);
free(line);
line = 0;
continue;
@@ -408,7 +416,7 @@ static void walk_pattern(char **pline, long plen)
line = 0;
} else {
line[len] = 0;
- logrus = (void *)TT.pattern;
+ command = (void *)TT.pattern;
}
continue;
} else if (c=='g') {
@@ -430,7 +438,7 @@ static void walk_pattern(char **pline, long plen)
memcpy(TT.remember+TT.rememberlen, line, len);
TT.remember[TT.rememberlen += len] = 0;
} else if (c=='i') {
- str = logrus->arg1+(char *)logrus;
+ str = command->arg1+(char *)command;
emit(str, strlen(str), 1);
} else if (c=='l') {
int i, x, off;
@@ -458,14 +466,14 @@ static void walk_pattern(char **pline, long plen)
toybuf[off++] = '$';
emit(toybuf, off, 1);
} else if (c=='n') {
- TT.restart = logrus->next+1;
+ TT.restart = command->next+1;
break;
} else if (c=='N') {
// Can't just grab next line because we could have multiple N and
// we need to actually read ahead to get N;$p EOF detection right.
if (pline) {
- TT.restart = logrus->next+1;
+ TT.restart = command->next+1;
extend_string(&line, TT.nextline, len, -TT.nextlen);
free(TT.nextline);
TT.nextline = line;
@@ -487,13 +495,13 @@ static void walk_pattern(char **pline, long plen)
break;
} else if (c=='s') {
- char *rline = line, *new = logrus->arg2 + (char *)logrus, *swap, *rswap;
+ char *rline = line, *new = command->arg2 + (char *)command, *swap, *rswap;
regmatch_t *match = (void *)toybuf;
- regex_t *reg = get_regex(logrus, logrus->arg1);
+ regex_t *reg = get_regex(command, command->arg1);
int mflags = 0, count = 0, zmatch = 1, rlen = len, mlen, off, newlen;
// Find match in remaining line (up to remaining len)
- while (!ghostwheel(reg, rline, rlen, 10, match, mflags)) {
+ while (!regex_null(reg, rline, rlen, 10, match, mflags)) {
mflags = REG_NOTBOL;
// Zero length matches don't count immediately after a previous match
@@ -506,7 +514,7 @@ static void walk_pattern(char **pline, long plen)
} else zmatch = 0;
// If we're replacing only a specific match, skip if this isn't it
- off = logrus->sflags>>3;
+ off = command->sflags>>3;
if (off && off != ++count) {
rline += match[0].rm_eo;
rlen -= match[0].rm_eo;
@@ -566,15 +574,15 @@ static void walk_pattern(char **pline, long plen)
line = swap;
// Stop after first substitution unless we have flag g
- if (!(logrus->sflags & 2)) break;
+ if (!(command->sflags & 2)) break;
}
if (mflags) {
// flag p
- if (logrus->sflags & 4) emit(line, len, eol);
+ if (command->sflags & 4) emit(line, len, eol);
tea = 1;
- if (logrus->w) goto writenow;
+ if (command->w) goto writenow;
}
} else if (c=='w') {
int fd, noeol;
@@ -586,14 +594,14 @@ writenow:
noeol = TT.noeol;
// We save filehandle and newline status before filename
- name = logrus->w + (char *)logrus;
+ name = command->w + (char *)command;
memcpy(&TT.fdout, name, 4);
name += 4;
TT.noeol = *(name++);
// write, then save/restore context
if (emit(line, len, eol))
- perror_exit("w '%s'", logrus->arg1+(char *)logrus);
+ perror_exit("w '%s'", command->arg1+(char *)command);
*(--name) = TT.noeol;
TT.noeol = noeol;
TT.fdout = fd;
@@ -606,11 +614,11 @@ writenow:
TT.rememberlen = len;
len = swap;
} else if (c=='y') {
- char *from, *to = (char *)logrus;
+ char *from, *to = (char *)command;
int i, j;
- from = to+logrus->arg1;
- to += logrus->arg2;
+ from = to+command->arg1;
+ to += command->arg2;
for (i = 0; i < len; i++) {
j = stridx(from, line[i]);
@@ -621,7 +629,7 @@ writenow:
emit(toybuf, strlen(toybuf), 1);
}
- logrus = logrus->next;
+ command = command->next;
}
if (line && !(toys.optflags & FLAG_n)) emit(line, len, eol);
@@ -680,7 +688,7 @@ static void do_sed(int fd, char *name)
char *tmp;
if (i) {
- struct step *primal;
+ struct sedcmd *command;
if (!fd && !strcmp(name, "-")) {
error_msg("-i on stdin");
@@ -688,12 +696,12 @@ static void do_sed(int fd, char *name)
}
TT.fdout = copy_tempfile(fd, name, &tmp);
TT.count = 0;
- for (primal = (void *)TT.pattern; primal; primal = primal->next)
- primal->hit = 0;
+ for (command = (void *)TT.pattern; command; command = command->next)
+ command->hit = 0;
}
- do_lines(fd, name, walk_pattern);
+ do_lines(fd, name, process_line);
if (i) {
- walk_pattern(0, 0);
+ process_line(0, 0);
replace_tempfile(-1, TT.fdout, &tmp);
TT.fdout = 1;
TT.nextline = 0;
@@ -755,37 +763,43 @@ static char *unescape_delimited_string(char **pstr, char *delim)
return delim;
}
-// Translate primal pattern into walkable form.
-static void jewel_of_judgement(char **pline, long len)
+// Translate pattern strings into command structures. Each command structure
+// is a single allocation (which requires some math and remalloc at times).
+static void parse_pattern(char **pline, long len)
{
- struct step *corwin = (void *)TT.pattern;
+ struct sedcmd *command = (void *)TT.pattern;
char *line, *reg, c, *errstart;
int i;
line = errstart = pline ? *pline : "";
if (len && line[len-1]=='\n') line[--len] = 0;
- // Append additional line to pattern argument string?
- // We temporarily repurpose "hit" to indicate line continuations
- if (corwin && corwin->prev->hit) {
+ // Append this line to previous multiline command? (hit indicates type.)
+ // During parsing "hit" stores data about line continuations, but in
+ // process_line() it means the match range attached to this command
+ // is active, so processing the continuation must zero it again.
+ if (command && command->prev->hit) {
// Remove half-finished entry from list so remalloc() doesn't confuse it
TT.pattern = TT.pattern->prev;
- corwin = dlist_pop(&TT.pattern);
- c = corwin->c;
- reg = (char *)corwin;
- reg += corwin->arg1 + strlen(reg + corwin->arg1);
-
- // Resume parsing for 'a' or 's' command
- if (corwin->hit < 256) goto resume_s;
+ command = dlist_pop(&TT.pattern);
+ c = command->c;
+ reg = (char *)command;
+ reg += command->arg1 + strlen(reg + command->arg1);
+
+ // Resume parsing for 'a' or 's' command. (Only two that can do this.)
+ // TODO: using 256 to indicate 'a' means our s/// delimiter can't be
+ // a unicode character.
+ if (command->hit < 256) goto resume_s;
else goto resume_a;
}
- // Loop through commands in line
+ // Loop through commands in this line.
- corwin = 0;
+ command = 0;
for (;;) {
- if (corwin) dlist_add_nomalloc(&TT.pattern, (void *)corwin);
+ if (command) dlist_add_nomalloc(&TT.pattern, (void *)command);
+ // If there's no more data on this line, return.
for (;;) {
while (isspace(*line) || *line == ';') line++;
if (*line == '#') while (*line && *line != '\n') line++;
@@ -793,28 +807,31 @@ static void jewel_of_judgement(char **pline, long len)
}
if (!*line) return;
+ // We start by writing data into toybuf. Later we'll allocate the
+ // ex
+
errstart = line;
- memset(toybuf, 0, sizeof(struct step));
- corwin = (void *)toybuf;
- reg = toybuf + sizeof(struct step);
+ memset(toybuf, 0, sizeof(struct sedcmd));
+ command = (void *)toybuf;
+ reg = toybuf + sizeof(struct sedcmd);
// Parse address range (if any)
for (i = 0; i < 2; i++) {
if (*line == ',') line++;
else if (i) break;
- if (isdigit(*line)) corwin->lmatch[i] = strtol(line, &line, 0);
+ if (isdigit(*line)) command->lmatch[i] = strtol(line, &line, 0);
else if (*line == '$') {
- corwin->lmatch[i] = -1;
+ command->lmatch[i] = -1;
line++;
} else if (*line == '/' || *line == '\\') {
char *s = line;
- if (!(s = unescape_delimited_string(&line, 0))) goto brand;
- if (!*s) corwin->rmatch[i] = 0;
+ if (!(s = unescape_delimited_string(&line, 0))) goto error;
+ if (!*s) command->rmatch[i] = 0;
else {
xregcomp((void *)reg, s, (toys.optflags & FLAG_r)*REG_EXTENDED);
- corwin->rmatch[i] = reg-toybuf;
+ command->rmatch[i] = reg-toybuf;
reg += sizeof(regex_t);
}
free(s);
@@ -825,58 +842,59 @@ static void jewel_of_judgement(char **pline, long len)
if (!*line) break;
while (*line == '!') {
- corwin->not = 1;
+ command->not = 1;
line++;
}
while (isspace(*line)) line++;
- c = corwin->c = *(line++);
+ c = command->c = *(line++);
if (strchr("}:", c) && i) break;
if (strchr("aiqr=", c) && i>1) break;
// Add step to pattern
- corwin = xmemdup(toybuf, reg-toybuf);
- reg = (reg-toybuf) + (char *)corwin;
+ command = xmemdup(toybuf, reg-toybuf);
+ reg = (reg-toybuf) + (char *)command;
// Parse arguments by command type
if (c == '{') TT.nextlen++;
else if (c == '}') {
if (!TT.nextlen--) break;
} else if (c == 's') {
- char *fiona, delim = 0;
+ char *end, delim = 0;
// s/pattern/replacement/flags
- // line continuations use arg1, so we fill out arg2 first (since the
- // regex part can't be multiple lines) and swap them back later.
+ // line continuations use arg1 (back at the start of the function),
+ // so let's fill out arg2 first (since the regex part can't be multiple
+ // lines) and swap them back later.
// get pattern (just record, we parse it later)
- corwin->arg2 = reg - (char *)corwin;
+ command->arg2 = reg - (char *)command;
if (!(TT.remember = unescape_delimited_string(&line, &delim)))
- goto brand;
+ goto error;
reg += sizeof(regex_t);
- corwin->arg1 = reg-(char *)corwin;
- corwin->hit = delim;
+ command->arg1 = reg-(char *)command;
+ command->hit = delim;
resume_s:
- // get replacement - don't replace escapes because \1 and \& need
+ // get replacement - don't replace escapes yet because \1 and \& need
// processing later, after we replace \\ with \ we can't tell \\1 from \1
- fiona = line;
- while (*fiona != corwin->hit) {
- if (!*fiona) goto brand;
- if (*fiona++ == '\\') {
- if (!*fiona || *fiona == '\n') {
- fiona[-1] = '\n';
+ end = line;
+ while (*end != command->hit) {
+ if (!*end) goto error;
+ if (*end++ == '\\') {
+ if (!*end || *end == '\n') {
+ end[-1] = '\n';
break;
}
- fiona++;
+ end++;
}
}
- reg = extend_string((void *)&corwin, line, reg-(char *)corwin,fiona-line);
- line = fiona;
+ reg = extend_string((void *)&command, line, reg-(char *)command,end-line);
+ line = end;
// line continuation? (note: '\n' can't be a valid delim).
- if (*line == corwin->hit) corwin->hit = 0;
+ if (*line == command->hit) command->hit = 0;
else {
if (!*line) continue;
reg--;
@@ -885,9 +903,9 @@ resume_s:
}
// swap arg1/arg2 so they're back in order arguments occur.
- i = corwin->arg1;
- corwin->arg1 = corwin->arg2;
- corwin->arg2 = i;
+ i = command->arg1;
+ command->arg1 = command->arg2;
+ command->arg2 = i;
// get flags
for (line++; *line; line++) {
@@ -895,18 +913,18 @@ resume_s:
if (isspace(*line) && *line != '\n') continue;
- if (0 <= (l = stridx("igp", *line))) corwin->sflags |= 1<<l;
- else if (!(corwin->sflags>>3) && 0<(l = strtol(line, &line, 10))) {
- corwin->sflags |= l << 3;
+ if (0 <= (l = stridx("igp", *line))) command->sflags |= 1<<l;
+ else if (!(command->sflags>>3) && 0<(l = strtol(line, &line, 10))) {
+ command->sflags |= l << 3;
line--;
} else break;
}
// We deferred actually parsing the regex until we had the s///i flag
// allocating the space was done by extend_string() above
- if (!*TT.remember) corwin->arg1 = 0;
- else xregcomp((void *)(corwin->arg1 + (char *)corwin), TT.remember,
- ((toys.optflags & FLAG_r)*REG_EXTENDED)|((corwin->sflags&1)*REG_ICASE));
+ if (!*TT.remember) command->arg1 = 0;
+ else xregcomp((void *)(command->arg1 + (char *)command), TT.remember,
+ ((toys.optflags & FLAG_r)*REG_EXTENDED)|((command->sflags&1)*REG_ICASE));
free(TT.remember);
TT.remember = 0;
if (*line == 'w') {
@@ -924,16 +942,16 @@ resume_s:
writenow:
while (isspace(*line)) line++;
- if (!*line) goto brand;
+ if (!*line) goto error;
for (cc = line; *cc; cc++) if (*cc == '\\' && cc[1] == ';') break;
delim = *cc;
*cc = 0;
fd = xcreate(line, O_WRONLY|O_CREAT|O_TRUNC, 0644);
*cc = delim;
- corwin->w = reg - (char *)corwin;
- corwin = xrealloc(corwin, corwin->w+(cc-line)+6);
- reg = corwin->w + (char *)corwin;
+ command->w = reg - (char *)command;
+ command = xrealloc(command, command->w+(cc-line)+6);
+ reg = command->w + (char *)command;
memcpy(reg, &fd, 4);
reg += 4;
@@ -948,15 +966,15 @@ writenow:
char *s, delim = 0;
int len;
- if (!(s = unescape_delimited_string(&line, &delim))) goto brand;
- corwin->arg1 = reg-(char *)corwin;
+ if (!(s = unescape_delimited_string(&line, &delim))) goto error;
+ command->arg1 = reg-(char *)command;
len = strlen(s);
- reg = extend_string((void *)&corwin, s, reg-(char *)corwin, len);
+ reg = extend_string((void *)&command, s, reg-(char *)command, len);
free(s);
- corwin->arg2 = reg-(char *)corwin;
- if (!(s = unescape_delimited_string(&line, &delim))) goto brand;
- if (len != strlen(s)) goto brand;
- reg = extend_string((void *)&corwin, s, reg-(char*)corwin, len);
+ command->arg2 = reg-(char *)command;
+ if (!(s = unescape_delimited_string(&line, &delim))) goto error;
+ if (len != strlen(s)) goto error;
+ reg = extend_string((void *)&command, s, reg-(char*)command, len);
free(s);
} else if (strchr("abcirtTw:", c)) {
int end;
@@ -967,25 +985,25 @@ writenow:
// Resume logic differs from 's' case because we don't add a newline
// unless it's after something, so we add it on return instead.
resume_a:
- corwin->hit = 0;
+ command->hit = 0;
// btT: end with space or semicolon, aicrw continue to newline.
if (!(end = strcspn(line, strchr(":btT", c) ? "; \t\r\n\v\f" : "\n"))) {
// Argument's optional for btT
if (strchr("btT", c)) continue;
- else if (!corwin->arg1) break;
+ else if (!command->arg1) break;
}
// Extend allocation to include new string. We use offsets instead of
// pointers so realloc() moving stuff doesn't break things. Ok to write
// \n over NUL terminator because call to extend_string() adds it back.
- if (!corwin->arg1) corwin->arg1 = reg - (char*)corwin;
- else if (*(corwin->arg1+(char *)corwin)) *(reg++) = '\n';
+ if (!command->arg1) command->arg1 = reg - (char*)command;
+ else if (*(command->arg1+(char *)command)) *(reg++) = '\n';
else if (!pline) {
- corwin->arg1 = 0;
+ command->arg1 = 0;
continue;
}
- reg = extend_string((void *)&corwin, line, reg - (char *)corwin, end);
+ reg = extend_string((void *)&command, line, reg - (char *)command, end);
// Recopy data to remove escape sequences and handle line continuation.
if (strchr("aci", c)) {
@@ -1001,7 +1019,7 @@ resume_a:
line++;
goto resume_a;
}
- corwin->hit = 256;
+ command->hit = 256;
break;
}
if (!(reg[-1] = unescape(*line))) reg[-1] = *line;
@@ -1015,14 +1033,13 @@ resume_a:
} else if (!strchr("{dDgGhHlnNpPqx=", c)) break;
}
-brand:
- // Reminisce about chestnut trees.
+error:
error_exit("bad pattern '%s'@%ld (%c)", errstart, line-errstart+1L, *line);
}
void sed_main(void)
{
- struct arg_list *dworkin;
+ struct arg_list *al;
char **args = toys.optargs;
// Lie to autoconf when it asks stupid questions, so configure regexes
@@ -1033,7 +1050,9 @@ void sed_main(void)
return;
}
- // Need a pattern. If no unicorns about, fight serpent and take its eye.
+ // Parse pattern into commands.
+
+ // If no -e or -f, first argument is the pattern.
if (!TT.e && !TT.f) {
if (!*toys.optargs) error_exit("no pattern");
(TT.e = xzalloc(sizeof(struct arg_list)))->arg = *(args++);
@@ -1042,11 +1061,11 @@ void sed_main(void)
// Option parsing infrastructure can't interlace "-e blah -f blah -e blah"
// so handle all -e, then all -f. (At least the behavior's consistent.)
- for (dworkin = TT.e; dworkin; dworkin = dworkin->next)
- jewel_of_judgement(&dworkin->arg, strlen(dworkin->arg));
- for (dworkin = TT.f; dworkin; dworkin = dworkin->next)
- do_lines(xopen(dworkin->arg, O_RDONLY), dworkin->arg, jewel_of_judgement);
- jewel_of_judgement(0, 0);
+ for (al = TT.e; al; al = al->next) parse_pattern(&al->arg, strlen(al->arg));
+ for (al = TT.f; al; al = al->next)
+ do_lines(strcmp(al->arg, "-") ? xopen(al->arg, O_RDONLY) : 0,
+ al->arg, parse_pattern);
+ parse_pattern(0, 0);
dlist_terminate(TT.pattern);
if (TT.nextlen) error_exit("no }");
@@ -1056,7 +1075,7 @@ void sed_main(void)
// Inflict pattern upon input files
loopfiles_rw(args, O_RDONLY, 0, 0, do_sed);
- if (!(toys.optflags & FLAG_i)) walk_pattern(0, 0);
+ if (!(toys.optflags & FLAG_i)) process_line(0, 0);
// todo: need to close fd when done for TOYBOX_FREE?
}
diff --git a/toys/posix/tail.c b/toys/posix/tail.c
index 1204f1c6..787e116e 100644
--- a/toys/posix/tail.c
+++ b/toys/posix/tail.c
@@ -231,10 +231,10 @@ void tail_main(void)
if (arg && *arg == '-' && arg[1]) {
TT.lines = atolx(*(args++));
toys.optc--;
+ } else {
+ // if nothing specified, default -n to -10
+ TT.lines = -10;
}
-
- // if nothing specified, default -n to -10
- TT.lines = -10;
}
// Allocate 2 ints per optarg for -f
diff --git a/www/roadmap.html b/www/roadmap.html
index 2bd62490..32d093f7 100755
--- a/www/roadmap.html
+++ b/www/roadmap.html
@@ -313,8 +313,7 @@ for later).</p>
<blockquote><b>
<span id=toolbox>
dd getevent iftop ioctl log logcat logwrapper
-nandread newfs_msdos ps sendevent
-start stop top
+nandread newfs_msdos sendevent start stop
</span>
</b></blockquote>
@@ -325,6 +324,12 @@ of "pending". These should be a priority for cleanup:</p>
dd expr lsof more netstat route tar tr traceroute
</b></blockquote>
+<p>Android wishlist:</p>
+
+<blockquote><b>
+mtools genvfatfs mke2fs gene2fs
+</b></blockquote>
+
<hr />
<h2><a name=tizen /><a href="#tizen">Use case: Tizen Core</a></h2>