diff options
author | Elliott Hughes <enh@google.com> | 2016-04-20 18:22:50 -0700 |
---|---|---|
committer | Elliott Hughes <enh@google.com> | 2016-04-20 18:22:50 -0700 |
commit | b9cc6c64274a1b67546ff8be78dbc0290b3e0634 (patch) | |
tree | 33fdd6f69968e684c5a0ff7e72a7e5a042da402f | |
parent | 1c2326b8af500adbb5864ea87afa94a0c6cf1ff0 (diff) | |
parent | 7b6957fa1e23f1b0f614bb5102c527bee2db3002 (diff) | |
download | android_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-- | Makefile | 3 | ||||
-rw-r--r-- | README | 2 | ||||
-rw-r--r-- | lib/interestingtimes.c | 2 | ||||
-rw-r--r-- | lib/lib.c | 7 | ||||
-rw-r--r-- | lib/lib.h | 2 | ||||
-rw-r--r-- | lib/portability.h | 10 | ||||
-rw-r--r-- | main.c | 2 | ||||
-rwxr-xr-x | scripts/genconfig.sh | 3 | ||||
-rwxr-xr-x | tests/diff.test | 30 | ||||
-rwxr-xr-x | tests/mv.test | 138 | ||||
-rwxr-xr-x | tests/sed.test | 1 | ||||
-rwxr-xr-x | tests/sh.test | 55 | ||||
-rwxr-xr-x | tests/tail.test | 1 | ||||
-rw-r--r-- | toys/other/rev.c | 6 | ||||
-rw-r--r-- | toys/pending/diff.c | 6 | ||||
-rw-r--r-- | toys/pending/modprobe.c | 2 | ||||
-rw-r--r-- | toys/pending/netstat.c | 2 | ||||
-rw-r--r-- | toys/pending/sh.c | 212 | ||||
-rw-r--r-- | toys/posix/cp.c | 17 | ||||
-rw-r--r-- | toys/posix/ps.c | 15 | ||||
-rw-r--r-- | toys/posix/sed.c | 307 | ||||
-rw-r--r-- | toys/posix/tail.c | 6 | ||||
-rwxr-xr-x | www/roadmap.html | 9 |
23 files changed, 478 insertions, 360 deletions
@@ -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' @@ -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); @@ -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; } @@ -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__ @@ -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> |