diff options
author | Elliott Hughes <enh@google.com> | 2019-03-27 09:10:04 -0700 |
---|---|---|
committer | android-build-merger <android-build-merger@google.com> | 2019-03-27 09:10:04 -0700 |
commit | 2326921ef9d6cc0a902e992304a3819b39de949d (patch) | |
tree | 2028151cb3f6c8ebf72646846c21d9e18fa3ba02 | |
parent | f3798e79707c6dfb700c2a93494d6b5b260f6b8d (diff) | |
parent | 0fc5c5bf45cdce2d655c426c2117802ed07f37f0 (diff) | |
download | toybox-2326921ef9d6cc0a902e992304a3819b39de949d.tar.gz |
Merge remote-tracking branch 'toybox/master' into HEAD am: 244fbabb55
am: 0fc5c5bf45
Change-Id: I85f771308f7b240abb74779303a262371e78eb15
-rw-r--r-- | generated/flags.h | 32 | ||||
-rw-r--r-- | generated/globals.h | 16 | ||||
-rw-r--r-- | generated/help.h | 6 | ||||
-rw-r--r-- | generated/newtoys.h | 4 | ||||
-rw-r--r-- | lib/lib.h | 2 | ||||
-rw-r--r-- | lib/xwrap.c | 108 | ||||
-rwxr-xr-x | scripts/genconfig.sh | 1 | ||||
-rwxr-xr-x | scripts/make.sh | 4 | ||||
-rw-r--r-- | scripts/portability.sh | 8 | ||||
-rwxr-xr-x | tests/find.test | 4 | ||||
-rwxr-xr-x | tests/rm.test | 10 | ||||
-rw-r--r-- | tests/tar.test | 106 | ||||
-rw-r--r-- | toys/other/login.c | 2 | ||||
-rw-r--r-- | toys/pending/bc.c | 4 | ||||
-rw-r--r-- | toys/pending/tar.c | 377 | ||||
-rw-r--r-- | toys/pending/vi.c | 894 | ||||
-rw-r--r-- | toys/posix/date.c | 133 | ||||
-rw-r--r-- | toys/posix/find.c | 2 | ||||
-rw-r--r-- | toys/posix/rm.c | 13 |
19 files changed, 1271 insertions, 455 deletions
diff --git a/generated/flags.h b/generated/flags.h index 02276c30..3be9e367 100644 --- a/generated/flags.h +++ b/generated/flags.h @@ -2321,12 +2321,13 @@ #undef FOR_rfkill #endif -// rm fiRr[-fi] fiRr[-fi] +// rm fiRrv[-fi] fiRrv[-fi] #undef OPTSTR_rm -#define OPTSTR_rm "fiRr[-fi]" +#define OPTSTR_rm "fiRrv[-fi]" #ifdef CLEANUP_rm #undef CLEANUP_rm #undef FOR_rm +#undef FLAG_v #undef FLAG_r #undef FLAG_R #undef FLAG_i @@ -2741,9 +2742,9 @@ #undef FLAG_f #endif -// tar &(no-recursion)(numeric-owner)(no-same-permissions)(overwrite)(exclude)*(to-command):o(no-same-owner)p(same-permissions)k(keep-old)c(create)|h(dereference)x(extract)|t(list)|v(verbose)j(bzip2)z(gzip)O(to-stdout)m(touch)X(exclude-from)*T(files-from)*C(directory):f(file):[!txc][!jz] &(no-recursion)(numeric-owner)(no-same-permissions)(overwrite)(exclude)*(to-command):o(no-same-owner)p(same-permissions)k(keep-old)c(create)|h(dereference)x(extract)|t(list)|v(verbose)j(bzip2)z(gzip)O(to-stdout)m(touch)X(exclude-from)*T(files-from)*C(directory):f(file):[!txc][!jz] +// tar &(no-recursion)(numeric-owner)(no-same-permissions)(overwrite)(exclude)*(group):(owner):(to-command):o(no-same-owner)p(same-permissions)k(keep-old)c(create)|h(dereference)x(extract)|t(list)|v(verbose)j(bzip2)z(gzip)O(to-stdout)m(touch)X(exclude-from)*T(files-from)*C(directory):f(file):[!txc][!jz] &(no-recursion)(numeric-owner)(no-same-permissions)(overwrite)(exclude)*(group):(owner):(to-command):o(no-same-owner)p(same-permissions)k(keep-old)c(create)|h(dereference)x(extract)|t(list)|v(verbose)j(bzip2)z(gzip)O(to-stdout)m(touch)X(exclude-from)*T(files-from)*C(directory):f(file):[!txc][!jz] #undef OPTSTR_tar -#define OPTSTR_tar "&(no-recursion)(numeric-owner)(no-same-permissions)(overwrite)(exclude)*(to-command):o(no-same-owner)p(same-permissions)k(keep-old)c(create)|h(dereference)x(extract)|t(list)|v(verbose)j(bzip2)z(gzip)O(to-stdout)m(touch)X(exclude-from)*T(files-from)*C(directory):f(file):[!txc][!jz]" +#define OPTSTR_tar "&(no-recursion)(numeric-owner)(no-same-permissions)(overwrite)(exclude)*(group):(owner):(to-command):o(no-same-owner)p(same-permissions)k(keep-old)c(create)|h(dereference)x(extract)|t(list)|v(verbose)j(bzip2)z(gzip)O(to-stdout)m(touch)X(exclude-from)*T(files-from)*C(directory):f(file):[!txc][!jz]" #ifdef CLEANUP_tar #undef CLEANUP_tar #undef FOR_tar @@ -2764,6 +2765,8 @@ #undef FLAG_p #undef FLAG_o #undef FLAG_to_command +#undef FLAG_owner +#undef FLAG_group #undef FLAG_exclude #undef FLAG_overwrite #undef FLAG_no_same_permissions @@ -5291,10 +5294,11 @@ #ifndef TT #define TT this.rm #endif -#define FLAG_r (1<<0) -#define FLAG_R (1<<1) -#define FLAG_i (1<<2) -#define FLAG_f (1<<3) +#define FLAG_v (1<<0) +#define FLAG_r (1<<1) +#define FLAG_R (1<<2) +#define FLAG_i (1<<3) +#define FLAG_f (1<<4) #endif #ifdef FOR_rmdir @@ -5656,11 +5660,13 @@ #define FLAG_p (1<<14) #define FLAG_o (1<<15) #define FLAG_to_command (1<<16) -#define FLAG_exclude (1<<17) -#define FLAG_overwrite (1<<18) -#define FLAG_no_same_permissions (1<<19) -#define FLAG_numeric_owner (1<<20) -#define FLAG_no_recursion (1<<21) +#define FLAG_owner (1<<17) +#define FLAG_group (1<<18) +#define FLAG_exclude (1<<19) +#define FLAG_overwrite (1<<20) +#define FLAG_no_same_permissions (1<<21) +#define FLAG_numeric_owner (1<<22) +#define FLAG_no_recursion (1<<23) #endif #ifdef FOR_taskset diff --git a/generated/globals.h b/generated/globals.h index 60f31597..79dc6b73 100644 --- a/generated/globals.h +++ b/generated/globals.h @@ -805,14 +805,13 @@ struct syslogd_data { struct tar_data { char *f, *C; struct arg_list *T, *X; - char *to_command; - struct arg_list *exc; + char *to_command, *owner, *group; + struct arg_list *exclude; -// exc is an argument but inc isn't? - struct arg_list *inc, *pass; + struct double_list *incl, *excl, *seen; void *inodes; char *cwd; - int fd; + int fd, ouid, ggid; // Parsed information about a tar header. struct { @@ -937,8 +936,11 @@ struct useradd_data { // toys/pending/vi.c struct vi_data { - struct linestack *ls; - char *statline; + int cur_col; + int cur_row; + unsigned screen_height; + unsigned screen_width; + int vi_mode; }; // toys/pending/wget.c diff --git a/generated/help.h b/generated/help.h index b5c532e7..bd0db872 100644 --- a/generated/help.h +++ b/generated/help.h @@ -320,7 +320,7 @@ #define HELP_wget "usage: wget -f filename URL\n-f filename: specify the filename to be saved\nURL: HTTP uniform resource location and only HTTP, not HTTPS\n\nexamples:\n wget -f index.html http://www.example.com\n wget -f sample.jpg http://www.example.com:8080/sample.jpg\n\n" -#define HELP_vi "usage: vi FILE\n\nVisual text editor. Predates the existence of standardized cursor keys,\nso the controls are weird and historical.\n\n" +#define HELP_vi "usage: vi FILE\nVisual text editor. Predates the existence of standardized cursor keys,\nso the controls are weird and historical.\n\n" #define HELP_userdel "usage: userdel [-r] USER\nusage: deluser [-r] USER\n\nDelete USER from the SYSTEM\n\n-r remove home directory\n\n" @@ -340,7 +340,7 @@ #define HELP_tcpsvd "usage: tcpsvd [-hEv] [-c N] [-C N[:MSG]] [-b N] [-u User] [-l Name] IP Port Prog\nusage: udpsvd [-hEv] [-c N] [-u User] [-l Name] IP Port Prog\n\nCreate TCP/UDP socket, bind to IP:PORT and listen for incoming connection.\nRun PROG for each connection.\n\nIP IP to listen on, 0 = all\nPORT Port to listen on\nPROG ARGS Program to run\n-l NAME Local hostname (else looks up local hostname in DNS)\n-u USER[:GRP] Change to user/group after bind\n-c N Handle up to N (> 0) connections simultaneously\n-b N (TCP Only) Allow a backlog of approximately N TCP SYNs\n-C N[:MSG] (TCP Only) Allow only up to N (> 0) connections from the same IP\n New connections from this IP address are closed\n immediately. MSG is written to the peer before close\n-h Look up peer's hostname\n-E Don't set up environment variables\n-v Verbose\n\n" -#define HELP_tar "usage: tar [-cxtjzhmvO] [-X FILE] [-T FILE] [-f TARFILE] [-C DIR]\n\nCreate, extract, or list files in a .tar (or compressed t?z) file.\n\nOptions:\nc Create x Extract t Test\nf Name of TARFILE C Change to DIR first v Verbose: show filenames\no Ignore owner h Follow symlinks m Ignore mtime\nj Force bzip2 format z Force gzip format\nO Extract to stdout\nX File of names to exclude\nT File of names to include\n--exclude=FILE File pattern(s) to exclude\n\n" +#define HELP_tar "usage: tar [-cxtjzhmvO] [-X FILE] [-T FILE] [-f TARFILE] [-C DIR]\n\nCreate, extract, or list files in a .tar (or compressed t?z) file.\n\nOptions:\nc Create x Extract t Test\nf Name of TARFILE C Change to DIR first v Verbose: show filenames\no Ignore owner h Follow symlinks m Ignore mtime\nj bzip2 compression z gzip compression\nO Extract to stdout X exclude names in FILE T include names in FILE\n--exclude=FILE File pattern(s) to exclude\n\n" #define HELP_syslogd "usage: syslogd [-a socket] [-O logfile] [-f config file] [-m interval]\n [-p socket] [-s SIZE] [-b N] [-R HOST] [-l N] [-nSLKD]\n\nSystem logging utility\n\n-a Extra unix socket for listen\n-O FILE Default log file <DEFAULT: /var/log/messages>\n-f FILE Config file <DEFAULT: /etc/syslog.conf>\n-p Alternative unix domain socket <DEFAULT : /dev/log>\n-n Avoid auto-backgrounding\n-S Smaller output\n-m MARK interval <DEFAULT: 20 minutes> (RANGE: 0 to 71582787)\n-R HOST Log to IP or hostname on PORT (default PORT=514/UDP)\"\n-L Log locally and via network (default is network only if -R)\"\n-s SIZE Max size (KB) before rotation (default:200KB, 0=off)\n-b N rotated logs to keep (default:1, max=99, 0=purge)\n-K Log to kernel printk buffer (use dmesg to read it)\n-l N Log only messages more urgent than prio(default:8 max:8 min:1)\n-D Drop duplicates\n\n" @@ -490,7 +490,7 @@ #define HELP_rmdir "usage: rmdir [-p] [dirname...]\n\nRemove one or more directories.\n\n-p Remove path\n\n" -#define HELP_rm "usage: rm [-fiRr] FILE...\n\nRemove each argument from the filesystem.\n\n-f Force: remove without confirmation, no error if it doesn't exist\n-i Interactive: prompt for confirmation\n-rR Recursive: remove directory contents\n\n" +#define HELP_rm "usage: rm [-fiRrv] FILE...\n\nRemove each argument from the filesystem.\n\n-f Force: remove without confirmation, no error if it doesn't exist\n-i Interactive: prompt for confirmation\n-rR Recursive: remove directory contents\n-v Verbose\n\n" #define HELP_renice "usage: renice [-gpu] -n increment ID ...\n\n" diff --git a/generated/newtoys.h b/generated/newtoys.h index 0c8ef6c7..591b7233 100644 --- a/generated/newtoys.h +++ b/generated/newtoys.h @@ -206,7 +206,7 @@ USE_RESET(NEWTOY(reset, 0, TOYFLAG_USR|TOYFLAG_BIN)) USE_RESTORECON(NEWTOY(restorecon, "<1DFnRrv", TOYFLAG_USR|TOYFLAG_SBIN)) USE_REV(NEWTOY(rev, NULL, TOYFLAG_USR|TOYFLAG_BIN)) USE_RFKILL(NEWTOY(rfkill, "<1>2", TOYFLAG_USR|TOYFLAG_SBIN)) -USE_RM(NEWTOY(rm, "fiRr[-fi]", TOYFLAG_BIN)) +USE_RM(NEWTOY(rm, "fiRrv[-fi]", TOYFLAG_BIN)) USE_RMDIR(NEWTOY(rmdir, "<1p", TOYFLAG_BIN)) USE_RMMOD(NEWTOY(rmmod, "<1wf", TOYFLAG_SBIN|TOYFLAG_NEEDROOT)) USE_ROUTE(NEWTOY(route, "?neA:", TOYFLAG_BIN)) @@ -246,7 +246,7 @@ USE_SYSCTL(NEWTOY(sysctl, "^neNqwpaA[!ap][!aq][!aw][+aA]", TOYFLAG_SBIN)) USE_SYSLOGD(NEWTOY(syslogd,">0l#<1>8=8R:b#<0>99=1s#<0=200m#<0>71582787=20O:p:f:a:nSKLD", TOYFLAG_SBIN|TOYFLAG_STAYROOT)) USE_TAC(NEWTOY(tac, NULL, TOYFLAG_USR|TOYFLAG_BIN)) USE_TAIL(NEWTOY(tail, "?fc-n-[-cn]", TOYFLAG_USR|TOYFLAG_BIN)) -USE_TAR(NEWTOY(tar, "&(no-recursion)(numeric-owner)(no-same-permissions)(overwrite)(exclude)*(to-command):o(no-same-owner)p(same-permissions)k(keep-old)c(create)|h(dereference)x(extract)|t(list)|v(verbose)j(bzip2)z(gzip)O(to-stdout)m(touch)X(exclude-from)*T(files-from)*C(directory):f(file):[!txc][!jz]", TOYFLAG_USR|TOYFLAG_BIN)) +USE_TAR(NEWTOY(tar, "&(no-recursion)(numeric-owner)(no-same-permissions)(overwrite)(exclude)*(group):(owner):(to-command):o(no-same-owner)p(same-permissions)k(keep-old)c(create)|h(dereference)x(extract)|t(list)|v(verbose)j(bzip2)z(gzip)O(to-stdout)m(touch)X(exclude-from)*T(files-from)*C(directory):f(file):[!txc][!jz]", TOYFLAG_USR|TOYFLAG_BIN)) USE_TASKSET(NEWTOY(taskset, "<1^pa", TOYFLAG_USR|TOYFLAG_BIN|TOYFLAG_STAYROOT)) USE_TCPSVD(NEWTOY(tcpsvd, "^<3c#=30<1C:b#=20<0u:l:hEv", TOYFLAG_USR|TOYFLAG_BIN)) USE_TEE(NEWTOY(tee, "ia", TOYFLAG_USR|TOYFLAG_BIN)) @@ -181,6 +181,8 @@ void xregcomp(regex_t *preg, char *rexec, int cflags); char *xtzset(char *new); void xsignal_flags(int signal, void *handler, int flags); void xsignal(int signal, void *handler); +time_t xvali_date(struct tm *tm, char *str); +void xparsedate(char *str, time_t *t, unsigned *nano); // lib.c void verror_msg(char *msg, int err, va_list va); diff --git a/lib/xwrap.c b/lib/xwrap.c index c133125a..ee07fda2 100644 --- a/lib/xwrap.c +++ b/lib/xwrap.c @@ -944,3 +944,111 @@ void xsignal(int signal, void *handler) { xsignal_flags(signal, handler, 0); } + + +time_t xvali_date(struct tm *tm, char *str) +{ + time_t t; + + if (tm && (unsigned)tm->tm_sec<=60 && (unsigned)tm->tm_min<=59 + && (unsigned)tm->tm_hour<=23 && tm->tm_mday && (unsigned)tm->tm_mday<=31 + && (unsigned)tm->tm_mon<=11 && (t = mktime(tm)) != -1) return t; + + error_exit("bad date %s", str); +} + +// Parse date string (relative to current *t). Sets time_t and nanoseconds. +void xparsedate(char *str, time_t *t, unsigned *nano) +{ + struct tm tm; + time_t now = *t; + int len = 0, i; + // Formats with years must come first. + char *s = str, *p, *formats[] = {"%F %T", "%FT%T", "%F %H:%M", "%F", + "%H:%M:%S", "%H:%M"}; + + *nano = 0; + + // Parse @UNIXTIME[.FRACTION] + if (*str == '@') { + long long ll; + + // Collect seconds and nanoseconds. + // &ll is not just t because we can't guarantee time_t is 64 bit (yet). + sscanf(s, "@%lld%n", &ll, &len); + if (s[len]=='.') { + s += len+1; + for (len = 0; len<9; len++) { + *nano *= 10; + if (isdigit(*s)) *nano += *s++-'0'; + } + } + if (s[len]) goto bad_dates; + *t = ll; + + return; + } + + // Is it one of the fancy formats? + for (i = 0; i<ARRAY_LEN(formats); i++) { + localtime_r(&now, &tm); + tm.tm_hour = tm.tm_min = tm.tm_sec = 0; + tm.tm_isdst = -1; + if ((p = strptime(s, formats[i], &tm)) && !*p) { + *t = xvali_date(&tm, str); + + return; + } + } + + // Posix format? + sscanf(s, "%2u%2u%2u%2u%n", &tm.tm_mon, &tm.tm_mday, &tm.tm_hour, + &tm.tm_min, &len); + if (len != 8) goto bad_dates; + s += len; + tm.tm_mon--; + + // If year specified, overwrite one we fetched earlier. + if (*s && *s != '.') { + unsigned year; + + len = 0; + sscanf(s, "%u%n", &year, &len); + if (len == 4) tm.tm_year = year - 1900; + else if (len != 2) goto bad_dates; + s += len; + + // 2 digit years, next 50 years are "future", last 50 years are "past". + // A "future" date in past is a century ahead. + // A non-future date in the future is a century behind. + if (len == 2) { + unsigned r1 = tm.tm_year % 100, r2 = (tm.tm_year + 50) % 100, + century = tm.tm_year - r1; + + if ((r1 < r2) ? (r1 < year && year < r2) : (year < r1 || year > r2)) { + if (year < r1) year += 100; + } else if (year > r1) year -= 100; + tm.tm_year = year + century; + } + } + // Fractional part? + if (*s == '.') { + len = 0; + sscanf(s, ".%2u%n", &tm.tm_sec, &len); + s += len; + for (len = 0; len<9; len++) { + *nano *= 10; + if (isdigit(*s)) *nano += *s++-'0'; + } + } else tm.tm_sec = 0; + + // Sanity check field ranges + *t = xvali_date(&tm, str); + + // Shouldn't be any trailing garbage. + if (!*s) return; + +bad_dates: + // monkey died + xvali_date(0, str); +} diff --git a/scripts/genconfig.sh b/scripts/genconfig.sh index c4940dfd..21e6c7b7 100755 --- a/scripts/genconfig.sh +++ b/scripts/genconfig.sh @@ -5,7 +5,6 @@ mkdir -p generated -source configure source scripts/portability.sh probecc() diff --git a/scripts/make.sh b/scripts/make.sh index 58456f6f..2fc94b4c 100755 --- a/scripts/make.sh +++ b/scripts/make.sh @@ -5,12 +5,8 @@ export LANG=c export LC_ALL=C set -o pipefail -source ./configure source scripts/portability.sh -[ ! -z "$CROSS_COMPILE" ] && [ ! -e "$CROSS_COMPILE"cc ] && - echo "missing ${CROSS_COMPILE}cc" && exit 1 - [ -z "$KCONFIG_CONFIG" ] && KCONFIG_CONFIG=.config [ -z "$OUTNAME" ] && OUTNAME=toybox UNSTRIPPED="generated/unstripped/$(basename "$OUTNAME")" diff --git a/scripts/portability.sh b/scripts/portability.sh index fddd84ec..abeb31f2 100644 --- a/scripts/portability.sh +++ b/scripts/portability.sh @@ -1,5 +1,13 @@ # sourced to find alternate names for things +source configure + +if [ -z "$(command -v "${CROSS_COMPILE}${CC}")" ] +then + echo "No ${CROSS_COMPILE}${CC} found" >&2 + exit 1 +fi + if [ -z "$SED" ] then [ ! -z "$(which gsed 2>/dev/null)" ] && SED=gsed || SED=sed diff --git a/tests/find.test b/tests/find.test index de48c5c8..6b0b0a8a 100755 --- a/tests/find.test +++ b/tests/find.test @@ -56,6 +56,10 @@ testing "-type f -user -exec" \ "find dir -type f -user $USER -exec ls {} \\;" "dir/file\n" "" "" testing "-type l -newer -exec" \ "find dir -type l -newer dir/file -exec ls {} \\;" "dir/link\n" "" "" +testing "-exec true \\; -print" \ + "find dir/file -exec true \\; -print" "dir/file\n" "" "" +testing "-exec false \\; -print" \ + "find dir/file -exec false \\; -print" "" "" "" testing "-perm (exact success)" \ "find perm -type f -perm 0444" "perm/all-read-only\n" "" "" testing "-perm (exact failure)" \ diff --git a/tests/rm.test b/tests/rm.test index 95710c82..42727442 100755 --- a/tests/rm.test +++ b/tests/rm.test @@ -48,3 +48,13 @@ mkdir -p one && touch one/two && chmod 000 one SKIP_HOST=1 testing "-rf 000 dir" \ "rm -rf one 2>/dev/null && [ ! -e one ] && echo yes" "yes\n" "" "" chmod 777 one 2>/dev/null ; rm -rf one + +mkdir -p d1 +touch d1/f1.txt d1/f2.txt +testing "-rv dir" \ + "rm -rv d1" "rm 'd1/f1.txt'\nrm 'd1/f2.txt'\nrmdir 'd1'\n" "" "" +rm -rf d1 + +touch "'" +testing "-v \\'" "rm -v \\'" "rm '''\n" "" "" # TODO: coreutils escapes quote +rm -f \' diff --git a/tests/tar.test b/tests/tar.test index 50a94e60..31aee386 100644 --- a/tests/tar.test +++ b/tests/tar.test @@ -1,104 +1,14 @@ #!/bin/bash -# Copyright 2014 Divya Kothari <divya.s.kothari@gmail.com> -# Copyright 2014 Naha Maggu <maggu.neha@gmail.com> - [ -f testing.sh ] && . testing.sh #testing "name" "command" "result" "infile" "stdin" -#Creating dir -mkdir dir/dir1 -p -echo "This is testdata" > dir/dir1/file -testing "tgz - compession, extraction and data validation" "tar -czf dir.tgz dir/ && [ -e dir.tgz ] && echo 'yes'; rm -rf dir; tar -xf dir.tgz && [ -f dir/dir1/file ] && cat dir/dir1/file; rm -rf dir.tgz" "yes\nThis is testdata\n" "" "" - -#Creating dir -mkdir dir/dir1 -p -echo "This is testdata" > dir/dir1/file -testing "tar.gz - compession, extraction and data validation" "tar -czf dir.tar.gz dir/ && [ -e dir.tar.gz ] && echo 'yes'; rm -rf dir; tar -xf dir.tar.gz && [ -f dir/dir1/file ] && cat dir/dir1/file; rm -rf dir.tar.gz" "yes\nThis is testdata\n" "" "" - -#Creating dir -mkdir dir/dir1 -p -echo "This is testdata" > dir/dir1/file -testing "verbose compression" "tar -cvzf dir.tgz dir/; rm -rf dir.tgz" "dir/\ndir/dir1/\ndir/dir1/file\n" "" "" -rm -rf dir/ - -#creating test file -dd if=/dev/zero of=testFile ibs=4096 obs=4096 count=1000 2>/dev/null -testing "- compession and extraction of a file" "tar -czf testFile.tgz testFile && [ -e testFile.tgz ] && echo 'yes'; rm -rf testFile; tar -xf testFile.tgz && [ -f testFile ] && echo 'yes'; rm -rf testFile.tgz" "yes\nyes\n" "" "" - -#creating empty test file -touch testFile -testing "- compession and extraction of a empty file" "tar -czf testFile.tgz testFile && [ -e testFile.tgz ] && echo 'yes'; rm -rf testFile; tar -xf testFile.tgz && [ -f testFile ] && echo 'yes'; rm -rf testFile.tgz" "yes\nyes\n" "" "" - -#Creating dir -mkdir dir/dir1 -p -touch dir/dir1/file1 dir/dir1/file2 dir/dir1/file3 dir/dir1/file4 -testing "-t option" "tar -czf dir.tar.gz dir/; rm -rf dir; tar -tf dir.tar.gz | sort; rm -rf dir.tar.gz" "dir/\ndir/dir1/\ndir/dir1/file1\ndir/dir1/file2\ndir/dir1/file3\ndir/dir1/file4\n" "" "" -rm -rf dir/ - -#Creating nested directory -mkdir -p dir/dir1 dir/dir2 dir/dir3 dir/dir4 -echo "This is testdata" > dir/dir1/file; echo "Dont exclude me" > dir/dir3/file1 ; -echo "Exclude me" > dir/dir3/file2 ; echo "YO" > dir/dir4/file1 ; echo "Hello" >dir/dir4/file2; echo "Dont" > dir/dir2/file1 -echo -ne "dir/dir4\ndir/dir3/file2\n" > exclude_file -testing "create with files excluded : -X" "tar -czf dir.tgz dir/ -X exclude_file ; rm -rf dir ; tar -tf dir.tgz | sort; rm -rf dir.tgz " "dir/\ndir/dir1/\ndir/dir1/file\ndir/dir2/\ndir/dir2/file1\ndir/dir3/\ndir/dir3/file1\n" "" "" -rm -rf exclude_file - -#Creating nested directory -mkdir dir/dir1 -p ; mkdir dir/dir2 ; mkdir dir/dir3 ; mkdir dir/dir4 -echo "This is testdata" > dir/dir1/file -echo "Dont exclude me" > dir/dir3/file1 ; echo "Exclude me" > dir/dir3/file2 ; echo "YO" > dir/dir4/file1 ; echo "Hello" >dir/dir4/file2; echo "Dont" > dir/dir2/file1 -testing "with pattern --exclude" "tar --exclude=dir/dir3/* -czf dir.tgz dir/ ; rm -rf dir ; tar -tf dir.tgz | sort; rm -rf dir.tgz " "dir/\ndir/dir1/\ndir/dir1/file\ndir/dir2/\ndir/dir2/file1\ndir/dir3/\ndir/dir4/\ndir/dir4/file1\ndir/dir4/file2\n" "" "" - -#Creating directory to be compressed -mkdir dir/dir1 -p -echo "This is testdata" > dir/dir1/file -mkdir temp -testing "extract with -C Dir" "tar -czf dir.tgz dir/ ;rm -rf dir ;tar -xf dir.tgz -C temp/ ; [ -e temp/dir ] && echo 'yes' ; rm -rf dir dir.tgz" "yes\n" "" "" -rm -rf temp - -#Creating nested directory -mkdir dir/dir1 -p ; mkdir dir/dir2 ; mkdir dir/dir3 ; mkdir dir/dir4 ; mkdir temp_dir -echo "dir1/file" > dir/dir1/file ; echo "temp_dir/file" > temp_dir/file -echo "dir3/file1" > dir/dir3/file1 ; echo "dir3/file2" > dir/dir3/file2 ; echo "YO" > dir/dir4/file1 ; echo "Hello" >dir/dir4/file2; echo "dir2/file1" > dir/dir2/file1 -echo "temp_dir/file" > exclude_file -testing "create with extra files/directory included : -T" "tar -czf dir.tgz dir/ -T exclude_file ; rm -rf dir ; tar -tf dir.tgz | sort; rm -rf dir.tgz " "dir/\ndir/dir1/\ndir/dir1/file\ndir/dir2/\ndir/dir2/file1\ndir/dir3/\ndir/dir3/file1\ndir/dir3/file2\ndir/dir4/\ndir/dir4/file1\ndir/dir4/file2\ntemp_dir/file\n" "" "" -rm -rf exclude_file -rm -rf temp_dir - -#Creating dir -mkdir dir/dir1 -p -echo "Inside dir/dir1" > dir/dir1/file ; echo "Hello Inside dir" > dir/file -testing "extract to STDOUT : -O" " tar -czf dir.tgz dir/ ; rm -rf dir ; tar -xf dir.tgz -O ; [ -e 'Inside dir/dir1/\nHello Inside dir\n' ] && echo 'yes'; rm -rf dir.tgz " "" "" "" - -#Creating short filename -f="filename_with_100_chars_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" -echo "This is testdata" > $f -testing "shortname filename" "tar -cf testFile.tar $f && [ -e testFile.tar ] && echo 'yes'; rm -f $f; tar -xf testFile.tar && [ -f $f ] && cat $f && strings testFile.tar | grep -o LongLink; rm -f testFile.tar; rm -f $f" "yes\nThis is testdata\n" "" "" - -#Creating long filename -f="filename_with_101_chars_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" -echo "This is testdata" > $f -testing "longname filename" "tar -cf testFile.tar $f && [ -e testFile.tar ] && echo 'yes'; rm -f $f; tar -xf testFile.tar && [ -f $f ] && cat $f && strings testFile.tar | grep -o LongLink; rm -f testFile.tar; rm -f $f" "yes\nThis is testdata\nLongLink\n" "" "" - -#Creating long pathname -d="dirname_with_50_chars_xxxxxxxxxxxxxxxxxxxxxxxxxxxx" -f="filename_with_50_chars_xxxxxxxxxxxxxxxxxxxxxxxxxxx" -mkdir $d -echo "This is testdata" > $d/$f -testing "longname pathname" "tar -cf testFile.tar $d/$f && [ -e testFile.tar ] && echo 'yes'; rm -rf $d; tar -xf testFile.tar && [ -f $d/$f ] && cat $d/$f && strings testFile.tar | grep -o LongLink; rm -f testFile.tar; rm -rf $d" "yes\nThis is testdata\nLongLink\n" "" "" - -# gzip -rm -rf d -mkdir d -echo "hello world" > d/f -testing "gzip compression" "tar zcf f d && file f | grep -q gzip && echo y ; rm -rf d ; tar xf f && cat d/f" "y\nhello world\n" "" "" -testing "gzip decompression" "tar xf $FILES/tar/tar.tgz && cat dir/file" "hello world\n" "" "" - -# bzip2 -rm -rf d -mkdir d -echo "hello world" > d/f -testing "bzip2 compression" "tar jcf f d && file f | grep -q bzip2 && echo y ; rm -rf d ; tar xf f && cat d/f" "y\nhello world\n" "" "" -testing "bzip2 decompression" "tar xf $FILES/tar/tar.tbz2 && cat dir/file" "hello world\n" "" "" +TARSUM='--owner root --group root | head -c $((3*512)) | sha1sum | sed "s/ .*//"' +touch -t 198001010101 file +testing "create file" "tar c file $TARSUM" \ + "d551292408833aa5e9db32c0d14d7f32e7e96882\n" "" "" +mkdir walrus +touch -t 198001010101 dir +testing "create dir" "tar c dir $TARSUM" \ + "c4e630d9c89f4f20d603a6f71ff4410ab56fe965\n" "" "" diff --git a/toys/other/login.c b/toys/other/login.c index 9bd6cc95..5214b937 100644 --- a/toys/other/login.c +++ b/toys/other/login.c @@ -119,7 +119,7 @@ void login_main(void) if (fchown(tty, pwd->pw_uid, pwd->pw_gid) || fchmod(tty, 0600)) printf("can't claim tty"); xsetuser(pwd); - reset_env(pwd, FLAG(p)); + reset_env(pwd, !FLAG(p)); // Message of the day if ((ss = readfile("/etc/motd", 0, 0))) puts(ss); diff --git a/toys/pending/bc.c b/toys/pending/bc.c index 142c0ce2..bb5d86cb 100644 --- a/toys/pending/bc.c +++ b/toys/pending/bc.c @@ -952,8 +952,8 @@ void bc_vec_concat(BcVec *v, char *str) { if (!v->len) bc_vec_pushByte(v, '\0'); len = strlen(str); - bc_vec_grow(v, len+1); - strcpy(v->v+v->len, str); + bc_vec_grow(v, len); + strcpy(v->v+v->len-1, str); v->len += len; } diff --git a/toys/pending/tar.c b/toys/pending/tar.c index 97e699b4..d0d840ba 100644 --- a/toys/pending/tar.c +++ b/toys/pending/tar.c @@ -18,11 +18,7 @@ * Extract into dir same as filename, --restrict? "Tarball is splodey" * -USE_TAR(NEWTOY(tar, "&(no-recursion)(numeric-owner)(no-same-permissions)(overwrite)(exclude)*(to-command):o(no-same-owner)p(same-permissions)k(keep-old)c(create)|h(dereference)x(extract)|t(list)|v(verbose)j(bzip2)z(gzip)O(to-stdout)m(touch)X(exclude-from)*T(files-from)*C(directory):f(file):[!txc][!jz]", TOYFLAG_USR|TOYFLAG_BIN)) - -todo: support .txz -todo: directory timestamps not set on extract -todo: extract into chmod 000 directory +USE_TAR(NEWTOY(tar, "&(no-recursion)(numeric-owner)(no-same-permissions)(overwrite)(exclude)*(group):(owner):(to-command):o(no-same-owner)p(same-permissions)k(keep-old)c(create)|h(dereference)x(extract)|t(list)|v(verbose)j(bzip2)z(gzip)O(to-stdout)m(touch)X(exclude-from)*T(files-from)*C(directory):f(file):[!txc][!jz]", TOYFLAG_USR|TOYFLAG_BIN)) config TAR bool "tar" @@ -36,10 +32,8 @@ config TAR c Create x Extract t Test f Name of TARFILE C Change to DIR first v Verbose: show filenames o Ignore owner h Follow symlinks m Ignore mtime - j Force bzip2 format z Force gzip format - O Extract to stdout - X File of names to exclude - T File of names to include + j bzip2 compression z gzip compression + O Extract to stdout X exclude names in FILE T include names in FILE --exclude=FILE File pattern(s) to exclude */ @@ -49,14 +43,13 @@ config TAR GLOBALS( char *f, *C; struct arg_list *T, *X; - char *to_command; - struct arg_list *exc; + char *to_command, *owner, *group; + struct arg_list *exclude; -// exc is an argument but inc isn't? - struct arg_list *inc, *pass; + struct double_list *incl, *excl, *seen; void *inodes; char *cwd; - int fd; + int fd, ouid, ggid; // Parsed information about a tar header. struct { @@ -76,6 +69,34 @@ struct tar_hdr { prefix[155], padd[12]; }; +// convert to int to octal (or base-256) +static void itoo(char *str, int len, unsigned long long val) +{ + // Do we need binary encoding? + if (!(val>>(3*(len-1)))) sprintf(str, "%0*llo", len-1, val); + else { + *str = 128; + while (--len) *++str = val>>(3*len); + } +} +#define ITOO(x, y) itoo(x, sizeof(x), y) + +//convert octal (or base-256) to int +static unsigned long long otoi(char *str, unsigned len) +{ + unsigned long long val = 0; + + // When tar value too big or octal, use binary encoding with high bit set + if (128&*str) while (--len) val = (val<<8)+*++str; + else { + while (len && *str>='0' && *str<='7') val = val*8+*str++-'0', len--; + if (len && *str && *str != ' ') error_exit("bad header"); + } + + return val; +} + + struct inode_list { struct inode_list *next; char *arg; @@ -83,13 +104,6 @@ struct inode_list { dev_t dev; }; -//convert to octal -static void itoo(char *str, int len, off_t val) -{ - sprintf(str, "%0*llo", len-1, (unsigned long long)val); -} - -// This really needs a hash table static struct inode_list *seen_inode(void **list, struct stat *st, char *name) { if (!S_ISDIR(st->st_mode) && st->st_nlink > 1) { @@ -121,31 +135,37 @@ static unsigned cksum(void *data) static void write_longname(char *name, char type) { - struct tar_hdr tmp[2]; + struct tar_hdr tmp; int sz = strlen(name) +1; - memset(tmp, 0, sizeof(tmp)); - strcpy(tmp->name, "././@LongLink"); - memset(tmp->mode, '0', sizeof(tmp->mode)-1); - memset(tmp->uid, '0', sizeof(tmp->uid)-1); - memset(tmp->gid, '0', sizeof(tmp->gid)-1); - itoo(tmp->size, sizeof(tmp->size), sz); - memset(tmp->mtime, '0', sizeof(tmp->mtime)-1); - tmp->type = type; - strcpy(tmp->magic, "ustar "); - - // Calculate checksum - itoo(tmp->chksum, sizeof(tmp->chksum), cksum(&tmp)); + memset(&tmp, 0, sizeof(tmp)); + strcpy(tmp.name, "././@LongLink"); + ITOO(tmp.mode, 0); + ITOO(tmp.uid, 0); + ITOO(tmp.gid, 0); + ITOO(tmp.size, sz); + ITOO(tmp.mtime, 0); + tmp.type = type; + strcpy(tmp.magic, "ustar "); + + // Calculate checksum. Since 0777777 is twice 512*255 it can never use more + // than 6 digits, last byte is ' ' or historical reasons. + itoo(tmp.chksum, sizeof(tmp.chksum)-1, cksum(&tmp)); + tmp.chksum[7] = ' '; // write header and name, padded with NUL to block size - xwrite(TT.fd, tmp, sizeof(*tmp)); + xwrite(TT.fd, &tmp, 512); xwrite(TT.fd, name, sz); - xwrite(TT.fd, tmp+1, 512-(sz%512)); + if (sz%512) xwrite(TT.fd, toybuf, 512-(sz%512)); } -static int filter(struct arg_list *lst, char *name) +static struct double_list *filter(struct double_list *lst, char *name) { - for (; lst; lst = lst->next) if (!fnmatch(lst->arg, name, 1<<3)) return 1; + struct double_list *end = lst; + + if (lst) + do if (!fnmatch(lst->data, name, 1<<3)) return lst; + while (end != (lst = lst->next)); return 0; } @@ -169,15 +189,15 @@ static void alloread(void *buf, int len) static void add_file(char **nam, struct stat *st) { struct tar_hdr hdr; - struct passwd *pw; - struct group *gr; + struct passwd *pw = pw; + struct group *gr = gr; struct inode_list *node = node; int i, fd =-1; - char *c, *p, *name = *nam, *lnk, *hname, buf[512] = {0,}; + char *c, *p, *name = *nam, *lnk, *hname; static int warn = 1; for (p = name; *p; p++) - if ((p == name || p[-1] == '/') && *p != '/' && filter(TT.exc, p)) return; + if ((p == name || p[-1] == '/') && *p != '/' && filter(TT.excl, p)) return; if (S_ISDIR(st->st_mode) && name[strlen(name)-1] != '/') { lnk = xmprintf("%s/",name); @@ -195,55 +215,52 @@ static void add_file(char **nam, struct stat *st) warn = 0; } + if (TT.owner) st->st_uid = TT.ouid; + if (TT.group) st->st_gid = TT.ggid; + memset(&hdr, 0, sizeof(hdr)); strncpy(hdr.name, hname, sizeof(hdr.name)); - itoo(hdr.mode, sizeof(hdr.mode), st->st_mode &07777); - itoo(hdr.uid, sizeof(hdr.uid), st->st_uid); - itoo(hdr.gid, sizeof(hdr.gid), st->st_gid); - itoo(hdr.size, sizeof(hdr.size), 0); //set size later - itoo(hdr.mtime, sizeof(hdr.mtime), st->st_mtime); + ITOO(hdr.mode, st->st_mode &07777); + ITOO(hdr.uid, st->st_uid); + ITOO(hdr.gid, st->st_gid); + ITOO(hdr.size, 0); //set size later + ITOO(hdr.mtime, st->st_mtime); // Hard link or symlink? i = !!S_ISLNK(st->st_mode); if (i || (node = seen_inode(&TT.inodes, st, hname))) { -// TODO: test preserve symlink ownership hdr.type = '1'+i; if (!(lnk = i ? xreadlink(name) : node->arg)) return perror_msg("readlink"); -// TODO: does this need NUL terminator? - if (strlen(lnk) > sizeof(hdr.link)) - write_longname(lnk, 'K'); //write longname LINK -// TODO: this will error_exit() if too long, not truncate. - xstrncpy(hdr.link, lnk, sizeof(hdr.link)); + if (strlen(lnk) > sizeof(hdr.link)) write_longname(lnk, 'K'); + strncpy(hdr.link, lnk, sizeof(hdr.link)); if (i) free(lnk); } else if (S_ISREG(st->st_mode)) { hdr.type = '0'; - if (st->st_size <= (off_t)077777777777LL) - itoo(hdr.size, sizeof(hdr.size), st->st_size); - else { -// TODO: test accept 12 7's but don't emit without terminator - return error_msg("TODO: need base-256 encoding for '%s' '%lld'\n", - hname, (unsigned long long)st->st_size); - } + ITOO(hdr.size, st->st_size); } else if (S_ISDIR(st->st_mode)) hdr.type = '5'; else if (S_ISFIFO(st->st_mode)) hdr.type = '6'; else if (S_ISBLK(st->st_mode) || S_ISCHR(st->st_mode)) { hdr.type = (S_ISCHR(st->st_mode))?'3':'4'; - itoo(hdr.major, sizeof(hdr.major), dev_major(st->st_rdev)); - itoo(hdr.minor, sizeof(hdr.minor), dev_minor(st->st_rdev)); + ITOO(hdr.major, dev_major(st->st_rdev)); + ITOO(hdr.minor, dev_minor(st->st_rdev)); } else return error_msg("unknown file type '%o'", st->st_mode & S_IFMT); - if (strlen(hname) > sizeof(hdr.name)) - write_longname(hname, 'L'); //write longname NAME + if (strlen(hname) > sizeof(hdr.name)) write_longname(hname, 'L'); strcpy(hdr.magic, "ustar "); - if ((pw = bufgetpwuid(st->st_uid))) - snprintf(hdr.uname, sizeof(hdr.uname), "%s", pw->pw_name); - else sprintf(hdr.uname, "%d", st->st_uid); - - if ((gr = bufgetgrgid(st->st_gid))) - snprintf(hdr.gname, sizeof(hdr.gname), "%s", gr->gr_name); - else sprintf(hdr.gname, "%d", st->st_gid); + if (!FLAG(numeric_owner)) { + if (!TT.owner && !(pw = bufgetpwuid(st->st_uid))) + sprintf(hdr.uname, "%d", st->st_uid); + else snprintf(hdr.uname, sizeof(hdr.uname), "%s", + TT.owner ? TT.owner : pw->pw_name); + if (!TT.group && !(gr = bufgetgrgid(st->st_gid))) + sprintf(hdr.gname, "%d", st->st_gid); + else snprintf(hdr.gname, sizeof(hdr.gname), "%s", + TT.group ? TT.group : gr->gr_name); + } itoo(hdr.chksum, sizeof(hdr.chksum)-1, cksum(&hdr)); + hdr.chksum[7] = ' '; + if (FLAG(v)) printf("%s\n",hname); xwrite(TT.fd, (void*)&hdr, 512); @@ -254,7 +271,7 @@ static void add_file(char **nam, struct stat *st) return; } xsendfile_pad(fd, TT.fd, st->st_size); - if (st->st_size%512) writeall(TT.fd, buf, (512-(st->st_size%512))); + if (st->st_size%512) writeall(TT.fd, toybuf, (512-(st->st_size%512))); close(fd); } @@ -273,8 +290,8 @@ static int add_to_tar(struct dirtree *node) path = dirtree_path(node, 0); add_file(&path, &(node->st)); //path may be modified free(path); - if (FLAG(no_recursion)) return 0; - return ((DIRTREE_RECURSE | (FLAG(h)?DIRTREE_SYMFOLLOW:0))); + + return (DIRTREE_RECURSE|(FLAG(h)?DIRTREE_SYMFOLLOW:0))*!FLAG(no_recursion); } // Does anybody actually use this? @@ -283,13 +300,10 @@ static void extract_to_command(void) int pipefd[2], status = 0; pid_t cpid; - xpipe(pipefd); if (!S_ISREG(TT.hdr.mode)) return; //only regular files are supported. - cpid = fork(); - if (cpid == -1) perror_exit("fork"); - - if (!cpid) { // Child reads from pipe + xpipe(pipefd); + if (!(cpid = xfork())) { // Child reads from pipe char buf[64], *argv[4] = {"sh", "-c", TT.to_command, NULL}; setenv("TAR_FILETYPE", "f", 1); @@ -327,7 +341,6 @@ static void extract_to_disk(void) char *s; struct stat ex; -// while not if flags = strlen(TT.hdr.name); if (flags>2) if (strstr(TT.hdr.name, "/../") || !strcmp(TT.hdr.name, "../") || @@ -381,25 +394,30 @@ COPY: xsendfile_len(TT.fd, dst_fd, TT.hdr.size); close(dst_fd); - if (!FLAG(o)) { + if (!FLAG(o) && !geteuid()) { //set ownership..., --no-same-owner, --numeric-owner uid_t u = TT.hdr.uid; gid_t g = TT.hdr.gid; - if (!FLAG(numeric_owner)) { - struct group *gr = getgrnam(TT.hdr.gname); + if (TT.owner) u = TT.ouid; + else if (!FLAG(numeric_owner)) { struct passwd *pw = getpwnam(TT.hdr.uname); - if (pw) u = pw->pw_uid; + if (pw && (TT.owner || !FLAG(numeric_owner))) u = pw->pw_uid; + } + + if (TT.group) g = TT.ggid; + else if (!FLAG(numeric_owner)) { + struct group *gr = getgrnam(TT.hdr.gname); if (gr) g = gr->gr_gid; } - if (!geteuid() && lchown(TT.hdr.name, u, g)) + + if (lchown(TT.hdr.name, u, g)) perror_msg("chown %d:%d '%s'", u, g, TT.hdr.name);; } // || !FLAG(no_same_permissions)) if (FLAG(p) && !S_ISLNK(TT.hdr.mode)) chmod(TT.hdr.name, TT.hdr.mode); -// TODO: defer directory mtime until we've finished with contents //apply mtime if (!FLAG(m)) { struct timeval times[2] = {{TT.hdr.mtime, 0},{TT.hdr.mtime, 0}}; @@ -407,59 +425,30 @@ COPY: } } -static void add_to_list(struct arg_list **llist, char *name) -{ - struct arg_list **list = llist; - - while (*list) list=&((*list)->next); - *list = xzalloc(sizeof(struct arg_list)); - (*list)->arg = name; - if ((name[strlen(name)-1] == '/') && strlen(name) != 1) - name[strlen(name)-1] = '\0'; -} - -static void file_to_list(char *file, struct arg_list **llist) -{ - int fd = xopenro(file); - char *line = 0; - - while ((line = get_line(fd))) add_to_list(llist, xstrdup(line)); - if (fd) close(fd); - free(line); -} - -//convert octal to int -static unsigned long long otoi(char *str, int len) -{ - unsigned long long val; - -// todo: base-256 encoding, just do it symmetrically for all fields - str[len-1] = 0; - val = strtoull(str, &str, 8); - if (*str && *str != ' ') error_exit("bad header"); - - return val; -} - static void unpack_tar(void) { + struct double_list *walk, *delete; struct tar_hdr tar; - int i; + int i, and = 0; char *s; for (;;) { // align to next block and read it if (TT.hdr.size%512) skippy(512-TT.hdr.size%512); + if (!(i = readall(TT.fd, &tar, 512))) return; - i = readall(TT.fd, &tar, 512); if (i != 512) error_exit("read error"); + + // Two consecutive empty headers ends tar even if there's more data + if (!*tar.name) { + if (and++) return; + TT.hdr.size = 0; + continue; + } // ensure null temination even of pathological packets - tar.padd[0] = 0; - // End of tar - if (!*tar.name) return; + tar.padd[0] = and = 0; -// can you append a bzip to a gzip _within_ a tarball? Nested compress? -// Or compressed data after uncompressed data? + // Is this a valid Unix Standard TAR header? if (memcmp(tar.magic, "ustar", 5)) error_exit("bad header"); if (cksum(&tar) != otoi(tar.chksum, sizeof(tar.chksum))) error_exit("bad cksum"); @@ -467,6 +456,8 @@ static void unpack_tar(void) // If this header isn't writing something to the filesystem if (tar.type<'0' || tar.type>'7') { + + // Long name extension header? if (tar.type == 'K') alloread(&TT.hdr.link_target, TT.hdr.size); else if (tar.type == 'L') alloread(&TT.hdr.name, TT.hdr.size); else if (tar.type == 'x') { @@ -479,7 +470,7 @@ static void unpack_tar(void) if ((i = sscanf(p, "%u path=%n", &len, &n))<1 || len<4 || len>TT.hdr.size) { - error_msg("corrupted extended header"); + error_msg("bad header"); break; } p[len-1] = 0; @@ -490,14 +481,13 @@ static void unpack_tar(void) } free(buf); - // This could be if (strchr("DMNSVg", tar.type)) but an unknown header - // type with trailing contents is unlikely to have a valid type & cksum + // Ignore everything else. } else skippy(TT.hdr.size); continue; } - // At this point, we're writing something to the filesystem. Parse fields. + // At this point, we have something to output. Convert metadata. TT.hdr.mode = otoi(tar.mode, sizeof(tar.mode)); TT.hdr.mode |= (char []){8,8,10,2,6,4,1,8}[tar.type-'0']<<12; TT.hdr.uid = otoi(tar.uid, sizeof(tar.uid)); @@ -506,57 +496,66 @@ static void unpack_tar(void) TT.hdr.device = dev_makedev(otoi(tar.major, sizeof(tar.major)), otoi(tar.minor, sizeof(tar.minor))); - TT.hdr.uname = xstrndup(tar.uname, sizeof(tar.uname)); - TT.hdr.gname = xstrndup(tar.gname, sizeof(tar.gname)); + TT.hdr.uname = xstrndup(TT.owner ? TT.owner : tar.uname,sizeof(tar.uname)); + TT.hdr.gname = xstrndup(TT.group ? TT.group : tar.gname,sizeof(tar.gname)); if (!TT.hdr.link_target && *tar.link) TT.hdr.link_target = xstrndup(tar.link, sizeof(tar.link)); if (!TT.hdr.name) { + // Glue prefix and name fields together with / if necessary i = strnlen(tar.prefix, sizeof(tar.prefix)); TT.hdr.name = xmprintf("%.*s%s%.*s", i, tar.prefix, (i && tar.prefix[i-1] != '/') ? "/" : "", (int)sizeof(tar.name), tar.name); } - // Directories sometimes recorded as "file with trailing slash" + // Old broken tar recorded dir as "file with trailing slash" if (S_ISREG(TT.hdr.mode) && (s = strend(TT.hdr.name, "/"))) { *s = 0; TT.hdr.mode = (TT.hdr.mode & ~S_IFMT) | S_IFDIR; } - // Hardlinks, symlinks, and directories do not have contents in archive - // (Neither do fifo, block or char devices, but not testing for that...?) - if ((TT.hdr.link_target && *TT.hdr.link_target) - || S_ISLNK(TT.hdr.mode) || S_ISDIR(TT.hdr.mode)) + // Non-regular files don't have contents stored in archive. + if ((TT.hdr.link_target && *TT.hdr.link_target) || !S_ISREG(TT.hdr.mode)) TT.hdr.size = 0; + // Files are seen even if excluded, so check them here. + // TT.seen points to first seen entry in TT.incl, or NULL if none yet. + if ((delete = filter(TT.incl, TT.hdr.name)) && TT.incl != TT.seen) { + if (!TT.seen) TT.seen = delete; + + // Move seen entry to end of list. + if (TT.incl == delete) TT.incl = TT.incl->next; + else for (walk = TT.incl; walk != TT.seen; walk = walk->next) { + if (walk == delete) { + dlist_pop(&walk); + dlist_add_nomalloc(&TT.incl, delete); + } + } + } + // Skip excluded files - if (filter(TT.exc, TT.hdr.name) || (TT.inc && !filter(TT.inc, TT.hdr.name))) + if (filter(TT.excl, TT.hdr.name) || (TT.incl && !delete)) skippy(TT.hdr.size); - else { - -// TODO: wrong, shouldn't grow endlessly, mark seen TT.inc instead - add_to_list(&TT.pass, xstrdup(TT.hdr.name)); - - if (FLAG(t)) { - if (FLAG(v)) { - char perm[11]; - struct tm *lc = localtime(&TT.hdr.mtime); - - mode_to_string(TT.hdr.mode, perm); - printf("%s %s/%s %9ld %d-%02d-%02d %02d:%02d:%02d ", perm, - TT.hdr.uname, TT.hdr.gname, (long)TT.hdr.size, 1900+lc->tm_year, - 1+lc->tm_mon, lc->tm_mday, lc->tm_hour, lc->tm_min, lc->tm_sec); - } - printf("%s", TT.hdr.name); - if (TT.hdr.link_target) printf(" -> %s", TT.hdr.link_target); - xputc('\n'); - skippy(TT.hdr.size); - } else { - if (FLAG(v)) printf("%s\n", TT.hdr.name); - if (FLAG(O)) xsendfile_len(TT.fd, 0, TT.hdr.size); - else if (FLAG(to_command)) extract_to_command(); - else extract_to_disk(); + else if (FLAG(t)) { + if (FLAG(v)) { + struct tm *lc = localtime(&TT.hdr.mtime); + char perm[11]; + + mode_to_string(TT.hdr.mode, perm); + printf("%s %s/%s %9lld %d-%02d-%02d %02d:%02d:%02d ", perm, + TT.hdr.uname, TT.hdr.gname, (long long)TT.hdr.size, + 1900+lc->tm_year, 1+lc->tm_mon, lc->tm_mday, lc->tm_hour, + lc->tm_min, lc->tm_sec); } + printf("%s", TT.hdr.name); + if (TT.hdr.link_target) printf(" -> %s", TT.hdr.link_target); + xputc('\n'); + skippy(TT.hdr.size); + } else { + if (FLAG(v)) printf("%s\n", TT.hdr.name); + if (FLAG(O)) xsendfile_len(TT.fd, 0, TT.hdr.size); + else if (FLAG(to_command)) extract_to_command(); + else extract_to_disk(); } free(TT.hdr.name); @@ -567,24 +566,37 @@ static void unpack_tar(void) } } +// Add copy of filename to TT.incl or TT.excl, minus trailing \n and / +static void trim_list(char **pline, long len) +{ + char *n = strdup(*pline); + int i = strlen(n); + + dlist_add(TT.X ? &TT.excl : &TT.incl, n); + if (i && n[i-1]=='\n') i--; + while (i && n[i-1] == '/') i--; + n[i] = 0; +} + void tar_main(void) { - struct arg_list *tmp; char *s, **args = toys.optargs; // When extracting to command signal(SIGPIPE, SIG_IGN); if (!geteuid()) toys.optflags |= FLAG_p; + if (TT.owner) TT.ouid = xgetuid(TT.owner); + if (TT.group) TT.ggid = xgetgid(TT.group); - // Collect file list - while (*args) add_to_list(&TT.inc, *args++); - for (;TT.T; TT.T = TT.T->next) file_to_list(TT.T->arg, &TT.inc); - for (;TT.X; TT.X = TT.X->next) file_to_list(TT.X->arg, &TT.exc); + // Collect file list. Note: trim_list appends to TT.incl when !TT.X + for (;TT.X; TT.X = TT.X->next) do_lines(xopenro(TT.X->arg), '\n', trim_list); + for (args = toys.optargs; *args; args++) trim_list(args, strlen(*args)); + for (;TT.T; TT.T = TT.T->next) do_lines(xopenro(TT.T->arg), '\n', trim_list); // Open archive file if (FLAG(c)) { - if (!TT.inc) error_exit("empty archive"); + if (!TT.incl) error_exit("empty archive"); TT.fd = 1; } if (TT.f && strcmp(TT.f, "-")) @@ -597,15 +609,6 @@ void tar_main(void) // Are we reading? if (FLAG(x)||FLAG(t)) { -// TODO: autodtect - -// Try detecting .gz or .bz2 by looking for their magic. -// if ((!memcmp(tar.name, "\x1f\x8b", 2) || !memcmp(tar.name, "BZh", 3)) -// && !lseek(TT.fd, -i, SEEK_CUR)) { -// toys.optflags |= (*tar.name == 'B') ? FLAG_j : FLAG_z; -// extract_stream(tar_hdl); -// continue; -// } if (FLAG(j)||FLAG(z)) { int pipefd[2] = {TT.fd, -1}; @@ -616,13 +619,18 @@ void tar_main(void) } unpack_tar(); - for (tmp = TT.inc; tmp; tmp = tmp->next) - if (!filter(TT.exc, tmp->arg) && !filter(TT.pass, tmp->arg)) - error_msg("'%s' not in archive", tmp->arg); + if (TT.seen != TT.incl) { + if (!TT.seen) TT.seen = TT.incl; + while (TT.incl != TT.seen) { + error_msg("'%s' not in archive", TT.incl->data); + TT.incl = TT.incl->next; + } + } // are we writing? (Don't have to test flag here one of 3 must be set) } else { -// TODO: autodetect + struct double_list *dl = TT.incl; + if (FLAG(j)||FLAG(z)) { int pipefd[2] = {-1, TT.fd}; @@ -630,10 +638,9 @@ void tar_main(void) close(TT.fd); TT.fd = pipefd[0]; } - for (tmp = TT.inc; tmp; tmp = tmp->next) - dirtree_flagread(tmp->arg, FLAG(h)?DIRTREE_SYMFOLLOW:0, add_to_tar); + do dirtree_flagread(dl->data, FLAG(h)?DIRTREE_SYMFOLLOW:0, add_to_tar); + while (TT.incl != (dl = dl->next)); - memset(toybuf, 0, 1024); writeall(TT.fd, toybuf, 1024); } } diff --git a/toys/pending/vi.c b/toys/pending/vi.c index bf8db841..6a1c2349 100644 --- a/toys/pending/vi.c +++ b/toys/pending/vi.c @@ -1,6 +1,7 @@ /* vi.c - You can't spell "evil" without "vi". * * Copyright 2015 Rob Landley <rob@landley.net> + * Copyright 2019 Jarno Mäkipää <jmakip87@gmail.com> * * See http://pubs.opengroup.org/onlinepubs/9699919799/utilities/vi.html @@ -11,7 +12,6 @@ config VI default n help usage: vi FILE - Visual text editor. Predates the existence of standardized cursor keys, so the controls are weird and historical. */ @@ -20,29 +20,899 @@ config VI #include "toys.h" GLOBALS( - struct linestack *ls; - char *statline; + int cur_col; + int cur_row; + unsigned screen_height; + unsigned screen_width; + int vi_mode; ) +/* + * + * TODO: + * BUGS: screen pos adjust does not cover "widelines" + * utf8 problems with some files. perhaps use lib utf8 functions instead + * append to EOL does not show input but works when ESC out + * + * + * REFACTOR: use dllist functions where possible. + * draw_page dont draw full page at time if nothing changed... + * ex callbacks + * + * FEATURE: ex: / ? % //atleast easy cases + * vi: x dw d$ d0 + * vi: yw yy (y0 y$) + * vi+ex: gg G //line movements + * ex: r + * ex: !external programs + * ex: w filename //only writes to same file now + * big file support? + */ + + struct linestack_show { struct linestack_show *next; long top, left; int x, width, y, height; }; -// linestack, what to show, where to show it -void linestack_show(struct linestack *ls, struct linestack_show *lss) +static void draw_page(); +static void draw_char(char c, int x, int y, int highlight); +//utf8 support +static int utf8_dec(char key, char *utf8_scratch, int *sta_p) ; +static int utf8_len(char *str); +static int draw_rune(char *c, int x, int y, int highlight); + + +static void cur_left(); +static void cur_right(); +static void cur_up(); +static void cur_down(); +static void check_cursor_bounds(); +static void adjust_screen_buffer(); + + +struct str_line { + int alloc_len; + int str_len; + char *str_data; +}; + +//lib dllist uses next and prev kinda opposite what im used to so I just +//renamed both ends to up and down +struct linelist { + struct linelist *up;//next + struct linelist *down;//prev + struct str_line *line; +}; +//inserted line not yet pushed to buffer +struct str_line *il; +struct linelist *text; //file loaded into buffer +struct linelist *scr_r;//current screen coord 0 row +struct linelist *c_r;//cursor position row +int modified; + +void dlist_insert_nomalloc(struct double_list **list, struct double_list *new) { - return; + if (*list) { + new->next = *list; + new->prev = (*list)->prev; + if ((*list)->prev) (*list)->prev->next = new; + (*list)->prev = new; + } else *list = new->next = new->prev = new; } -void vi_main(void) + +// Add an entry to the end of a doubly linked list +struct double_list *dlist_insert(struct double_list **list, char *data) { - int i; + struct double_list *new = xmalloc(sizeof(struct double_list)); + new->data = data; + dlist_insert_nomalloc(list, new); - if (!(TT.ls = linestack_load(*toys.optargs))) - TT.ls = xzalloc(sizeof(struct linestack)); + return new; +} +void linelist_unload() +{ - for (i=0; i<TT.ls->len; i++) - printf("%.*s\n", (int)TT.ls->idx[i].len, (char *)TT.ls->idx[i].ptr); +} + +void write_file(char *filename) +{ + struct linelist *lst = text; + FILE *fp = 0; + if (!filename) + filename = (char*)*toys.optargs; + fp = fopen(filename, "w"); + if (!fp) return ; + while (lst) { + fprintf(fp, "%s\n", lst->line->str_data); + lst = lst->down; + } + fclose(fp); +} + +int linelist_load(char *filename) +{ + struct linelist *lst = c_r;//cursor position or 0 + FILE *fp = 0; + if (!filename) + filename = (char*)*toys.optargs; + + fp = fopen(filename, "r"); + if (!fp) return 0; + + for (;;) { + char *line = xzalloc(80); + ssize_t alc = 80; + ssize_t len; + if ((len = getline(&line, (void *)&alc, fp)) == -1) { + if (errno == EINVAL || errno == ENOMEM) { + printf("error %d\n", errno); + } + free(line); + break; + } + lst = (struct linelist*)dlist_add((struct double_list**)&lst, + xzalloc(sizeof(struct str_line))); + lst->line->alloc_len = alc; + lst->line->str_len = len; + lst->line->str_data = line; + + if (lst->line->str_data[len-1] == '\n') { + lst->line->str_data[len-1] = 0; + lst->line->str_len--; + } + if (text == 0) { + text = lst; + } + + } + if (text) { + dlist_terminate(text->up); + } + fclose(fp); + return 1; + +} +//TODO this is overly complicated refactor with lib dllist +int ex_dd(int count) +{ + struct linelist *lst = c_r; + if (c_r == text && text == scr_r) { + if (!text->down && !text->up && text->line) { + text->line->str_len = 1; + sprintf(text->line->str_data, " "); + goto success_exit; + } + if (text->down) { + text = text->down; + text->up = 0; + c_r = text; + scr_r = text; + free(lst->line->str_data); + free(lst->line); + free(lst); + } + goto recursion_exit; + } + //TODO use lib dllist stuff + if (lst) + { + if (lst->down) { + lst->down->up = lst->up; + } + if (lst->up) { + lst->up->down = lst->down; + } + if (scr_r == c_r) { + scr_r = c_r->down ? c_r->down : c_r->up; + } + if (c_r->down) + c_r = c_r->down; + else { + c_r = c_r->up; + count = 1; + } + free(lst->line->str_data); + free(lst->line); + free(lst); + } + +recursion_exit: + count--; + //make this recursive + if (count) + return ex_dd(count); +success_exit: + check_cursor_bounds(); + adjust_screen_buffer(); + return 1; +} + +int ex_dw(int count) +{ + return 1; +} + +int ex_deol(int count) +{ + return 1; +} + +//does not work with utf8 yet +int vi_x(int count) +{ + char *s; + int *l; + int *p; + if (!c_r) + return 0; + s = c_r->line->str_data; + l = &c_r->line->str_len; + p = &TT.cur_col; + if (!(*l)) return 0; + if ((*p) == (*l)-1) { + s[*p] = 0; + if (*p) (*p)--; + (*l)--; + } else { + memmove(s+(*p), s+(*p)+1, (*l)-(*p)); + s[*l] = 0; + (*l)--; + } + count--; + return (count) ? vi_x(count) : 1; +} + +//move commands does not behave correct way yet. +//only jump to next space for now. +int vi_movw(int count) +{ + if (!c_r) + return 0; + //could we call moveend first + while (c_r->line->str_data[TT.cur_col] > ' ') + TT.cur_col++; + while (c_r->line->str_data[TT.cur_col] <= ' ') { + TT.cur_col++; + if (!c_r->line->str_data[TT.cur_col]) { + //we could call j and g0 + if (!c_r->down) return 0; + c_r = c_r->down; + TT.cur_col = 0; + } + } + count--; + if (count>1) + return vi_movw(count); + + check_cursor_bounds(); + adjust_screen_buffer(); + return 1; +} + +int vi_movb(int count) +{ + if (!c_r) + return 0; + if (!TT.cur_col) { + if (!c_r->up) return 0; + c_r = c_r->up; + TT.cur_col = (c_r->line->str_len) ? c_r->line->str_len-1 : 0; + goto exit_function; + } + if (TT.cur_col) + TT.cur_col--; + while (c_r->line->str_data[TT.cur_col] <= ' ') { + if (TT.cur_col) TT.cur_col--; + else goto exit_function; + } + while (c_r->line->str_data[TT.cur_col] > ' ') { + if (TT.cur_col)TT.cur_col--; + else goto exit_function; + } + TT.cur_col++; +exit_function: + count--; + if (count>1) + return vi_movb(count); + check_cursor_bounds(); + adjust_screen_buffer(); + return 1; +} + +int vi_move(int count) +{ + if (!c_r) + return 0; + if (TT.cur_col < c_r->line->str_len) + TT.cur_col++; + if (c_r->line->str_data[TT.cur_col] <= ' ' || count > 1) + vi_movw(count); //find next word; + while (c_r->line->str_data[TT.cur_col] > ' ') + TT.cur_col++; + if (TT.cur_col) TT.cur_col--; + + check_cursor_bounds(); + adjust_screen_buffer(); + return 1; +} + +void i_insert() +{ + char *t = xzalloc(c_r->line->alloc_len); + char *s = c_r->line->str_data; + int sel = c_r->line->str_len-TT.cur_col; + strncpy(t, &s[TT.cur_col], sel); + t[sel+1] = 0; + if (c_r->line->alloc_len < c_r->line->str_len+il->str_len+5) { + c_r->line->str_data = xrealloc(c_r->line->str_data, + c_r->line->alloc_len*2+il->alloc_len*2); + + c_r->line->alloc_len = c_r->line->alloc_len*2+2*il->alloc_len; + memset(&c_r->line->str_data[c_r->line->str_len], 0, + c_r->line->alloc_len-c_r->line->str_len); + + s = c_r->line->str_data; + } + strcpy(&s[TT.cur_col], il->str_data); + strcpy(&s[TT.cur_col+il->str_len], t); + TT.cur_col += il->str_len; + if (TT.cur_col) TT.cur_col--; + c_r->line->str_len += il->str_len; + free(t); + +} + +//new line at split pos; +void i_split() +{ + struct str_line *l = xmalloc(sizeof(struct str_line)); + int l_a = c_r->line->alloc_len; + int l_len = c_r->line->str_len-TT.cur_col; + l->str_data = xzalloc(l_a); + l->alloc_len = l_a; + l->str_len = l_len; + strncpy(l->str_data, &c_r->line->str_data[TT.cur_col], l_len); + l->str_data[l_len] = 0; + c_r->line->str_len -= l_len; + c_r->line->str_data[c_r->line->str_len] = 0; + c_r = (struct linelist*)dlist_insert((struct double_list**)&c_r, (char*)l); + c_r->line = l; + TT.cur_col = 0; + check_cursor_bounds(); + adjust_screen_buffer(); +} + +struct vi_cmd_param { + const char *cmd; + int (*vi_cmd_ptr)(int); +}; + +struct vi_cmd_param vi_cmds[7] = +{ + {"dd", &ex_dd}, + {"dw", &ex_dw}, + {"d$", &ex_deol}, + {"w", &vi_movw}, + {"b", &vi_movb}, + {"e", &vi_move}, + {"x", &vi_x}, +}; + +int run_vi_cmd(char *cmd) +{ + int val = 0; + char *cmd_e; + errno = 0; + int i = 0; + val = strtol(cmd, &cmd_e, 10); + if (errno || val == 0) { + val = 1; + } + else { + cmd = cmd_e; + } + for (; i<7; i++) { + if (strstr(cmd, vi_cmds[i].cmd)) { + return vi_cmds[i].vi_cmd_ptr(val); + } + } + return 0; + +} + +int search_str(char *s) +{ + struct linelist *lst = c_r; + char *c = strstr(&c_r->line->str_data[TT.cur_col], s); + if (c) { + TT.cur_col = c_r->line->str_data-c; + TT.cur_col = c-c_r->line->str_data; + } + else for (; !c;) { + lst = lst->down; + if (!lst) return 1; + c = strstr(&lst->line->str_data[TT.cur_col], s); + } + c_r = lst; + TT.cur_col = c-c_r->line->str_data; + return 0; +} + +int run_ex_cmd(char *cmd) +{ + if (cmd[0] == '/') { + //search pattern + if (!search_str(&cmd[1]) ) { + check_cursor_bounds(); + adjust_screen_buffer(); + } + } else if (cmd[0] == '?') { + + } else if (cmd[0] == ':') { + if (strstr(&cmd[1], "q!")) { + //exit_application; + return -1; + } + else if (strstr(&cmd[1], "wq")) { + write_file(0); + return -1; + } + else if (strstr(&cmd[1], "w")) { + write_file(0); + return 1; + } + } + return 0; + +} + +void vi_main(void) +{ + char keybuf[16]; + char utf8_code[8]; + int utf8_dec_p = 0; + int key = 0; + char vi_buf[16]; + int vi_buf_pos = 0; + il = xzalloc(sizeof(struct str_line)); + il->str_data = xzalloc(80); + il->alloc_len = 80; + keybuf[0] = 0; + memset(vi_buf, 0, 16); + memset(utf8_code, 0, 8); + linelist_load(0); + scr_r = text; + c_r = text; + TT.cur_row = 0; + TT.cur_col = 0; + TT.screen_width = 80; + TT.screen_height = 24; + TT.vi_mode = 1; + terminal_size(&TT.screen_width, &TT.screen_height); + TT.screen_height -= 2; //TODO this is hack fix visual alignment + set_terminal(0, 1, 0, 0); + //writes stdout into different xterm buffer so when we exit + //we dont get scroll log full of junk + tty_esc("?1049h"); + tty_esc("H"); + xflush(); + draw_page(); + while(1) { + key = scan_key(keybuf, -1); + printf("key %d\n", key); + switch (key) { + case -1: + case 3: + case 4: + goto cleanup_vi; + } + if (TT.vi_mode == 1) { //NORMAL + switch (key) { + case 'h': + cur_left(); + break; + case 'j': + cur_down(); + break; + case 'k': + cur_up(); + break; + case 'l': + cur_right(); + break; + case '/': + case '?': + case ':': + TT.vi_mode = 0; + il->str_data[0]=key; + il->str_len++; + break; + case 'a': + TT.cur_col++; + case 'i': + TT.vi_mode = 2; + break; + case 27: + vi_buf[0] = 0; + vi_buf_pos = 0; + break; + default: + if (key > 0x20 && key < 0x7B) { + vi_buf[vi_buf_pos] = key; + vi_buf_pos++; + if (run_vi_cmd(vi_buf)) { + memset(vi_buf, 0, 16); + vi_buf_pos = 0; + } + else if (vi_buf_pos == 16) { + vi_buf_pos = 0; + } + + } + + break; + } + } else if (TT.vi_mode == 0) { //EX MODE + switch (key) { + case 27: + TT.vi_mode = 1; + il->str_len = 0; + memset(il->str_data, 0, il->alloc_len); + break; + case 0x7F: + case 0x08: + if (il->str_len) { + il->str_data[il->str_len] = 0; + if (il->str_len > 1) il->str_len--; + } + break; + case 0x0D: + if (run_ex_cmd(il->str_data) == -1) + goto cleanup_vi; + TT.vi_mode = 1; + il->str_len = 0; + memset(il->str_data, 0, il->alloc_len); + break; + default: //add chars to ex command until ENTER + if (key >= 0x20 && key < 0x7F) { //might be utf? + if (il->str_len == il->alloc_len) { + il->str_data = realloc(il->str_data, il->alloc_len*2); + il->alloc_len *= 2; + } + il->str_data[il->str_len] = key; + il->str_len++; + } + break; + } + } else if (TT.vi_mode == 2) {//INSERT MODE + switch (key) { + case 27: + i_insert(); + TT.vi_mode = 1; + il->str_len = 0; + memset(il->str_data, 0, il->alloc_len); + break; + case 0x7F: + case 0x08: + if (il->str_len) + il->str_data[il->str_len--] = 0; + break; + case 0x09: + //TODO implement real tabs + il->str_data[il->str_len++] = ' '; + il->str_data[il->str_len++] = ' '; + break; + + case 0x0D: + //insert newline + // + i_insert(); + il->str_len = 0; + memset(il->str_data, 0, il->alloc_len); + i_split(); + break; + default: + if (key >= 0x20 && utf8_dec(key, utf8_code, &utf8_dec_p)) { + if (il->str_len+utf8_dec_p+1 >= il->alloc_len) { + il->str_data = realloc(il->str_data, il->alloc_len*2); + il->alloc_len *= 2; + } + strcpy(il->str_data+il->str_len, utf8_code); + il->str_len += utf8_dec_p; + utf8_dec_p = 0; + *utf8_code = 0; + + } + break; + } + } + + draw_page(); + + } +cleanup_vi: + tty_reset(); + tty_esc("?1049l"); +} + +static void draw_page() +{ + unsigned y = 0; + int cy_scr = 0; + int cx_scr = 0; + int utf_l = 0; + struct linelist *scr_buf= scr_r; + //clear screen + tty_esc("2J"); + tty_esc("H"); + + + tty_jump(0, 0); + for (; y < TT.screen_height; ) { + if (scr_buf && scr_buf->line->str_data && scr_buf->line->str_len) { + int p = 0; + for (; p < scr_buf->line->str_len; y++) { + unsigned x = 0; + for (; x < TT.screen_width; x++) { + if (p < scr_buf->line->str_len) { + int hi = 0; + if (scr_buf == c_r && p == TT.cur_col) { + if (TT.vi_mode == 2) { + tty_jump(x, y); + + tty_esc("1m"); //bold + printf("%s", il->str_data); + x += il->str_len; + tty_esc("0m"); + } + cy_scr = y; + cx_scr = x; + } + utf_l = draw_rune(&scr_buf->line->str_data[p], x, y, hi); + if (!utf_l) + break; + p += utf_l; + if (utf_l > 2) x++;//traditional chinese is somehow 2 width in tty??? + } + else { + if (scr_buf == c_r && p == TT.cur_col) { + if (TT.vi_mode == 2) { + tty_jump(x, y); + + tty_esc("1m"); //bold + printf("%s", il->str_data); + x += il->str_len; + tty_esc("0m"); + } + cy_scr = y; + cx_scr = x; + } + break; + } + } + printf("\r\n"); + } + } + else { + if (scr_buf == c_r){ + cy_scr = y; + cx_scr = 0; + if (TT.vi_mode == 2) { + tty_jump(0, y); + tty_esc("1m"); //bold + printf("%s", il->str_data); + cx_scr += il->str_len; + tty_esc("0m"); + } else draw_char(' ', 0, y, 1); + } + y++; + } + printf("\n"); + if (scr_buf->down) + scr_buf=scr_buf->down; + else break; + } + for (; y < TT.screen_height; y++) { + printf("\n"); + } + + tty_jump(0, TT.screen_height); + switch (TT.vi_mode) { + case 0: + tty_esc("30;44m"); + printf("COMMAND|"); + break; + case 1: + tty_esc("30;42m"); + printf("NORMAL|"); + break; + case 2: + tty_esc("30;41m"); + printf("INSERT|"); + break; + + } + //DEBUG + tty_esc("47m"); + tty_esc("30m"); + utf_l = utf8_len(&c_r->line->str_data[TT.cur_col]); + if (utf_l) { + char t[5] = {0, 0, 0, 0, 0}; + strncpy(t, &c_r->line->str_data[TT.cur_col], utf_l); + printf("utf: %d %s", utf_l, t); + } + printf("| %d, %d\n", cx_scr, cy_scr); //screen coord + + tty_jump(TT.screen_width-12, TT.screen_height); + printf("| %d, %d\n", TT.cur_row, TT.cur_col); + tty_esc("37m"); + tty_esc("40m"); + if (!TT.vi_mode) { + tty_esc("1m"); + tty_jump(0, TT.screen_height+1); + printf("%s", il->str_data); + } else tty_jump(cx_scr, cy_scr); + xflush(); + +} + +static void draw_char(char c, int x, int y, int highlight) +{ + tty_jump(x, y); + if (highlight) { + tty_esc("30m"); //foreground black + tty_esc("47m"); //background white + } + printf("%c", c); +} + +//utf rune draw +//printf and useless copy could be replaced by direct write() to stdout +static int draw_rune(char *c, int x, int y, int highlight) +{ + int l = utf8_len(c); + char t[5] = {0, 0, 0, 0, 0}; + if (!l) return 0; + tty_jump(x, y); + tty_esc("0m"); + if (highlight) { + tty_esc("30m"); //foreground black + tty_esc("47m"); //background white + } + strncpy(t, c, 5); + printf("%s", t); + tty_esc("0m"); + return l; +} + +static void check_cursor_bounds() +{ + if (c_r->line->str_len-1 < TT.cur_col) { + if (c_r->line->str_len == 0) + TT.cur_col = 0; + else + TT.cur_col = c_r->line->str_len-1; + } +} + +static void adjust_screen_buffer() +{ + //search cursor and screen TODO move this perhaps + struct linelist *t = text; + int c = -1; + int s = -1; + int i = 0; + for (;;) { + i++; + if (t == c_r) + c = i; + if (t == scr_r) + s = i; + t = t->down; + if ( ((c != -1) && (s != -1)) || t == 0) + break; + } + if (c <= s) { + scr_r = c_r; + } + else if ( c > s ) { + //should count multiline long strings! + int distance = c - s + 1; + //TODO instead iterate scr_r up and check strlen%screen_width + //for each iteration + if (distance >= (int)TT.screen_height) { + int adj = distance - TT.screen_height; + while(adj--) { + scr_r = scr_r->down; + } + } + } + TT.cur_row = c; + +} + +//return 0 if not ASCII nor UTF-8 +//this is not fully tested +//naive implementation with branches +//there is better branchless lookup table versions out there +//1 0xxxxxxx +//2 110xxxxx 10xxxxxx +//3 1110xxxx 10xxxxxx 10xxxxxx +//4 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx +static int utf8_len(char *str) +{ + int len = 0; + int i = 0; + uint8_t *c = (uint8_t*)str; + if (!c || !(*c)) return 0; + if (*c < 0x7F) return 1; + if ((*c & 0xE0) == 0xc0) len = 2; + else if ((*c & 0xF0) == 0xE0 ) len = 3; + else if ((*c & 0xF8) == 0xF0 ) len = 4; + else return 0; + c++; + for (i = len-1; i > 0; i--) { + if ((*c++ & 0xc0) != 0x80) return 0; + } + return len; +} + +static int utf8_dec(char key, char *utf8_scratch, int *sta_p) +{ + int len = 0; + char *c = utf8_scratch; + c[*sta_p] = key; + if (!(*sta_p)) *c = key; + if (*c < 0x7F) { *sta_p = 1; return 1; } + if ((*c & 0xE0) == 0xc0) len = 2; + else if ((*c & 0xF0) == 0xE0 ) len = 3; + else if ((*c & 0xF8) == 0xF0 ) len = 4; + else {*sta_p = 0; return 0; } + + (*sta_p)++; + + if (*sta_p == 1) return 0; + if ((c[*sta_p-1] & 0xc0) != 0x80) {*sta_p = 0; return 0; } + + if (*sta_p == len) { c[(*sta_p)] = 0; return 1; } + + return 0; +} + +static void cur_left() +{ + if (!TT.cur_col) return; + TT.cur_col--; + + if (!utf8_len(&c_r->line->str_data[TT.cur_col])) cur_left(); +} + +static void cur_right() +{ + if (TT.cur_col == c_r->line->str_len-1) return; + TT.cur_col++; + if (!utf8_len(&c_r->line->str_data[TT.cur_col])) cur_right(); +} + +static void cur_up() +{ + if (c_r->up != 0) + c_r = c_r->up; + + if (!utf8_len(&c_r->line->str_data[TT.cur_col])) cur_left(); + check_cursor_bounds(); + adjust_screen_buffer(); +} + +static void cur_down() +{ + if (c_r->down != 0) + c_r = c_r->down; + + if (!utf8_len(&c_r->line->str_data[TT.cur_col])) cur_left(); + check_cursor_bounds(); + adjust_screen_buffer(); } diff --git a/toys/posix/date.c b/toys/posix/date.c index 43b558a5..685ac8bf 100644 --- a/toys/posix/date.c +++ b/toys/posix/date.c @@ -59,135 +59,29 @@ GLOBALS( unsigned nano; ) -static void check_range(int a, int low, int high) -{ - if (a<low) error_exit("%d<%d", a, low); - if (a>high) error_exit("%d>%d", a, high); -} - -static void check_tm(struct tm *tm) -{ - check_range(tm->tm_sec, 0, 60); - check_range(tm->tm_min, 0, 59); - check_range(tm->tm_hour, 0, 23); - check_range(tm->tm_mday, 1, 31); - check_range(tm->tm_mon, 0, 11); -} - -// Returns 0 success, nonzero for error. -static int parse_formats(char *str, time_t *t) -{ - struct tm tm; - time_t now; - int len = 0, i; - char *formats[] = { - // Formats with years must come first. - "%Y-%m-%d %H:%M:%S", "%Y-%m-%d %H:%M", "%Y-%m-%d", - "%H:%M:%S", "%H:%M" - }; - - // Parse @UNIXTIME[.FRACTION] - if (*str == '@') { - long long ll; - - // Collect seconds and nanoseconds. - // &ll is not just t because we can't guarantee time_t is 64 bit (yet). - sscanf(str, "@%lld%n", &ll, &len); - if (str[len]=='.') { - str += len+1; - for (len = 0; len<9; len++) { - TT.nano *= 10; - if (isdigit(str[len])) TT.nano += str[len]-'0'; - } - } - if (str[len]) return 1; - *t = ll; - return 0; - } - - // Is it one of the fancy formats? - for (i = 0; i<ARRAY_LEN(formats); i++) { - char *p; - - now = time(0); - localtime_r(&now, &tm); - tm.tm_hour = tm.tm_min = tm.tm_sec = 0; - tm.tm_isdst = -1; - if ((p = strptime(str,formats[i],&tm)) && !*p) { - if ((*t = mktime(&tm)) != -1) return 0; - } - } - - // Posix format? - sscanf(str, "%2u%2u%2u%2u%n", &tm.tm_mon, &tm.tm_mday, &tm.tm_hour, - &tm.tm_min, &len); - if (len != 8) return 1; - str += len; - tm.tm_mon--; - - // If year specified, overwrite one we fetched earlier. - if (*str && *str != '.') { - unsigned year; - - len = 0; - sscanf(str, "%u%n", &year, &len); - if (len == 4) tm.tm_year = year - 1900; - else if (len != 2) return 1; - str += len; - - // 2 digit years, next 50 years are "future", last 50 years are "past". - // A "future" date in past is a century ahead. - // A non-future date in the future is a century behind. - if (len == 2) { - unsigned r1 = tm.tm_year % 100, r2 = (tm.tm_year + 50) % 100, - century = tm.tm_year - r1; - - if ((r1 < r2) ? (r1 < year && year < r2) : (year < r1 || year > r2)) { - if (year < r1) year += 100; - } else if (year > r1) year -= 100; - tm.tm_year = year + century; - } - } - // Fractional part? - if (*str == '.') { - len = 0; - sscanf(str, ".%u%n", &tm.tm_sec, &len); - str += len; - } else tm.tm_sec = 0; - - // Does that look like a valid date? - check_tm(&tm); - if ((*t = mktime(&tm)) == -1) return 1; - - // Shouldn't be any trailing garbage. - return *str; -} - // Handles any leading `TZ="blah" ` in the input string. -static int parse_date(char *str, time_t *t) +static void parse_date(char *str, time_t *t) { - char *new_tz = NULL, *old_tz; - int result; + char *new_tz = NULL, *old_tz, *s = str; if (!strncmp(str, "TZ=\"", 4)) { // Extract the time zone and skip any whitespace. new_tz = str+4; - str = strchr(new_tz, '"'); - if (!str) return 1; - *str++ = '\0'; - while (isspace(*str)) ++str; + if (!(str = strchr(new_tz, '"'))) xvali_date(0, s); + *str++ = 0; + while (isspace(*str)) str++; // Switch $TZ. old_tz = getenv("TZ"); setenv("TZ", new_tz, 1); tzset(); } - result = parse_formats(str, t); + time(t); + xparsedate(str, t, &TT.nano); if (new_tz) { if (old_tz) setenv("TZ", old_tz, 1); else unsetenv("TZ"); } - return result; } // Print strftime plus %N escape(s). note: modifies fmt for %N @@ -236,10 +130,8 @@ void date_main(void) struct tm tm = {}; char *s = strptime(TT.d, TT.D+(*TT.D=='+'), &tm); - if (!s || *s) goto bad_showdate; - check_tm(&tm); - if ((t = mktime(&tm)) == -1) goto bad_showdate; - } else if (parse_date(TT.d, &t)) goto bad_showdate; + t = (s && *s) ? xvali_date(&tm, s) : xvali_date(0, TT.d); + } else parse_date(TT.d, &t); } else { struct timespec ts; struct stat st; @@ -264,7 +156,7 @@ void date_main(void) } else if (setdate) { struct timeval tv; - if (parse_date(setdate, &t)) goto bad_setdate; + parse_date(setdate, &t); tv.tv_sec = t; tv.tv_usec = TT.nano/1000; if (settimeofday(&tv, NULL) < 0) perror_msg("cannot set date"); @@ -279,9 +171,4 @@ void date_main(void) } return; - -bad_showdate: - setdate = TT.d; -bad_setdate: - error_exit("bad date '%s'", setdate); } diff --git a/toys/posix/find.c b/toys/posix/find.c index 1c35155b..5cefbf15 100644 --- a/toys/posix/find.c +++ b/toys/posix/find.c @@ -530,7 +530,7 @@ static int do_find(struct dirtree *new) aa->plus = 1; toys.exitval |= flush_exec(new, aa); } - } else test = flush_exec(new, aa); + } else test = !flush_exec(new, aa); } // Argument consumed, skip the check. diff --git a/toys/posix/rm.c b/toys/posix/rm.c index 608e1ca1..6c64e13f 100644 --- a/toys/posix/rm.c +++ b/toys/posix/rm.c @@ -4,19 +4,20 @@ * * See http://pubs.opengroup.org/onlinepubs/9699919799/utilities/rm.html -USE_RM(NEWTOY(rm, "fiRr[-fi]", TOYFLAG_BIN)) +USE_RM(NEWTOY(rm, "fiRrv[-fi]", TOYFLAG_BIN)) config RM bool "rm" default y help - usage: rm [-fiRr] FILE... + usage: rm [-fiRrv] FILE... Remove each argument from the filesystem. -f Force: remove without confirmation, no error if it doesn't exist -i Interactive: prompt for confirmation -rR Recursive: remove directory contents + -v Verbose */ #define FOR_rm @@ -68,7 +69,13 @@ static int do_rm(struct dirtree *try) } skip: - if (unlinkat(fd, try->name, using)) { + if (!unlinkat(fd, try->name, using)) { + if (flags & FLAG_v) { + char *s = dirtree_path(try, 0); + printf("%s%s '%s'\n", toys.which->name, dir ? "dir" : "", s); + free(s); + } + } else { if (!dir || try->symlink != (char *)2) perror_msg_raw(try->name); nodelete: if (try->parent) try->parent->symlink = (char *)2; |