diff options
205 files changed, 6675 insertions, 2917 deletions
@@ -1,7 +1,9 @@ syntax: glob .config* +change/ generated/ kconfig/mconf kconfig/conf +kconfig/*.c toybox toybox_unstripped @@ -13,12 +13,13 @@ config TOYBOX bool default y help - usage: toybox [--long | [command] [arguments...]] + usage: toybox [--long | --version | [command] [arguments...]] With no arguments, shows available commands. First argument is name of a command to run, followed by any arguments to that command. --long Show path to each command + --version Show toybox version To install command symlinks, try: for i in $(/bin/toybox --long); do ln -s /bin/toybox $i; done @@ -34,6 +35,33 @@ config TOYBOX_SUID chown root:root toybox; chmod +s toybox +choice + prompt "Security Blanket" + default TOYBOX_LSM_NONE + help + Select a Linux Security Module to complicate your system + until you can't find holes in it. + +config TOYBOX_LSM_NONE + bool "None" + help + Don't try to achieve "watertight" by plugging the holes in a + collander, instead use conventional unix security (and possibly + Linux Containers) for a simple straightforward system. + +config TOYBOX_SELINUX + bool "SELinux support" + help + Include SELinux options in commands such as ls, and add + SELinux-specific commands such as chcon to the Android menu. + +config TOYBOX_SMACK + bool "SMACK support" + help + Include SMACK options in commands like ls for systems like Tizen. + +endchoice + config TOYBOX_FLOAT bool "Floating point support" default y @@ -4,14 +4,18 @@ all: toybox KCONFIG_CONFIG ?= .config -toybox toybox_unstripped: $(KCONFIG_CONFIG) *.[ch] lib/*.[ch] toys/*.h toys/*/*.c scripts/*.sh + +toybox_stuff: $(KCONFIG_CONFIG) *.[ch] lib/*.[ch] toys/*.h toys/*/*.c scripts/*.sh + +toybox toybox_unstripped: toybox_stuff scripts/make.sh .PHONY: clean distclean baseline bloatcheck install install_flat \ - uinstall uninstall_flat test tests help + uinstall uninstall_flat test tests help toybox_stuff change include kconfig/Makefile +$(KCONFIG_CONFIG): $(KCONFIG_TOP) $(KCONFIG_TOP): generated/Config.in generated/Config.in: toys/*/*.c scripts/genconfig.sh scripts/genconfig.sh @@ -25,7 +29,8 @@ baseline: toybox_unstripped bloatcheck: toybox_old toybox_unstripped @scripts/bloatcheck toybox_old toybox_unstripped -generated/instlist: toybox +generated/instlist: toybox_stuff + NOBUILD=1 scripts/make.sh $(HOSTCC) -I . scripts/install.c -o generated/instlist install_flat: generated/instlist @@ -40,8 +45,11 @@ uninstall_flat: generated/instlist uninstall: scripts/install.sh --long --uninstall +change: + scripts/change.sh + clean:: - rm -rf toybox toybox_unstripped generated .singleconfig* + rm -rf toybox toybox_unstripped generated change .singleconfig* distclean: clean rm -f toybox_old .config* @@ -53,7 +61,8 @@ tests: help:: @echo ' toybox - Build toybox.' - @echo ' baseline - Create busybox_old for use by bloatcheck.' + @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' @echo ' test - Run test suite against compiled commands.' @echo ' clean - Delete temporary files.' @@ -21,7 +21,7 @@ Type "make help" for build instructions. Usually you want something like: make defconfig - CFLAGS="--static" CROSS_COMPILE=armv5l- make toybox + LDFLAGS="--static" CROSS_COMPILE=armv5l- make toybox PREFIX=/path/to/root/filesystem make install The CROSS_COMPILE argument is optional, and without it builds a version of @@ -68,8 +68,7 @@ Toybox is not a complete operating system, it's a program that runs under an operating system. Booting a simple system to a shell prompt requires three packages: an operating system kernel (Linux) to drive the hardware, a program for the system to run (toybox), and a C library to tie them -together (toybox has been tested with musl, uClibc, and glibc, on Android -systems musl is recommended). +together (toybox has been tested with musl, uClibc, glibc, and bionic). The C library is part of a "toolchain", which is an integrated suite of compiler, assembler, and linker, plus the standard headers and libraries @@ -77,12 +76,44 @@ necessary to build C programs. Static linking (with the --static option) copies the shared library contents into the program, resulting in larger but more portable programs, which -can run even if they'rr the only file in the filesystem. Otherwise, +can run even if they're the only file in the filesystem. Otherwise, the "dynamically" linked programs require the library files to be present on the target system ("man ldd" and "man ld.so" for details). -Toybox is not a kernel, it needs Linux to drive the hardware. - An example toybox-based system is Aboriginal Linux: - http://landley.net/aboriginal + http://landley.net/aboriginal/about.html + +That's designed to run under qemu, emulating several different hardware +architectures (x86, x86-64, arm, mips, sparc, powerpc, sh4). Each toybox +release is regression tested by building Linux From Scratch under this +toybox-based system on each supported architecture, using QEMU to emulate +big and little endian systems with different word size and alignment +requirements. + +--- Presentations + +1) "Why Toybox?" 2013 talk here at CELF + + video: http://youtu.be/SGmtP5Lg_t0 + outline: http://landley.net/talks/celf-2013.txt + linked from http://landley.net/toybox/ in nav bar on left as "Why is it?" + - march 21, 2013 entry has section links. + +2) "Why Public Domain?" The rise and fall of copyleft, Ohio LinuxFest 2013 + + audio: https://archive.org/download/OhioLinuxfest2013/24-Rob_Landley-The_Rise_and_Fall_of_Copyleft.mp3 + outline: http://landley.net/talks/ohio-2013.txt + +3) Why did I do Aboriginal Linux (which led me here) + + 260 slide presentation: + https://speakerdeck.com/landley/developing-for-non-x86-targets-using-qemu + + How and why to make android self-hosting: + http://landley.net/aboriginal/about.html#selfhost + +4) What's new with toybox (ELC 2015 status update): + + video: http://elinux.org/ELC_2015_Presentations + outline: http://landley.net/talks/celf-2015.txt @@ -4,14 +4,19 @@ # A synonym. [ -z "$CROSS_COMPILE" ] && CROSS_COMPILE="$CROSS" + +# CFLAGS and OPTIMIZE are different so you can add extra CFLAGS without +# disabling default optimizations [ -z "$CFLAGS" ] && CFLAGS="-Wall -Wundef -Wno-char-subscripts" # Required for our expected ABI. we're 8-bit clean thus "char" must be unsigned. CFLAGS="$CFLAGS -funsigned-char" - [ -z "$OPTIMIZE" ] && OPTIMIZE="-Os -ffunction-sections -fdata-sections -fno-asynchronous-unwind-tables" + +# We accept LDFLAGS, but by default don't have anything in it [ -z "$LDOPTIMIZE" ] && LDOPTIMIZE="-Wl,--gc-sections" + [ -z "$CC" ] && CC=cc -[ -z "$STRIP" ] && STRIP=strip -# If HOSTCC needs CFLAGS, add them to the variable ala HOSTCC="blah-cc --static" +# If HOSTCC needs CFLAGS or LDFLAGS, just add them to the variable +# ala HOSTCC="blah-cc --static" [ -z "$HOSTCC" ] && HOSTCC=gcc diff --git a/kconfig/Makefile b/kconfig/Makefile index 7eaee95..a2547cc 100644 --- a/kconfig/Makefile +++ b/kconfig/Makefile @@ -21,16 +21,16 @@ silentoldconfig: $(obj)/conf $(KCONFIG_TOP) $< -s $(KCONFIG_TOP) randconfig: $(obj)/conf $(KCONFIG_TOP) - $< -r $(KCONFIG_TOP) + $< -r $(KCONFIG_TOP) > /dev/null allyesconfig: $(obj)/conf $(KCONFIG_TOP) - $< -y $(KCONFIG_TOP) + $< -y $(KCONFIG_TOP) > /dev/null allnoconfig: $(obj)/conf $(KCONFIG_TOP) - $< -n $(KCONFIG_TOP) + $< -n $(KCONFIG_TOP) > /dev/null defconfig: $(obj)/conf $(KCONFIG_TOP) - $< -D /dev/null $(KCONFIG_TOP) + $< -D /dev/null $(KCONFIG_TOP) > /dev/null # Help text used by make help help:: diff --git a/kconfig/README b/kconfig/README new file mode 100644 index 0000000..8809bbf --- /dev/null +++ b/kconfig/README @@ -0,0 +1,26 @@ +This is a snapshot of linux 2.6.12 kconfig as washed through busybox and +further modified by Rob Landley. + +Way back when I tried to push my local changes to kconfig upstream +in 2005 https://lwn.net/Articles/161086/ +and 2006 http://lkml.iu.edu/hypermail/linux/kernel/0607.0/1805.html +and 2007 http://lkml.iu.edu/hypermail/linux/kernel/0707.1/1741.html +each of which spawned long "I think you should go do this and this and this +but I'm not going to lift a finger personally" threads from the kernel +developers. Twice I came back a year later to see if there was any interest +in what I _had_ done, and the third thread was the longest of the lot but +no code was merged as a result. + +*shrug* That's the linux-kernel community for you. I had an easier time +than the author of squashfs, who spent 5 years actively trying to get his code +merged, finally quitting his job to spend an unpaid year working on upstreaming +squashfs _after_ after every major Linux distro had been locally carrying it +for years. No really, here's where he wrote about it himself: + +https://lwn.net/Articles/563578/ + +This code is _going_away_. Rewriting it is low priority, but removing it is a +checklist item for the 1.0 toybox release. This directory contains the only +GPL code left in the tree, and none of its code winds up in the resulting +binary. It's just an editor that reads our Config.in files to update the top +level .config file; you can edit they by hand if you really want to. diff --git a/kconfig/zconf.hash.c_shipped b/kconfig/zconf.hash.c_shipped index 47c8b5b..0287aa3 100644 --- a/kconfig/zconf.hash.c_shipped +++ b/kconfig/zconf.hash.c_shipped @@ -159,7 +159,7 @@ static struct kconf_id_strings_t kconf_id_strings_contents = "enable" }; #define kconf_id_strings ((const char *) &kconf_id_strings_contents) -#ifdef __GNUC__ +#if defined(__GNUC__) && __STDC_VERSION__ < 199901L __inline #endif struct kconf_id * @@ -3,6 +3,12 @@ * Copyright 2006 Rob Landley <rob@landley.net> */ +// NOTE: If option parsing segfaults, switch on TOYBOX_DEBUG in menuconfig. + +// Enabling TOYBOX_DEBUG in .config adds syntax checks to option string parsing +// which aren't needed in the final code (your option string is hardwired and +// should be correct when you ship), but are useful for development. + #include "toys.h" // Design goals: @@ -39,10 +45,6 @@ * this[1]="fruit" (argument to -b) */ -// Enabling TOYBOX_DEBUG in .config adds syntax checks to option string parsing -// which aren't needed in the final code (your option string is hardwired and -// should be correct when you ship), but are useful for development. - // What you can put in a get_opt string: // Any otherwise unused character (all letters, unprefixed numbers) specify // an option that sets a flag. The bit value is the same as the binary digit @@ -82,7 +84,6 @@ // + Synonyms (switch on all) [+abc] means -ab=-abc, -c=-abc // ! More than one in group is error [!abc] means -ab calls error_exit() // primarily useful if you can switch things back off again. -// // Notes from getopt man page // - and -- cannot be arguments. @@ -327,6 +328,7 @@ void parse_optflaglist(struct getoptflagstate *gof) for (new = gof->opts; new; new = new->next) { unsigned u = 1<<idx++; + if (new->c == 1) new->c = 0; new->dex[1] = u; if (new->flags & 1) gof->requires |= u; if (new->type) { diff --git a/lib/dirtree.c b/lib/dirtree.c index 60ce56b..1e89816 100644 --- a/lib/dirtree.c +++ b/lib/dirtree.c @@ -24,22 +24,21 @@ int dirtree_notdotdot(struct dirtree *catch) // (This doesn't open directory filehandles yet so as not to exhaust the // filehandle space on large trees, dirtree_handle_callback() does that.) -struct dirtree *dirtree_add_node(struct dirtree *parent, char *name, - int symfollow) +struct dirtree *dirtree_add_node(struct dirtree *parent, char *name, int flags) { struct dirtree *dt = NULL; struct stat st; - char buf[4096]; int len = 0, linklen = 0; if (name) { // open code this because haven't got node to call dirtree_parentfd() on yet int fd = parent ? parent->data : AT_FDCWD; - if (fstatat(fd, name, &st, symfollow ? 0 : AT_SYMLINK_NOFOLLOW)) goto error; + if (fstatat(fd, name, &st, AT_SYMLINK_NOFOLLOW*!(flags&DIRTREE_SYMFOLLOW))) + goto error; if (S_ISLNK(st.st_mode)) { - if (0>(linklen = readlinkat(fd, name, buf, 4095))) goto error; - buf[linklen++]=0; + if (0>(linklen = readlinkat(fd, name, libbuf, 4095))) goto error; + libbuf[linklen++]=0; } len = strlen(name); } @@ -50,7 +49,7 @@ struct dirtree *dirtree_add_node(struct dirtree *parent, char *name, strcpy(dt->name, name); if (linklen) { - dt->symlink = memcpy(len+(char *)dt, buf, linklen); + dt->symlink = memcpy(len+(char *)dt, libbuf, linklen); dt->data = --linklen; } } @@ -58,7 +57,7 @@ struct dirtree *dirtree_add_node(struct dirtree *parent, char *name, return dt; error: - if (notdotdot(name)) { + if (!(flags&DIRTREE_SHUTUP) && notdotdot(name)) { char *path = parent ? dirtree_path(parent, 0) : ""; perror_msg("%s%s%s", path, parent ? "/" : "", name); @@ -111,13 +110,13 @@ int dirtree_parentfd(struct dirtree *node) struct dirtree *dirtree_handle_callback(struct dirtree *new, int (*callback)(struct dirtree *node)) { - int flags, dir = S_ISDIR(new->st.st_mode); + int flags; + if (!new) return 0; if (!callback) callback = dirtree_notdotdot; - flags = callback(new); - if (dir) { + if (S_ISDIR(new->st.st_mode)) { if (flags & (DIRTREE_RECURSE|DIRTREE_COMEAGAIN)) { new->data = openat(dirtree_parentfd(new), new->name, O_CLOEXEC); flags = dirtree_recurse(new, callback, flags); @@ -144,9 +143,11 @@ int dirtree_recurse(struct dirtree *node, DIR *dir; if (node->data == -1 || !(dir = fdopendir(node->data))) { - char *path = dirtree_path(node, 0); - perror_msg("No %s", path); - free(path); + if (!(flags & DIRTREE_SHUTUP)) { + char *path = dirtree_path(node, 0); + perror_msg("No %s", path); + free(path); + } close(node->data); return flags; @@ -157,8 +158,7 @@ int dirtree_recurse(struct dirtree *node, // The extra parentheses are to shut the stupid compiler up. while ((entry = readdir(dir))) { - if (!(new = dirtree_add_node(node, entry->d_name, flags&DIRTREE_SYMFOLLOW))) - continue; + if (!(new = dirtree_add_node(node, entry->d_name, flags))) continue; new = dirtree_handle_callback(new, callback); if (new == DIRTREE_ABORTVAL) break; if (new) { @@ -179,14 +179,19 @@ int dirtree_recurse(struct dirtree *node, return flags; } +// Create dirtree root +struct dirtree *dirtree_start(char *name, int symfollow) +{ + return dirtree_add_node(0, name, DIRTREE_SYMFOLLOW*!!symfollow); +} + // Create dirtree from path, using callback to filter nodes. // If callback == NULL allocate a tree of struct dirtree nodes and return // pointer to root node. -// symfollow is just for the top of tree, callback return code controls children struct dirtree *dirtree_read(char *path, int (*callback)(struct dirtree *node)) { - struct dirtree *root = dirtree_add_node(0, path, 0); + struct dirtree *root = dirtree_start(path, 0); return root ? dirtree_handle_callback(root, callback) : DIRTREE_ABORTVAL; } diff --git a/lib/getmountlist.c b/lib/getmountlist.c index 5f4bc63..4fec41b 100644 --- a/lib/getmountlist.c +++ b/lib/getmountlist.c @@ -41,7 +41,7 @@ char *comma_iterate(char **list, int *len) return start; } -static void deslash(char *s) +static void octal_deslash(char *s) { char *o = s; @@ -78,7 +78,7 @@ int comma_scan(char *optlist, char *opt, int clean) no = 2*(*s == 'n' && s[1] == 'o'); if (optlen == len-no && !strncmp(opt, s+no, optlen)) { got = !no; - if (clean) memmove(s, optlist, strlen(optlist)+1); + if (clean && optlist) memmove(s, optlist, strlen(optlist)+1); } } @@ -90,7 +90,7 @@ int comma_scanall(char *optlist, char *scanlist) { int i = 1; - for (;;) { + while (scanlist && *scanlist) { char *opt = comma_iterate(&scanlist, &i), *s = xstrndup(opt, i); i = comma_scan(optlist, s, 0); @@ -165,8 +165,8 @@ struct mtab_list *xgetmountlist(char *path) mt->opts = stpcpy(mt->device, me->mnt_fsname)+1; strcpy(mt->opts, me->mnt_opts); - deslash(mt->dir); - deslash(mt->device); + octal_deslash(mt->dir); + octal_deslash(mt->device); } endmntent(fp); @@ -10,7 +10,7 @@ void show_help(void) {;} #undef NEWTOY #undef OLDTOY #define NEWTOY(name,opt,flags) help_##name "\0" -#define OLDTOY(name,oldname,opts,flags) "\xff" #oldname "\0" +#define OLDTOY(name,oldname,flags) "\xff" #oldname "\0" static char *help_data = #include "generated/newtoys.h" ; diff --git a/lib/interestingtimes.c b/lib/interestingtimes.c new file mode 100644 index 0000000..8f8b35c --- /dev/null +++ b/lib/interestingtimes.c @@ -0,0 +1,163 @@ +/* interestingtimes.c - cursor control + * + * Copyright 2015 Rob Landley <rob@landley.net> + */ + +#include "toys.h" + +int xgettty(void) +{ + int i, j; + + for (i = 0; i<3; i++) if (isatty(j = (i+1)%3)) return j; + + return xopen("/dev/tty", O_RDWR); +} + +// Quick and dirty query size of terminal, doesn't do ANSI probe fallback. +// set x=80 y=25 before calling to provide defaults. Returns 0 if couldn't +// determine size. + +int terminal_size(unsigned *xx, unsigned *yy) +{ + struct winsize ws; + unsigned i, x = 0, y = 0; + char *s; + + // stdin, stdout, stderr + for (i=0; i<3; i++) { + memset(&ws, 0, sizeof(ws)); + if (!ioctl(i, TIOCGWINSZ, &ws)) { + if (ws.ws_col) x = ws.ws_col; + if (ws.ws_row) y = ws.ws_row; + + break; + } + } + s = getenv("COLUMNS"); + if (s) sscanf(s, "%u", &x); + s = getenv("LINES"); + if (s) sscanf(s, "%u", &y); + + // Never return 0 for either value, leave it at default instead. + if (xx && x) *xx = x; + if (yy && y) *yy = y; + + return x || y; +} + +// Reset terminal to known state, saving copy of old state if old != NULL. +int set_terminal(int fd, int raw, struct termios *old) +{ + struct termios termio; + + // Fetch local copy of old terminfo, and copy struct contents to *old if set + if (!tcgetattr(fd, &termio) && old) *old = termio; + + // the following are the bits set for an xterm. Linux text mode TTYs by + // default add two additional bits that only matter for serial processing + // (turn serial line break into an interrupt, and XON/XOFF flow control) + + // Any key unblocks output, swap CR and NL on input + termio.c_iflag = IXANY|ICRNL|INLCR; + if (toys.which->flags & TOYFLAG_LOCALE) termio.c_iflag |= IUTF8; + + // Output appends CR to NL, does magic undocumented postprocessing + termio.c_oflag = ONLCR|OPOST; + + // Leave serial port speed alone + // termio.c_cflag = C_READ|CS8|EXTB; + + // Generate signals, input entire line at once, echo output + // erase, line kill, escape control characters with ^ + // erase line char at a time + // "extended" behavior: ctrl-V quotes next char, ctrl-R reprints unread chars, + // ctrl-W erases word + termio.c_lflag = ISIG|ICANON|ECHO|ECHOE|ECHOK|ECHOCTL|ECHOKE|IEXTEN; + + if (raw) cfmakeraw(&termio); + + return tcsetattr(fd, TCSANOW, &termio); +} + +// Scan stdin for a keypress, parsing known escape sequences +// Returns: 0-255=literal, -1=EOF, -2=NONE, 256-...=index into seq +// scratch space is necessary because last char of !seq could start new seq +// Zero out first byte of scratch before first call to scan_key +// block=0 allows fetching multiple characters before updating display +int scan_key(char *scratch, int block) +{ + // up down right left pgup pgdn home end ins + char *seqs[] = {"\033[A", "\033[B", "\033[C", "\033[D", "\033[5~", "\033[6~", + "\033OH", "\033OF", "\033[2~", 0}; + struct pollfd pfd; + int maybe, i, j; + char *test; + + for (;;) { + pfd.fd = 0; + pfd.events = POLLIN; + pfd.revents = 0; + + // check sequences + maybe = 0; + if (*scratch) { + for (i = maybe = 0; (test = seqs[i]); i++) { + for (j = 0; j<*scratch; j++) if (scratch[j+1] != test[j]) break; + if (j == *scratch) { + maybe = 1; + if (!test[j]) { + // We recognized current sequence: consume and return + *scratch = 0; + return 256+i; + } + } + } + // If current data can't be a known sequence, return next raw char + if (!maybe) break; + } + + // Need more data to decide + + // 30 miliseconds is about the gap between characters at 300 baud + if (maybe || !block) if (!xpoll(&pfd, 1, 30*maybe)) break; + + if (1 != read(0, scratch+1+*scratch, 1)) return -1; + ++*scratch; + } + + // Was not a sequence + if (!*scratch) return -2; + i = scratch[1]; + if (--*scratch) memmove(scratch+1, scratch+2, *scratch); + + return i; +} + +void tty_esc(char *s) +{ + printf("\033[%s", s); +} + +void tty_jump(int x, int y) +{ + char s[32]; + + sprintf(s, "%d;%dH", y+1, x+1); + tty_esc(s); +} + +void tty_reset(void) +{ + set_terminal(1, 0, 0); + tty_esc("?25h"); + tty_esc("0m"); + tty_jump(0, 999); + tty_esc("K"); +} + +void tty_sigreset(int i) +{ + tty_reset(); + _exit(128+i); +} @@ -155,7 +155,7 @@ int mkpathat(int atfd, char *dir, mode_t lastmode, int flags) if (!(flags&2) || errno != EEXIST) return 1; } else if (flags&4) fprintf(stderr, "%s: created directory '%s'\n", toys.which->name, dir); - + if (!(*s = save)) break; } @@ -177,7 +177,7 @@ struct string_list **splitpath(char *path, struct string_list **list) if (len > 0) { *list = xmalloc(sizeof(struct string_list) + len + 1); (*list)->next = 0; - strncpy((*list)->str, new, len); + memcpy((*list)->str, new, len); (*list)->str[len] = 0; list = &(*list)->next; } @@ -210,7 +210,8 @@ struct string_list *find_in_path(char *path, char *filename) if (!len) sprintf(rnext->str, "%s/%s", cwd, filename); else { char *res = rnext->str; - strncpy(res, path, len); + + memcpy(res, path, len); res += len; *(res++) = '/'; strcpy(res, filename); @@ -232,13 +233,30 @@ struct string_list *find_in_path(char *path, char *filename) return rlist; } +long estrtol(char *str, char **end, int base) +{ + errno = 0; + + return strtol(str, end, base); +} + +long xstrtol(char *str, char **end, int base) +{ + long l = estrtol(str, end, base); + + if (errno) perror_exit("%s", str); + + return l; +} + // atol() with the kilo/mega/giga/tera/peta/exa extensions. // (zetta and yotta don't fit in 64 bits.) long atolx(char *numstr) { char *c, *suffixes="cbkmgtpe", *end; - long val = strtol(numstr, &c, 0); + long val; + val = xstrtol(numstr, &c, 0); if (*c) { if (c != numstr && (end = strchr(suffixes, tolower(*c)))) { int shift = end-suffixes-2; @@ -262,16 +280,6 @@ long atolx_range(char *numstr, long low, long high) return val; } -int numlen(long l) -{ - int len = 0; - while (l) { - l /= 10; - len++; - } - return len; -} - int stridx(char *haystack, char needle) { char *off; @@ -344,14 +352,12 @@ off_t fdlength(int fd) // Read contents of file as a single nul-terminated string. // malloc new one if buf=len=0 -char *readfile(char *name, char *ibuf, off_t len) +char *readfileat(int dirfd, char *name, char *ibuf, off_t len) { int fd; char *buf; - fd = open(name, O_RDONLY); - if (fd == -1) return 0; - + if (-1 == (fd = openat(dirfd, name, O_RDONLY))) return 0; if (len<1) { len = fdlength(fd); // proc files don't report a length, so try 1 page minimum. @@ -370,6 +376,11 @@ char *readfile(char *name, char *ibuf, off_t len) return buf; } +char *readfile(char *name, char *ibuf, off_t len) +{ + return readfileat(AT_FDCWD, name, ibuf, len); +} + // Sleep for this many thousandths of a second void msleep(long miliseconds) { @@ -511,8 +522,7 @@ int copy_tempfile(int fdin, char *name, char **tempname) struct stat statbuf; int fd; - *tempname = xstrndup(name, strlen(name)+6); - strcat(*tempname,"XXXXXX"); + *tempname = xmprintf("%s%s", name, "XXXXXX"); if(-1 == (fd = mkstemp(*tempname))) error_exit("no temp file"); if (!tempfile2zap) sigatexit(tempfile_handler); tempfile2zap = *tempname; @@ -570,36 +580,19 @@ void crc_init(unsigned int *crc_table, int little_endian) } } -// Quick and dirty query size of terminal, doesn't do ANSI probe fallback. -// set x=80 y=25 before calling to provide defaults. Returns 0 if couldn't -// determine size. +// Init base64 table -int terminal_size(unsigned *xx, unsigned *yy) +void base64_init(char *p) { - struct winsize ws; - unsigned i, x = 0, y = 0; - char *s; - - // stdin, stdout, stderr - for (i=0; i<3; i++) { - memset(&ws, 0, sizeof(ws)); - if (!ioctl(i, TIOCGWINSZ, &ws)) { - if (ws.ws_col) x = ws.ws_col; - if (ws.ws_row) y = ws.ws_row; + int i; - break; - } + for (i = 'A'; i != ':'; i++) { + if (i == 'Z'+1) i = 'a'; + if (i == 'z'+1) i = '0'; + *(p++) = i; } - s = getenv("COLUMNS"); - if (s) sscanf(s, "%u", &x); - s = getenv("ROWS"); - if (s) sscanf(s, "%u", &y); - - // Never return 0 for either value, leave it at default instead. - if (xx && x) *xx = x; - if (yy && y) *yy = y; - - return x || y; + *(p++) = '+'; + *(p++) = '/'; } int yesno(char *prompt, int def) @@ -670,8 +663,9 @@ int sig_to_num(char *pidstr) if (pidstr) { char *s; - i = strtol(pidstr, &s, 10); - if (!*s) return i; + + i = estrtol(pidstr, &s, 10); + if (!errno && !*s) return i; if (!strncasecmp(pidstr, "sig", 3)) pidstr+=3; } @@ -700,8 +694,8 @@ mode_t string_to_mode(char *modestr, mode_t mode) // Handle octal mode if (isdigit(*str)) { - mode = strtol(str, &s, 8); - if (*s || (mode & ~(07777))) goto barf; + mode = estrtol(str, &s, 8); + if (errno || *s || (mode & ~(07777))) goto barf; return mode | extrabits; } @@ -804,6 +798,14 @@ void mode_to_string(mode_t mode, char *buf) *buf = c; } +char *basename_r(char *name) +{ + char *s = strrchr(name, '/'); + + if (s) return s+1; + return name; +} + // Execute a callback for each PID that matches a process name from a list. void names_to_pid(char **names, int (*callback)(pid_t pid, char *name)) { @@ -822,7 +824,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(cmd), basename(*curname))) + : !strcmp(basename_r(cmd), basename_r(*curname))) if (callback(u, *curname)) break; if (*curname) break; } @@ -835,8 +837,8 @@ int human_readable(char *buf, unsigned long long num) { int end, len; - len = sprintf(buf, "%lld", num); - end = ((len-1)%3)+1; + len = sprintf(buf, "%lld", num)-1; + end = (len%3)+1; len /= 3; if (len && end == 1) { @@ -851,3 +853,23 @@ int human_readable(char *buf, unsigned long long num) return end; } + +// The qsort man page says you can use alphasort, the posix committee +// disagreed, and doubled down: http://austingroupbugs.net/view.php?id=142 +// So just do our own. (The const is entirely to humor the stupid compiler.) +int qstrcmp(const void *a, const void *b) +{ + return strcmp(*(char **)a, *(char **)b); +} + +int xpoll(struct pollfd *fds, int nfds, int timeout) +{ + int i; + + for (;;) { + if (0>(i = poll(fds, nfds, timeout))) { + if (errno != EINTR && errno != ENOMEM) perror_exit("xpoll"); + else if (timeout>0) timeout--; + } else return i; + } +} @@ -50,6 +50,8 @@ void get_optflags(void); #define DIRTREE_COMEAGAIN 4 // Follow symlinks to directories #define DIRTREE_SYMFOLLOW 8 +// Don't warn about failure to stat +#define DIRTREE_SHUTUP 16 // Don't look at any more files in this directory. #define DIRTREE_ABORT 256 @@ -65,7 +67,8 @@ struct dirtree { char name[]; }; -struct dirtree *dirtree_add_node(struct dirtree *p, char *name, int symfollow); +struct dirtree *dirtree_start(char *name, int symfollow); +struct dirtree *dirtree_add_node(struct dirtree *p, char *name, int flags); char *dirtree_path(struct dirtree *node, int *plen); int dirtree_notdotdot(struct dirtree *catch); int dirtree_parentfd(struct dirtree *node); @@ -81,19 +84,18 @@ void show_help(void); // xwrap.c void xstrncpy(char *dest, char *src, size_t size); +void xstrncat(char *dest, char *src, size_t size); void xexit(void) noreturn; void *xmalloc(size_t size); void *xzalloc(size_t size); void *xrealloc(void *ptr, size_t size); char *xstrndup(char *s, size_t n); char *xstrdup(char *s); -char *xmprintf(char *format, ...); -void xprintf(char *format, ...); +char *xmprintf(char *format, ...) printf_format; +void xprintf(char *format, ...) printf_format; void xputs(char *s); void xputc(char c); void xflush(void); -pid_t xfork(void); -void xexec_optargs(int skip); void xexec(char **argv); pid_t xpopen_both(char **argv, int *pipes); int xpclose_both(pid_t pid, int *pipes); @@ -124,23 +126,28 @@ struct passwd *xgetpwuid(uid_t uid); struct group *xgetgrgid(gid_t gid); struct passwd *xgetpwnam(char *name); struct group *xgetgrnam(char *name); +struct passwd *xgetpwnamid(char *user); +struct group *xgetgrnamid(char *group); void xsetuser(struct passwd *pwd); char *xreadlink(char *name); long xparsetime(char *arg, long units, long *fraction); void xpidfile(char *name); void xregcomp(regex_t *preg, char *rexec, int cflags); +char *xtzset(char *new); +void xsignal(int signal, void *handler); // lib.c void verror_msg(char *msg, int err, va_list va); -void error_msg(char *msg, ...); -void perror_msg(char *msg, ...); -void error_exit(char *msg, ...) noreturn; -void perror_exit(char *msg, ...) noreturn; +void error_msg(char *msg, ...) printf_format; +void perror_msg(char *msg, ...) printf_format; +void error_exit(char *msg, ...) printf_format noreturn; +void perror_exit(char *msg, ...) printf_format noreturn; ssize_t readall(int fd, void *buf, size_t len); ssize_t writeall(int fd, void *buf, size_t len); off_t lskip(int fd, off_t offset); int mkpathat(int atfd, char *dir, mode_t lastmode, int flags); struct string_list **splitpath(char *path, struct string_list **list); +char *readfileat(int dirfd, char *name, char *buf, off_t len); char *readfile(char *name, char *buf, off_t len); void msleep(long miliseconds); int64_t peek_le(void *ptr, unsigned size); @@ -148,9 +155,10 @@ int64_t peek_be(void *ptr, unsigned size); int64_t peek(void *ptr, unsigned size); void poke(void *ptr, uint64_t val, int size); struct string_list *find_in_path(char *path, char *filename); +long estrtol(char *str, char **end, int base); +long xstrtol(char *str, char **end, int base); long atolx(char *c); long atolx_range(char *numstr, long low, long high); -int numlen(long l); int stridx(char *haystack, char needle); int unescape(char c); int strstart(char **a, char *b); @@ -166,12 +174,36 @@ int copy_tempfile(int fdin, char *name, char **tempname); void delete_tempfile(int fdin, int fdout, char **tempname); void replace_tempfile(int fdin, int fdout, char **tempname); void crc_init(unsigned int *crc_table, int little_endian); -int terminal_size(unsigned *x, unsigned *y); +void base64_init(char *p); int yesno(char *prompt, int def); int human_readable(char *buf, unsigned long long num); +int qstrcmp(const void *a, const void *b); +int xpoll(struct pollfd *fds, int nfds, int timeout); + +// interestingtimes.c +int xgettty(void); +int terminal_size(unsigned *xx, unsigned *yy); +int set_terminal(int fd, int raw, struct termios *old); +int scan_key(char *scratch, int block); +void tty_esc(char *s); +void tty_jump(int x, int y); +void tty_reset(void); +void tty_sigreset(int i); + +// Results from scan_key() +#define KEY_UP 256 +#define KEY_DOWN 257 +#define KEY_RIGHT 258 +#define KEY_LEFT 259 +#define KEY_PGUP 260 +#define KEY_PGDN 261 +#define KEY_HOME 262 +#define KEY_END 263 +#define KEY_INSERT 264 // net.c int xsocket(int domain, int type, int protocol); +void xsetsockopt(int fd, int level, int opt, void *val, socklen_t len); // password.c int get_salt(char *salt, char * algo); @@ -203,6 +235,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); void names_to_pid(char **names, int (*callback)(pid_t pid, char *name)); // Functions in need of further review/cleanup diff --git a/lib/lsm.h b/lib/lsm.h new file mode 100644 index 0000000..d7e7de9 --- /dev/null +++ b/lib/lsm.h @@ -0,0 +1,115 @@ +/* lsm.h - header file for lib directory + * + * Copyright 2015 Rob Landley <rob@landley.net> + */ + +#if CFG_TOYBOX_SELINUX +#include <selinux/selinux.h> +#else +#define is_selinux_enabled() 0 +#define setfscreatecon(...) (-1) +#define getcon(...) (-1) +#define getfilecon(...) (-1) +#define lgetfilecon(...) (-1) +#define fgetfilecon(...) (-1) +#define setfilecon(...) (-1) +#define lsetfilecon(...) (-1) +#define fsetfilecon(...) (-1) +#endif + +#if CFG_TOYBOX_SMACK +#include <sys/smack.h> +#include <sys/xattr.h> +#include <linux/xattr.h> +#else +#define XATTR_NAME_SMACK 0 +//ssize_t fgetxattr (int fd, char *name, void *value, size_t size); +#define smack_smackfs_path(...) (-1) +#define smack_new_label_from_self(...) (-1) +#define smack_new_label_from_path(...) (-1) +#define smack_new_label_from_file(...) (-1) +#define smack_set_label_for_self(...) (-1) +#define smack_set_label_for_path(...) (-1) +#define smack_set_label_for_file(...) (-1) +#endif + +// This turns into "return 0" when no LSM and lets code optimize out. +static inline int lsm_enabled(void) +{ + if (CFG_TOYBOX_SMACK) return !!smack_smackfs_path(); + else return is_selinux_enabled() == 1; +} + +static inline char *lsm_name(void) +{ + if (CFG_TOYBOX_SMACK) return "Smack"; + if (CFG_TOYBOX_SELINUX) return "SELinux"; + + return "LSM"; +} + +// Fetch this process's lsm context +static inline char *lsm_context(void) +{ + int ok = 0; + char *result; + + if (CFG_TOYBOX_SMACK) ok = smack_new_label_from_self(&result) > 0; + else ok = getcon(&result) == 0; + + return ok ? result : strdup("?"); +} + +// Set default label to apply to newly created stuff (NULL to clear it) +static inline int lsm_set_create(char *context) +{ + if (CFG_TOYBOX_SMACK) return smack_set_label_for_self(context); + else return setfscreatecon(context); +} + +// Label a file, following symlinks +static inline int lsm_set_context(char *filename, char *context) +{ + if (CFG_TOYBOX_SMACK) + return smack_set_label_for_path(filename, XATTR_NAME_SMACK, 1, context); + else return setfilecon(filename, context); +} + +// Label a file, don't follow symlinks +static inline int lsm_lset_context(char *filename, char *context) +{ + if (CFG_TOYBOX_SMACK) + return smack_set_label_for_path(filename, XATTR_NAME_SMACK, 0, context); + else return lsetfilecon(filename, context); +} + +// Label a file by filehandle +static inline int lsm_fset_context(int file, char *context) +{ + if (CFG_TOYBOX_SMACK) + return smack_set_label_for_file(file, XATTR_NAME_SMACK, context); + else return fsetfilecon(file, context); +} + +// returns -1 in case of error or else the length of the context */ +// context can be NULL to get the length only */ +static inline int lsm_get_context(char *filename, char **context) +{ + if (CFG_TOYBOX_SMACK) + return smack_new_label_from_path(filename, XATTR_NAME_SMACK, 1, context); + else return getfilecon(filename, context); +} + +static inline int lsm_lget_context(char *filename, char **context) +{ + if (CFG_TOYBOX_SMACK) + return smack_new_label_from_path(filename, XATTR_NAME_SMACK, 0, context); + else return lgetfilecon(filename, context); +} + +static inline int lsm_fget_context(int file, char **context) +{ + if (CFG_TOYBOX_SMACK) + return smack_new_label_from_file(file, XATTR_NAME_SMACK, context); + return fgetfilecon(file, context); +} @@ -7,3 +7,8 @@ int xsocket(int domain, int type, int protocol) if (fd < 0) perror_exit("socket %x %x", type, protocol); return fd; } + +void xsetsockopt(int fd, int level, int opt, void *val, socklen_t len) +{ + if (-1 == setsockopt(fd, level, opt, val, len)) perror_exit("setsockopt"); +} diff --git a/lib/password.c b/lib/password.c index 985bd57..bf13c44 100644 --- a/lib/password.c +++ b/lib/password.c @@ -1,6 +1,8 @@ /* password.c - password read/update helper functions. * * Copyright 2012 Ashwini Kumar <ak.ashwini@gmail.com> + * + * TODO: cleanup */ #include "toys.h" @@ -8,7 +10,7 @@ // generate appropriate random salt string for given encryption algorithm. int get_salt(char *salt, char *algo) -{ +{ struct { char *type, id, len; } al[] = {{"des", 0, 2}, {"md5", 1, 8}, {"sha256", 5, 16}, {"sha512", 6, 16}}; @@ -46,39 +48,6 @@ int get_salt(char *salt, char *algo) return -1; } -// Reset terminal to known state, returning old state if old != NULL. -int set_terminal(int fd, int raw, struct termios *old) -{ - struct termios termio; - - if (!tcgetattr(fd, &termio) && old) *old = termio; - - // the following are the bits set for an xterm. Linux text mode TTYs by - // default add two additional bits that only matter for serial processing - // (turn serial line break into an interrupt, and XON/XOFF flow control) - - // Any key unblocks output, swap CR and NL on input - termio.c_iflag = IXANY|ICRNL|INLCR; - if (toys.which->flags & TOYFLAG_LOCALE) termio.c_iflag |= IUTF8; - - // Output appends CR to NL, does magic undocumented postprocessing - termio.c_oflag = ONLCR|OPOST; - - // Leave serial port speed alone - // termio.c_cflag = C_READ|CS8|EXTB; - - // Generate signals, input entire line at once, echo output - // erase, line kill, escape control characters with ^ - // erase line char at a time - // "extended" behavior: ctrl-V quotes next char, ctrl-R reprints unread chars, - // ctrl-W erases word - termio.c_lflag = ISIG|ICANON|ECHO|ECHOE|ECHOK|ECHOCTL|ECHOKE|IEXTEN; - - if (raw) cfmakeraw(&termio); - - return tcsetattr(fd, TCSANOW, &termio); -} - // Prompt with mesg, read password into buf, return 0 for success 1 for fail int read_password(char *buf, int buflen, char *mesg) { @@ -128,9 +97,9 @@ static char *get_nextcolon(char *line, int cnt) } /*update_password is used by multiple utilities to update /etc/passwd, - * /etc/shadow, /etc/group and /etc/gshadow files, + * /etc/shadow, /etc/group and /etc/gshadow files, * which are used as user, group databeses - * entry can be + * entry can be * 1. encrypted password, when updating user password. * 2. complete entry for user details, when creating new user * 3. group members comma',' separated list, when adding user to group @@ -197,7 +166,7 @@ int update_password(char *filename, char* username, char* entry) current_ptr = get_nextcolon(current_ptr, 1); fprintf(newfp, "%s\n",current_ptr); } else fprintf(newfp, "%s\n",current_ptr); - } else if (!strcmp(toys.which->name, "groupadd") || + } else if (!strcmp(toys.which->name, "groupadd") || !strcmp(toys.which->name, "addgroup") || !strcmp(toys.which->name, "delgroup") || !strcmp(toys.which->name, "groupdel")){ diff --git a/lib/pending.h b/lib/pending.h index c67d81c..ffbd025 100644 --- a/lib/pending.h +++ b/lib/pending.h @@ -4,3 +4,5 @@ #define MAX_SALT_LEN 20 //3 for id, 16 for key, 1 for '\0' int read_password(char * buff, int buflen, char* mesg); int update_password(char *filename, char* username, char* encrypted); + +// TODO this goes away when lib/password.c cleaned up diff --git a/lib/portability.c b/lib/portability.c index 29608bc..6441076 100644 --- a/lib/portability.c +++ b/lib/portability.c @@ -9,7 +9,31 @@ #include <asm/unistd.h> #endif -#if defined(__APPLE__) || defined(__ANDROID__) +// We can't fork() on nommu systems, and vfork() requires an exec() or exit() +// before resuming the parent (because they share a heap until then). And no, +// we can't implement our own clone() call that does the equivalent of fork() +// because nommu heaps use physical addresses so if we copy the heap all our +// pointers are wrong. (You need an mmu in order to map two heaps to the same +// address range without interfering with each other.) In the absence of +// a portable way to tell malloc() to start a new heap without freeing the old +// one, you pretty much need the exec().) + +// So we exec ourselves (via /proc/self/exe, if anybody knows a way to +// re-exec self without depending on the filesystem, I'm all ears), +// and use the arguments to signal reentry. + +#if CFG_TOYBOX_FORK +pid_t xfork(void) +{ + pid_t pid = fork(); + + if (pid < 0) perror_exit("fork"); + + return pid; +} +#endif + +#if defined(__APPLE__) ssize_t getdelim(char **linep, size_t *np, int delim, FILE *stream) { int ch; @@ -37,7 +61,7 @@ ssize_t getdelim(char **linep, size_t *np, int delim, FILE *stream) new_line = realloc(*linep, new_len); if (!new_line) return -1; *np = new_len; - *linep = new_line; + line = *linep = new_line; } line[i] = ch; @@ -51,7 +75,7 @@ ssize_t getdelim(char **linep, size_t *np, int delim, FILE *stream) new_line = realloc(*linep, new_len); if (!new_line) return -1; *np = new_len; - *linep = new_line; + line = *linep = new_line; } line[i + 1] = '\0'; @@ -62,16 +86,7 @@ ssize_t getline(char **linep, size_t *np, FILE *stream) { return getdelim(linep, np, '\n', stream); } -#endif - -#if defined(__ANDROID__) -int sethostname(const char *name, size_t len) -{ - return syscall(__NR_sethostname, name, len); -} -#endif -#if defined(__APPLE__) extern char **environ; int clearenv(void) diff --git a/lib/portability.h b/lib/portability.h index 0dace96..ff22fa5 100644 --- a/lib/portability.h +++ b/lib/portability.h @@ -4,15 +4,6 @@ // in specific compiler, library, or OS versions, localize all that here // and in portability.c -// The tendency of gcc to produce stupid warnings continues with -// warn_unused_result, which warns about things like ignoring the return code -// of nice(2) (which is completely useless since -1 is a legitimate return -// value on success and even the man page tells you to use errno instead). - -// This makes it stop. - -#undef _FORTIFY_SOURCE - // For musl #define _ALL_SOURCE @@ -20,8 +11,14 @@ #ifdef __GNUC__ #define noreturn __attribute__((noreturn)) +#if CFG_TOYBOX_DEBUG +#define printf_format __attribute__((format(printf, 1, 2))) +#else +#define printf_format +#endif #else #define noreturn +#define printf_format #endif // Always use long file support. @@ -31,6 +28,9 @@ #include <features.h> +// Types various replacement prototypes need +#include <sys/types.h> + // Various constants old build environments might not have even if kernel does #ifndef AT_FDCWD @@ -69,6 +69,19 @@ int wcwidth(wchar_t wc); #include <time.h> char *strptime(const char *buf, const char *format, struct tm *tm); +// They didn't like posix basename so they defined another function with the +// same name and if you include libgen.h it #defines basename to something +// else (where they implemented the real basename), and that define breaks +// the table entry for the basename command. They didn't make a new function +// with a different name for their new behavior because gnu. +// +// Solution: don't use their broken header, provide an inline to redirect the +// correct name to the broken name. + +char *dirname(char *path); +char *__xpg_basename(char *path); +static inline char *basename(char *path) { return __xpg_basename(path); } + // uClibc pretends to be glibc and copied a lot of its bugs, but has a few more #if defined(__UCLIBC__) #include <unistd.h> @@ -81,13 +94,28 @@ pid_t getsid(pid_t pid); // any flag newer than MS_MOVE, which was added in 2001 (linux 2.5.0.5), // eleven years earlier. +#include <sys/mount.h> +#ifndef MS_MOVE #define MS_MOVE (1<<13) +#endif +#ifndef MS_REC #define MS_REC (1<<14) +#endif +#ifndef MS_SILENT #define MS_SILENT (1<<15) +#endif +#ifndef MS_UNBINDABLE #define MS_UNBINDABLE (1<<17) +#endif +#ifndef MS_PRIVATE #define MS_PRIVATE (1<<18) +#endif +#ifndef MS_SLAVE #define MS_SLAVE (1<<19) +#endif +#ifndef MS_SHARED #define MS_SHARED (1<<20) +#endif // When building under obsolete glibc (Ubuntu 8.04-ish), hold its hand a bit. #elif __GLIBC__ == 2 && __GLIBC_MINOR__ < 10 @@ -122,14 +150,21 @@ int utimensat(int fd, const char *path, const struct timespec times[2], int flag #ifndef MNT_DETACH #define MNT_DETACH 2 #endif -#endif +#endif // Old glibc +#endif // glibc in general + +#if !defined(__GLIBC__) && !defined(__BIONIC__) +// POSIX basename. +#include <libgen.h> #endif -#ifdef __MUSL__ -#include <unistd.h> -// Without this "rm -r dir" fails with "is directory". -#define faccessat(A, B, C, D) faccessat(A, B, C, 0) +// 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 @@ -173,7 +208,7 @@ int clearenv(void); #define SWAP_LE64(x) (x) #endif -#if defined(__APPLE__) || defined(__ANDROID__) \ +#if defined(__APPLE__) \ || (defined(__GLIBC__) && __GLIBC__ == 2 && __GLIBC_MINOR__ < 10) ssize_t getdelim(char **lineptr, size_t *n, int delim, FILE *stream); ssize_t getline(char **lineptr, size_t *n, FILE *stream); @@ -184,9 +219,6 @@ ssize_t getline(char **lineptr, size_t *n, FILE *stream); #include <sys/swap.h> // Android is missing some headers and functions -#if defined(__ANDROID__) -int sethostname(const char *name, size_t len); -#endif // "generated/config.h" is included first #if CFG_TOYBOX_SHADOW #include <shadow.h> @@ -194,12 +226,6 @@ int sethostname(const char *name, size_t len); #if CFG_TOYBOX_UTMPX #include <utmpx.h> #endif -#if CFG_TOYBOX_PTY -#include <pty.h> -#else -pid_t forkpty(int *amaster, char *name, void *termp, void *winp); -#endif - // Some systems don't define O_NOFOLLOW, and it varies by architecture, so... #include <fcntl.h> @@ -207,10 +233,18 @@ pid_t forkpty(int *amaster, char *name, void *termp, void *winp); #define O_NOFOLLOW 0 #endif +#ifndef O_NOATIME +#define O_NOATIME 01000000 +#endif + #ifndef O_CLOEXEC #define O_CLOEXEC 02000000 #endif +#ifndef O_PATH +#define O_PATH 010000000 +#endif + #if defined(__SIZEOF_DOUBLE__) && defined(__SIZEOF_LONG__) \ && __SIZEOF_DOUBLE__ <= __SIZEOF_LONG__ typedef double FLOAT; @@ -218,3 +252,10 @@ typedef double FLOAT; typedef float FLOAT; #endif +#ifndef __uClinux__ +pid_t xfork(void); +#endif + +//#define strncpy(...) @@strncpyisbadmmkay@@ +//#define strncat(...) @@strncatisbadmmkay@@ + diff --git a/lib/xwrap.c b/lib/xwrap.c index 6216d91..54f2cbb 100644 --- a/lib/xwrap.c +++ b/lib/xwrap.c @@ -9,15 +9,29 @@ #include "toys.h" -// Strcpy with size checking: exit if there's not enough space for the string. +// strcpy and strncat with size checking. Size is the total space in "dest", +// including null terminator. Exit if there's not enough space for the string +// (including space for the null terminator), because silently truncating is +// still broken behavior. (And leaving the string unterminated is INSANE.) void xstrncpy(char *dest, char *src, size_t size) { if (strlen(src)+1 > size) error_exit("'%s' > %ld bytes", src, (long)size); strcpy(dest, src); } +void xstrncat(char *dest, char *src, size_t size) +{ + long len = strlen(src); + + if (len+strlen(dest)+1 > size) + error_exit("'%s%s' > %ld bytes", dest, src, (long)size); + strcpy(dest+len, src); +} + void xexit(void) { + if (fflush(NULL) || ferror(stdout)) + if (!toys.exitval) perror_msg("write"); if (toys.rebound) longjmp(*toys.rebound, 1); else exit(toys.exitval); } @@ -52,9 +66,10 @@ void *xrealloc(void *ptr, size_t size) // Die unless we can allocate a copy of this many bytes of string. char *xstrndup(char *s, size_t n) { - char *ret = xmalloc(++n); - strncpy(ret, s, n); - ret[--n]=0; + char *ret = strndup(s, ++n); + + if (!ret) error_exit("xstrndup"); + ret[--n] = 0; return ret; } @@ -114,26 +129,6 @@ void xflush(void) if (fflush(stdout) || ferror(stdout)) perror_exit("write");; } -pid_t xfork(void) -{ - pid_t pid = fork(); - - if (pid < 0) perror_exit("fork"); - - return pid; -} - -// Call xexec with a chunk of optargs, starting at skip. (You can't just -// call xexec() directly because toy_init() frees optargs.) -void xexec_optargs(int skip) -{ - char **s = toys.optargs; - - toys.optargs = 0; - xexec(s+skip); -} - - // Die unless we can exec argv[] (or run builtin command). Note that anything // with a path isn't a builtin, so /bin/sh won't match the builtin sh. void xexec(char **argv) @@ -343,7 +338,7 @@ void xstat(char *path, struct stat *st) // Cannonicalize path, even to file with one or more missing components at end. // if exact, require last path component to exist -char *xabspath(char *path, int exact) +char *xabspath(char *path, int exact) { struct string_list *todo, *done = 0; int try = 9999, dirfd = open("/", 0);; @@ -482,6 +477,38 @@ struct group *xgetgrgid(gid_t gid) return group; } +struct passwd *xgetpwnamid(char *user) +{ + struct passwd *up = getpwnam(user); + uid_t uid; + + if (!up) { + char *s = 0; + + uid = estrtol(user, &s, 10); + if (!errno && s && !*s) up = getpwuid(uid); + } + if (!up) perror_exit("user '%s'", user); + + return up; +} + +struct group *xgetgrnamid(char *group) +{ + struct group *gr = getgrnam(group); + gid_t gid; + + if (!gr) { + char *s = 0; + + gid = estrtol(group, &s, 10); + if (!errno && s && !*s) gr = getgrgid(gid); + } + if (!gr) perror_exit("group '%s'", group); + + return gr; +} + struct passwd *xgetpwnam(char *name) { struct passwd *up = getpwnam(name); @@ -587,13 +614,12 @@ void xpidfile(char *name) void xsendfile(int in, int out) { long len; - char buf[4096]; if (in<0) return; for (;;) { - len = xread(in, buf, 4096); + len = xread(in, libbuf, sizeof(libbuf)); if (len<1) break; - xwrite(out, buf, len); + xwrite(out, libbuf, len); } } @@ -605,7 +631,7 @@ long xparsetime(char *arg, long units, long *fraction) if (CFG_TOYBOX_FLOAT) d = strtod(arg, &arg); else l = strtoul(arg, &arg, 10); - + // Parse suffix if (*arg) { int ismhd[]={1,60,3600,86400}, i = stridx("smhd", *arg); @@ -633,3 +659,25 @@ void xregcomp(regex_t *preg, char *regex, int cflags) error_exit("xregcomp: %s", libbuf); } } + +char *xtzset(char *new) +{ + char *tz = getenv("TZ"); + + if (tz) tz = xstrdup(tz); + if (setenv("TZ", new, 1)) perror_exit("setenv"); + tzset(); + + return tz; +} + +// Set a signal handler +void xsignal(int signal, void *handler) +{ + struct sigaction *sa = (void *)libbuf; + + memset(sa, 0, sizeof(struct sigaction)); + sa->sa_handler = handler; + + if (sigaction(signal, sa, 0)) perror_exit("xsignal %d", signal); +} @@ -5,12 +5,17 @@ #include "toys.h" +#ifndef TOYBOX_VERSION +#define TOYBOX_VERSION "0.5.2" +#endif + // Populate toy_list[]. #undef NEWTOY #undef OLDTOY #define NEWTOY(name, opts, flags) {#name, name##_main, opts, flags}, -#define OLDTOY(name, oldname, opts, flags) {#name, oldname##_main, opts, flags}, +#define OLDTOY(name, oldname, flags) \ + {#name, oldname##_main, OPTSTR_##oldname, flags}, struct toy_list toy_list[] = { #include "generated/newtoys.h" @@ -57,7 +62,7 @@ struct toy_list *toy_find(char *name) #undef NEWTOY #undef OLDTOY #define NEWTOY(name, opts, flags) opts || -#define OLDTOY(name, oldname, opts, flags) opts || +#define OLDTOY(name, oldname, flags) OPTSTR_##oldname || static const int NEED_OPTIONS = #include "generated/newtoys.h" 0; // Ends the opts || opts || opts... @@ -115,6 +120,7 @@ void toy_init(struct toy_list *which, char *argv[]) if (toys.optargs != toys.argv+1) free(toys.optargs); memset(&toys, 0, offsetof(struct toy_context, rebound)); + if (toys.recursion > 1) memset(&this, 0, sizeof(this)); // Subset of init needed by singlemain. toy_singleinit(which, argv); @@ -132,10 +138,12 @@ void toy_exec(char *argv[]) if (toys.recursion && (which->flags & TOYFLAG_ROOTONLY) && getuid()) return; if (toys.recursion++ > 5) return; + // don't blank old optargs if our new argc lives in the old optargs. + if (argv>=toys.optargs && argv<=toys.optargs+toys.optc) toys.optargs = 0; + // Run command toy_init(which, argv); if (toys.which) toys.which->toy_main(); - if (fflush(NULL) || ferror(stdout)) perror_exit("write"); xexit(); } @@ -149,14 +157,15 @@ void toybox_main(void) toys.which = toy_list; if (toys.argv[1]) { - toys.optc = 0; + toys.optc = toys.recursion = 0; toy_exec(toys.argv+1); - if (toys.argv[1][0] == '-') goto list; - - error_exit("Unknown command %s",toys.argv[1]); + if (!strcmp("--version", toys.argv[1])) { + xputs(TOYBOX_VERSION); + xexit(); + } + if (toys.argv[1][0] != '-') error_exit("Unknown command %s", toys.argv[1]); } -list: // Output list of command. for (i=1; i<ARRAY_LEN(toy_list); i++) { int fl = toy_list[i].flags; @@ -176,6 +185,9 @@ list: int main(int argc, char *argv[]) { + // We check our own stdout errors, disable sigpipe killer + signal(SIGPIPE, SIG_IGN); + if (CFG_TOYBOX) { // Trim path off of command name *argv = basename(*argv); @@ -188,8 +200,7 @@ int main(int argc, char *argv[]) // a single toybox command built standalone with no multiplexer toy_singleinit(toy_list, argv); toy_list->toy_main(); - if (fflush(NULL) || ferror(stdout)) perror_exit("write"); } - return toys.exitval; + xexit(); } diff --git a/scripts/change.sh b/scripts/change.sh new file mode 100755 index 0000000..dcd581e --- /dev/null +++ b/scripts/change.sh @@ -0,0 +1,20 @@ +#!/bin/bash + +# build each command as a standalone executable + +NOBUILD=1 scripts/make.sh > /dev/null && +${HOSTCC:-cc} -I . scripts/install.c -o generated/instlist && +export PREFIX=${PREFIX:-change/} && +mkdir -p "$PREFIX" || exit 1 + +# Build all the commands standalone except: + +# sh - shell builtins like "cd" and "exit" need the multiplexer +# help - needs to know what other commands are enabled (use command --help) + +for i in $(generated/instlist | egrep -vw "sh|help") +do + echo -n " $i" && + scripts/single.sh $i > /dev/null 2>$PREFIX/${i}.bad && + rm $PREFIX/${i}.bad || echo -n '*' +done diff --git a/scripts/genconfig.sh b/scripts/genconfig.sh index e59aa39..031e97e 100755 --- a/scripts/genconfig.sh +++ b/scripts/genconfig.sh @@ -7,12 +7,16 @@ mkdir -p generated source configure +probecc() +{ + ${CROSS_COMPILE}${CC} $CFLAGS -xc -o /dev/null $1 - +} + # Probe for a single config symbol with a "compiles or not" test. # Symbol name is first argument, flags second, feed C file to stdin probesymbol() { - ${CROSS_COMPILE}${CC} $CFLAGS -xc -o /dev/null $2 - 2>/dev/null - [ $? -eq 0 ] && DEFAULT=y || DEFAULT=n + probecc $2 2>/dev/null && DEFAULT=y || DEFAULT=n rm a.out 2>/dev/null echo -e "config $1\n\tbool" || exit 1 echo -e "\tdefault $DEFAULT\n" || exit 1 @@ -20,6 +24,13 @@ probesymbol() probeconfig() { + > generated/cflags + # llvm produces its own really stupid warnings about things that aren't wrong, + # and although you can turn the warning off, gcc reacts badly to command line + # arguments it doesn't understand. So probe. + [ -z "$(probecc -Wno-string-plus-int <<< \#warn warn 2>&1 | grep string-plus-int)" ] && + echo -Wno-string-plus-int >> generated/cflags + # Probe for container support on target probesymbol TOYBOX_CONTAINER << EOF #include <linux/sched.h> @@ -58,20 +69,26 @@ EOF } EOF - # Android is missing shadow.h and pty.h - probesymbol TOYBOX_PTY -c << EOF - #include <pty.h> - int main(int argc, char *argv[]) { - int master; return forkpty(&master, 0, 0, 0); - } -EOF - + # Android is missing shadow.h probesymbol TOYBOX_SHADOW -c << EOF #include <shadow.h> int main(int argc, char *argv[]) { struct spwd *a = getspnam("root"); return 0; } EOF + + # Some commands are android-specific + probesymbol TOYBOX_ON_ANDROID -c << EOF + #ifndef __ANDROID__ + #error nope + #endif +EOF + + # nommu support + probesymbol TOYBOX_FORK << EOF + #include <unistd.h> + int main(int argc, char *argv[]) { return fork(); } +EOF } genconfig() diff --git a/scripts/install.c b/scripts/install.c index cda8fc2..a9a0550 100644 --- a/scripts/install.c +++ b/scripts/install.c @@ -7,8 +7,8 @@ #undef NEWTOY #undef OLDTOY -#define NEWTOY(name, opts, flags) {#name, 0, opts, flags}, -#define OLDTOY(name, oldname, opts, flags) {#name, 0, opts, flags}, +#define NEWTOY(name, opts, flags) {#name, 0, 0, flags}, +#define OLDTOY(name, oldname, flags) {#name, 0, 0, flags}, // Populate toy_list[]. diff --git a/scripts/make.sh b/scripts/make.sh index dab5206..7ebe148 100755 --- a/scripts/make.sh +++ b/scripts/make.sh @@ -15,13 +15,99 @@ source ./configure CPUS=$((($(echo /sys/devices/system/cpu/cpu[0-9]* | wc -w)*3)/2)) # Respond to V= by echoing command lines as well as running them +DOTPROG= do_loudly() { - [ ! -z "$V" ] && echo "$@" + [ ! -z "$V" ] && echo "$@" || echo -n "$DOTPROG" "$@" } +# Is anything under directory $2 newer than file $1 +isnewer() +{ + [ ! -z "$(find "$2" -newer "$1" 2>/dev/null || echo yes)" ] +} + +echo "Generate headers from toys/*/*.c..." + mkdir -p generated + +if isnewer generated/Config.in toys +then + echo "Extract configuration information from toys/*.c files..." + scripts/genconfig.sh +fi + +# Create a list of all the commands toybox can provide. Note that the first +# entry is out of order on purpose (the toybox multiplexer command must be the +# first element of the array). The rest must be sorted in alphabetical order +# for fast binary search. + +if isnewer generated/newtoys.h toys +then + echo -n "generated/newtoys.h " + + echo "USE_TOYBOX(NEWTOY(toybox, NULL, TOYFLAG_STAYROOT))" > generated/newtoys.h + sed -n -e 's/^USE_[A-Z0-9_]*(/&/p' toys/*/*.c \ + | sed 's/\(.*TOY(\)\([^,]*\),\(.*\)/\2 \1\2,\3/' | sort -s -k 1,1 \ + | sed 's/[^ ]* //' >> generated/newtoys.h || exit 1 +fi + +[ ! -z "$V" ] && echo "Which C files to build..." + +# Extract a list of toys/*/*.c files to compile from the data in $KCONFIG_CONFIG +# (First command names, then filenames with relevant {NEW,OLD}TOY() macro.) + +GITHASH="$(git describe --tags --abbrev=12 2>/dev/null)" +[ ! -z "$GITHASH" ] && GITHASH="-DTOYBOX_VERSION=\"$GITHASH\"" +TOYFILES="$(sed -n 's/^CONFIG_\([^=]*\)=.*/\1/p' "$KCONFIG_CONFIG" | xargs | tr ' [A-Z]' '|[a-z]')" +TOYFILES="$(egrep -l "TOY[(]($TOYFILES)[ ,]" toys/*/*.c)" +CFLAGS="$CFLAGS $(cat generated/cflags)" +BUILD="$(echo ${CROSS_COMPILE}${CC} $CFLAGS -I . $OPTIMIZE $GITHASH)" +FILES="$(echo lib/*.c main.c $TOYFILES)" + +genbuildsh() +{ + # Write a canned build line for use on crippled build machines. + + echo "#!/bin/sh" + echo + echo "BUILD=\"$BUILD\"" + echo + echo "FILES=\"$FILES\"" + echo + echo "LINK=\"$LINK\"" + echo + echo + echo '$BUILD $FILES $LINK' +} + +if ! cmp -s <(genbuildsh | head -n 3) \ + <(head -n 3 generated/build.sh 2>/dev/null) +then + echo -n "Library probe" + + # We trust --as-needed to remove each library if we don't use any symbols + # out of it, this loop is because the compiler has no way to ignore a library + # that doesn't exist, so we have to detect and skip nonexistent libraries + # for it. + + > generated/optlibs.dat + for i in util crypt m resolv selinux smack attr + do + echo "int main(int argc, char *argv[]) {return 0;}" | \ + ${CROSS_COMPILE}${CC} $CFLAGS -xc - -o /dev/null -Wl,--as-needed -l$i > /dev/null 2>/dev/null && + echo -l$i >> generated/optlibs.dat + echo -n . + done + echo +fi + +# LINK needs optlibs.dat, above + +LINK="$(echo $LDOPTIMIZE $LDFLAGS -o toybox_unstripped -Wl,--as-needed $(cat generated/optlibs.dat))" +genbuildsh > generated/build.sh && chmod +x generated/build.sh || exit 1 + echo "Make generated/config.h from $KCONFIG_CONFIG." # This long and roundabout sed invocation is to make old versions of sed happy. @@ -48,27 +134,10 @@ sed -n \ -e 's/.*/#define USE_&(...) __VA_ARGS__/p' \ $KCONFIG_CONFIG > generated/config.h || exit 1 - -echo "Extract configuration information from toys/*.c files..." -scripts/genconfig.sh - -echo "Generate headers from toys/*/*.c..." - -# Create a list of all the commands toybox can provide. Note that the first -# entry is out of order on purpose (the toybox multiplexer command must be the -# first element of the array). The rest must be sorted in alphabetical order -# for fast binary search. - -echo -n "generated/newtoys.h " - -echo "USE_TOYBOX(NEWTOY(toybox, NULL, TOYFLAG_STAYROOT))" > generated/newtoys.h -sed -n -e 's/^USE_[A-Z0-9_]*(/&/p' toys/*/*.c \ - | sed 's/\(.*TOY(\)\([^,]*\),\(.*\)/\2 \1\2,\3/' | sort -k 1,1 \ - | sed 's/[^ ]* //' >> generated/newtoys.h -sed -n -e 's/.*(NEWTOY(\([^,]*\), *\(\("[^"]*"[^,]*\)*\),.*/#define OPTSTR_\1\t\2/p' \ - generated/newtoys.h > generated/oldtoys.h - -do_loudly $HOSTCC scripts/mkflags.c -o generated/mkflags || exit 1 +if [ generated/mkflags -ot scripts/mkflags.c ] +then + do_loudly $HOSTCC scripts/mkflags.c -o generated/mkflags || exit 1 +fi echo -n "generated/flags.h " @@ -108,7 +177,7 @@ do # If no pair (because command's disabled in config), use " " for flags # so allflags can define the appropriate zero macros. -done | sort | sed -n 's/ A / /;t pair;h;s/\([^ ]*\).*/\1 " "/;x;b single;:pair;h;n;:single;s/[^ ]* B //;H;g;s/\n/ /;p' |\ +done | sort -s | sed -n 's/ A / /;t pair;h;s/\([^ ]*\).*/\1 " "/;x;b single;:pair;h;n;:single;s/[^ ]* B //;H;g;s/\n/ /;p' |\ generated/mkflags > generated/flags.h || exit 1 # Extract global structure definitions and flag definitions from toys/*/*.c @@ -126,80 +195,56 @@ function getglobals() done } -echo -n "generated/globals.h " - -GLOBSTRUCT="$(getglobals)" -( - echo "$GLOBSTRUCT" - echo - echo "extern union global_union {" - echo "$GLOBSTRUCT" | sed -n 's/struct \(.*\)_data {/ struct \1_data \1;/p' - echo "} this;" -) > generated/globals.h +if isnewer generated/globals.h toys +then + echo -n "generated/globals.h " + GLOBSTRUCT="$(getglobals)" + ( + echo "$GLOBSTRUCT" + echo + echo "extern union global_union {" + echo "$GLOBSTRUCT" | \ + sed -n 's/struct \(.*\)_data {/ struct \1_data \1;/p' + echo "} this;" + ) > generated/globals.h +fi echo "generated/help.h" -do_loudly $HOSTCC scripts/config2help.c -I . lib/xwrap.c lib/llist.c lib/lib.c \ - -o generated/config2help && \ +if [ generated/config2help -ot scripts/config2help.c ] +then + do_loudly $HOSTCC scripts/config2help.c -I . lib/xwrap.c lib/llist.c \ + lib/lib.c lib/portability.c -o generated/config2help || exit 1 +fi generated/config2help Config.in $KCONFIG_CONFIG > generated/help.h || exit 1 -echo -n "Library probe" - -# We trust --as-needed to remove each library if we don't use any symbols -# out of it, this loop is because the compiler has no way to ignore a library -# that doesn't exist, so we have to detect and skip nonexistent libraries -# for it. - -> generated/optlibs.dat -for i in util crypt m resolv -do - echo "int main(int argc, char *argv[]) {return 0;}" | \ - ${CROSS_COMPILE}${CC} $CFLAGS -xc - -o /dev/null -Wl,--as-needed -l$i > /dev/null 2>/dev/null && - echo -l$i >> generated/optlibs.dat - echo -n . -done -echo +[ ! -z "$NOBUILD" ] && exit 0 echo -n "Compile toybox" [ ! -z "$V" ] && echo - -# Extract a list of toys/*/*.c files to compile from the data in $KCONFIG_CONFIG - -TOYFILES="$(egrep -l "TOY[(]($(sed -n 's/^CONFIG_\([^=]*\)=.*/\1/p' "$KCONFIG_CONFIG" | xargs | tr ' [A-Z]' '|[a-z]'))[ ,]" toys/*/*.c)" - -do_loudly() -{ - [ ! -z "$V" ] && echo "$@" || echo -n . - "$@" -} - -BUILD="$(echo ${CROSS_COMPILE}${CC} $CFLAGS -I . $OPTIMIZE)" -FILES="$(echo lib/*.c main.c $TOYFILES)" -LINK="$(echo $LDOPTIMIZE -o toybox_unstripped -Wl,--as-needed $(cat generated/optlibs.dat))" +DOTPROG=. # This is a parallel version of: do_loudly $BUILD $FILES $LINK || exit 1 -# Write a canned build line for use on crippled build machines. -( - echo "#!/bin/sh" - echo - echo "BUILD=\"$BUILD\"" - echo - echo "LINK=\"$LINK\"" - echo - echo "FILES=\"$FILES\"" - echo - echo '$BUILD $FILES $LINK' -) > generated/build.sh && chmod +x generated/build.sh || echo 1 - -rm -rf generated/obj && mkdir -p generated/obj || exit 1 +X="$(ls -1t generated/obj/* 2>/dev/null | tail -n 1)" +if [ ! -e "$X" ] || [ ! -z "$(find toys -name "*.h" -newer "$X")" ] +then + rm -rf generated/obj && mkdir -p generated/obj || exit 1 +else + rm -f generated/obj/{main,lib_help}.o || exit 1 +fi PENDING= +LFILES= +DONE=0 for i in $FILES do # build each generated/obj/*.o file in parallel X=${i/lib\//lib_} X=${X##*/} - do_loudly $BUILD -c $i -o generated/obj/${X%%.c}.o & + OUT="generated/obj/${X%%.c}.o" + LFILES="$LFILES $OUT" + [ "$OUT" -nt "$i" ] && continue + do_loudly $BUILD -c $i -o $OUT & # ratelimit to $CPUS many parallel jobs, detecting errors @@ -208,20 +253,29 @@ do PENDING="$(echo $PENDING $(jobs -rp) | tr ' ' '\n' | sort -u)" [ $(echo -n "$PENDING" | wc -l) -lt "$CPUS" ] && break; - wait $(echo "$PENDING" | head -n 1) || exit 1 + wait $(echo "$PENDING" | head -n 1) + DONE=$(($DONE+$?)) PENDING="$(echo "$PENDING" | tail -n +2)" done + [ $DONE -ne 0 ] && break done # wait for all background jobs, detecting errors for i in $PENDING do - wait $i || exit 1 + wait $i + DONE=$(($DONE+$?)) done -do_loudly $BUILD generated/obj/*.o $LINK || exit 1 -do_loudly ${CROSS_COMPILE}${STRIP} toybox_unstripped -o toybox || exit 1 +[ $DONE -ne 0 ] && exit 1 + +do_loudly $BUILD $LFILES $LINK || exit 1 +if ! do_loudly ${CROSS_COMPILE}strip toybox_unstripped -o toybox +then + echo "strip failed, using unstripped" && cp toybox_unstripped toybox || + exit 1 +fi # gcc 4.4's strip command is buggy, and doesn't set the executable bit on # its output the way SUSv4 suggests it do so. do_loudly chmod +x toybox || exit 1 diff --git a/scripts/mkflags.c b/scripts/mkflags.c index 23cb83e..d87087b 100644 --- a/scripts/mkflags.c +++ b/scripts/mkflags.c @@ -17,11 +17,42 @@ struct flag { struct flag *lopt; }; +// replace chopped out USE_BLAH() sections with low-ascii characters +// showing how many flags got skipped + +char *mark_gaps(char *flags, char *all) +{ + char *n, *new, c; + + // Shell feeds in " " for blank args, leading space not meaningful. + while (isspace(*flags)) flags++; + while (isspace(*all)) all++; + + n = new = strdup(all); + while (*all) { + if (*flags == *all) { + *(new++) = *(all++); + *flags++; + continue; + } + + c = *(all++); + if (strchr("?&^-:#|@*; ", c)); + else if (strchr("=<>", c)) while (isdigit(*all)) all++; + else if (c == '(') while(*(all++) != ')'); + else *(new++) = 1; + } + *new = 0; + + return n; +} + // Break down a command string into struct flag list. struct flag *digest(char *string) { struct flag *list = NULL; + char *err = string; while (*string) { // Groups must be at end. @@ -52,6 +83,10 @@ struct flag *digest(char *string) if (strchr("?&^-:#|@*; ", *string)) string++; else if (strchr("=<>", *string)) { + if (!isdigit(string[1])) { + fprintf(stderr, "%c without number in '%s'", *string, err); + exit(1); + } while (isdigit(*++string)) { if (!list) { string++; @@ -79,14 +114,22 @@ int main(int argc, char *argv[]) // See "intentionally crappy", above. if (!(out = outbuf)) return 1; + printf("#ifdef FORCE_FLAGS\n#define FORCED_FLAG 1\n" + "#else\n#define FORCED_FLAG 0\n#endif\n\n"); + for (;;) { struct flag *flist, *aflist, *offlist; + char *gaps, *mgaps, c; unsigned bit; - *command = 0; + *command = *flags = *allflags = 0; bit = fscanf(stdin, "%255s \"%1023[^\"]\" \"%1023[^\"]\"\n", command, flags, allflags); + if (getenv("DEBUG")) + fprintf(stderr, "command=%s, flags=%s, allflags=%s\n", + command, flags, allflags); + if (!*command) break; if (bit != 3) { fprintf(stderr, "\nError in %s (duplicate command?)\n", command); @@ -95,6 +138,16 @@ int main(int argc, char *argv[]) bit = 0; printf("// %s %s %s\n", command, flags, allflags); + mgaps = mark_gaps(flags, allflags); + for (gaps = mgaps; *gaps == 1; gaps++); + if (*gaps) c = '"'; + else { + c = ' '; + gaps = "0"; + } + printf("#undef OPTSTR_%s\n#define OPTSTR_%s %c%s%c\n", + command, command, c, gaps, c); + free(mgaps); flist = digest(flags); offlist = aflist = digest(allflags); @@ -124,29 +177,27 @@ int main(int argc, char *argv[]) { sprintf(out, "#define FLAG_%s (1<<%d)\n", flist->lopt->command, bit); flist->lopt = flist->lopt->next; - } else sprintf(out, "#define FLAG_%s 0\n", aflist->lopt->command); + } else sprintf(out, "#define FLAG_%s (FORCED_FLAG<<%d)\n", + aflist->lopt->command, bit); aflist->lopt = aflist->lopt->next; if (!aflist->command) { aflist = aflist->next; - if (flist) { - flist = flist->next; - bit++; - } + bit++; + if (flist) flist = flist->next; } } else if (aflist->command) { - if (flist && (!aflist->command || *aflist->command == *flist->command)) - { + if (flist && (!flist->command || *aflist->command == *flist->command)) { if (aflist->command) sprintf(out, "#define FLAG_%c (1<<%d)\n", *aflist->command, bit); - bit++; flist = flist->next; - } else sprintf(out, "#define FLAG_%c 0\n", *aflist->command); + } else sprintf(out, "#define FLAG_%c (FORCED_FLAG<<%d)\n", + *aflist->command, bit); + bit++; aflist = aflist->next; } out += strlen(out); } - sprintf(out, "#endif\n\n"); - out += strlen(out); + out = stpcpy(out, "#endif\n\n"); } if (fflush(0) && ferror(stdout)) return 1; @@ -158,4 +209,6 @@ int main(int argc, char *argv[]) if (i<0) return 1; out += i; } + + return 0; } diff --git a/scripts/mkstatus.py b/scripts/mkstatus.py index 5ac6f08..3b1bbc8 100755 --- a/scripts/mkstatus.py +++ b/scripts/mkstatus.py @@ -4,10 +4,10 @@ import subprocess,sys -def readit(args): +def readit(args, shell=False): ret={} arr=[] - blob=subprocess.Popen(args, stdout=subprocess.PIPE, shell=False) + blob=subprocess.Popen(args, stdout=subprocess.PIPE, shell=shell) for i in blob.stdout.read().split("\n"): if not i: continue i=i.split() @@ -15,40 +15,44 @@ def readit(args): arr.extend(i) return ret,arr -# Run sed on roadmap and status pages to get command lists, and run toybox too +# Run sed on roadmap and source to get command lists, and run toybox too # This gives us a dictionary of types, each with a list of commands +print "Collecting data..." + stuff,blah=readit(["sed","-n", 's/<span id=\\([a-z_]*\\)>/\\1 /;t good;d;:good;h;:loop;n;s@</span>@@;t out;H;b loop;:out;g;s/\\n/ /g;p', "www/roadmap.html", "www/status.html"]) blah,toystuff=readit(["./toybox"]) +blah,pending=readit(["sed -n 's/[^ \\t].*TOY(\\([^,]*\\),.*/\\1/p' toys/pending/*.c"], 1) +blah,version=readit(["git","describe","--tags"]) + +print "Analyzing..." -# Create reverse mappings: command is in which +# Create reverse mappings: reverse["command"] gives list of categories it's in reverse={} for i in stuff: for j in stuff[i]: try: reverse[j].append(i) except: reverse[j]=[i] +print "all commands=%s" % len(reverse) -for i in toystuff: - try: - if ("ready" in reverse[i]) or ("pending" in reverse[i]): continue - except: pass - print i +# Run a couple sanity checks on input -pending=[] -done=[] +for i in toystuff: + if (i in pending): print "barf %s" % i -print "all commands=%s" % len(reverse) +unknowns=[] +for i in toystuff + pending: + if not i in reverse: unknowns.append(i) -outfile=open("www/status.gen", "w") -outfile.write("<a name=all><h2><a href=#all>All commands</a></h2><blockquote><p>\n") +if unknowns: print "uncategorized: %s" % " ".join(unknowns) conv = [("posix", '<a href="http://pubs.opengroup.org/onlinepubs/9699919799/utilities/%s.html">%%s</a>', "[%s]"), ("lsb", '<a href="http://refspecs.linuxfoundation.org/LSB_4.1.0/LSB-Core-generic/LSB-Core-generic/%s.html">%%s</a>', '<%s>'), ("development", '<a href="http://linux.die.net/man/1/%s">%%s</a>', '(%s)'), ("toolbox", "", '{%s}'), ("klibc_cmd", "", '=%s='), ("sash_cmd", "", '#%s#'), ("sbase_cmd", "", '@%s@'), - ("beastiebox_cmd", "", '*%s*'), + ("beastiebox_cmd", "", '*%s*'), ("tizen", "", '$%s$'), ("request", '<a href="http://linux.die.net/man/1/%s">%%s</a>', '+%s+')] @@ -69,22 +73,38 @@ def categorize(reverse, i, skippy=""): return linky % out +# Sort/annotate done, pending, and todo item lists + +allcmd=[] +done=[] +pend=[] +todo=[] blah=list(reverse) blah.sort() for i in blah: out=categorize(reverse, i) - if "ready" in reverse[i] or "pending" in reverse[i]: - done.append(out) + allcmd.append(out) + if i in toystuff or i in pending: + if i in toystuff: done.append(out) + else: pend.append(out) out='<strike>%s</strike>' % out - else: pending.append(out) + else: todo.append(out) - outfile.write(out+"\n") +print "implemented=%s" % len(toystuff) -print "done=%s" % len(done) -outfile.write("</p></blockquote>\n") +# Write data to output file -outfile.write("<a name=todo><h2><a href=#todo>TODO</a></h2><blockquote><p>%s</p></blockquote>\n" % "\n".join(pending)) -outfile.write("<a name=done><h2><a href=#done>Done</a></h2><blockquote><p>%s</p></blockquote>\n" % "\n".join(done)) +outfile=open("www/status.gen", "w") +outfile.write("<h1>Status of toybox %s</h1>\n" % version[0]); +outfile.write("<h3>Legend: [posix] <lsb> (development) {android}\n") +outfile.write("=klibc= #sash# @sbase@ *beastiebox* $tizen$ +request+ other\n") +outfile.write("<strike>pending</strike></h3>\n"); + +outfile.write("<a name=done><h2><a href=#done>Completed</a></h2><blockquote><p>%s</p></blockquote>\n" % "\n".join(done)) +outfile.write("<a name=part><h2><a href=#part>Partially implemented</a></h2><blockquote><p>%s</p></blockquote>\n" % "\n".join(pend)) +outfile.write("<a name=todo><h2><a href=#todo>Not started yet</a></h2><blockquote><p>%s</p></blockquote>\n" % "\n".join(todo)) + +# Output unfinished commands by category outfile.write("<hr><h2>Categories of remaining todo items</h2>") @@ -92,8 +112,8 @@ for i in stuff: todo = [] for j in stuff[i]: - if "ready" in reverse[j]: continue - elif "pending" in reverse[j]: todo.append('<strike>%s</strike>' % j) + if j in toystuff: continue + if j in pending: todo.append('<strike>%s</strike>' % j) else: todo.append(categorize(reverse,j,i)) if todo: @@ -105,3 +125,5 @@ for i in stuff: outfile.write("<a name=%s><h2><a href=#%s>%s<a></h2><blockquote><p>" % (i,i,k)) outfile.write(" ".join(todo)) outfile.write("</p></blockquote>\n") + +outfile.write("<hr><a name=all><h2><a href=#all>All commands together in one big list</a></h2><blockquote><p>%s</p></blockquote>\n" % "\n".join(allcmd)) diff --git a/scripts/runtest.sh b/scripts/runtest.sh index ce4dedf..7f9e8b9 100644 --- a/scripts/runtest.sh +++ b/scripts/runtest.sh @@ -86,6 +86,10 @@ testing() echo -ne "$5" | eval "$2" > actual RETVAL=$? + # Catch segfaults + [ $RETVAL -gt 128 ] && [ $RETVAL -lt 255 ] && + echo "exited with signal (or returned $RETVAL)" >> actual + cmp expected actual > /dev/null 2>&1 if [ $? -ne 0 ] then @@ -95,7 +99,7 @@ testing() then [ ! -z "$4" ] && echo "echo -ne \"$4\" > input" echo "echo -ne '$5' | $2" - diff -u expected actual + diff -au expected actual [ "$VERBOSE" == fail ] && exit 1 fi else diff --git a/scripts/single.sh b/scripts/single.sh index c6416a3..1d075fd 100755 --- a/scripts/single.sh +++ b/scripts/single.sh @@ -10,18 +10,24 @@ fi for i in "$@" do + + TOYFILE="$(egrep -l "TOY[(]($i)[ ,]" toys/*/*.c)" + + if [ -z "$TOYFILE" ] + then + echo "Unknown command '$i'" >&2 + exit 1 + fi + + DEPENDS="$(sed -n 's/^[ \t]*depends on //;T;s/[!][A-Z0-9_]*//g;s/ *&& */|/g;p' $TOYFILE | grep -v SMACK | xargs | tr ' ' '|')" + NAME=$(echo $i | tr a-z- A-Z_) export KCONFIG_CONFIG=.singleconfig - USET="is not set" make allnoconfig > /dev/null && - sed -i -e "s/\(CONFIG_TOYBOX\)=y/# \1 $USET/" \ - -e "s/# \(CONFIG_$NAME\) $USET/\1=y/" \ - -e "s/# \(CONFIG_${NAME}_.*\) $USET/\1=y/" \ - -e "s/# \(CONFIG_TOYBOX_HELP.*\) $USET/\1=y/" \ - -e "s/# \(CONFIG_TOYBOX_I18N\) $USET/\1=y/" \ - -e "s/# \(CONFIG_TOYBOX_FLOAT\) $USET/\1=y/" \ - "$KCONFIG_CONFIG" && + sed -ri -e "s/CONFIG_TOYBOX=y/# CONFIG_TOYBOX is not set/;t" \ + -e "s/# (CONFIG_(TOYBOX(|_HELP.*|_I18N|_FLOAT)|$NAME|${NAME}_.*${DEPENDS:+|$DEPENDS})) is not set/\1=y/" \ + "$KCONFIG_CONFIG" && make && mv toybox $PREFIX$i || exit 1 done diff --git a/tests/bzcat.test b/tests/bzcat.test index 8bc5f5c..8bc5f5c 100644..100755 --- a/tests/bzcat.test +++ b/tests/bzcat.test diff --git a/tests/chmod.test b/tests/chmod.test index 77106a6..77106a6 100644..100755 --- a/tests/chmod.test +++ b/tests/chmod.test diff --git a/tests/chown.test b/tests/chown.test new file mode 100755 index 0000000..40ed7b5 --- /dev/null +++ b/tests/chown.test @@ -0,0 +1,41 @@ +#!/bin/bash + +[ -f testing.sh ] && . testing.sh + +if [ "$(id -u)" -ne 0 ] +then + echo "SKIPPED: chown (not root)" + continue 2>/dev/null + exit +fi + +# We chown between user "root" and the last user in /etc/passwd, +# and group "root" and the last group in /etc/group. + +USR="$(sed -n '$s/:.*//p' /etc/passwd)" +GRP="$(sed -n '$s/:.*//p' /etc/group)" + +# Set up a little testing hierarchy + +rm -rf testdir && +mkdir testdir && +touch testdir/file +F=testdir/file + +# Wrapper to reset groups and return results + +OUT="&& echo \$(ls -l testdir/file | awk '{print \$3,\$4}')" + +#testing "name" "command" "result" "infile" "stdin" + +# Basic smoketest +testing "chown initial" "chown root:root $F $OUT" "root root\n" "" "" +testing "chown usr:grp" "chown $USR:$GRP $F $OUT" "$USR $GRP\n" "" "" +testing "chown root" "chown root $F $OUT" "root $GRP\n" "" "" +# TODO: can we test "owner:"? +testing "chown :grp" "chown root:root $F && chown :$GRP $F $OUT" \ + "root $GRP\n" "" "" +testing "chown :" "chown $USR:$GRP $F && chown : $F $OUT" \ + "$USR $GRP\n" "" "" + +rm -rf testdir diff --git a/tests/cut.test b/tests/cut.test index a001952..a001952 100644..100755 --- a/tests/cut.test +++ b/tests/cut.test diff --git a/tests/dd.test b/tests/dd.test index 9bdcac5..9bdcac5 100644..100755 --- a/tests/dd.test +++ b/tests/dd.test diff --git a/tests/du.test b/tests/du.test index fabb800..81fb528 100755 --- a/tests/du.test +++ b/tests/du.test @@ -18,6 +18,9 @@ ln -s ../du_2 du_test/xyz # allocated file space is zero. testing "du counts symlinks without following" "du -ks du_test" "8\tdu_test\n" "" "" testing "du -L follows symlinks" "du -ksL du_test" "16\tdu_test\n" "" "" +ln -s . du_test/up +testing "du -L avoid endless loop" "du -ksL du_test" "16\tdu_test\n" "" "" +rm du_test/up # if -H and -L are specified, the last takes priority testing "du -HL follows symlinks" "du -ksHL du_test" "16\tdu_test\n" "" "" testing "du -H does not follow unspecified symlinks" "du -ksH du_test" "8\tdu_test\n" "" "" diff --git a/tests/expr.test b/tests/expr.test index cce7d9d..33900d7 100644..100755 --- a/tests/expr.test +++ b/tests/expr.test @@ -8,3 +8,14 @@ testing "expr string" "expr astring" "astring\n" "" "" testing "expr 1 + 3" "expr 1 + 3" "4\n" "" "" testing "expr 5 + 6 * 3" "expr 5 + 6 \* 3" "23\n" "" "" testing "expr ( 5 + 6 ) * 3" "expr \( 5 + 6 \) \* 3" "33\n" "" "" +testing "expr * / same priority" "expr 4 \* 3 / 2" "6\n" "" "" +testing "expr / * same priority" "expr 3 / 2 \* 4" "4\n" "" "" +testing "expr & before |" "expr 0 \| 1 \& 0" "0\n" "" "" +testing "expr | after &" "expr 1 \| 0 \& 0" "1\n" "" "" +testing "expr | & same priority" "expr 0 \& 0 \| 1" "1\n" "" "" +testing "expr % * same priority" "expr 3 % 2 \* 4" "4\n" "" "" +testing "expr * % same priority" "expr 3 \* 2 % 4" "2\n" "" "" +testing "expr = > same priority" "expr 0 = 2 \> 3" "0\n" "" "" +testing "expr > = same priority" "expr 3 \> 2 = 1" "1\n" "" "" +testing "expr string becomes integer" "expr ab21xx : '[^0-9]*\([0-9]*\)' + 3" \ + "24\n" "" "" diff --git a/tests/factor.test b/tests/factor.test index a3e4cbf..ed1cc22 100755 --- a/tests/factor.test +++ b/tests/factor.test @@ -16,3 +16,7 @@ testing "factor 10000000018" "factor 10000000018" \ "10000000018: 2 131 521 73259\n" "" "" testing "factor 10000000019" "factor 10000000019" \ "10000000019: 10000000019\n" "" "" + +testing "factor 3 6 from stdin" "factor" "3: 3\n6: 2 3\n" "" "3 6" +testing "factor stdin newline" "factor" "3: 3\n6: 2 3\n" "" "3\n6\n" + diff --git a/tests/find.test b/tests/find.test index cbbce5f..cbbce5f 100644..100755 --- a/tests/find.test +++ b/tests/find.test diff --git a/tests/groupadd.test b/tests/groupadd.test index 0395e01..0395e01 100644..100755 --- a/tests/groupadd.test +++ b/tests/groupadd.test diff --git a/tests/groupdel.test b/tests/groupdel.test index d46db53..d46db53 100644..100755 --- a/tests/groupdel.test +++ b/tests/groupdel.test diff --git a/tests/head.test b/tests/head.test index eeb07e0..d4eecd9 100755 --- a/tests/head.test +++ b/tests/head.test @@ -7,6 +7,8 @@ testing "head, stdin" "head -n 1 && echo yes" "one\nyes\n" "" "one\ntwo" testing "head, stdin via -" "head -n 1 - && echo yes" "one\nyes\n" "" "one\ntwo" testing "head, file" "head input -n 1 && echo yes" "one\nyes\n" "one\ntwo" "" +testing "head -number" "head -2 input && echo yes" "one\ntwo\nyes\n" \ + "one\ntwo\nthree\nfour" "" testing "head, default lines" "head" "1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n" "" "1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12" echo "foo diff --git a/tests/hostname.test b/tests/hostname.test index 1194845..1194845 100644..100755 --- a/tests/hostname.test +++ b/tests/hostname.test diff --git a/tests/ifconfig.test b/tests/ifconfig.test new file mode 100755 index 0000000..0c0af46 --- /dev/null +++ b/tests/ifconfig.test @@ -0,0 +1,192 @@ +#!/bin/bash +# Copyright 2014 Cynthia Rempel <cynthia@rtems.org> +# +# Brief: Some cursery coverage tests of ifconfig... +# Note: requires permissions to run modprobe and all ifconfig options +# Commands used: grep, grep -i, ip link, ip tuntap, wc -l +# +# Possible improvements: +# 1. Verify the dummy interface actually has the modified characteristics +# instead of relying on ifconfig output +# 2. Introduce more error cases, to verify ifconfig fails gracefully +# 3. Do more complex calls to ifconfig, while mixing the order of the +# arguments +# 4. Cover more ifconfig options: +# hw ether|infiniband ADDRESS - set LAN hardware address (AA:BB:CC...) +# txqueuelen LEN - number of buffered packets before output blocks +# Obsolete fields included for historical purposes: +# irq|io_addr|mem_start ADDR - micromanage obsolete hardware +# outfill|keepalive INTEGER - SLIP analog dialup line quality monitoring +# metric INTEGER - added to Linux 0.9.10 with comment "never used", still true + +[ -f testing.sh ] && . testing.sh + +if [ "$(id -u)" -ne 0 ] +then + echo "SKIPPED: ifconfig (not root)" + continue 2>/dev/null + exit +fi + +#testing "name" "command" "result" "infile" "stdin" + +# Add a dummy interface to test with +ifconfig dummy0 up + +# Test Description: Disable the dummy0 interface +# Results Expected: After calling ifconfig, no lines with dummy0 are displayed +testing "ifconfig dummy0 down and if config /-only" \ +"ifconfig dummy0 down && ifconfig dummy0 | grep dummy | wc -l" \ +"0\n" "" "" + +# Test Description: Enable the dummy0 interface +# Results Expected: After calling ifconfig, one line with dummy0 is displayed +testing "ifconfig dummy0 up" \ +"ifconfig dummy0 up && ifconfig dummy0 | grep dummy | wc -l" \ +"1\n" "" "" + +# Test Description: Set the ip address of the dummy0 interface +# Results Expected: After calling ifconfig dummy0, one line displays the ip +# address selected +testing "ifconfig dummy0 10.240.240.240" \ +"ifconfig dummy0 10.240.240.240 && ifconfig dummy0 | grep 10\.240\.240\.240 | wc -l" \ +"1\n" "" "" + +# Test Description: Change the netmask to the interface +# Results Expected: After calling ifconfig dummy0, one line displays the +# netmask selected +testing "ifconfig dummy0 netmask 255.255.240.0" \ +"ifconfig dummy0 netmask 255.255.240.0 && ifconfig dummy0 | grep 255\.255\.240\.0 | wc -l" \ +"1\n" "" "" + +# Test Description: Change the broadcast address to the interface +# Results Expected: After calling ifconfig dummy0, one line displays the +# broadcast address selected +testing "ifconfig dummy0 broadcast 10.240.240.255" \ +"ifconfig dummy0 broadcast 10.240.240.255 && ifconfig dummy0 | grep 10\.240\.240\.255 | wc -l" \ +"1\n" "" "" + +# Test Description: Revert to the default ip address +# Results Expected: After calling ifconfig dummy0, there are no lines +# displaying the ip address previously selected +testing "ifconfig dummy0 default" \ +"ifconfig dummy0 default && ifconfig dummy0 | grep 10\.240\.240\.240 | wc -l" \ +"0\n" "" "" + +# Test Description: Change the Maximum transmission unit (MTU) of the interface +# Results Expected: After calling ifconfig dummy0, there is one line with the +# selected MTU +testing "ifconfig dummy0 mtu 1269" \ +"ifconfig dummy0 mtu 1269 && ifconfig dummy0 | grep 1269 | wc -l" \ +"1\n" "" "" + +# Test Description: Verify ifconfig add fails with such a small mtu +# Results Expected: There is one line of error message containing +# "No buffer space available" +testing "ifconfig dummy0 add ::2 -- too small mtu" \ +"ifconfig dummy0 add ::2 2>&1 | grep No\ buffer\ space\ available | wc -l" \ +"1\n" "" "" + +# Test Description: Change the Maximum transmission unit (MTU) of the interface +# Results Expected: After calling ifconfig dummy0, there is one line with the +# selected MTU +testing "ifconfig dummy0 mtu 2000" \ +"ifconfig dummy0 mtu 2000 && ifconfig dummy0 | grep 2000 | wc -l" \ +"1\n" "" "" + +# Test Description: Verify ifconfig add succeeds with a larger mtu +# Results Expected: after calling ifconfig dummy0, there is one line with the +# selected ip address +testing "ifconfig dummy0 add ::2" \ +"ifconfig dummy0 add ::2/126 && ifconfig dummy0 | grep \:\:2\/126 | wc -l" \ +"1\n" "" "" + +# Test Description: Verify ifconfig del removes the selected ip6 address +# Results Expected: after calling ifconfig dummy0, there are no lines with the +# selected ip address +testing "ifconfig dummy0 del ::2" \ +"ifconfig dummy0 del ::2/126 && ifconfig dummy0 | grep \:\:2 | wc -l" \ +"0\n" "" "" + +# Test Description: Remove the noarp flag and bring the interface down in +# preparation for the next test +# Results Expected: After calling ifconfig dummy0, there are no lines with the +# NOARP flag +testing "ifconfig dummy0 arp down" \ +"ifconfig dummy0 arp down && ifconfig dummy0 | grep -i NOARP | wc -l" \ +"0\n" "" "" + +# Test Description: Call the pointtopoint option with no argument +# Results Expected: After calling ifconfig dummy0, there is one line with the +# NOARP and UP flags +testing "ifconfig dummy0 pointtopoint" \ +"ifconfig dummy0 pointtopoint && ifconfig dummy0 | grep -i NOARP | grep -i UP | wc -l" \ +"1\n" "" "" + +# Test Description: Test the pointtopoint option and set the ipaddress +# Results Expected: After calling ifconfig dummy0, there is one line with the +# word inet and the selected ip address +testing "ifconfig dummy0 pointtopoint 127.0.0.2" \ +"ifconfig dummy0 pointtopoint 127.0.0.2 && ifconfig dummy0 | grep -i inet | grep -i 127\.0\.0\.2 | wc -l" \ +"1\n" "" "" + +####### Flags you can set on an interface (or -remove by prefixing with -): ############### + +# Test Description: Enable allmulti mode on the interface +# Results Expected: After calling ifconfig dummy0, there is one line with the +# allmulti flag +testing "ifconfig dummy0 allmulti" \ +"ifconfig dummy0 allmulti && ifconfig dummy0 | grep -i allmulti | wc -l" "1\n" \ +"" "" + +# Test Description: Disable multicast mode the interface +# Results Expected: After calling ifconfig dummy0, there are no lines with the +# allmulti flag +testing "ifconfig dummy0 -allmulti" \ +"ifconfig dummy0 -allmulti && ifconfig dummy0 | grep -i allmulti | wc -l" "0\n" \ +"" "" + +# Test Description: Disable NOARP mode on the interface +# Results Expected: After calling ifconfig dummy0, there are no lines with the +# NOARP flag +testing "ifconfig dummy0 arp" \ +"ifconfig dummy0 arp && ifconfig dummy0 | grep -i NOARP | wc -l" "0\n" \ +"" "" + +# Test Description: Enable NOARP mode on the interface +# Results Expected: After calling ifconfig dummy0, there is one line with the +# NOARP flag +testing "ifconfig dummy0 -arp" \ +"ifconfig dummy0 -arp && ifconfig dummy0 | grep -i NOARP | wc -l" "1\n" \ +"" "" + +# Test Description: Enable multicast mode on the interface +# Results Expected: After calling ifconfig dummy0, there is one line with the +# multicast flag +testing "ifconfig dummy0 multicast" \ +"ifconfig dummy0 multicast && ifconfig dummy0 | grep -i multicast | wc -l" \ +"1\n" "" "" + +# Test Description: Disable multicast mode the interface +# Results Expected: After calling ifconfig dummy0, there are no lines with the +# multicast flag +testing "ifconfig dummy0 -multicast" \ +"ifconfig dummy0 -multicast && ifconfig dummy0 | grep -i multicast | wc -l" \ +"0\n" "" "" + +# Test Description: Enable promiscuous mode the interface +# Results Expected: After calling ifconfig dummy0, there is one line with the +# promisc flag +testing "ifconfig dummy0 promisc" \ +"ifconfig dummy0 promisc && ifconfig dummy0 | grep -i promisc | wc -l" "1\n" \ +"" "" + +# Disable promiscuous mode the interface +# Results Expected: After calling ifconfig dummy0, there are no lines with the +# promisc flag +testing "ifconfig dummy0 -promisc" \ +"ifconfig dummy0 -promisc && ifconfig dummy0 | grep -i promisc | wc -l" "0\n" \ +"" "" + +# Disable the dummy interface +ifconfig dummy0 down diff --git a/tests/ln.test b/tests/ln.test index aea3ab0..aea3ab0 100644..100755 --- a/tests/ln.test +++ b/tests/ln.test diff --git a/tests/ls.test b/tests/ls.test index 9866eb7..9866eb7 100644..100755 --- a/tests/ls.test +++ b/tests/ls.test diff --git a/tests/lsattr.test b/tests/lsattr.test index 870b3f6..870b3f6 100644..100755 --- a/tests/lsattr.test +++ b/tests/lsattr.test diff --git a/tests/mount.test b/tests/mount.test index 1fdc00f..d64bfc6 100644..100755 --- a/tests/mount.test +++ b/tests/mount.test @@ -82,6 +82,10 @@ testing "mount -o ro $tmp_b_fs /mnt" \ "mount -o ro $tmp_b_fs /mnt >/dev/null 2>&1 && mkdir /mnt/testDir 2>/dev/null || sleep 1 && umount /mnt" "" "" "" reCreateTmpFs +testing "mount -o ro,remount $tmp_b_fs /mnt" \ + "mount -o ro $tmp_b_fs /mnt >/dev/null 2>&1 && + mkdir /mnt/testDir 2>/dev/null || sleep 1 && umount /mnt" "" "" "" +reCreateTmpFs umount testDir1 rm -f $tmp_b_fs diff --git a/tests/mv.test b/tests/mv.test index 53fc999..53fc999 100644..100755 --- a/tests/mv.test +++ b/tests/mv.test diff --git a/tests/nl.test b/tests/nl.test index 958cb41..958cb41 100644..100755 --- a/tests/nl.test +++ b/tests/nl.test diff --git a/tests/pgrep.test b/tests/pgrep.test index 36df573..36df573 100644..100755 --- a/tests/pgrep.test +++ b/tests/pgrep.test diff --git a/tests/printf.test b/tests/printf.test index 96789bd..20d5982 100644..100755 --- a/tests/printf.test +++ b/tests/printf.test @@ -6,24 +6,56 @@ [ -f testing.sh ] && . testing.sh #testing "name" "command" "result" "infile" "stdin" -#set -x -testing "printf TEXT" "printf toyTestText" "toyTestText" "" "" -testing "printf MULTILINE_TEXT" \ - "printf 'Testing\nmultiline\ntext\nfrom\ntoybox\tcommand.\b'" \ - "Testing\nmultiline\ntext\nfrom\ntoybox\tcommand.\b" "" "" +# Disable shell builtin +PRINTF="$(which printf)" + +testing "printf text" "$PRINTF TEXT" "TEXT" "" "" +testing "printf escapes" "$PRINTF 'one\ntwo\n\v\t\r\f\e\b\athree'" \ + "one\ntwo\n\v\t\r\f\e\b\athree" "" "" +testing "printf %b escapes" "$PRINTF %b 'one\ntwo\n\v\t\r\f\e\b\athree'" \ + "one\ntwo\n\v\t\r\f\e\b\athree" "" "" +testing "printf null" "$PRINTF 'x\0y' | od -An -tx1" ' 78 00 79\n' "" "" +testing "printf trailing slash" "$PRINTF 'abc\'" 'abc\' "" "" +testing "printf octal" "$PRINTF ' \1\002\429\045x'" ' \001\002"9%x' "" "" +testing "printf not octal" "$PRINTF '\9'" '\9' "" "" +testing "printf hex" "$PRINTF 'A\x1b\x2B\x3Q\xa' | od -An -tx1" \ + ' 41 1b 2b 03 51 0a\n' "" "" +testing "printf %x" "$PRINTF '%x\n' 0x2a" "2a\n" "" "" + +testing "printf %d 42" "$PRINTF %d 42" "42" "" "" +testing "printf %d 0x2a" "$PRINTF %d 0x2a" "42" "" "" +testing "printf %d 052" "$PRINTF %d 052" "42" "" "" + +testing "printf %s width precision" \ + "$PRINTF '%3s,%.3s,%10s,%10.3s' abcde fghij klmno pqrst" \ + "abcde,fgh, klmno, pqr" "" "" + +# posix: "The format operand shall be reused as often as necessary to satisfy +# the argument operands." + +testing "printf extra args" "$PRINTF 'abc%s!%ddef\n' X 42 ARG 36" \ + "abcX!42def\nabcARG!36def\n" "" "" + +testing "printf '%3c'" "$PRINTF '%3c' x" " x" "" "" +testing "printf '%-3c'" "$PRINTF '%-3c' x" "x " "" "" +testing "printf '%+d'" "$PRINTF '%+d' 5" "+5" "" "" + + testing "printf '%5d%4d' 1 21 321 4321 54321" \ - "printf '%5d%4d' 1 21 321 4321 54321" " 1 21 321432154321 0" "" "" -testing "printf '%c %c' 78 79" "printf '%c %c' 78 79" "7 7" "" "" -testing "printf '%d %d' 78 79" "printf '%d %d' 78 79" "78 79" "" "" -testing "printf '%f %f' 78 79" "printf '%f %f' 78 79" \ + "$PRINTF '%5d%4d' 1 21 321 4321 54321" " 1 21 321432154321 0" "" "" +testing "printf '%c %c' 78 79" "$PRINTF '%c %c' 78 79" "7 7" "" "" +testing "printf '%d %d' 78 79" "$PRINTF '%d %d' 78 79" "78 79" "" "" +testing "printf '%f %f' 78 79" "$PRINTF '%f %f' 78 79" \ "78.000000 79.000000" "" "" -testing "printf 'f f' 78 79" "printf 'f f' 78 79" "f f" "" "" -testing "printf '%i %i' 78 79" "printf '%i %i' 78 79" "78 79" "" "" -testing "printf '%o %o' 78 79" "printf '%o %o' 78 79" "116 117" "" "" -testing "printf '%u %u' 78 79" "printf '%u %u' 78 79" "78 79" "" "" -testing "printf '%u %u' -1 -2" "printf '%u %u' -1 -2" \ +testing "printf 'f f' 78 79" "$PRINTF 'f f' 78 79" "f f" "" "" +testing "printf '%i %i' 78 79" "$PRINTF '%i %i' 78 79" "78 79" "" "" +testing "printf '%o %o' 78 79" "$PRINTF '%o %o' 78 79" "116 117" "" "" +testing "printf '%u %u' 78 79" "$PRINTF '%u %u' 78 79" "78 79" "" "" +testing "printf '%u %u' -1 -2" "$PRINTF '%u %u' -1 -2" \ "18446744073709551615 18446744073709551614" "" "" -testing "printf '%x %X' 78 79" "printf '%x %X' 78 79" "4e 4F" "" "" -testing "printf '%g %G' 78 79" "printf '%g %G' 78 79" "78 79" "" "" -testing "printf '%s %s' 78 79" "printf '%s %s' 78 79" "78 79" "" "" +testing "printf '%x %X' 78 79" "$PRINTF '%x %X' 78 79" "4e 4F" "" "" +testing "printf '%g %G' 78 79" "$PRINTF '%g %G' 78 79" "78 79" "" "" +testing "printf '%s %s' 78 79" "$PRINTF '%s %s' 78 79" "78 79" "" "" + +testing "printf %.s acts like %.0s" "$PRINTF %.s_ 1 2 3 4 5" "_____" "" "" diff --git a/tests/renice.test b/tests/renice.test index e87111c..e87111c 100644..100755 --- a/tests/renice.test +++ b/tests/renice.test diff --git a/tests/rev.test b/tests/rev.test index c7622b9..c7622b9 100644..100755 --- a/tests/rev.test +++ b/tests/rev.test diff --git a/tests/rm.test b/tests/rm.test index 0dca853..0dca853 100644..100755 --- a/tests/rm.test +++ b/tests/rm.test diff --git a/tests/sed.test b/tests/sed.test index 2d019eb..9f2956f 100644 --- a/tests/sed.test +++ b/tests/sed.test @@ -3,6 +3,12 @@ #testing "name" "command" "result" "infile" "stdin" testing 'sed as cat' 'sed ""' "one\ntwo\nthree" "" "one\ntwo\nthree" +# This segfaults ubuntu 12.04's sed. No really. +SKIP_HOST=1 testing 'sed - - twice' 'sed "" - -' "hello\n" "" "hello\n" +testing 'sed -n' 'sed -n ""' "" "" "one\ntwo\nthree" +testing 'sed -n p' 'sed -n p' "one\ntwo\nthree" "" "one\ntwo\nthree" +testing 'sed explicit pattern' 'sed -e p -n' "one\ntwo\nthree" "" \ + "one\ntwo\nthree" # Exploring the wonders of sed addressing modes testing '' 'sed -n 1p' "one\n" "" "one\ntwo\nthree" @@ -10,7 +16,7 @@ testing '' 'sed 2p' "one\ntwo\ntwo\nthree" "" "one\ntwo\nthree" testing '' 'sed -n 2p' "two\n" "" "one\ntwo\nthree" testing 'sed -n $p' 'sed -n \$p' "three" "" "one\ntwo\nthree" testing 'sed as cat #2' "sed -n '1,\$p'" "one\ntwo\nthree" "" "one\ntwo\nthree" -testing 'no input means no last line' "sed '\$a boing'" "" "" "" +testing 'sed no input means no last line' "sed '\$a boing'" "" "" "" testing 'sed -n $,$p' 'sed -n \$,\$p' 'three' '' 'one\ntwo\nthree' testing '' 'sed -n 1,2p' "one\ntwo\n" "" "one\ntwo\nthree" testing '' 'sed -n 2,3p' "two\nthree" "" "one\ntwo\nthree" @@ -27,9 +33,9 @@ testing 'sed -n /two/,$p' 'sed -n /two/,\$p' 'two\nthree' '' 'one\ntwo\nthree' # Fun with newlines! testing '' 'sed -n 3p' "three" "" "one\ntwo\nthree" -testing 'prodigal newline' "sed -n '1,\$p' - input" "one\ntwo\nthree\nfour\n" \ - "four\n" "one\ntwo\nthree" -testing 'Newline only added if further output' "sed -n 3p - input" "three" \ +testing 'sed prodigal newline' "sed -n '1,\$p' - input" \ + "one\ntwo\nthree\nfour\n" "four\n" "one\ntwo\nthree" +testing 'sed Newline only added if further output' "sed -n 3p - input" "three" \ "four\n" "one\ntwo\nthree" # Fun with match delimiters and escapes @@ -40,9 +46,9 @@ testing 'sed match t delim makes \t literal t' \ testing 'sed match n delim' "sed -n '\n\txnp'" "\tx\n" "" "\tx\n" testing 'sed match n delim disables \n newline' "sed -n '\n\nxnp'" "" "" "\nx\n" SKIP_HOST=1 testing 'sed match \n literal n' "sed -n '\n\nxnp'" "nx\n" "" "nx\n" -testing 'end match does not check starting match line' \ +testing 'sed end match does not check starting match line' \ "sed -n '/two/,/two/p'" "two\nthree" "" "one\ntwo\nthree" -testing 'end match/start match mixing number/letter' \ +testing 'sed end match/start match mixing number/letter' \ "sed -n '2,/two/p'" "two\nthree" "" "one\ntwo\nthree" testing 'sed num then regex' 'sed -n 2,/d/p' 'b\nc\nd\n' '' 'a\nb\nc\nd\ne\nf\n' testing 'sed regex then num' 'sed -n /b/,4p' 'b\nc\nd\n' '' 'a\nb\nc\nd\ne\nf\n' @@ -52,18 +58,37 @@ testing 'sed multiple regex address match' 'sed -n /on/,/off/p' \ testing 'sed regex address overlap' 'sed -n /on/,/off/p' "on\nzap\noffon\n" "" \ 'on\nzap\noffon\nping\noff\n' +# gGhHlnNpPqrstwxy:= +# s///#comment +# abcdDi + +testing 'sed prodigaler newline' 'sed -e a\\ -e woo' 'one\nwoo\n' '' 'one' testing "sed aci" \ "sed -e '3a boom' -e '/hre/i bang' -e '3a whack' -e '3c bong'" \ "one\ntwo\nbang\nbong\nboom\nwhack\nfour\n" "" \ "one\ntwo\nthree\nfour\n" -testing 'sed prodigaler newline' 'sed -e a\\ -e woo' 'one\nwoo\n' '' 'one' +testing "sed b loop" "sed ':woo;=;b woo' | head -n 5" '1\n1\n1\n1\n1\n' "" "X" +testing "sed b skip" "sed -n '2b zap;d;:zap;p'" "two\n" "" "one\ntwo\nthree" +testing "sed b end" "sed -n '2b;p'" "one\nthree" "" "one\ntwo\nthree" +testing "sed c range" "sed '2,4c blah'" "one\nblah\nfive\nsix" "" \ + "one\ntwo\nthree\nfour\nfive\nsix" +testing "sed c {range}" "sed -e '2,4{c blah' -e '}'" \ + "one\nblah\nblah\nblah\nfive\nsix" \ + "" "one\ntwo\nthree\nfour\nfive\nsix" +testing "sed c multiple continuation" \ + "sed -e 'c\\' -e 'two\\' -e ''" "two\n\n" "" "hello" +testing "sed D further processing depends on whether line is blank" \ + "sed -e '/one/,/three/{' -e 'i meep' -e'N;2D;}'" \ + "meep\nmeep\ntwo\nthree\n" "" "one\ntwo\nthree\n" testing 'sed newline staying away' 'sed s/o/x/' 'xne\ntwx' '' 'one\ntwo' + # Why on _earth_ is this not an error? There's a \ with no continuation! -testing 'sed what, _really_?' 'sed -e a\\ && echo yes really' \ - 'one\nyes really\n' '' 'one\n' +#testing 'sed what, _really_?' 'sed -e a\\ && echo yes really' \ +# 'one\nyes really\n' '' 'one\n' # all the s/// test +testing "sed match empty line" "sed -e 's/^\$/@/'" "@\n" "" "\n" testing 'sed \1' "sed 's/t\\(w\\)o/za\\1py/'" "one\nzawpy\nthree" "" \ "one\ntwo\nthree" @@ -82,5 +107,34 @@ testing 'sed backref error' \ testing 'sed empty match after nonempty match' "sed -e 's/a*/c/g'" 'cbcncgc' \ '' 'baaang' testing 'sed empty match' "sed -e 's/[^ac]*/A/g'" 'AaAcA' '' 'abcde' -testing 'sed s///#' "sed -e 's/TWO/four/i#comment'" "one\nfour\nthree" \ +testing 'sed s///#comment' "sed -e 's/TWO/four/i#comment'" "one\nfour\nthree" \ "" "one\ntwo\nthree" + +testing 'sed N flushes pending a and advances match counter' \ + "sed -e 'a woo' -e 'N;\$p'" 'woo\none\ntwo\none\ntwo' "" 'one\ntwo' +testing "sed delimiter in regex [char range] doesn't count" "sed -e 's/[/]//'" \ + "onetwo\n" "" 'one/two\n' +testing "sed delete regex range start line after trigger" \ + "sed -e '/one/,/three/{' -e 'i meep' -e '1D;}'" \ + "meep\nmeep\ntwo\nmeep\nthree" "" "one\ntwo\nthree" +testing "sed D further processing depends on whether line is blank" \ + "sed -e '/one/,/three/{' -e 'i meep' -e'N;2D;}'" \ + "meep\nmeep\ntwo\nthree\n" "" "one\ntwo\nthree\n" + +# Different ways of parsing line continuations + +testing "" "sed -e '1a\' -e 'huh'" "meep\nhuh\n" "" "meep" +testing "" "sed -f input" "blah\nboom\n" '1a\\\nboom' 'blah' +testing "" "sed '1a\ +hello'" "merp\nhello\n" "" "merp" +#echo meep | sed/sed -e '1a\' -e 'huh' +#echo blah | sed/sed -f <(echo -e "1a\\\\\nboom") +#echo merp | sed/sed "1a\\ +#hello" + +testing "sed bonus backslashes" \ + "sed -e 'a \l \x\' -e \"\$(echo -e 'ab\\\nc')\"" \ + "hello\nl x\nab\nc\n" "" "hello\n" +# -i with $ last line test + +exit $FAILCOUNT diff --git a/tests/tac.test b/tests/tac.test index 96f2531..96f2531 100644..100755 --- a/tests/tac.test +++ b/tests/tac.test diff --git a/tests/tar.test b/tests/tar.test index 187186a..187186a 100644..100755 --- a/tests/tar.test +++ b/tests/tar.test diff --git a/tests/touch.test b/tests/touch.test index 03ab8ce..d6430d0 100755 --- a/tests/touch.test +++ b/tests/touch.test @@ -11,14 +11,20 @@ testing "touch -c" "touch -c walrus && [ -e walrus ] && echo yes" "yes\n" "" "" testing "touch -c missing" "touch -c warrus && [ ! -e warrus ] && echo yes" \ "yes\n" "" "" -# This isn't testing fraction of a second because I dunno how to read it back - testing "touch -d" \ - "touch -d 2009-02-13T23:31:30.12Z walrus && date -r walrus +%s.%N" "1234567890\n" \ - "" "" -#testing "touch -t" "touch -t 200902132331.42 -#testing "touch -r" + "touch -d 2009-02-13T23:31:30Z walrus && date -r walrus +%s" \ + "1234567890\n" "" "" + +testing "touch -d nanoseconds" \ + "touch -d 2009-02-13T23:31:30.123456789Z walrus && date -r walrus +%s.%N" \ + "1234567890.123456789\n" "" "" + +testing "touch -r" \ + "touch -r walrus walrus2 && date -r walrus2 +%s.%N" \ + "1234567890.123456789\n" "" "" + #testing "touch -a" #testing "touch -m" #testing "touch -am" -rm walrus +#testing "touch -t" +rm walrus walrus2 diff --git a/tests/truncate.test b/tests/truncate.test new file mode 100755 index 0000000..ee2b2bb --- /dev/null +++ b/tests/truncate.test @@ -0,0 +1,28 @@ +#!/bin/bash + +[ -f testing.sh ] && . testing.sh + +#testing "name" "command" "result" "infile" "stdin" + +SIZE='&& stat -c %s freep' +testing "truncate 0" "truncate -s 0 freep $SIZE" "0\n" "" "" +testing "truncate 12345" "truncate -s 12345 freep $SIZE" "12345\n" "" "" +testing "truncate 1m" "truncate -s 1m freep $SIZE" "1048576\n" "" "" +testing "truncate is sparse" "truncate -s 1g freep && stat -c %b freep" \ + "0\n" "" "" +testing "truncate +" "truncate -s 1k freep && truncate -s +1k freep $SIZE" \ + "2048\n" "" "" +testing "truncate -" "truncate -s 4k freep && truncate -s -1k freep $SIZE" \ + "3072\n" "" "" +testing "truncate < hit" \ + "truncate -s 5k freep && truncate -s \<4k freep $SIZE" "4096\n" "" "" +testing "truncate < miss" \ + "truncate -s 4k freep && truncate -s \<6k freep $SIZE" "4096\n" "" "" +testing "truncate > hit" \ + "truncate -s 3k freep && truncate -s \>4k freep $SIZE" "4096\n" "" "" +testing "truncate > miss" \ + "truncate -s 4k freep && truncate -s \>2k freep $SIZE" "4096\n" "" "" +testing "truncate /" "truncate -s 7k freep && truncate -s /3k freep $SIZE" \ + "6144\n" "" "" +testing "truncate %" "truncate -s 7k freep && truncate -s %3k freep $SIZE" \ + "9216\n" "" "" diff --git a/tests/useradd.test b/tests/useradd.test index a87da07..a954e72 100644..100755 --- a/tests/useradd.test +++ b/tests/useradd.test @@ -5,8 +5,12 @@ [ -f testing.sh ] && . testing.sh -# 70 characters long string; hereafter, we will use it as per our need. -_s70="abcdefghijklmnopqrstuvwxyz123456789abcdefghijklmnopqrstuvwxyz123456789" +if [ "$(id -u)" -ne 0 ] +then + echo "SKIPPED: useradd (not root)" + continue 2>/dev/null + exit +fi # Redirecting all output to /dev/null for grep, adduser and deluser arg="&>/dev/null" @@ -16,75 +20,85 @@ arg="&>/dev/null" # Default password for adding user is: 'password' pass=`echo -ne 'password\npassword\n'` -testing "adduser user_name (text)" "useradd toyTestUser $arg || - grep '^toyTestUser:' /etc/passwd $arg && test -d /home/toyTestUser && - userdel toyTestUser $arg && rm -rf /home/toyTestUser && echo 'yes'" \ - "yes\n" "" "$pass" - -testing "adduser user_name (alphanumeric)" "useradd toy1Test2User3 $arg || - grep '^toy1Test2User3:' /etc/passwd $arg && test -d /home/toy1Test2User3 && - userdel toy1Test2User3 $arg && rm -rf /home/toy1Test2User3 && echo 'yes'" \ - "yes\n" "" "$pass" - -testing "adduser user_name (numeric)" "useradd 987654321 $arg || - grep '^987654321:' /etc/passwd $arg && test -d /home/987654321 && - userdel 987654321 $arg && rm -rf /home/987654321 && echo 'yes'" \ - "yes\n" "" "$pass" +user="toyTestUser" +testing "adduser user_name (text)" "useradd $user $arg || + grep '^$user:' /etc/passwd $arg && [ -d /home/$user ] && + echo 'yes'" "yes\n" "" "$pass" +userdel -r $user $arg + +user="toy1Test2User3" +testing "adduser user_name (alphanumeric)" "useradd $user $arg || + grep '^$user:' /etc/passwd $arg && [ -d /home/$user ] && + echo 'yes'" "yes\n" "" "$pass" +userdel -r $user $arg + +user="987654321" +testing "adduser user_name (numeric)" "useradd $user $arg || + grep '^$user:' /etc/passwd $arg && [ -d /home/$user ] && + echo 'yes'" "yes\n" "" "$pass" +userdel -r $user $arg + +user="toy.1Test-2User_3" +testing "adduser user_name (with ./-/_)" "useradd $user $arg || + grep '^$user:' /etc/passwd $arg && [ -d /home/$user ] && + echo 'yes'" "yes\n" "" "$pass" +userdel -r $user $arg -testing "adduser user_name (with ./-/_)" "useradd toy.1Test-2User_3 $arg || - grep '^toy.1Test-2User_3:' /etc/passwd $arg && - test -d /home/toy.1Test-2User_3 && userdel toy.1Test-2User_3 $arg && - rm -rf /home/toy.1Test-2User_3 && echo 'yes'" "yes\n" "" "$pass" - -testing "adduser user_name (long string)" "useradd $_s70 $arg || - grep '^$_s70:' /etc/passwd $arg && test -d /home/$_s70 && - userdel $_s70 $arg && rm -rf /home/$_s70 && echo 'yes'" "yes\n" "" "$pass" - -testing "adduser user_name with dir" "useradd -h $PWD/dir toyTestUser $arg || - grep '^toyTestUser:.*dir' /etc/passwd $arg && test -d $PWD/dir && - userdel toyTestUser $arg && rm -rf $PWD/dir && echo 'yes'" "yes\n" "" "$pass" +# 70 characters long string; hereafter, we will use it as per our need. +user="abcdefghijklmnopqrstuvwxyz123456789abcdefghijklmnopqrstuvwxyz123456789" +testing "adduser user_name (long string)" "useradd $user $arg || + grep '^$user:' /etc/passwd $arg && [ -d /home/$user ] && + echo 'yes'" "yes\n" "" "$pass" +userdel -r $user $arg + +user="toyTestUser" +testing "adduser user_name with dir" "useradd -h $PWD/dir $user $arg || + grep '^$user:.*dir' /etc/passwd $arg && [ -d $PWD/dir ] && + echo 'yes'" "yes\n" "" "$pass" +userdel -r $user $arg +rm -rf $PWD/dir gecos="aaa,bbb,ccc,ddd,eee" -testing "adduser user_name with gecos" "useradd -g '$gecos' toyTestUser $arg || - grep '^toyTestUser:.*$gecos' /etc/passwd $arg && test -d /home/toyTestUser && - userdel toyTestUser $arg && rm -rf /home/toyTestUser && echo 'yes'" \ - "yes\n" "" "$pass" +testing "adduser user_name with gecos" "useradd -g '$gecos' $user $arg || + grep '^$user:.*$gecos' /etc/passwd $arg && [ -d /home/$user ] && + echo 'yes'" "yes\n" "" "$pass" +userdel -r $user $arg shl="/bin/sh" -testing "adduser user_name with shell" "useradd -s $shl toyTestUser $arg || - grep '^toyTestUser:.*$shl$' /etc/passwd $arg && test -d /home/toyTestUser && - userdel toyTestUser $arg && rm -rf /home/toyTestUser && echo 'yes'" \ - "yes\n" "" "$pass" +testing "adduser user_name with shell" "useradd -s $shl $user $arg || + grep '^$user:.*$shl$' /etc/passwd $arg && [ -d /home/$user ] && + echo 'yes'" "yes\n" "" "$pass" +userdel -r $user $arg g_name="root" -g_id=`grep $g_name /etc/group | cut -d : -f 3` -testing "adduser user_name with group" "useradd -G $g_name toyTestUser $arg || - grep '^toyTestUser:.*:.*:$g_id:.*' /etc/passwd $arg && - test -d /home/toyTestUser && userdel toyTestUser $arg && - rm -rf /home/toyTestUser && echo 'yes'" "yes\n" "" "$pass" - -testing "adduser user_name (system user)" "useradd -S toyTestUser $arg || - grep '^toyTestUser:.*:.*:.*' /etc/passwd $arg && - test ! -e /home/toyTestUser && userdel toyTestUser $arg && echo 'yes'" \ - "yes\n" "" "$pass" - -testing "adduser user_name with -D" "useradd -D toyTestUser $arg || - grep '^toyTestUser:.*:.*:.*' /etc/passwd $arg && test -d /home/toyTestUser && - userdel toyTestUser $arg && rm -rf /home/toyTestUser && echo 'yes'" \ - "yes\n" "" "$pass" - -testing "adduser user_name with -H" "useradd -H toyTestUser $arg || - grep '^toyTestUser:.*:.*:.*' /etc/passwd $arg && - test ! -e /home/toyTestUser && userdel toyTestUser $arg && echo 'yes'" \ - "yes\n" "" "$pass" - -testing "adduser user_name with dir and -H" \ - "useradd -H -h $PWD/dir toyTestUser $arg || - grep '^toyTestUser:.*dir' /etc/passwd $arg && test ! -e $PWD/dir && - userdel toyTestUser $arg && echo 'yes'" "yes\n" "" "$pass" - -testing "adduser user_name with user_id" "useradd -u 49999 toyTestUser $arg || - grep '^toyTestUser:x:49999:.*' /etc/passwd $arg && - test -d /home/toyTestUser && userdel toyTestUser $arg && - rm -rf /home/toyTestUser && echo 'yes'" "yes\n" "" "$pass" +g_id=`grep $g_name':.*:.*' /etc/group | cut -d : -f 3` +testing "adduser user_name with group" "useradd -G $g_name $user $arg || + grep '^$user:.*:.*:$g_id:.*' /etc/passwd $arg && [ -d /home/$user ] && + echo 'yes'" "yes\n" "" "$pass" +userdel -r $user $arg + +testing "adduser user_name (system user)" "useradd -S $user $arg || + grep '^$user:.*:.*:.*' /etc/passwd $arg && [ ! -e /home/$user ] && + echo 'yes'" "yes\n" "" "$pass" +userdel -r $user $arg + +testing "adduser user_name with -D" "useradd -D $user $arg || + grep '^$user:.*:.*:.*' /etc/passwd $arg && [ -d /home/$user ] && + echo 'yes'" "yes\n" "" "$pass" +userdel -r $user $arg + +testing "adduser user_name with -H" "useradd -H $user $arg || + grep '^$user:.*:.*:.*' /etc/passwd $arg && [ ! -e /home/$user ] && + echo 'yes'" "yes\n" "" "$pass" +userdel -r $user $arg + +testing "adduser user_name with dir and -H" "useradd -H -h $PWD/dir $user $arg || + grep '^$user:.*dir' /etc/passwd $arg && [ ! -e $PWD/dir ] && + echo 'yes'" "yes\n" "" "$pass" +userdel -r $user $arg + +testing "adduser user_name with user_id" "useradd -u 49999 $user $arg || + grep '^$user:x:49999:.*' /etc/passwd $arg && [ -d /home/$user ] && + echo 'yes'" "yes\n" "" "$pass" +userdel -r $user $arg diff --git a/tests/uudecode.test b/tests/uudecode.test index b9d7d01..b9d7d01 100644..100755 --- a/tests/uudecode.test +++ b/tests/uudecode.test diff --git a/tests/uuencode.test b/tests/uuencode.test index 7c19faa..7c19faa 100644..100755 --- a/tests/uuencode.test +++ b/tests/uuencode.test diff --git a/tests/xxd.test b/tests/xxd.test new file mode 100644 index 0000000..e036865 --- /dev/null +++ b/tests/xxd.test @@ -0,0 +1,28 @@ +#!/bin/bash + +[ -f testing.sh ] && . testing.sh + +#testing "name" "command" "result" "infile" "stdin" + +echo "this is some text" > file1 +echo -n > file2 + +# Note that the xxd in vim-common on Ubuntu 14 uses %07x for the file offset. + +testing "xxd file1" "xxd file1" \ + "00000000: 7468 6973 2069 7320 736f 6d65 2074 6578 this is some tex\n00000010: 740a t.\n" \ + "" "" +testing "xxd file1 -l" "xxd -l 2 file1" \ + "00000000: 7468 th\n" \ + "" "" +testing "xxd file2" "xxd file2" "" "" "" +testing "xxd -" "xxd -" \ + "00000000: 6865 6c6c 6f hello\n" "" "hello" +testing "xxd" "xxd" \ + "00000000: 776f 726c 64 world\n" "" "world" +testing "xxd -c 8 -g 4 file1" "xxd -c 8 -g 4 file1" \ + "00000000: 74686973 20697320 this is \n00000008: 736f6d65 20746578 some tex\n00000010: 740a t.\n" "" "" +testing "xxd -c 8 -g 3 file1" "xxd -c 8 -g 3 file1" \ + "00000000: 746869 732069 7320 this is \n00000008: 736f6d 652074 6578 some tex\n00000010: 740a t.\n" "" "" + +rm file1 file2 diff --git a/tests/xzcat.test b/tests/xzcat.test index e82ca88..e82ca88 100644..100755 --- a/tests/xzcat.test +++ b/tests/xzcat.test diff --git a/tests/zcat.test b/tests/zcat.test index c1297c9..c1297c9 100644..100755 --- a/tests/zcat.test +++ b/tests/zcat.test @@ -17,7 +17,6 @@ #include <grp.h> #include <inttypes.h> #include <limits.h> -#include <libgen.h> #include <math.h> #include <pwd.h> #include <regex.h> @@ -37,7 +36,6 @@ #include <sys/statvfs.h> #include <sys/time.h> #include <sys/times.h> -#include <sys/types.h> #include <sys/utsname.h> #include <sys/wait.h> #include <syslog.h> @@ -69,14 +67,14 @@ #include <sys/sysinfo.h> #include "lib/lib.h" +#include "lib/lsm.h" #include "toys/e2fs.h" // Get list of function prototypes for all enabled command_main() functions. #define NEWTOY(name, opts, flags) void name##_main(void); -#define OLDTOY(name, oldname, opts, flags) void oldname##_main(void); +#define OLDTOY(name, oldname, flags) void oldname##_main(void); #include "generated/newtoys.h" -#include "generated/oldtoys.h" #include "generated/flags.h" #include "generated/globals.h" diff --git a/toys/android/README b/toys/android/README new file mode 100644 index 0000000..d471a20 --- /dev/null +++ b/toys/android/README @@ -0,0 +1,5 @@ +Android + +Commands primarily used by Android, not vanilla Linux. (Also SELinux stuff.) + +Bug Elliott Hughes <enh@google.com> about this. diff --git a/toys/android/getenforce.c b/toys/android/getenforce.c new file mode 100644 index 0000000..b828ce5 --- /dev/null +++ b/toys/android/getenforce.c @@ -0,0 +1,29 @@ +/* getenforce.c - Get the current SELinux mode + * + * Copyright 2014 The Android Open Source Project + +USE_GETENFORCE(NEWTOY(getenforce, ">0", TOYFLAG_USR|TOYFLAG_SBIN)) + +config GETENFORCE + bool "getenforce" + default y + depends on TOYBOX_SELINUX + help + usage: getenforce + + Shows whether SELinux is disabled, enforcing, or permissive. +*/ + +#define FOR_getenforce +#include "toys.h" + +void getenforce_main(void) +{ + if (!is_selinux_enabled()) puts("Disabled"); + else { + int ret = security_getenforce(); + + if (ret == -1) perror_exit("Couldn't get enforcing status"); + else puts(ret ? "Enforcing" : "Permissive"); + } +} diff --git a/toys/android/getprop.c b/toys/android/getprop.c new file mode 100644 index 0000000..09bb0f0 --- /dev/null +++ b/toys/android/getprop.c @@ -0,0 +1,48 @@ +/* getprop.c - Get an Android system property + * + * Copyright 2015 The Android Open Source Project + +USE_GETPROP(NEWTOY(getprop, ">2", TOYFLAG_USR|TOYFLAG_SBIN)) + +config GETPROP + bool "getprop" + default y + depends on TOYBOX_ON_ANDROID + help + usage: getprop [NAME [DEFAULT]] + + Gets an Android system property, or lists them all. +*/ + +#define FOR_getprop +#include "toys.h" + +#include <cutils/properties.h> + +GLOBALS( + size_t size; + char **nv; // name/value pairs: even=name, odd=value +) + +static void add_property(char *name, char *value, void *unused) +{ + if (!(TT.size&31)) TT.nv = xrealloc(TT.nv, (TT.size+32)*2*sizeof(char *)); + + TT.nv[2*TT.size] = xstrdup(name); + TT.nv[1+2*TT.size++] = xstrdup(value); +} + +void getprop_main(void) +{ + if (*toys.optargs) { + property_get(*toys.optargs, toybuf, toys.optargs[1] ? toys.optargs[1] : ""); + puts(toybuf); + } else { + size_t i; + + if (property_list((void *)add_property, 0)) error_exit("property_list"); + qsort(TT.nv, TT.size, 2*sizeof(char *), qstrcmp); + for (i = 0; i<TT.size; i++) printf("[%s]: [%s]\n", TT.nv[i*2],TT.nv[1+i*2]); + if (CFG_TOYBOX_FREE) free(TT.nv); + } +} diff --git a/toys/android/load_policy.c b/toys/android/load_policy.c new file mode 100644 index 0000000..8496736 --- /dev/null +++ b/toys/android/load_policy.c @@ -0,0 +1,32 @@ +/* load_policy.c - Load a policy file + * + * Copyright 2015 The Android Open Source Project + +USE_LOAD_POLICY(NEWTOY(load_policy, "<1>1", TOYFLAG_USR|TOYFLAG_SBIN)) + +config LOAD_POLICY + bool "load_policy" + depends on TOYBOX_SELINUX + default y + help + usage: load_policy FILE + + Load the specified policy file. +*/ + +#define FOR_load_policy +#include "toys.h" + +void load_policy_main(void) +{ + char *path = *toys.optargs; + int fd = xopen(path, O_RDONLY); + off_t policy_len = fdlength(fd); + char *policy_data = mmap(0, policy_len, PROT_READ, MAP_PRIVATE, fd, 0); + + close(fd); + if (!policy_data || security_load_policy(policy_data, policy_len) < 0) + perror_exit("Couldn't %s %s", policy_data ? "load" : "read", path); + + munmap(policy_data, policy_len); +} diff --git a/toys/android/restorecon.c b/toys/android/restorecon.c new file mode 100644 index 0000000..5ea6b3f --- /dev/null +++ b/toys/android/restorecon.c @@ -0,0 +1,47 @@ +/* restorecon.c - Restore default security contexts for files + * + * Copyright 2015 The Android Open Source Project + +USE_RESTORECON(NEWTOY(restorecon, "<1DFnRrv", TOYFLAG_USR|TOYFLAG_SBIN)) + +config RESTORECON + bool "restorecon" + depends on TOYBOX_SELINUX + default y + help + usage: restorecon [-D] [-F] [-R] [-n] [-v] FILE... + + Restores the default security contexts for the given files. + + -D apply to /data/data too + -F force reset + -R recurse into directories + -n don't make any changes; useful with -v to see what would change + -v verbose: show any changes +*/ + +#define FOR_restorecon +#include "toys.h" + +#if defined(__ANDROID__) +#include <selinux/android.h> +#endif + +void restorecon_main(void) +{ +#if defined(__ANDROID__) + char **s; + int flags = 0; + + if (toys.optflags & FLAG_D) flags |= SELINUX_ANDROID_RESTORECON_DATADATA; + if (toys.optflags & FLAG_F) flags |= SELINUX_ANDROID_RESTORECON_FORCE; + if (toys.optflags & (FLAG_R|FLAG_r)) + flags |= SELINUX_ANDROID_RESTORECON_RECURSE; + if (toys.optflags & FLAG_n) flags |= SELINUX_ANDROID_RESTORECON_NOCHANGE; + if (toys.optflags & FLAG_v) flags |= SELINUX_ANDROID_RESTORECON_VERBOSE; + + for (s = toys.optargs; *s; s++) + if (selinux_android_restorecon(*s, flags) < 0) + perror_msg("restorecon failed: %s", *s); +#endif +} diff --git a/toys/android/runcon.c b/toys/android/runcon.c new file mode 100644 index 0000000..c2f71e2 --- /dev/null +++ b/toys/android/runcon.c @@ -0,0 +1,27 @@ +/* runcon.c - Run command in specified security context + * + * Copyright 2015 The Android Open Source Project + +USE_RUNCON(NEWTOY(runcon, "<2", TOYFLAG_USR|TOYFLAG_SBIN)) + +config RUNCON + bool "runcon" + depends on TOYBOX_SELINUX + default y + help + usage: runcon CONTEXT COMMAND [ARGS...] + + Run a command in a specified security context. +*/ + +#define FOR_runcon +#include "toys.h" + +void runcon_main(void) +{ + char *context = *toys.optargs; + + if (setexeccon(context)) perror_exit("Could not set context to %s", context); + + xexec(++toys.optargs); +} diff --git a/toys/android/setenforce.c b/toys/android/setenforce.c new file mode 100644 index 0000000..8194484 --- /dev/null +++ b/toys/android/setenforce.c @@ -0,0 +1,32 @@ +/* setenforce.c - Set the current SELinux mode + * + * Copyright 2014 The Android Open Source Project + +USE_SETENFORCE(NEWTOY(setenforce, "<1>1", TOYFLAG_USR|TOYFLAG_SBIN)) + +config SETENFORCE + bool "setenforce" + default y + depends on TOYBOX_SELINUX + help + usage: setenforce [enforcing|permissive|1|0] + + Sets whether SELinux is enforcing (1) or permissive (0). +*/ + +#define FOR_setenforce +#include "toys.h" + +void setenforce_main(void) +{ + char *new = *toys.optargs; + int state, ret; + + if (!is_selinux_enabled()) error_exit("SELinux is disabled"); + else if (!strcmp(new, "1") || !strcasecmp(new, "enforcing")) state = 1; + else if (!strcmp(new, "0") || !strcasecmp(new, "permissive")) state = 0; + else error_exit("Invalid state: %s", new); + + ret = security_setenforce(state); + if (ret == -1) perror_msg("Couldn't set enforcing status to '%s'", new); +} diff --git a/toys/android/setprop.c b/toys/android/setprop.c new file mode 100644 index 0000000..cbcd152 --- /dev/null +++ b/toys/android/setprop.c @@ -0,0 +1,49 @@ +/* setprop.c - Set an Android system property + * + * Copyright 2015 The Android Open Source Project + +USE_SETPROP(NEWTOY(setprop, "<2>2", TOYFLAG_USR|TOYFLAG_SBIN)) + +config SETPROP + bool "setprop" + default y + depends on TOYBOX_ON_ANDROID + help + usage: setprop NAME VALUE + + Sets an Android system property. +*/ + +#define FOR_setprop +#include "toys.h" + +#include <cutils/properties.h> + +void setprop_main(void) +{ + char *name = toys.optargs[0], *value = toys.optargs[1]; + char *p; + size_t name_len = strlen(name), value_len = strlen(value); + + // property_set doesn't tell us why it failed, and actually can't + // recognize most failures (because it doesn't wait for init), so + // we duplicate all of init's checks here to help the user. + + if (name_len >= PROP_NAME_MAX) + error_exit("name '%s' too long; try '%.*s'", + name, PROP_NAME_MAX - 1, name); + if (value_len >= PROP_VALUE_MAX) + error_exit("value '%s' too long; try '%.*s'", + value, PROP_VALUE_MAX - 1, value); + + if (*name == '.' || name[name_len - 1] == '.') + error_exit("property names must not start or end with '.'"); + if (strstr(name, "..")) + error_exit("'..' is not allowed in a property name"); + for (p = name; *p; ++p) + if (!isalnum(*p) && !strchr("_.-", *p)) + error_exit("invalid character '%c' in name '%s'", *p, name); + + if (property_set(name, value)) + error_msg("failed to set property '%s' to '%s'", name, value); +} diff --git a/toys/lsb/dmesg.c b/toys/lsb/dmesg.c index 1003256..d608446 100644 --- a/toys/lsb/dmesg.c +++ b/toys/lsb/dmesg.c @@ -4,19 +4,22 @@ * * http://refspecs.linuxfoundation.org/LSB_4.1.0/LSB-Core-generic/LSB-Core-generic/dmesg.html -USE_DMESG(NEWTOY(dmesg, "s#n#c", TOYFLAG_BIN)) +// We care that FLAG_c is 1, so keep c at the end. +USE_DMESG(NEWTOY(dmesg, "trs#<1n#c[!tr]", TOYFLAG_BIN)) config DMESG bool "dmesg" default y help - usage: dmesg [-n level] [-s bufsize] | -c + usage: dmesg [-c] [-r|-t] [-n LEVEL] [-s SIZE] Print or control the kernel ring buffer. - -n Set kernel logging level (1-9). - -s Size of buffer to read (in bytes), default 16384. - -c Clear the ring buffer after printing. + -c Clear the ring buffer after printing + -n Set kernel logging LEVEL (1-9) + -r Raw output (with <level markers>) + -s Show the last SIZE many bytes + -t Don't print kernel's timestamps */ #define FOR_dmesg @@ -31,25 +34,38 @@ GLOBALS( void dmesg_main(void) { // For -n just tell kernel to which messages to keep. - if (toys.optflags & 2) { - if (klogctl(8, NULL, TT.level)) error_exit("klogctl"); + if (toys.optflags & FLAG_n) { + if (klogctl(8, NULL, TT.level)) perror_exit("klogctl"); } else { - int size, i, last = '\n'; - char *data; + char *data, *to, *from; + int size; // Figure out how much data we need, and fetch it. size = TT.size; - if (size<2) size = 16384; - data = xmalloc(size); - size = klogctl(3 + (toys.optflags&1), data, size); - if (size < 0) error_exit("klogctl"); - - // Display data, filtering out level markers. - for (i=0; i<size; ) { - if (last=='\n' && data[i]=='<') i += 3; - else xputc(last = data[i++]); + if (!size && 1>(size = klogctl(10, 0, 0))) perror_exit("klogctl");; + data = to = from = xmalloc(size+1); + size = klogctl(3 + (toys.optflags & FLAG_c), data, size); + if (size < 0) perror_exit("klogctl"); + data[size] = 0; + + // Filter out level markers and optionally time markers + if (!(toys.optflags & FLAG_r)) while ((from - data) < size) { + if (from == data || from[-1] == '\n') { + char *to; + + if (*from == '<' && (to = strchr(from, '>'))) from = ++to; + if ((toys.optflags&FLAG_t) && *from == '[' && (to = strchr(from, ']'))) + from = to+1+(to[1]==' '); + } + *(to++) = *(from++); + } else to = data+size; + + // Write result. The odds of somebody requesting a buffer of size 3 and + // getting "<1>" are remote, but don't segfault if they do. + if (to != data) { + xwrite(1, data, to-data); + if (to[-1] != '\n') xputc('\n'); } - if (last!='\n') xputc('\n'); if (CFG_TOYBOX_FREE) free(data); } } diff --git a/toys/lsb/mknod.c b/toys/lsb/mknod.c index bf9288a..39073fa 100644 --- a/toys/lsb/mknod.c +++ b/toys/lsb/mknod.c @@ -4,28 +4,42 @@ * * http://refspecs.linuxfoundation.org/LSB_4.1.0/LSB-Core-generic/LSB-Core-generic/mknod.html -USE_MKNOD(NEWTOY(mknod, "<2>4", TOYFLAG_BIN)) +USE_MKNOD(NEWTOY(mknod, "<2>4m(mode):"USE_MKNOD_Z("Z:"), TOYFLAG_BIN|TOYFLAG_UMASK)) config MKNOD bool "mknod" default y help - usage: mknod NAME TYPE [MAJOR MINOR] + usage: mknod [-m MODE] NAME TYPE [MAJOR MINOR] - Create a special file NAME with a given type, possible types are - b block device - c or u character device - p named pipe (ignores MAJOR/MINOR) + Create a special file NAME with a given type. TYPE is b for block device, + c or u for character device, p for named pipe (which ignores MAJOR/MINOR). + + -m Mode (file permissions) of new device, in octal or u+x format + +config MKNOD_Z + bool + default y + depends on MKNOD && !TOYBOX_LSM_NONE + help + usage: mknod [-Z CONTEXT] ... + + -Z Set security context to created file */ #define FOR_mknod #include "toys.h" +GLOBALS( + char *arg_context; + char *m; +) + void mknod_main(void) { mode_t modes[] = {S_IFIFO, S_IFCHR, S_IFCHR, S_IFBLK}; int major=0, minor=0, type; - int mode = 0660; + int mode = TT.m ? string_to_mode(TT.m, 0777) : 0660; type = stridx("pcub", *toys.optargs[1]); if (type == -1) perror_exit("bad type '%c'", *toys.optargs[1]); @@ -36,6 +50,9 @@ void mknod_main(void) minor = atoi(toys.optargs[3]); } - if (mknod(toys.optargs[0], mode | modes[type], makedev(major, minor))) - perror_exit("mknod %s failed", toys.optargs[0]); + if (toys.optflags & FLAG_Z) + if (-1 == lsm_set_create(TT.arg_context)) + perror_exit("-Z '%s' failed", TT.arg_context); + if (mknod(*toys.optargs, mode|modes[type], makedev(major, minor))) + perror_exit("%s", *toys.optargs); } diff --git a/toys/lsb/mktemp.c b/toys/lsb/mktemp.c index c1175fe..cee62c1 100644 --- a/toys/lsb/mktemp.c +++ b/toys/lsb/mktemp.c @@ -4,7 +4,7 @@ * * http://refspecs.linuxfoundation.org/LSB_4.1.0/LSB-Core-generic/LSB-Core-generic/mktemp.html -USE_MKTEMP(NEWTOY(mktemp, ">1q(directory)d(tmpdir)p:", TOYFLAG_BIN)) +USE_MKTEMP(NEWTOY(mktemp, ">1qd(directory)p(tmpdir):", TOYFLAG_BIN)) config MKTEMP bool "mktemp" @@ -12,41 +12,42 @@ config MKTEMP help usage: mktemp [-dq] [-p DIR] [TEMPLATE] - Safely create new file and print its name. Default TEMPLATE is - /tmp/tmp.XXXXXX and each trailing X is replaced with random char. + Safely create a new file "DIR/TEMPLATE" and print its name. - -d, --directory Create directory instead of file - -p DIR, --tmpdir=DIR Put new file in DIR - -q Quiet + -d Create directory instead of file (--directory) + -p Put new file in DIR (--tmpdir) + -q Quiet, no error messages + + Each X in TEMPLATE is replaced with a random printable character. The + default TEMPLATE is tmp.XXXXXX, and the default DIR is $TMPDIR if set, + else "/tmp". */ #define FOR_mktemp #include "toys.h" GLOBALS( - char * tmpdir; + char *tmpdir; ) void mktemp_main(void) { - int d_flag = toys.optflags & FLAG_d; - char *tmp; + int d_flag = toys.optflags & FLAG_d; + char *template = *toys.optargs; - tmp = *toys.optargs; + if (!template) template = "tmp.XXXXXX"; - if (!tmp) { - if (!TT.tmpdir) TT.tmpdir = "/tmp"; - tmp = "tmp.xxxxxx"; - } - if (TT.tmpdir) tmp = xmprintf("%s/%s", TT.tmpdir ? TT.tmpdir : "/tmp", - *toys.optargs ? *toys.optargs : "tmp.XXXXXX"); + if (!TT.tmpdir) TT.tmpdir = getenv("TMPDIR"); + if (!TT.tmpdir || !*TT.tmpdir) TT.tmpdir = "/tmp"; - if (d_flag ? mkdtemp(tmp) == NULL : mkstemp(tmp) == -1) - if (toys.optflags & FLAG_q) - perror_exit("Failed to create temporary %s", - d_flag ? "directory" : "file"); + template = strchr(template, '/') ? xstrdup(template) + : xmprintf("%s/%s", TT.tmpdir, template); - xputs(tmp); + if (d_flag ? !mkdtemp(template) : mkstemp(template) == -1) { + if (toys.optflags & FLAG_q) toys.exitval = 1; + else perror_exit("Failed to create %s %s/%s", + d_flag ? "directory" : "file", TT.tmpdir, template); + } else xputs(template); - if (CFG_TOYBOX_FREE && TT.tmpdir) free(tmp); + if (CFG_TOYBOX_FREE) free(template); } diff --git a/toys/lsb/mount.c b/toys/lsb/mount.c index 01f5c32..789d9a5 100644 --- a/toys/lsb/mount.c +++ b/toys/lsb/mount.c @@ -6,8 +6,8 @@ * Note: -hV is bad spec, haven't implemented -FsLU yet * no mtab (/proc/mounts does it) so -n is NOP. -USE_MOUNT(NEWTOY(mount, "?O:afnrvwt:o*[-rw]", TOYFLAG_USR|TOYFLAG_BIN|TOYFLAG_STAYROOT)) -USE_NFSMOUNT(NEWTOY(nfsmount, "?<2>2", TOYFLAG_USR|TOYFLAG_BIN|TOYFLAG_STAYROOT)) +USE_MOUNT(NEWTOY(mount, "?O:afnrvwt:o*[-rw]", TOYFLAG_BIN|TOYFLAG_STAYROOT)) +//USE_NFSMOUNT(NEWTOY(nfsmount, "?<2>2", TOYFLAG_USR|TOYFLAG_BIN|TOYFLAG_STAYROOT)) config MOUNT bool "mount" @@ -34,13 +34,13 @@ config MOUNT to say --bind or --loop. You can also "mount -a /path" to mount everything in /etc/fstab under /path, even if it's noauto. -config NFSMOUNT - bool "nfsmount" - default n - help - usage: nfsmount SHARE DIR - - Invoke an eldrich horror from the dawn of time. +#config NFSMOUNT +# bool "nfsmount" +# default n +# help +# usage: nfsmount SHARE DIR +# +# Invoke an eldrich horror from the dawn of time. */ #define FOR_mount @@ -56,6 +56,7 @@ GLOBALS( int okuser; ) +// mount.tests should check for all of this: // TODO detect existing identical mount (procfs with different dev name?) // TODO user, users, owner, group, nofail // TODO -p (passfd) @@ -144,7 +145,8 @@ static void mount_filesystem(char *dev, char *dir, char *type, if (getuid()) { if (TT.okuser) TT.okuser = 0; else { - error_msg("'%s' not user mountable in fstab"); + error_msg("'%s' not user mountable in fstab", dev); + return; } } @@ -169,6 +171,8 @@ static void mount_filesystem(char *dev, char *dir, char *type, toys.exitval |= xrun((char *[]){"swapon", "--", dev, 0}); for (;;) { + int fd = -1, ro = 0; + // If type wasn't specified, try all of them in order. if (fp && !buf) { size_t i; @@ -192,6 +196,14 @@ static void mount_filesystem(char *dev, char *dir, char *type, for (;;) { rc = mount(dev, dir, type, flags, opts); if ((rc != EACCES && rc != EROFS) || (flags & MS_RDONLY)) break; + if (rc == EROFS && fd == -1) { + if (-1 != (fd = open(dev, O_RDONLY))) { + ioctl(fd, BLKROSET, &ro); + close(fd); + + continue; + } + } fprintf(stderr, "'%s' is read-only", dev); flags |= MS_RDONLY; } @@ -246,8 +258,9 @@ void mount_main(void) char *opts = 0, *dev = 0, *dir = 0, **ss; long flags = MS_SILENT; struct arg_list *o; - struct mtab_list *mtl, *mm, *remount = 0; + struct mtab_list *mtl, *mtl2 = 0, *mm, *remount; +// TODO // remount // - overmounts // shared subtree @@ -275,9 +288,13 @@ void mount_main(void) if ((toys.optflags & FLAG_a) && dir) error_exit("-a with >1 arg"); // For remount we need _last_ match (in case of overmounts), so traverse - // in reverse order. - if (comma_scan(opts, "remount", 1)) - remount = dlist_terminate(mtl = xgetmountlist("/proc/mounts")); + // in reverse order. (Yes I'm using remount as a boolean for a bit here, + // the double cast is to get gcc to shut up about it.) + remount = (void *)(long)comma_scan(opts, "remount", 1); + if (((toys.optflags & FLAG_a) && !access("/proc/mounts", R_OK)) || remount) { + mm = dlist_terminate(mtl = mtl2 = xgetmountlist(0)); + if (remount) remount = mm; + } // Do we need to do an /etc/fstab trawl? // This covers -a, -o remount, one argument, all user mounts @@ -286,12 +303,14 @@ void mount_main(void) for (mm = remount ? remount : mtl; mm; mm = (remount ? mm->prev : mm->next)) { - int aflags, noauto, len; char *aopts = 0; + struct mtab_list *mmm = 0; + int aflags, noauto, len; // Check for noauto and get it out of the option list. (Unknown options // that make it to the kernel give filesystem drivers indigestion.) noauto = comma_scan(mm->opts, "noauto", 1); + if (toys.optflags & FLAG_a) { // "mount -a /path" to mount all entries under /path if (dev) { @@ -307,17 +326,29 @@ void mount_main(void) continue; } + // Don't overmount the same dev on the same directory + // (Unless root explicitly says to in non -a mode.) + if (mtl2 && !remount) + for (mmm = mtl2; mmm; mmm = mmm->next) + if (!strcmp(mm->dir, mmm->dir) && !strcmp(mm->device, mmm->device)) + break; + // user only counts from fstab, not opts. - TT.okuser = comma_scan(mm->opts, "user", 1); - aflags = flag_opts(mm->opts, flags, &aopts); - aflags = flag_opts(opts, aflags, &aopts); + if (!mmm) { + TT.okuser = comma_scan(mm->opts, "user", 1); + aflags = flag_opts(mm->opts, flags, &aopts); + aflags = flag_opts(opts, aflags, &aopts); - mount_filesystem(mm->device, mm->dir, mm->type, aflags, aopts); + mount_filesystem(mm->device, mm->dir, mm->type, aflags, aopts); + } // TODO else if (getuid()) error_msg("already there") ? free(aopts); if (!(toys.optflags & FLAG_a)) break; } - if (CFG_TOYBOX_FREE) llist_traverse(mtl, free); + if (CFG_TOYBOX_FREE) { + llist_traverse(mtl, free); + llist_traverse(mtl2, free); + } if (!mm && !(toys.optflags & FLAG_a)) error_exit("'%s' not in %s", dir ? dir : dev, remount ? "/proc/mounts" : "fstab"); diff --git a/toys/lsb/pidof.c b/toys/lsb/pidof.c index 51b742f..a8fc8ef 100644 --- a/toys/lsb/pidof.c +++ b/toys/lsb/pidof.c @@ -5,7 +5,7 @@ * * http://refspecs.linuxfoundation.org/LSB_4.1.0/LSB-Core-generic/LSB-Core-generic/pidof.html -USE_PIDOF(NEWTOY(pidof, "<1so:", TOYFLAG_USR|TOYFLAG_BIN)) +USE_PIDOF(NEWTOY(pidof, "<1so:", TOYFLAG_BIN)) config PIDOF bool "pidof" diff --git a/toys/lsb/su.c b/toys/lsb/su.c index 616541b..612daef 100644 --- a/toys/lsb/su.c +++ b/toys/lsb/su.c @@ -54,11 +54,13 @@ void su_main() else name = "root"; if (!(shp = getspnam(name))) perror_exit("no '%s'", name); - if (*shp->sp_pwdp != '$') goto deny; - if (read_password(toybuf, sizeof(toybuf), "Password: ")) goto deny; - passhash = crypt(toybuf, shp->sp_pwdp); - memset(toybuf, 0, sizeof(toybuf)); - if (!passhash || strcmp(passhash, shp->sp_pwdp)) goto deny; + if (getuid()) { + if (*shp->sp_pwdp != '$') goto deny; + if (read_password(toybuf, sizeof(toybuf), "Password: ")) goto deny; + passhash = crypt(toybuf, shp->sp_pwdp); + memset(toybuf, 0, sizeof(toybuf)); + if (!passhash || strcmp(passhash, shp->sp_pwdp)) goto deny; + } up = xgetpwnam(name); xsetuser(up); diff --git a/toys/other/acpi.c b/toys/other/acpi.c index c293e84..44fd03b 100644 --- a/toys/other/acpi.c +++ b/toys/other/acpi.c @@ -38,7 +38,7 @@ int read_int_at(int dirfd, char *name) FILE *fil; if ((fd = openat(dirfd, name, O_RDONLY)) < 0) return -1; - fscanf(fil = xfdopen(fd, "r"), "%d", &ret); + if (!fscanf(fil = xfdopen(fd, "r"), "%d", &ret)) perror_exit("%s", name); fclose(fil); return ret; @@ -111,10 +111,11 @@ int temp_callback(struct dirtree *tree) int cool_callback(struct dirtree *tree) { int dfd=5, cur, max; + errno = 0; memset(toybuf, 0, sizeof(toybuf)); - if (tree->name[0]=='.') return 0; + if (*tree->name == '.') return 0; if (!tree->parent) return DIRTREE_RECURSE|DIRTREE_SYMFOLLOW; @@ -136,14 +137,11 @@ int cool_callback(struct dirtree *tree) void acpi_main(void) { - if (toys.optflags & FLAG_V) - toys.optflags = FLAG_a|FLAG_b|FLAG_c|FLAG_t; + if (toys.optflags & FLAG_V) toys.optflags = FLAG_a|FLAG_b|FLAG_c|FLAG_t; if (!toys.optflags) toys.optflags = FLAG_b; if (toys.optflags & (FLAG_a|FLAG_b)) dirtree_read("/sys/class/power_supply", acpi_callback); - if (toys.optflags & FLAG_t) - dirtree_read("/sys/class", temp_callback); - if (toys.optflags & FLAG_c) - dirtree_read("/sys/class/thermal", cool_callback); + if (toys.optflags & FLAG_t) dirtree_read("/sys/class", temp_callback); + if (toys.optflags & FLAG_c) dirtree_read("/sys/class/thermal", cool_callback); } diff --git a/toys/other/base64.c b/toys/other/base64.c new file mode 100644 index 0000000..7dc6114 --- /dev/null +++ b/toys/other/base64.c @@ -0,0 +1,87 @@ +/* base64.c - Encode and decode base64 + * + * Copyright 2014 Rob Landley <rob@landley.net> + * + * No standard + +USE_BASE64(NEWTOY(base64, "diw#<1[!dw]", TOYFLAG_USR|TOYFLAG_BIN)) + +config BASE64 + bool "base64" + default y + help + usage: base64 [-di] [-w COLUMNS] [FILE...] + + Encode or decode in base64. + + -d decode + -i ignore non-alphabetic characters + -w wrap output at COLUMNS (default 76) +*/ + +#define FOR_base64 +#include "toys.h" + +GLOBALS( + long columns; +) + +static void do_base64(int fd, char *name) +{ + int out = 0, bits = 0, x = 0, i, len; + char *buf = toybuf+128; + + for (;;) { + if (!(len = xread(fd, buf, sizeof(toybuf)-128))) { + if (!(toys.optflags & FLAG_d)) { + if (bits) { + putchar(toybuf[out<<(6-bits)]); + x++; + } + while (x++&3) putchar('='); + if (x != 1) xputc('\n'); + } + + return; + } + for (i=0; i<len; i++) { + if (toys.optflags & FLAG_d) { + if (buf[i] == '=') return; + + if ((x = stridx(toybuf, buf[i])) != -1) { + out = (out<<6) + x; + bits += 6; + if (bits >= 8) { + putchar(out >> (bits -= 8)); + out &= (1<<bits)-1; + if (ferror(stdout)) perror_exit(0); + } + + continue; + } + if (buf[i] == '\n' || (toys.optflags & FLAG_i)) continue; + + break; + } else { + out = (out<<8) + buf[i]; + bits += 8; + while (bits >= 6) { + putchar(toybuf[out >> (bits -= 6)]); + out &= (1<<bits)-1; + if (TT.columns == ++x) { + xputc('\n'); + x = 0; + } + } + } + } + } +} + +void base64_main(void) +{ + if (!TT.columns) TT.columns = 76; + + base64_init(toybuf); + loopfiles(toys.optargs, do_base64); +} diff --git a/toys/other/blkid.c b/toys/other/blkid.c index 5c69a1e..725f163 100644 --- a/toys/other/blkid.c +++ b/toys/other/blkid.c @@ -5,15 +5,23 @@ * See ftp://ftp.kernel.org/pub/linux/utils/util-linux/v2.24/libblkid-docs/api-index-full.html USE_BLKID(NEWTOY(blkid, "<1", TOYFLAG_BIN)) -USE_BLKID(OLDTOY(fstype, blkid, "<1", TOYFLAG_BIN)) +USE_FSTYPE(NEWTOY(fstype, "<1", TOYFLAG_BIN)) config BLKID bool "blkid" default y help - usage: blkid [block device...] + usage: blkid DEV... - Prints type, label and UUID of filesystem. + Prints type, label and UUID of filesystem on a block device or image. + +config FSTYPE + bool "fstype" + default y + help + usage: fstype DEV... + + Prints type of filesystem on a block device or image. */ #define FOR_blkid @@ -26,9 +34,8 @@ struct fstype { }; static const struct fstype fstypes[] = { - // ext3 = buf[1116]&4 ext4 = buf[1120]&64 - {"ext2", 0xEF53, 2, 1080, 1128, 16, 1144}, - // label actually 8/16 0x4d80 but horrible: 16 bit wide characters via + {"ext2", 0xEF53, 2, 1080, 1128, 16, 1144}, // keep this first for ext3/4 check + // NTFS label actually 8/16 0x4d80 but horrible: 16 bit wide characters via // codepage, something called a uuid that's only 8 bytes long... {"ntfs", 0x5346544e, 4, 3, 0x48+(8<<24), 0, 0}, @@ -124,10 +131,15 @@ void do_blkid(int fd, char *name) printf("\""); } - printf(" TYPE=\"%s\"\n", fstypes[i].name); + printf(" TYPE=\"%s\"\n", type); } void blkid_main(void) { loopfiles(toys.optargs, do_blkid); } + +void fstype_main(void) +{ + blkid_main(); +} diff --git a/toys/other/catv.c b/toys/other/catv.c deleted file mode 100644 index 62520c4..0000000 --- a/toys/other/catv.c +++ /dev/null @@ -1,67 +0,0 @@ -/* cat -v implementation for toybox - * - * Copyright (C) 2006, 2007 Rob Landley <rob@landley.net> - * - * See "Cat -v considered harmful" at - * http://cm.bell-labs.com/cm/cs/doc/84/kp.ps.gz - -USE_CATV(NEWTOY(catv, "vte", TOYFLAG_USR|TOYFLAG_BIN)) - -config CATV - bool "catv" - default y - help - usage: catv [-evt] [filename...] - - Display nonprinting characters as escape sequences. Use M-x for - high ascii characters (>127), and ^x for other nonprinting chars. - - -e Mark each newline with $ - -t Show tabs as ^I - -v Don't use ^x or M-x escapes. -*/ - -#define FOR_catv -#include "toys.h" - -// Callback function for loopfiles() - -static void do_catv(int fd, char *name) -{ - for(;;) { - int i, len; - - len = read(fd, toybuf, sizeof(toybuf)); - if (len < 0) toys.exitval = EXIT_FAILURE; - if (len < 1) break; - for (i=0; i<len; i++) { - char c=toybuf[i]; - - if (c > 126 && (toys.optflags & FLAG_v)) { - if (c > 127) { - printf("M-"); - c -= 128; - } - if (c == 127) { - printf("^?"); - continue; - } - } - if (c < 32) { - if (c == 10) { - if (toys.optflags & FLAG_e) xputc('$'); - } else if (toys.optflags & (c==9 ? FLAG_t : FLAG_v)) { - printf("^%c", c+'@'); - continue; - } - } - xputc(c); - } - } -} - -void catv_main(void) -{ - toys.optflags ^= FLAG_v; - loopfiles(toys.optargs, do_catv); -} diff --git a/toys/other/chcon.c b/toys/other/chcon.c new file mode 100644 index 0000000..a2bbb66 --- /dev/null +++ b/toys/other/chcon.c @@ -0,0 +1,44 @@ +/* chcon.c - Change file security context + * + * Copyright 2014 The Android Open Source Project + +USE_CHCON(NEWTOY(chcon, "<2hvR", TOYFLAG_USR|TOYFLAG_BIN)) + +config CHCON + bool "chcon" + depends on TOYBOX_SELINUX + default y + help + usage: chcon [-hRv] CONTEXT FILE... + + Change the SELinux security context of listed file[s]. + + -h change symlinks instead of what they point to. + -R recurse into subdirectories. + -v verbose output. +*/ + +#define FOR_chcon +#include "toys.h" + +int do_chcon(struct dirtree *try) +{ + char *path, *con = *toys.optargs; + + if (!dirtree_notdotdot(try)) return 0; + + path = dirtree_path(try, 0); + if (toys.optflags & FLAG_v) printf("chcon '%s' to %s\n", path, con); + if (-1 == ((toys.optflags & FLAG_h) ? lsetfilecon : setfilecon)(path, con)) + perror_msg("'%s' to %s", path, con); + free(path); + + return (toys.optflags & FLAG_R)*DIRTREE_RECURSE; +} + +void chcon_main(void) +{ + char **file; + + for (file = toys.optargs+1; *file; file++) dirtree_read(*file, do_chcon); +} diff --git a/toys/other/chroot.c b/toys/other/chroot.c index 00fb4e7..4260d98 100644 --- a/toys/other/chroot.c +++ b/toys/other/chroot.c @@ -1,6 +1,11 @@ /* chroot.c - Run command in new root directory. * * Copyright 2007 Rob Landley <rob@landley.net> + * + * TODO: The test for root is "==" so root can trivially escape a chroot by + * moving it below cwd, ala mkdir("sub"); chroot("sub"); chdir("../../../..") + * The container guys use pivot_root() to deal with this, which does actually + * edit mount tree. (New option? Kernel patch?) USE_CHROOT(NEWTOY(chroot, "^<1", TOYFLAG_USR|TOYFLAG_SBIN)) @@ -20,6 +25,6 @@ void chroot_main(void) char *binsh[] = {"/bin/sh", "-i", 0}; if (chdir(*toys.optargs) || chroot(".")) perror_exit("%s", *toys.optargs); - if (toys.optargs[1]) xexec_optargs(1); + if (toys.optargs[1]) xexec(toys.optargs+1); else xexec(binsh); } diff --git a/toys/other/chvt.c b/toys/other/chvt.c index 6544265..a93327f 100644 --- a/toys/other/chvt.c +++ b/toys/other/chvt.c @@ -2,7 +2,7 @@ * * Copyright (C) 2008 David Anders <danders@amltd.com> -USE_CHVT(NEWTOY(chvt, "<1", TOYFLAG_USR|TOYFLAG_SBIN)) +USE_CHVT(NEWTOY(chvt, "<1", TOYFLAG_USR|TOYFLAG_BIN)) config CHVT bool "chvt" diff --git a/toys/other/clear.c b/toys/other/clear.c index 2515f73..4061ea8 100644 --- a/toys/other/clear.c +++ b/toys/other/clear.c @@ -15,5 +15,5 @@ config CLEAR void clear_main(void) { - write(1, "\e[2J\e[H", 7); + xwrite(1, "\e[2J\e[H", 7); } diff --git a/toys/other/dos2unix.c b/toys/other/dos2unix.c index 3e1feb0..021ba38 100644 --- a/toys/other/dos2unix.c +++ b/toys/other/dos2unix.c @@ -2,16 +2,25 @@ * * Copyright 2012 Rob Landley <rob@landley.net> -USE_DOS2UNIX(NEWTOY(dos2unix, NULL, TOYFLAG_BIN)) -USE_DOS2UNIX(OLDTOY(unix2dos, dos2unix, NULL, TOYFLAG_BIN)) +USE_DOS2UNIX(NEWTOY(dos2unix, 0, TOYFLAG_BIN)) +USE_UNIX2DOS(NEWTOY(unix2dos, 0, TOYFLAG_BIN)) config DOS2UNIX bool "dos2unix/unix2dos" default y help - usage: dos2unix/unix2dos [file...] + usage: dos2unix [FILE...] - Convert newline format between dos (\r\n) and unix (just \n) + Convert newline format from dos "\r\n" to unix "\n". + If no files listed copy from stdin, "-" is a synonym for stdin. + +config UNIX2DOS + bool "unix2dos" + default y + help + usage: unix2dos [FILE...] + + Convert newline format from unix "\n" to dos "\r\n". If no files listed copy from stdin, "-" is a synonym for stdin. */ @@ -60,3 +69,8 @@ void dos2unix_main(void) { loopfiles(toys.optargs, do_dos2unix); } + +void unix2dos_main(void) +{ + dos2unix_main(); +} diff --git a/toys/other/factor.c b/toys/other/factor.c index 9dc1cca..570270e 100644 --- a/toys/other/factor.c +++ b/toys/other/factor.c @@ -21,46 +21,54 @@ static void factor(char *s) { long l, ll; - l = strtol(s, &s, 0); - if (*s) { - error_msg("%s: not integer"); - return; - } + for (;;) { + char *err = s; - printf("%ld:", l); + while(isspace(*s)) s++; + if (!*s) return; - // Negative numbers have -1 as a factor - if (l < 0) { - printf(" -1"); - l *= -1; - } + l = strtol(s, &s, 0); + if (*s && !isspace(*s)) { + error_msg("%s: not integer", err); - // Deal with 0 and 1 (and 2 since we're here) - if (l < 3) { - printf(" %ld\n", l); - return; - } + return; + } - // Special case factors of 2 - while (l && !(l&1)) { - printf(" 2"); - l >>= 1; - } + printf("%ld:", l); - // test odd numbers. - for (ll=3; ;ll += 2) { - long lll = ll*ll; + // Negative numbers have -1 as a factor + if (l < 0) { + printf(" -1"); + l *= -1; + } + + // Nothing below 4 has factors + if (l < 4) { + printf(" %ld\n", l); + continue; + } - if (lll>l || lll<ll) { - if (l>1) printf(" %ld", l); - break; + // Special case factors of 2 + while (l && !(l&1)) { + printf(" 2"); + l >>= 1; } - while (!(l%ll)) { - printf(" %ld", ll); - l /= ll; + + // test odd numbers. + for (ll=3; ;ll += 2) { + long lll = ll*ll; + + if (lll>l || lll<ll) { + if (l>1) printf(" %ld", l); + break; + } + while (!(l%ll)) { + printf(" %ld", ll); + l /= ll; + } } + xputc('\n'); } - xputc('\n'); } void factor_main(void) diff --git a/toys/other/hexedit.c b/toys/other/hexedit.c new file mode 100644 index 0000000..1f6b42e --- /dev/null +++ b/toys/other/hexedit.c @@ -0,0 +1,250 @@ +/* hexedit.c - Hexadecimal file editor + * + * Copyright 2015 Rob Landley <rob@landley.net> + * + * No standard + +USE_HEXEDIT(NEWTOY(hexedit, "<1>1r", TOYFLAG_USR|TOYFLAG_BIN|TOYFLAG_LOCALE)) + +config HEXEDIT + bool "hexedit" + default y + help + usage: hexedit FILENAME + + Hexadecimal file editor. + + -r Read only (display but don't edit) +*/ + +#define FOR_hexedit +#include "toys.h" + +GLOBALS( + char *data; + long long len, base; + int numlen, undo, undolen; + unsigned height; +) + +#define UNDO_LEN (sizeof(toybuf)/(sizeof(long long)+1)) + +// Render all characters printable, using color to distinguish. +static void draw_char(char broiled) +{ + if (broiled<32 || broiled>=127) { + if (broiled>127) { + tty_esc("2m"); + broiled &= 127; + } + if (broiled<32 || broiled==127) { + tty_esc("7m"); + if (broiled==127) broiled = 32; + else broiled += 64; + } + printf("%c", broiled); + tty_esc("0m"); + } else printf("%c", broiled); +} + +static void draw_tail(void) +{ + int i = 0, width = 0, w, len; + char *start = *toys.optargs, *end; + + tty_jump(0, TT.height); + tty_esc("K"); + + // First time, make sure we fit in 71 chars (advancing start as necessary). + // Second time, print from start to end, escaping nonprintable chars. + for (i=0; i<2; i++) { + for (end = start; *end;) { + wchar_t wc; + + len = mbrtowc(&wc, end, 99, 0); + if (len<0 || wc<32 || (w = wcwidth(wc))<0) { + len = w = 1; + if (i) draw_char(*end); + } else if (i) fwrite(end, len, 1, stdout); + end += len; + + if (!i) { + width += w; + while (width > 71) { + len = mbrtowc(&wc, start, 99, 0); + if (len<0 || wc<32 || (w = wcwidth(wc))<0) len = w = 1; + width -= w; + start += len; + } + } + } + } +} + +static void draw_line(long long yy) +{ + int x, xx = 16; + + yy = (TT.base+yy)*16; + if (yy+xx>=TT.len) xx = TT.len-yy; + + if (yy<TT.len) { + printf("\r%0*llX ", TT.numlen, yy); + for (x=0; x<xx; x++) printf(" %02X", TT.data[yy+x]); + printf("%*s", 2+3*(16-xx), ""); + for (x=0; x<xx; x++) draw_char(TT.data[yy+x]); + printf("%*s", 16-xx, ""); + } + tty_esc("K"); +} + +static void draw_page(void) +{ + int y; + + tty_jump(0, 0); + for (y = 0; y<TT.height; y++) { + if (y) printf("\r\n"); + draw_line(y); + } + draw_tail(); +} + +// side: 0 = editing left, 1 = editing right, 2 = clear, 3 = read only +static void highlight(int xx, int yy, int side) +{ + char cc = TT.data[16*(TT.base+yy)+xx]; + int i; + + // Display cursor + tty_jump(2+TT.numlen+3*xx, yy); + tty_esc("0m"); + if (side!=2) tty_esc("7m"); + if (side>1) printf("%02X", cc); + else for (i=0; i<2;) { + if (side==i) tty_esc("32m"); + printf("%X", (cc>>(4*(1&++i)))&15); + } + tty_esc("0m"); + tty_jump(TT.numlen+17*3+xx, yy); + draw_char(cc); +} + +void hexedit_main(void) +{ + long long pos = 0, y; + int x, i, side = 0, key, ro = toys.optflags&FLAG_r, + fd = xopen(*toys.optargs, ro ? O_RDONLY : O_RDWR); + char keybuf[16]; + + // Terminal setup + TT.height = 25; + terminal_size(0, &TT.height); + if (TT.height) TT.height--; + sigatexit(tty_sigreset); + tty_esc("0m"); + tty_esc("?25l"); + fflush(0); + set_terminal(1, 1, 0); + + if ((TT.len = fdlength(fd))<0) error_exit("bad length"); + if (sizeof(long)==32 && TT.len>SIZE_MAX) TT.len = SIZE_MAX; + // count file length hex in digits, rounded up to multiple of 4 + for (pos = TT.len, TT.numlen = 0; pos; pos >>= 4, TT.numlen++); + TT.numlen += (4-TT.numlen)&3; + + TT.data = mmap(0, TT.len, PROT_READ|(PROT_WRITE*!ro), MAP_SHARED, fd, 0); + draw_page(); + + for (;;) { + // Scroll display if necessary + if (pos<0) pos = 0; + if (pos>TT.len) pos = TT.len-1; + x = pos&15; + y = pos/16; + + i = 0; + while (y<TT.base) { + if (TT.base-y>(TT.height/2)) { + TT.base = y; + draw_page(); + } else { + TT.base--; + i++; + tty_esc("1T"); + tty_jump(0, 0); + draw_line(0); + } + } + while (y>=TT.base+TT.height) { + if (y-(TT.base+TT.height)>(TT.height/2)) { + TT.base = y-TT.height-1; + draw_page(); + } else { + TT.base++; + i++; + tty_esc("1S"); + tty_jump(0, TT.height-1); + draw_line(TT.height-1); + } + } + if (i) draw_tail(); + y -= TT.base; + + // Display cursor and flush output + highlight(x, y, ro ? 3 : side); + xprintf(""); + + // Wait for next key + key = scan_key(keybuf, 1); + // Exit for q, ctrl-c, ctrl-d, escape, or EOF + if (key==-1 || key==3 || key==4 || key==27 || key=='q') break; + highlight(x, y, 2); + + // Hex digit? + if (key>='a' && key<='f') key-=32; + if (!ro && ((key>='0' && key<='9') || (key>='A' && key<='F'))) { + if (!side) { + long long *ll = (long long *)toybuf; + + ll[TT.undo] = pos; + toybuf[(sizeof(long long)*UNDO_LEN)+TT.undo++] = TT.data[pos]; + if (TT.undolen < UNDO_LEN) TT.undolen++; + TT.undo %= UNDO_LEN; + } + + i = key - '0'; + if (i>9) i -= 7; + TT.data[pos] &= 15<<(4*side); + TT.data[pos] |= i<<(4*!side); + + if (++side==2) { + highlight(x, y, side); + side = 0; + ++pos; + } + } else side = 0; + if (key=='u') { + if (TT.undolen) { + long long *ll = (long long *)toybuf; + + TT.undolen--; + if (!TT.undo) TT.undo = UNDO_LEN; + pos = ll[--TT.undo]; + TT.data[pos] = toybuf[sizeof(long long)*UNDO_LEN+TT.undo]; + } + } else if (key==KEY_UP) pos -= 16; + else if (key==KEY_DOWN) pos += 16; + else if (key==KEY_RIGHT) { + if (x<15) pos++; + } else if (key==KEY_LEFT) { + if (x) pos--; + } else if (key==KEY_PGUP) pos -= 16*TT.height; + else if (key==KEY_PGDN) pos += 16*TT.height; + else if (key==KEY_HOME) pos = 0; + else if (key==KEY_END) pos = TT.len-1; + } + munmap(TT.data, TT.len); + close(fd); + tty_reset(); +} diff --git a/toys/other/hwclock.c b/toys/other/hwclock.c new file mode 100644 index 0000000..75e0641 --- /dev/null +++ b/toys/other/hwclock.c @@ -0,0 +1,136 @@ +/* hwclock.c - get and set the hwclock + * + * Copyright 2014 Bilal Qureshi <bilal.jmi@gmail.com> + * + * No standard, but see Documentation/rtc.txt in the linux kernel source.. + * +USE_HWCLOCK(NEWTOY(hwclock, ">0(fast)f(rtc):u(utc)l(localtime)t(systz)s(hctosys)r(show)w(systohc)[-ul][!rtsw]", TOYFLAG_USR|TOYFLAG_BIN)) + +config HWCLOCK + bool "hwclock" + default y + help + usage: hwclock [-rswtluf] + + -f FILE Use specified device file instead of /dev/rtc (--rtc) + -l Hardware clock uses localtime (--localtime) + -r Show hardware clock time (--show) + -s Set system time from hardware clock (--hctosys) + -t Set the system time based on the current timezone (--systz) + -u Hardware clock uses UTC (--utc) + -w Set hardware clock from system time (--systohc) +*/ + +#define FOR_hwclock +#include "toys.h" +#include <linux/rtc.h> + +GLOBALS( + char *fname; + + int utc; +) + +static int rtc_find(struct dirtree* node) +{ + FILE *fp; + + if (!node->parent) return DIRTREE_RECURSE; + + snprintf(toybuf, sizeof(toybuf), "/sys/class/rtc/%s/hctosys", node->name); + fp = fopen(toybuf, "r"); + if (fp) { + int hctosys = 0, items = fscanf(fp, "%d", &hctosys); + + fclose(fp); + if (items == 1 && hctosys == 1) { + snprintf(toybuf, sizeof(toybuf), "/dev/%s", node->name); + TT.fname = toybuf; + + return DIRTREE_ABORT; + } + } + + return 0; +} + +void hwclock_main() +{ + struct timezone tzone; + struct timeval timeval; + struct tm tm; + time_t time; + int fd = -1; + + // check for Grenich Mean Time + if (toys.optflags & FLAG_u) TT.utc = 1; + else { + FILE *fp; + char *s = 0; + + for (fp = fopen("/etc/adjtime", "r"); + fp && getline(&s, (void *)toybuf, fp)>0; + free(s), s = 0) TT.utc += !strncmp(s, "UTC", 3); + if (fp) fclose(fp); + } + + if (!(toys.optflags&FLAG_t)) { + int w = toys.optflags & FLAG_w, flag = O_WRONLY*w; + + // Open /dev/rtc (if your system has no /dev/rtc symlink, search for it). + if (!TT.fname && (fd = open("/dev/rtc", flag)) == -1) { + dirtree_read("/sys/class/rtc", rtc_find); + if (!TT.fname) TT.fname = "/dev/misc/rtc"; + } + if (fd == -1) fd = xopen(TT.fname, flag); + + // Get current time in seconds from rtc device. todo: get subsecond time + if (!w) { + char *s = s; + + xioctl(fd, RTC_RD_TIME, &tm); + if (TT.utc) s = xtzset("UTC0"); + if ((time = mktime(&tm)) < 0) error_exit("mktime failed"); + if (TT.utc) { + free(xtzset(s)); + free(s); + } + } + } + + if (toys.optflags & (FLAG_w|FLAG_t)) { + if (gettimeofday(&timeval, 0)) perror_exit("gettimeofday failed"); + if (!(TT.utc ? gmtime_r : localtime_r)(&timeval.tv_sec, &tm)) + error_exit(TT.utc ? "gmtime_r failed" : "localtime_r failed"); + } + + if (toys.optflags & FLAG_w) { + /* The value of tm_isdst will positive if daylight saving time is in effect, + * zero if it is not and negative if the information is not available. + * todo: so why isn't this negative...? */ + tm.tm_isdst = 0; + xioctl(fd, RTC_SET_TIME, &tm); + } else if (toys.optflags & FLAG_s) { + tzone.tz_minuteswest = timezone / 60 - 60 * daylight; + timeval.tv_sec = time; + timeval.tv_usec = 0; // todo: fixit + } else if (toys.optflags & FLAG_t) { + // Adjust seconds for timezone and daylight saving time + // extern long timezone is defined in header sys/time.h + tzone.tz_minuteswest = timezone / 60; + if (tm.tm_isdst) tzone.tz_minuteswest -= 60; + if (!TT.utc) timeval.tv_sec += tzone.tz_minuteswest * 60; + } else { + char *c = ctime(&time), *s = strrchr(c, '\n'); + + if (s) *s = '\0'; + // TODO: implement this. + xprintf("%s 0.000000 seconds\n", c); + } + if (toys.optflags & (FLAG_t|FLAG_s)) { + tzone.tz_dsttime = 0; + if (settimeofday(&timeval, &tzone)) perror_exit("settimeofday failed"); + } + + if (fd != -1) close(fd); +} diff --git a/toys/other/ifconfig.c b/toys/other/ifconfig.c index 4d5c74d..948043e 100644 --- a/toys/other/ifconfig.c +++ b/toys/other/ifconfig.c @@ -6,7 +6,7 @@ * * Not in SUSv4. -USE_IFCONFIG(NEWTOY(ifconfig, "^?a", TOYFLAG_BIN)) +USE_IFCONFIG(NEWTOY(ifconfig, "^?a", TOYFLAG_SBIN)) config IFCONFIG bool "ifconfig" @@ -123,7 +123,7 @@ static void display_ifconfig(char *name, int always, unsigned long long val[]) xprintf("%-9s Link encap:%s ", name, types[i].title); if(i >= 0 && ifre.ifr_hwaddr.sa_family == ARPHRD_ETHER) { xprintf("HWaddr "); - for (i=0; i<6; i++) xprintf(":%02X"+!i, ifre.ifr_hwaddr.sa_data[i]); + for (i=0; i<6; i++) xprintf(":%02x"+!i, ifre.ifr_hwaddr.sa_data[i]); } xputc('\n'); @@ -254,7 +254,7 @@ static void display_ifconfig(char *name, int always, unsigned long long val[]) xprintf("%10c", ' '); if(ifre.ifr_map.irq) xprintf("Interrupt:%d ", ifre.ifr_map.irq); if(ifre.ifr_map.base_addr >= 0x100) // IO_MAP_INDEX - xprintf("Base address:0x%lx ", ifre.ifr_map.base_addr); + xprintf("Base address:0x%x ", ifre.ifr_map.base_addr); if(ifre.ifr_map.mem_start) xprintf("Memory:%lx-%lx ", ifre.ifr_map.mem_start, ifre.ifr_map.mem_end); if(ifre.ifr_map.dma) xprintf("DMA chan:%x ", ifre.ifr_map.dma); @@ -449,7 +449,7 @@ void ifconfig_main(void) if (!argv[1]) { toys.exithelp++; - error_exit(*argv); + error_exit("%s", *argv); } plen = get_addrinfo(argv[1], AF_INET6, &ifre6.addr); @@ -462,7 +462,7 @@ void ifconfig_main(void) close(fd6); continue; // Iterate through table to find/perform operation - } else for (i = 0; i < sizeof(try)/sizeof(*try); i++) { + } else for (i = 0; i < ARRAY_LEN(try); i++) { struct argh *t = try+i; int on = t->on, off = t->off; @@ -485,7 +485,7 @@ void ifconfig_main(void) poke((on>>16) + (char *)&ifre, l, on&15); xioctl(TT.sockfd, off, &ifre); break; - } else if (t->name || !strchr(ifre.ifr_name, ':')) { + } else { struct sockaddr_in *si = (struct sockaddr_in *)&ifre.ifr_addr; int mask = -1; diff --git a/toys/other/insmod.c b/toys/other/insmod.c index 81721a3..cb222a5 100644 --- a/toys/other/insmod.c +++ b/toys/other/insmod.c @@ -2,7 +2,7 @@ * * Copyright 2012 Elie De Brauwer <eliedebrauwer@gmail.com> -USE_INSMOD(NEWTOY(insmod, "<1", TOYFLAG_BIN|TOYFLAG_NEEDROOT)) +USE_INSMOD(NEWTOY(insmod, "<1", TOYFLAG_SBIN|TOYFLAG_NEEDROOT)) config INSMOD bool "insmod" diff --git a/toys/other/ionice.c b/toys/other/ionice.c new file mode 100644 index 0000000..e44270a --- /dev/null +++ b/toys/other/ionice.c @@ -0,0 +1,97 @@ +/* ionice.c - set or get process I/O scheduling class and priority + * + * Copyright 2015 Rob Landley <rob@landley.net> + * + * It would be really nice if there was a standard, but no. There is + * Documentation/block/ioprio.txt in the linux source. + +USE_IONICE(NEWTOY(ionice, "^tc#<0>3n#<0>7=5p#", TOYFLAG_USR|TOYFLAG_BIN)) +USE_IORENICE(NEWTOY(iorenice, "?<1>3", TOYFLAG_USR|TOYFLAG_BIN)) + +config IONICE + bool "ionice" + default y + help + usage: ionice [-t] [-c CLASS] [-n LEVEL] [COMMAND...|-p PID] + + Change the I/O scheduling priority of a process. With no arguments + (or just -p), display process' existing I/O class/priority. + + -c CLASS = 1-3: 1(realtime), 2(best-effort, default), 3(when-idle) + -n LEVEL = 0-7: (0 is highest priority, default = 5) + -p Affect existing PID instead of spawning new child + -t Ignore failure to set I/O priority + + System default iopriority is generally -c 2 -n 4. + +config IORENICE + bool "iorenice" + default y + help + usage: iorenice PID [CLASS] [PRIORITY] + + Display or change I/O priority of existing process. CLASS can be + "rt" for realtime, "be" for best effort, "idle" for only when idle, or + "none" to leave it alone. PRIORITY can be 0-7 (0 is highest, default 4). +*/ + +#define FOR_ionice +#include "toys.h" +#include <sys/syscall.h> + +GLOBALS( + long pid; + long level; + long class; +) + +static int ioprio_get(void) +{ + return syscall(__NR_ioprio_get, 1, (int)TT.pid); +} + +static int ioprio_set(void) +{ + int prio = ((int)TT.class << 13) | (int)TT.level; + + return syscall(__NR_ioprio_set, 1, (int)TT.pid, prio); +} + +void ionice_main(void) +{ + if (!TT.pid && !toys.optc) error_exit("Need -p or COMMAND"); + if (toys.optflags == FLAG_p) { + int p = ioprio_get(); + xprintf("%s: prio %d\n", + (char *[]){"unknown", "Realtime", "Best-effort", "Idle"}[(p>>13)&3], + p&7); + } else { + if (-1 == ioprio_set() && !(toys.optflags&FLAG_t)) perror_exit("set"); + if (!TT.pid) xexec(toys.optargs); + } +} + +void iorenice_main(void) +{ + char *classes[] = {"none", "rt", "be", "idle"}; + + TT.pid = atolx(*toys.optargs); + if (toys.optc == 1) { + int p = ioprio_get(); + + if (p == -1) perror_exit("read priority"); + TT.class = (p>>13)&3; + p &= 7; + xprintf("Pid %d, class %s (%d), prio %d\n", + TT.pid, classes[TT.class], TT.class, p); + return; + } + + for (TT.class = 0; TT.class<4; TT.class++) + if (!strcmp(toys.optargs[toys.optc-1], classes[TT.class])) break; + if (toys.optc == 3 || TT.class == 4) TT.level = atolx(toys.optargs[1]); + else TT.level = 4; + TT.class &= 3; + + if (-1 == ioprio_set()) perror_exit("set"); +} diff --git a/toys/other/login.c b/toys/other/login.c index 91523d4..b728286 100644 --- a/toys/other/login.c +++ b/toys/other/login.c @@ -4,6 +4,9 @@ * * No support for PAM/securetty/selinux/login script/issue/utmp * Relies on libcrypt for hash calculation. + * + * TODO: this command predates "pending" but needs cleanup. It #defines + * random stuff, calls exit() form a signal handler... yeah. USE_LOGIN(NEWTOY(login, ">1fph:", TOYFLAG_BIN)) @@ -156,7 +159,7 @@ void login_main(void) if (!isatty(0) || !isatty(1) || !isatty(2)) error_exit("no tty"); openlog("login", LOG_PID | LOG_CONS, LOG_AUTH); - signal(SIGALRM, login_timeout_handler); + xsignal(SIGALRM, login_timeout_handler); alarm(TT.login_timeout = 60); for (ss = forbid; *ss; ss++) unsetenv(*ss); @@ -165,7 +168,7 @@ void login_main(void) tcflush(0, TCIFLUSH); username[sizeof(username)-1] = 0; - if (*toys.optargs) strncpy(username, *toys.optargs, sizeof(username)-1); + if (*toys.optargs) xstrncpy(username, *toys.optargs, sizeof(username)); else { read_user(username, sizeof(username)); if (!*username) continue; diff --git a/toys/other/losetup.c b/toys/other/losetup.c index e3094ef..9568627 100644 --- a/toys/other/losetup.c +++ b/toys/other/losetup.c @@ -112,7 +112,7 @@ static void loopback_setup(char *device, char *file) if (ioctl(lfd, LOOP_SET_FD, ffd)) perror_exit("%s=%s", device, file); loop->lo_offset = TT.offset; loop->lo_sizelimit = TT.size; - strncpy((char *)loop->lo_file_name, s, LO_NAME_SIZE); + xstrncpy((char *)loop->lo_file_name, s, LO_NAME_SIZE); s[LO_NAME_SIZE-1] = 0; if (ioctl(lfd, LOOP_SET_STATUS64, loop)) perror_exit("%s=%s", device, file); if (flags & FLAG_s) printf("%s", device); diff --git a/toys/other/lsmod.c b/toys/other/lsmod.c index b8f5d82..4d16048 100644 --- a/toys/other/lsmod.c +++ b/toys/other/lsmod.c @@ -2,7 +2,7 @@ * * Copyright 2012 Elie De Brauwer <eliedebrauwer@gmail.com> -USE_LSMOD(NEWTOY(lsmod, NULL, TOYFLAG_BIN)) +USE_LSMOD(NEWTOY(lsmod, NULL, TOYFLAG_SBIN)) config LSMOD bool "lsmod" diff --git a/toys/other/lspci.c b/toys/other/lspci.c index 40e0c0a..c9b22ab 100644 --- a/toys/other/lspci.c +++ b/toys/other/lspci.c @@ -50,9 +50,10 @@ int do_lspci(struct dirtree *new) if (-1 == (dirfd = openat(dirtree_parentfd(new), new->name, O_RDONLY))) return 0; + // it's ok for the driver link not to be there, whatever fortify says *driver = 0; if (toys.optflags & FLAG_k) - readlinkat(dirfd, "driver", driver, sizeof(driver)); + if (readlinkat(dirfd, "driver", driver, sizeof(driver))) {}; for (fields = (char*[]){"class", "vendor", "device", 0}; *fields; fields++) { int fd, size = 6 + 2*((toys.optflags & FLAG_e) && p == toybuf); diff --git a/toys/other/makedevs.c b/toys/other/makedevs.c index f6e7ece..0f0a661 100644 --- a/toys/other/makedevs.c +++ b/toys/other/makedevs.c @@ -41,7 +41,7 @@ GLOBALS( void makedevs_main() { - int value, fd = 0, line_no, i; + int fd = 0, line_no, i; char *line = NULL; // Open file and chdir, verbosely @@ -78,33 +78,14 @@ void makedevs_main() continue; } else mode |= (mode_t[]){S_IFIFO, S_IFCHR, S_IFBLK, 0, 0}[i]; - if (*user) { - struct passwd *usr; - - if (!(usr = getpwnam(user)) && isdigit(*user)) { - sscanf(user, "%u", &value); - usr = xgetpwuid(value); - } - if (!usr) error_exit("bad user '%s'", user); - uid = usr->pw_uid; - } else uid = getuid(); - - if (*group) { - struct group *grp; - - if (!(grp = getgrnam(group)) && isdigit(*group)) { - sscanf (group, "%u", &value); - grp = getgrgid(value); - } - if (!grp) error_exit("bad group '%s'", group); - gid = grp->gr_gid; - } else gid = getgid(); + uid = *user ? xgetpwnamid(user)->pw_uid : getuid(); + gid = *group ? xgetgrnamid(group)->gr_gid : getgid(); while (*node == '/') node++; // using relative path for (i = 0; (!cnt && !i) || i < cnt; i++) { - if (cnt) { - snprintf(toybuf, sizeof(toybuf), "%s%u", node, st_val + i); + if (cnt>1) { + snprintf(toybuf, sizeof(toybuf), "%.999s%u", node, st_val + i); ptr = toybuf; } else ptr = node; diff --git a/toys/other/mix.c b/toys/other/mix.c new file mode 100644 index 0000000..845aac1 --- /dev/null +++ b/toys/other/mix.c @@ -0,0 +1,68 @@ +/* mix.c - A very basic mixer. + * + * Copyright 2014 Brad Conroy, dedicated to the Public Domain. + * + +USE_MIX(NEWTOY(mix, "c:d:l#r#", TOYFLAG_USR|TOYFLAG_BIN)) + +config MIX + bool "mix" + default y + help + usage: mix [-d DEV] [-c CHANNEL] [-l VOL] [-r RIGHT] + + List OSS sound channels (module snd-mixer-oss), or set volume(s). + + -c CHANNEL Set/show volume of CHANNEL (default first channel found) + -d DEV Device node (default /dev/mixer) + -l VOL Volume level + -r RIGHT Volume of right stereo channel (with -r, -l sets left volume) +*/ + +#define FOR_mix +#include "toys.h" +#include <linux/soundcard.h> + +GLOBALS( + long right; + long level; + char *dev; + char *chan; +) + +void mix_main(void) +{ + const char *channels[SOUND_MIXER_NRDEVICES] = SOUND_DEVICE_NAMES; + int mask, channel = -1, level, fd; + + if (!TT.dev) TT.dev = "/dev/mixer"; + fd = xopen(TT.dev, O_RDWR|O_NONBLOCK); + xioctl(fd, SOUND_MIXER_READ_DEVMASK, &mask); + + for (channel = 0; channel < SOUND_MIXER_NRDEVICES; channel++) { + if ((1<<channel) & mask) { + if (TT.chan) { + if (!strcmp(channels[channel], TT.chan)) break; + } else if (toys.optflags & FLAG_l) break; + else printf("%s\n", channels[channel]); + } + } + + if (!(toys.optflags & (FLAG_c|FLAG_l))) return; + else if (channel == SOUND_MIXER_NRDEVICES) error_exit("bad -c '%s'", TT.chan); + + if (!(toys.optflags & FLAG_l)) { + xioctl(fd, MIXER_READ(channel), &level); + if (level > 0xFF) + xprintf("%s:%s = left:%d\t right:%d\n", + TT.dev, channels[channel], level>>8, level & 0xFF); + else xprintf("%s:%s = %d\n", TT.dev, channels[channel], level); + } else { + level = TT.level; + if (!(toys.optflags & FLAG_r)) level = TT.right | (level<<8); + + xioctl(fd, MIXER_WRITE(channel), &level); + } + + if (CFG_TOYBOX_FREE) close(fd); +} diff --git a/toys/other/modinfo.c b/toys/other/modinfo.c index f572a99..3a7e821 100644 --- a/toys/other/modinfo.c +++ b/toys/other/modinfo.c @@ -1,6 +1,8 @@ /* modinfo.c - Display module info * * Copyright 2012 Andre Renaud <andre@bluewatersys.com> + * + * TODO: cleanup USE_MODINFO(NEWTOY(modinfo, "<1b:k:F:0", TOYFLAG_BIN)) @@ -27,7 +29,7 @@ GLOBALS( static void output_field(char *field, char *value) { - if (!TT.field) xprintf("%s:%*c", field, 15 - strlen(field), ' '); + if (!TT.field) xprintf("%s:%*c", field, 15-(int)strlen(field), ' '); else if (strcmp(TT.field, field)) return; xprintf("%s", value); xputc((toys.optflags & FLAG_0) ? 0 : '\n'); diff --git a/toys/other/mountpoint.c b/toys/other/mountpoint.c index 29b8ae6..150865c 100644 --- a/toys/other/mountpoint.c +++ b/toys/other/mountpoint.c @@ -2,7 +2,7 @@ * * Copyright 2012 Elie De Brauwer <eliedebrauwer@gmail.com> -USE_MOUNTPOINT(NEWTOY(mountpoint, "<1qdx", TOYFLAG_BIN)) +USE_MOUNTPOINT(NEWTOY(mountpoint, "<1qdx[-dx]", TOYFLAG_BIN)) config MOUNTPOINT bool "mountpoint" @@ -19,36 +19,45 @@ config MOUNTPOINT #define FOR_mountpoint #include "toys.h" +static void die(char *gripe) +{ + if (!(toys.optflags & FLAG_q)) printf("%s: not a %s\n", *toys.optargs, gripe); + + toys.exitval++; + xexit(); +} + void mountpoint_main(void) { struct stat st1, st2; - int res = 0; + char *arg = *toys.optargs; int quiet = toys.optflags & FLAG_q; - toys.exitval = 1; // be pessimistic - strncpy(toybuf, toys.optargs[0], sizeof(toybuf)); - if (((toys.optflags & FLAG_x) && lstat(toybuf, &st1)) || stat(toybuf, &st1)) - perror_exit("%s", toybuf); - if (toys.optflags & FLAG_x){ + if (lstat(arg, &st1)) perror_exit("%s", arg); + + if (toys.optflags & FLAG_x) { if (S_ISBLK(st1.st_mode)) { if (!quiet) printf("%u:%u\n", major(st1.st_rdev), minor(st1.st_rdev)); - toys.exitval = 0; + return; } - if (!quiet) printf("%s: not a block device\n", toybuf); - return; + die("block device"); } - if(!S_ISDIR(st1.st_mode)){ - if (!quiet) printf("%s: not a directory\n", toybuf); - return; - } - strncat(toybuf, "/..", sizeof(toybuf)); - stat(toybuf, &st2); - res = (st1.st_dev != st2.st_dev) || - (st1.st_dev == st2.st_dev && st1.st_ino == st2.st_ino); - if (!quiet) printf("%s is %sa mountpoint\n", toys.optargs[0], res ? "" : "not "); + // TODO: Ignore the fact a file can be a mountpoint for --bind mounts. + if (!S_ISDIR(st1.st_mode)) die("directory"); + + arg = xmprintf("%s/..", arg); + xstat(arg, &st2); + if (CFG_TOYBOX_FREE) free(arg); + + // If the device is different, it's a mount point. If the device _and_ + // inode are the same, it's probably "/". This misses --bind mounts from + // elsewhere in the same filesystem, but so does the other one and in the + // absence of a spec I guess that's the expected behavior? + toys.exitval = !(st1.st_dev != st2.st_dev || st1.st_ino == st2.st_ino); if (toys.optflags & FLAG_d) printf("%u:%u\n", major(st1.st_dev), minor(st1.st_dev)); - toys.exitval = res ? 0 : 1; + else if (!quiet) + printf("%s is %sa mountpoint\n", *toys.optargs, toys.exitval ? "not " : ""); } diff --git a/toys/other/nbd_client.c b/toys/other/nbd_client.c index 6b437c7..c16585a 100644 --- a/toys/other/nbd_client.c +++ b/toys/other/nbd_client.c @@ -8,7 +8,7 @@ // things like prototype "nbd-client_main" which isn't a valid symbol. So // we hide the underscore name and OLDTOY the name we want. USE_NBD_CLIENT(NEWTOY(nbd_client, "<3>3ns", 0)) -USE_NBD_CLIENT(OLDTOY(nbd-client, nbd_client, OPTSTR_nbd_client, TOYFLAG_USR|TOYFLAG_BIN)) +USE_NBD_CLIENT(OLDTOY(nbd-client, nbd_client, TOYFLAG_USR|TOYFLAG_BIN)) config NBD_CLIENT bool "nbd-client" @@ -112,7 +112,7 @@ void nbd_client_main(void) // Daemonize here. - daemon(0,0); + if (daemon(0,0)) perror_exit("daemonize"); // Process NBD requests until further notice. diff --git a/toys/other/netcat.c b/toys/other/netcat.c index 2c1ec7b..4d70d17 100644 --- a/toys/other/netcat.c +++ b/toys/other/netcat.c @@ -4,8 +4,8 @@ * * TODO: udp, ipv6, genericize for telnet/microcom/tail-f -USE_NETCAT(OLDTOY(nc, netcat, USE_NETCAT_LISTEN("tl^L^")"w#p#s:q#f:", TOYFLAG_BIN)) -USE_NETCAT(NEWTOY(netcat, USE_NETCAT_LISTEN("tl^L^")"w#p#s:q#f:", TOYFLAG_BIN)) +USE_NETCAT(OLDTOY(nc, netcat, TOYFLAG_USR|TOYFLAG_BIN)) +USE_NETCAT(NEWTOY(netcat, USE_NETCAT_LISTEN("^tlL")"w#p#s:q#f:", TOYFLAG_BIN)) config NETCAT bool "netcat" @@ -55,12 +55,13 @@ GLOBALS( static void timeout(int signum) { if (TT.wait) error_exit("Timeout"); + // This should be xexit() but would need siglongjmp()... exit(0); } static void set_alarm(int seconds) { - signal(SIGALRM, seconds ? timeout : SIG_DFL); + xsignal(SIGALRM, seconds ? timeout : SIG_DFL); alarm(seconds); } @@ -186,7 +187,7 @@ void netcat_main(void) set_alarm(0); if (CFG_NETCAT_LISTEN && (toys.optflags&(FLAG_L|FLAG_l) && toys.optc)) - xexec_optargs(0); + xexec(toys.optargs); // Poll loop copying stdin->socket and socket->stdout. for (;;) { diff --git a/toys/other/nsenter.c b/toys/other/nsenter.c new file mode 100644 index 0000000..18a2cd2 --- /dev/null +++ b/toys/other/nsenter.c @@ -0,0 +1,167 @@ +/* nsenter.c - Enter existing namespaces + * + * Copyright 2014 andy Lutomirski <luto@amacapital.net> + * + * No standard + * + * unshare.c - run command in new context + * + * Copyright 2011 Rob Landley <rob@landley.net> + * + * No Standard + * + +// Note: flags go in same order (right to left) for shared subset +USE_NSENTER(NEWTOY(nsenter, "<1F(no-fork)t#<1(target)i:(ipc);m:(mount);n:(net);p:(pid);u:(uts);U:(user);", TOYFLAG_USR|TOYFLAG_BIN)) +USE_UNSHARE(NEWTOY(unshare, "<1^rimnpuU", TOYFLAG_USR|TOYFLAG_BIN)) + +config UNSHARE + bool "unshare" + default y + depends on TOYBOX_CONTAINER + help + usage: unshare [-imnpuUr] COMMAND... + + Create new container namespace(s) for this process and its children, so + some attribute is not shared with the parent process. + + -i SysV IPC (message queues, semaphores, shared memory) + -m Mount/unmount tree + -n Network address, sockets, routing, iptables + -p Process IDs and init + -r Become root (map current euid/egid to 0/0, implies -U) + -u Host and domain names + -U UIDs, GIDs, capabilities + + A namespace allows a set of processes to have a different view of the + system than other sets of processes. + +config NSENTER + bool "nsenter" + default y + help + usage: nsenter [-t pid] [-F] [-i] [-m] [-n] [-p] [-u] [-U] COMMAND... + + Run COMMAND in an existing (set of) namespace(s). + + -t PID to take namespaces from (--target) + -F don't fork, even if -p is used (--no-fork) + + The namespaces to switch are: + + -i SysV IPC: message queues, semaphores, shared memory (--ipc) + -m Mount/unmount tree (--mnt) + -n Network address, sockets, routing, iptables (--net) + -p Process IDs and init, will fork unless -F is used (--pid) + -u Host and domain names (--uts) + -U UIDs, GIDs, capabilities (--user) + + If -t isn't specified, each namespace argument must provide a path + to a namespace file, ala "-i=/proc/$PID/ns/ipc" +*/ + +#define FOR_nsenter +#include "toys.h" +#include <linux/sched.h> +int unshare(int flags); +int setns(int fd, int nstype); + +GLOBALS( + char *nsnames[6]; + long targetpid; +) + +// Code that must run in unshare's flag context +#define CLEANUP_nsenter +#define FOR_unshare +#include <generated/flags.h> + +static void write_ugid_map(char *map, unsigned eugid) +{ + int bytes = sprintf(toybuf, "0 %u 1", eugid), fd = xopen(map, O_WRONLY); + + xwrite(fd, toybuf, bytes); + xclose(fd); +} + +static void handle_r(int euid, int egid) +{ + int fd; + + if ((fd = open("/proc/self/setgroups", O_WRONLY)) >= 0) { + xwrite(fd, "deny", 4); + close(fd); + } + + write_ugid_map("/proc/self/uid_map", euid); + write_ugid_map("/proc/self/gid_map", egid); +} + +static int test_r() +{ + return toys.optflags & FLAG_r; +} + +// Shift back to the context GLOBALS lives in (I.E. matching the filename). +#define CLEANUP_unshare +#define FOR_nsenter +#include <generated/flags.h> + +void unshare_main(void) +{ + unsigned flags[]={CLONE_NEWUSER, CLONE_NEWUTS, CLONE_NEWPID, CLONE_NEWNET, + CLONE_NEWNS, CLONE_NEWIPC}, f = 0; + int i, fd; + + // Create new namespace(s)? + if (CFG_UNSHARE && *toys.which->name=='u') { + // For -r, we have to save our original [ug]id before calling unshare() + int euid = geteuid(), egid = getegid(); + + // unshare -U does not imply -r, so we cannot use [+rU] + if (test_r()) toys.optflags |= FLAG_U; + + for (i = 0; i<ARRAY_LEN(flags); i++) + if (toys.optflags & (1<<i)) f |= flags[i]; + + if (unshare(f)) perror_exit(0); + if (test_r()) handle_r(euid, egid); + + // Bind to existing namespace(s)? + } else if (CFG_NSENTER) { + char *nsnames = "user\0uts\0pid\0net\0mnt\0ipc"; + + for (i = 0; i<ARRAY_LEN(flags); i++) { + char *filename = TT.nsnames[i]; + + if (toys.optflags & (1<<i)) { + if (!filename || !*filename) { + if (!(toys.optflags & FLAG_t)) error_exit("need -t or =filename"); + sprintf(toybuf, "/proc/%ld/ns/%s", TT.targetpid, nsnames); + filename = toybuf; + } + + if (setns(fd = xopen(filename, O_RDONLY), flags[i])) + perror_exit("setns"); + close(fd); + } + nsnames += strlen(nsnames)+1; + } + + if ((toys.optflags & FLAG_p) && !(toys.optflags & FLAG_F)) { + pid_t pid = xfork(); + + if (pid) { + while (waitpid(pid, 0, 0) == -1 && errno == EINTR); + return; + } + } + } + + xexec(toys.optargs); +} + +void nsenter_main(void) +{ + unshare_main(); +} diff --git a/toys/other/oneit.c b/toys/other/oneit.c index 72395cc..8e4b713 100644 --- a/toys/other/oneit.c +++ b/toys/other/oneit.c @@ -2,7 +2,7 @@ * * Copyright 2005, 2007 by Rob Landley <rob@landley.net>. -USE_ONEIT(NEWTOY(oneit, "^<1c:p", TOYFLAG_SBIN)) +USE_ONEIT(NEWTOY(oneit, "^<1nc:p3[!pn]", TOYFLAG_SBIN)) config ONEIT bool "oneit" @@ -10,16 +10,18 @@ config ONEIT help usage: oneit [-p] [-c /dev/tty0] command [...] - A simple init program that runs a single supplied command line with a + Simple init program that runs a single supplied command line with a controlling tty (so CTRL-C can kill it). + -c Which console device to use (/dev/console doesn't do CTRL-C, etc). -p Power off instead of rebooting when command exits. - -c Which console device to use. + -r Restart child when it exits. + -3 Write 32 bit PID of each exiting reparented process to fd 3 of child. + (Blocking writes, child must read to avoid eventual deadlock.) - The oneit command runs the supplied command line as a child process - (because PID 1 has signals blocked), attached to /dev/tty0, in its - own session. Then oneit reaps zombies until the child exits, at - which point it reboots (or with -p, powers off) the system. + Spawns a single child process (because PID 1 has signals blocked) + in its own session, reaps zombies until the child exits, then + reboots the system (or powers off with -p, or restarts the child with -r). */ #define FOR_oneit @@ -40,37 +42,70 @@ GLOBALS( // PID 1 then reaps zombies until the child process it spawned exits, at which // point it calls sync() and reboot(). I could stick a kill -1 in there. +// Perform actions in response to signals. (Only root can send us signals.) +static void oneit_signaled(int signal) +{ + int action = RB_AUTOBOOT; + + toys.signal = signal; + if (signal == SIGUSR1) action = RB_HALT_SYSTEM; + if (signal == SIGUSR2) action = RB_POWER_OFF; + + // PID 1 can't call reboot() because it kills the task that calls it, + // which causes the kernel to panic before the actual reboot happens. + sync(); + if (!vfork()) reboot(action); +} void oneit_main(void) { - int i; - pid_t pid; - - // Create a new child process. - pid = vfork(); - if (pid) { - - // pid 1 just reaps zombies until it gets its child, then halts the system. - while (pid != wait(&i)); - sync(); - - // PID 1 can't call reboot() because it kills the task that calls it, - // which causes the kernel to panic before the actual reboot happens. - if (!vfork()) reboot((toys.optflags & FLAG_p) ? RB_POWER_OFF : RB_AUTOBOOT); - sleep(5); - _exit(1); + int i, pid, pipes[] = {SIGUSR1, SIGUSR2, SIGTERM, SIGINT}; + + if (FLAG_3) { + // Ensure next available filehandle is #3 + while (open("/", 0) < 3); + close(3); + close(4); + if (pipe(pipes)) perror_exit("pipe"); + fcntl(4, F_SETFD, FD_CLOEXEC); } - // Redirect stdio to /dev/tty0, with new session ID, so ctrl-c works. - setsid(); - for (i=0; i<3; i++) { - close(i); - // Remember, O_CLOEXEC is backwards for xopen() - xopen(TT.console ? TT.console : "/dev/tty0", O_RDWR|O_CLOEXEC); + // Setup signal handlers for signals of interest + for (i = 0; i<ARRAY_LEN(pipes); i++) xsignal(pipes[i], oneit_signaled); + + while (!toys.signal) { + + // Create a new child process. + pid = vfork(); + if (pid) { + + // pid 1 reaps zombies until it gets its child, then halts system. + // We ignore the return value of write (what would we do with it?) + // but save it in a variable we never read to make fortify shut up. + // (Real problem is if pid2 never reads, write() fills pipe and blocks.) + while (pid != wait(&i)) if (FLAG_3) i = write(4, &pid, 4); + if (toys.optflags & FLAG_n) continue; + + oneit_signaled((toys.optflags & FLAG_p) ? SIGUSR2 : SIGTERM); + } else { + // Redirect stdio to /dev/tty0, with new session ID, so ctrl-c works. + setsid(); + for (i=0; i<3; i++) { + close(i); + // Remember, O_CLOEXEC is backwards for xopen() + xopen(TT.console ? TT.console : "/dev/tty0", O_RDWR|O_CLOEXEC); + } + + // Can't xexec() here, we vforked so we don't want to error_exit(). + toy_exec(toys.optargs); + execvp(*toys.optargs, toys.optargs); + perror_msg("%s not in PATH=%s", *toys.optargs, getenv("PATH")); + + break; + } } - // Can't xexec() here, because we vforked so we don't want to error_exit(). - toy_exec(toys.optargs); - execvp(*toys.optargs, toys.optargs); + // Give reboot() time to kick in, or avoid rapid spinning if exec failed + sleep(5); _exit(127); } diff --git a/toys/other/pivot_root.c b/toys/other/pivot_root.c index 3e4beac..7748032 100644 --- a/toys/other/pivot_root.c +++ b/toys/other/pivot_root.c @@ -2,7 +2,7 @@ * * Copyright 2012 Rob Landley <rob@landley.net> -USE_PIVOT_ROOT(NEWTOY(pivot_root, "<2>2", TOYFLAG_USR|TOYFLAG_BIN)) +USE_PIVOT_ROOT(NEWTOY(pivot_root, "<2>2", TOYFLAG_SBIN)) config PIVOT_ROOT bool "pivot_root" @@ -22,7 +22,8 @@ config PIVOT_ROOT #define FOR_pivot_root #include "toys.h" -#include <linux/unistd.h> +#include <sys/syscall.h> +#include <unistd.h> void pivot_root_main(void) { diff --git a/toys/other/pmap.c b/toys/other/pmap.c index ab0b61c..a93ea3e 100644 --- a/toys/other/pmap.c +++ b/toys/other/pmap.c @@ -53,7 +53,7 @@ void pmap_main(void) if ((toys.optflags & (FLAG_q|FLAG_x)) == FLAG_x) xprintf("Address%*cKbytes PSS Dirty Swap Mode Mapping\n", - (sizeof(long)*2)-4, ' '); + (int)(sizeof(long)*2)-4, ' '); // Loop through mappings for (;;) { diff --git a/toys/other/readlink.c b/toys/other/readlink.c index 1c33362..fecd1ef 100644 --- a/toys/other/readlink.c +++ b/toys/other/readlink.c @@ -2,7 +2,7 @@ * * Copyright 2007 Rob Landley <rob@landley.net> -USE_READLINK(NEWTOY(readlink, "<1>1fenq[-fe]", TOYFLAG_BIN)) +USE_READLINK(NEWTOY(readlink, "<1>1fenq[-fe]", TOYFLAG_USR|TOYFLAG_BIN)) config READLINK bool "readlink" diff --git a/toys/other/reboot.c b/toys/other/reboot.c index 5cbc4f8..a135888 100644 --- a/toys/other/reboot.c +++ b/toys/other/reboot.c @@ -2,9 +2,9 @@ * * Copyright 2013 Elie De Brauwer <eliedebrauwer@gmail.com> -USE_REBOOT(NEWTOY(reboot, "n", TOYFLAG_BIN|TOYFLAG_NEEDROOT)) -USE_REBOOT(OLDTOY(halt, reboot, "n", TOYFLAG_BIN|TOYFLAG_NEEDROOT)) -USE_REBOOT(OLDTOY(poweroff, reboot, "n", TOYFLAG_BIN|TOYFLAG_NEEDROOT)) +USE_REBOOT(NEWTOY(reboot, "n", TOYFLAG_SBIN|TOYFLAG_NEEDROOT)) +USE_REBOOT(OLDTOY(halt, reboot, TOYFLAG_SBIN|TOYFLAG_NEEDROOT)) +USE_REBOOT(OLDTOY(poweroff, reboot, TOYFLAG_SBIN|TOYFLAG_NEEDROOT)) config REBOOT bool "reboot" diff --git a/toys/other/reset.c b/toys/other/reset.c new file mode 100644 index 0000000..0c2089c --- /dev/null +++ b/toys/other/reset.c @@ -0,0 +1,23 @@ +/* reset.c - reset the terminal. + * + * Copyright 2015 Rob Landley <rob@landley.net> + * + * No standard. + +USE_RESET(NEWTOY(reset, 0, TOYFLAG_USR|TOYFLAG_BIN)) + +config RESET + bool "reset" + default y + help + usage: reset + + reset the terminal +*/ +#include "toys.h" + +void reset_main(void) +{ + // man 4 console codes: reset terminal is ESC (no left bracket) c + xwrite(xgettty(), "\033c", 2); +} diff --git a/toys/other/rfkill.c b/toys/other/rfkill.c index af3efe1..36fe320 100644 --- a/toys/other/rfkill.c +++ b/toys/other/rfkill.c @@ -5,7 +5,7 @@ * * No Standard -USE_RFKILL(NEWTOY(rfkill, "<1>2", TOYFLAG_SBIN)) +USE_RFKILL(NEWTOY(rfkill, "<1>2", TOYFLAG_USR|TOYFLAG_SBIN)) config RFKILL bool "rfkill" @@ -49,7 +49,7 @@ void rfkill_main(void) {"wimax", RFKILL_TYPE_WIMAX}, {"wwan", RFKILL_TYPE_WWAN}, {"gps", RFKILL_TYPE_GPS}, {"fm", 7}}; // RFKILL_TYPE_FM = 7 - if (!*++optargs) error_exit("'%s' needs IDENTIFIER"); + if (!*++optargs) error_exit("'%s' needs IDENTIFIER", optargs[-1]); for (i = 0; i < ARRAY_LEN(rftypes); i++) if (!strcmp(rftypes[i].name, *optargs)) break; if (i == ARRAY_LEN(rftypes)) idx = atolx_range(*optargs, 0, INT_MAX); diff --git a/toys/other/rmmod.c b/toys/other/rmmod.c index b789acc..10c134c 100644 --- a/toys/other/rmmod.c +++ b/toys/other/rmmod.c @@ -2,7 +2,7 @@ * * Copyright 2012 Elie De Brauwer <eliedebrauwer@gmail.com> -USE_RMMOD(NEWTOY(rmmod, "<1wf", TOYFLAG_BIN|TOYFLAG_NEEDROOT)) +USE_RMMOD(NEWTOY(rmmod, "<1wf", TOYFLAG_SBIN|TOYFLAG_NEEDROOT)) config RMMOD bool "rmmod" diff --git a/toys/other/setsid.c b/toys/other/setsid.c index 8f0a064..59a1d78 100644 --- a/toys/other/setsid.c +++ b/toys/other/setsid.c @@ -24,5 +24,5 @@ void setsid_main(void) setpgid(0,0); tcsetpgrp(0, getpid()); } - xexec_optargs(0); + xexec(toys.optargs); } diff --git a/toys/other/shred.c b/toys/other/shred.c new file mode 100644 index 0000000..64db5ed --- /dev/null +++ b/toys/other/shred.c @@ -0,0 +1,106 @@ +/* shred.c - Overwrite a file to securely delete + * + * Copyright 2014 Rob Landley <rob@landley.net> + * + * No standard + +USE_SHRED(NEWTOY(shred, "<1zxus#<1n#<1o#<0f", TOYFLAG_USR|TOYFLAG_BIN)) + +config SHRED + bool "shred" + default y + help + usage: shred [-fuz] [-n COUNT] [-s SIZE] FILE... + + Securely delete a file by overwriting its contents with random data. + + -f Force (chmod if necessary) + -n COUNT Random overwrite iterations (default 1) + -o OFFSET Start at OFFSET + -s SIZE Use SIZE instead of detecting file size + -u unlink (actually delete file when done) + -x Use exact size (default without -s rounds up to next 4k) + -z zero at end + + Note: data journaling filesystems render this command useless, you must + overwrite all free space (fill up disk) to erase old data on those. +*/ + +#define FOR_shred +#include "toys.h" + +GLOBALS( + long offset; + long iterations; + long size; + + int ufd; +) + +void shred_main(void) +{ + char **try; + + if (!(toys.optflags & FLAG_n)) TT.iterations++; + TT.ufd = xopen("/dev/urandom", O_RDONLY); + + // We don't use loopfiles() here because "-" isn't stdin, and want to + // respond to files we can't open via chmod. + + for (try = toys.optargs; *try; try++) { + off_t pos = 0, len = TT.size; + int fd = open(*try, O_RDWR), iter = 0, throw; + + // do -f chmod if necessary + if (fd == -1 && (toys.optflags & FLAG_f)) { + chmod(*try, 0600); + fd = open(*try, O_RDWR); + } + if (fd == -1) { + perror_msg("%s", *try); + continue; + } + + // determine length + if (!len) len = fdlength(fd); + if (len<1) { + error_msg("%s: needs -s", *try); + close(fd); + continue; + } + + // Loop through, writing to this file + for (;;) { + // Advance to next -n or -z? + + if (pos >= len) { + pos = -1; + if (++iter == TT.iterations && (toys.optargs && FLAG_z)) { + memset(toybuf, 0, sizeof(toybuf)); + continue; + } + if (iter >= TT.iterations) break; + } + + if (pos < TT.offset) { + if (TT.offset != lseek(fd, TT.offset, SEEK_SET)) { + perror_msg("%s", *try); + break; + } + pos = TT.offset; + } + + // Determine length, read random data if not zeroing, write. + + throw = sizeof(toybuf); + if (toys.optflags & FLAG_x) + if (len-pos < throw) throw = len-pos; + + if (iter != TT.iterations) xread(TT.ufd, toybuf, throw); + if (throw != writeall(fd, toybuf, throw)) perror_msg("%s"); + pos += throw; + } + if (toys.optflags & FLAG_u) + if (unlink(*try)) perror_msg("unlink '%s'", *try); + } +} diff --git a/toys/other/stat.c b/toys/other/stat.c index d603316..d6db44d 100644 --- a/toys/other/stat.c +++ b/toys/other/stat.c @@ -82,7 +82,7 @@ static void print_stat(char type) if (!stat->st_size && filetype == S_IFREG) t = "regular empty file"; xprintf("%s", t); } else if (type == 'g') xprintf("%lu", stat->st_gid); - else if (type == 'G') xprintf("%8s", TT.user_name->pw_name); + else if (type == 'G') xprintf("%8s", TT.group_name->gr_name); else if (type == 'h') xprintf("%lu", stat->st_nlink); else if (type == 'i') xprintf("%llu", stat->st_ino); else if (type == 'N') { @@ -106,11 +106,11 @@ static void print_stat(char type) static void print_statfs(char type) { struct statfs *statfs = (struct statfs *)&TT.stat; - if (type == 'a') xprintf("%lu", statfs->f_bavail); - else if (type == 'b') xprintf("%lu", statfs->f_blocks); - else if (type == 'c') xprintf("%lu", statfs->f_files); - else if (type == 'd') xprintf("%lu", statfs->f_ffree); - else if (type == 'f') xprintf("%lu", statfs->f_bfree); + if (type == 'a') xprintf("%llu", statfs->f_bavail); + else if (type == 'b') xprintf("%llu", statfs->f_blocks); + else if (type == 'c') xprintf("%llu", statfs->f_files); + else if (type == 'd') xprintf("%llu", statfs->f_ffree); + else if (type == 'f') xprintf("%llu", statfs->f_bfree); else if (type == 'l') xprintf("%ld", statfs->f_namelen); else if (type == 't') xprintf("%lx", statfs->f_type); else if (type == 'i') diff --git a/toys/other/swapoff.c b/toys/other/swapoff.c index b89e915..fb17130 100644 --- a/toys/other/swapoff.c +++ b/toys/other/swapoff.c @@ -2,7 +2,7 @@ * * Copyright 2012 Elie De Brauwer <eliedebrauwer@gmail.com> -USE_SWAPOFF(NEWTOY(swapoff, "<1>1", TOYFLAG_BIN|TOYFLAG_NEEDROOT)) +USE_SWAPOFF(NEWTOY(swapoff, "<1>1", TOYFLAG_SBIN|TOYFLAG_NEEDROOT)) config SWAPOFF bool "swapoff" diff --git a/toys/other/swapon.c b/toys/other/swapon.c index 49f1249..838d382 100644 --- a/toys/other/swapon.c +++ b/toys/other/swapon.c @@ -2,7 +2,7 @@ * * Copyright 2012 Elie De Brauwer <eliedebrauwer@gmail.com> -USE_SWAPON(NEWTOY(swapon, "<1>1p#<0>32767", TOYFLAG_BIN|TOYFLAG_NEEDROOT)) +USE_SWAPON(NEWTOY(swapon, "<1>1p#<0>32767", TOYFLAG_SBIN|TOYFLAG_NEEDROOT)) config SWAPON bool "swapon" diff --git a/toys/other/sysctl.c b/toys/other/sysctl.c index 8e57ca1..d4ed1b0 100644 --- a/toys/other/sysctl.c +++ b/toys/other/sysctl.c @@ -5,7 +5,7 @@ * * No Standard -USE_SYSCTL(NEWTOY(sysctl, "^neNqwpaA[!ap][!aq][!aw][+aA]", TOYFLAG_USR|TOYFLAG_BIN)) +USE_SYSCTL(NEWTOY(sysctl, "^neNqwpaA[!ap][!aq][!aw][+aA]", TOYFLAG_SBIN)) config SYSCTL bool "sysctl" @@ -97,7 +97,7 @@ static void process_key(char *key, char *value) if (!value) value = split_key(key); if ((toys.optflags & FLAG_w) && !value) { - error_msg("'%s' not key=value"); + error_msg("'%s' not key=value", key); return; } diff --git a/toys/other/taskset.c b/toys/other/taskset.c index bcc0347..2851923 100644 --- a/toys/other/taskset.c +++ b/toys/other/taskset.c @@ -3,6 +3,17 @@ * Copyright 2012 Elie De Brauwer <eliedebrauwer@gmail.com> USE_TASKSET(NEWTOY(taskset, "<1^pa", TOYFLAG_BIN|TOYFLAG_STAYROOT)) +USE_NPROC(NEWTOY(nproc, "(all)", TOYFLAG_USR|TOYFLAG_BIN)) + +config NPROC + bool "nproc" + default y + help + usage: nproc [--all] + + Print number of processors. + + --all Show all processors, not just ones this task can run on. config TASKSET bool "taskset" @@ -29,6 +40,10 @@ config TASKSET #define sched_getaffinity(pid, size, cpuset) \ syscall(__NR_sched_getaffinity, (pid_t)pid, (size_t)size, (void *)cpuset) +GLOBALS( + int nproc; +) + // mask is an array of long, which makes the layout a bit weird on big // endian systems but as long as it's consistent... @@ -90,7 +105,7 @@ void taskset_main(void) if (!(toys.optflags & FLAG_p)) { if (toys.optc < 2) error_exit("Needs 2 args"); do_taskset(getpid(), 1); - xexec_optargs(1); + xexec(toys.optargs+1); } else { char *c; pid_t pid = strtol(toys.optargs[toys.optc-1], &c, 10); @@ -104,3 +119,30 @@ void taskset_main(void) } else do_taskset(pid, 0); } } + +int do_nproc(struct dirtree *new) +{ + if (!new->parent) return DIRTREE_RECURSE; + if (!strncmp(new->name, "cpu", 3) && isdigit(new->name[3])) TT.nproc++; + + return 0; +} + +void nproc_main(void) +{ + int i, j; + + // This can only detect 32768 processors. Call getaffinity and count bits. + if (!toys.optflags && -1!=sched_getaffinity(getpid(), 4096, toybuf)) { + for (i = 0; i<4096; i++) + if (toybuf[i]) + for (j=0; j<8; j++) + if (toybuf[i]&(1<<j)) + TT.nproc++; + } + + // If getaffinity failed or --all, count cpu entries in proc + if (!TT.nproc) dirtree_read("/sys/devices/system/cpu", do_nproc); + + xprintf("%d\n", TT.nproc); +} diff --git a/toys/other/timeout.c b/toys/other/timeout.c index f8acabf..06b1e89 100644 --- a/toys/other/timeout.c +++ b/toys/other/timeout.c @@ -4,7 +4,7 @@ * * No standard -USE_TIMEOUT(NEWTOY(timeout, "<2^k:s: ", TOYFLAG_BIN)) +USE_TIMEOUT(NEWTOY(timeout, "<2^vk:s: ", TOYFLAG_BIN)) config TIMEOUT bool "timeout" @@ -21,6 +21,7 @@ config TIMEOUT -s Send specified signal (default TERM) -k Send KILL signal if child still running this long after first signal. + -v Verbose */ #define FOR_timeout @@ -38,14 +39,15 @@ GLOBALS( static void handler(int i) { + fprintf(stderr, "timeout pid %d signal %d\n", TT.pid, TT.nextsig); kill(TT.pid, TT.nextsig); if (TT.k_timeout) { TT.k_timeout = 0; TT.nextsig = SIGKILL; - signal(SIGALRM, handler); + xsignal(SIGALRM, handler); TT.itv.it_value = TT.ktv; - setitimer(ITIMER_REAL, &TT.itv, (void *)&toybuf); + setitimer(ITIMER_REAL, &TT.itv, (void *)toybuf); } } @@ -60,12 +62,12 @@ void timeout_main(void) if (TT.s_signal && -1 == (TT.nextsig = sig_to_num(TT.s_signal))) error_exit("bad -s: '%s'", TT.s_signal); - if (!(TT.pid = xfork())) xexec_optargs(1); + if (!(TT.pid = xfork())) xexec(toys.optargs+1); else { int status; - signal(SIGALRM, handler); - setitimer(ITIMER_REAL, &TT.itv, (void *)&toybuf); + xsignal(SIGALRM, handler); + setitimer(ITIMER_REAL, &TT.itv, (void *)toybuf); while (-1 == waitpid(TT.pid, &status, 0) && errno == EINTR); toys.exitval = WIFEXITED(status) ? WEXITSTATUS(status) : WTERMSIG(status) + 127; diff --git a/toys/other/truncate.c b/toys/other/truncate.c index d09818f..bfe1f10 100644 --- a/toys/other/truncate.c +++ b/toys/other/truncate.c @@ -2,37 +2,62 @@ * * Copyright 2011 Rob Landley <rob@landley.net> -USE_TRUNCATE(NEWTOY(truncate, "<1s#|c", TOYFLAG_BIN)) +USE_TRUNCATE(NEWTOY(truncate, "<1s:|c", TOYFLAG_BIN)) config TRUNCATE bool "truncate" default y help - usage: truncate [-c] -s file... + usage: truncate [-c] -s SIZE file... Set length of file(s), extending sparsely if necessary. -c Don't create file if it doesn't exist. - -s New size + -s New size (with optional prefix and suffix) + + SIZE prefix: + add, - subtract, < shrink to, > expand to, + / multiple rounding down, % multiple rounding up + SIZE suffix: k=1024, m=1024^2, g=1024^3, t=1024^4, p=1024^5, e=1024^6 */ #define FOR_truncate #include "toys.h" GLOBALS( + char *s; + long size; + int type; ) static void do_truncate(int fd, char *name) { + long long size; + if (fd<0) return; - if (ftruncate(fd, TT.size)) perror_msg("'%s' to '%ld'", name, TT.size); + + if (TT.type == -1) size = TT.size; + else { + size = fdlength(fd); + if (TT.type<2) size += TT.size*(1-(2*TT.type)); + else if (TT.type<4) { + if ((TT.type==2) ? (size <= TT.size) : (size >= TT.size)) return; + size = TT.size; + } else { + size = (size+(TT.type-4)*(TT.size-1))/TT.size; + size *= TT.size; + } + } + if (ftruncate(fd, size)) perror_msg("'%s' to '%lld'", name, size); } void truncate_main(void) { int cr = !(toys.optflags&1); + if (-1 != (TT.type = stridx("+-<>/%", *TT.s))) TT.s++; + TT.size = atolx(TT.s); + // Create files with mask rwrwrw. // Nonexistent files are only an error if we're supposed to create them. loopfiles_rw(toys.optargs, O_WRONLY|O_CLOEXEC|(cr ? O_CREAT : 0), 0666, cr, diff --git a/toys/other/unshare.c b/toys/other/unshare.c deleted file mode 100644 index 68c1ebd..0000000 --- a/toys/other/unshare.c +++ /dev/null @@ -1,42 +0,0 @@ -/* unshare.c - run command in new context - * - * Copyright 2011 Rob Landley <rob@landley.net> - -USE_UNSHARE(NEWTOY(unshare, "<1^imnpuU", TOYFLAG_USR|TOYFLAG_BIN)) - -config UNSHARE - bool "unshare" - default y - depends on TOYBOX_CONTAINER - help - usage: unshare [-imnpuU] COMMAND... - - Create new namespace(s) for this process and its children, so some - attribute is not shared with the parent process. This is part of - Linux Containers. Each process can have its own: - - -i SysV IPC (message queues, semaphores, shared memory) - -m Mount/unmount tree - -n Network address, sockets, routing, iptables - -p Process IDs and init - -u Host and domain names - -U UIDs, GIDs, capabilities -*/ - -#include "toys.h" -#include <linux/sched.h> -extern int unshare (int __flags); - -void unshare_main(void) -{ - unsigned flags[]={CLONE_NEWUSER, CLONE_NEWUTS, CLONE_NEWPID, CLONE_NEWNET, - CLONE_NEWNS, CLONE_NEWIPC, 0}; - unsigned f=0; - int i; - - for (i=0; flags[i]; i++) if (toys.optflags & (1<<i)) f |= flags[i]; - - if (unshare(f)) perror_exit("failed"); - - xexec_optargs(0); -} diff --git a/toys/other/vconfig.c b/toys/other/vconfig.c index eff918c..fd78527 100644 --- a/toys/other/vconfig.c +++ b/toys/other/vconfig.c @@ -4,6 +4,8 @@ * Copyright 2012 Kyungwan Han <asura321@gmail.com> * * No standard + * + * TODO: cleanup USE_VCONFIG(NEWTOY(vconfig, "<2>4", TOYFLAG_NEEDROOT|TOYFLAG_SBIN)) diff --git a/toys/other/vmstat.c b/toys/other/vmstat.c index 49c7cc7..c11e46b 100644 --- a/toys/other/vmstat.c +++ b/toys/other/vmstat.c @@ -1,6 +1,9 @@ /* vmstat.c - Report virtual memory statistics. * * Copyright 2012 Elie De Brauwer <eliedebrauwer@gmail.com> + * + * TODO: I have no idea how the "io" and "system" categories are calculated. + * whatever we're doing isn't matching what other implementations are doing. USE_VMSTAT(NEWTOY(vmstat, ">2n", TOYFLAG_BIN)) @@ -84,11 +87,13 @@ void vmstat_main(void) if (toys.optc) loop_delay = atolx_range(toys.optargs[0], 0, INT_MAX); if (toys.optc > 1) loop_max = atolx_range(toys.optargs[1], 1, INT_MAX) - 1; - for (loop = 0; loop <= loop_max; loop++) { + for (loop = 0; !loop_max || loop <= loop_max; loop++) { unsigned idx = loop&1, offset = 0, expected = 0; uint64_t units, total_hz, *ptr = (uint64_t *)(top+idx), *oldptr = (uint64_t *)(top+!idx); + if (loop && loop_delay) sleep(loop_delay); + // Print headers if (rows>3 && !(loop % (rows-3))) { if (isatty(1)) terminal_size(0, &rows); @@ -147,7 +152,6 @@ void vmstat_main(void) } xputc('\n'); - if (loop_delay) sleep(loop_delay); - else break; + if (!loop_delay) break; } } diff --git a/toys/other/xxd.c b/toys/other/xxd.c new file mode 100644 index 0000000..e9ad839 --- /dev/null +++ b/toys/other/xxd.c @@ -0,0 +1,65 @@ +/* xxd.c - hexdump. + * + * Copyright 2015 The Android Open Source Project + * + * No obvious standard, output looks like: + * 0000000: 4c69 6e75 7820 7665 7273 696f 6e20 332e Linux version 3. + * + * TODO: support for reversing a hexdump back into the original data. + * TODO: -s seek + +USE_XXD(NEWTOY(xxd, ">1c#<1>4096=16l#g#<1=2", TOYFLAG_USR|TOYFLAG_BIN)) + +config XXD + bool "xxd" + default y + help + usage: xxd [-c n] [-g n] [-l n] [file] + + Hexdump a file to stdout. If no file is listed, copy from stdin. + Filename "-" is a synonym for stdin. + + -c n Show n bytes per line (default 16). + -g n Group bytes by adding a ' ' every n bytes (default 2). + -l n Limit of n bytes before stopping (default is no limit). +*/ + +#define FOR_xxd +#include "toys.h" + +GLOBALS( + long g; + long l; + long c; +) + +static void do_xxd(int fd, char *name) +{ + long long pos = 0; + int i, len, space; + + while (0<(len = readall(fd, toybuf, (TT.l && TT.l-pos<TT.c)?TT.l-pos:TT.c))) { + printf("%08llx: ", pos); + pos += len; + space = 2*TT.c+TT.c/TT.g+1; + + for (i=0; i<len;) { + space -= printf("%02x", toybuf[i]); + if (!(++i%TT.g)) { + putchar(' '); + space--; + } + } + + printf("%*s", space, ""); + for (i=0; i<len; i++) + putchar((toybuf[i]>=' ' && toybuf[i]<='~') ? toybuf[i] : '.'); + putchar('\n'); + } + if (len<0) perror_exit("read"); +} + +void xxd_main(void) +{ + loopfiles(toys.optargs, do_xxd); +} diff --git a/toys/pending/README b/toys/pending/README index f42f34b..2eb83e1 100644 --- a/toys/pending/README +++ b/toys/pending/README @@ -1,4 +1,4 @@ -pending +pending (see toys/pending/README) Commands in this directory are external submissions awaiting review and/or cleanup before being "promoted" to one of the other directories. @@ -7,12 +7,9 @@ Code in this directory may or may not work, some of the commands here are unfinished stubs, others just need a more thorough inspection than we've had time for yet. Everything in here defaults to "n" in defconfig. -Library code awaiting cleanup lives in lib/pending.c +Outside of this directory, several commands (and some library code) have +TODO annotations. -The following commands predate the pending directory, and are awaiting -cleanup but don't live here: - - vmstat, login, du, vconfig, mountpoint, chroot, cut, touch, - modinfo, expand, xargs - - lib/password.c +This directory should go away before the 1.0 release. It's just a staging +area so code submissions don't get lost while awaiting more thorough (and +very time consuming) scrutiny as described in www/cleanup.html. diff --git a/toys/pending/arp.c b/toys/pending/arp.c index 1153ebc..e725112 100644 --- a/toys/pending/arp.c +++ b/toys/pending/arp.c @@ -175,7 +175,7 @@ static int set_entry(void) flags = ATF_PERM | ATF_COM; if (toys.optargs[2]) check_flags(&flags, (toys.optargs+2)); req.arp_flags = flags; - strncpy(req.arp_dev, TT.device, sizeof(req.arp_dev)); + xstrncpy(req.arp_dev, TT.device, sizeof(req.arp_dev)); xioctl(TT.sockfd, SIOCSARP, &req); if (toys.optflags & FLAG_v) xprintf("Entry set for %s\n", toys.optargs[0]); @@ -204,7 +204,7 @@ static int delete_entry(void) flags = ATF_PERM; if (toys.optargs[1]) check_flags(&flags, (toys.optargs+1)); req.arp_flags = flags; - strncpy(req.arp_dev, TT.device, sizeof(req.arp_dev)); + xstrncpy(req.arp_dev, TT.device, sizeof(req.arp_dev)); xioctl(TT.sockfd, SIOCDARP, &req); if (toys.optflags & FLAG_v) xprintf("Delete entry for %s\n", toys.optargs[0]); diff --git a/toys/pending/arping.c b/toys/pending/arping.c index fe5c76b..3e522bd 100644 --- a/toys/pending/arping.c +++ b/toys/pending/arping.c @@ -63,7 +63,7 @@ static void get_interface(char *interface, int *ifindex, uint32_t *oip, int fd = xsocket(AF_INET, SOCK_RAW, IPPROTO_RAW); req.ifr_addr.sa_family = AF_INET; - strncpy(req.ifr_name, interface, IFNAMSIZ); + xstrncpy(req.ifr_name, interface, IFNAMSIZ); req.ifr_name[IFNAMSIZ-1] = '\0'; xioctl(fd, SIOCGIFFLAGS, &req); @@ -116,18 +116,18 @@ static void send_packet() ptr = mempcpy(ptr, &src_pk.sll_addr, src_pk.sll_halen); ptr = mempcpy(ptr, &src_addr, 4); - if (toys.optflags & FLAG_A) - ptr = mempcpy(ptr, &src_pk.sll_addr, src_pk.sll_halen); - else ptr = mempcpy(ptr, &dst_pk.sll_addr, src_pk.sll_halen); - + ptr = mempcpy(ptr, + (toys.optflags & FLAG_A) ? &src_pk.sll_addr : &dst_pk.sll_addr, + src_pk.sll_halen); ptr = mempcpy(ptr, &dest_addr, 4); + ret = sendto(TT.sockfd, sbuf, ptr - sbuf, 0, (struct sockaddr *)&dst_pk, sizeof(dst_pk)); if (ret == ptr - sbuf) { struct timeval tval; gettimeofday(&tval, NULL); - TT.sent_at = (tval.tv_sec * 1000000ULL + (tval.tv_usec)); + TT.sent_at = tval.tv_sec * 1000000ULL + tval.tv_usec; TT.sent_nr++; if (!TT.unicast_flag) TT.brd_sent++; } @@ -215,7 +215,7 @@ void arping_main(void) TT.sockfd = xsocket(AF_PACKET, SOCK_DGRAM, 0); memset(&ifr, 0, sizeof(ifr)); - strncpy(ifr.ifr_name, TT.iface, IFNAMSIZ); + xstrncpy(ifr.ifr_name, TT.iface, IFNAMSIZ); get_interface(TT.iface, &if_index, NULL, NULL); src_pk.sll_ifindex = if_index; diff --git a/toys/pending/bootchartd.c b/toys/pending/bootchartd.c index 72ade69..d903405 100644 --- a/toys/pending/bootchartd.c +++ b/toys/pending/bootchartd.c @@ -294,7 +294,6 @@ void bootchartd_main() putenv("PATH=/sbin:/usr/sbin:/bin:/usr/bin"); start_logging(); stop_logging(tmp_dir, bchartd_opt == 1 ? toys.optargs[1] : NULL); - free(tmp_dir); return; } waitpid(lgr_pid, NULL, WUNTRACED); @@ -310,7 +309,7 @@ void bootchartd_main() if (bchartd_opt == 1 && toys.optargs[1]) { pid_t prog_pid; - if (!(prog_pid = xfork())) xexec_optargs(1); + if (!(prog_pid = xfork())) xexec(toys.optargs+1); waitpid(prog_pid, NULL, 0); kill(lgr_pid, SIGUSR1); } diff --git a/toys/pending/brctl.c b/toys/pending/brctl.c index fecbf68..e3b1526 100644 --- a/toys/pending/brctl.c +++ b/toys/pending/brctl.c @@ -49,7 +49,7 @@ static void get_ports(char *bridge, int *indices) memset(ifindices, 0, MAX_BRIDGES); args[1] = (unsigned long)ifindices; - strncpy(ifr.ifr_name, bridge, IFNAMSIZ); + xstrncpy(ifr.ifr_name, bridge, IFNAMSIZ); ifr.ifr_data = (char *)args; xioctl(TT.sockfd, SIOCDEVPRIVATE, &ifr); if (indices) memcpy(indices, ifindices, sizeof(ifindices)); @@ -62,7 +62,7 @@ void get_br_info(char *bridge, struct __bridge_info *info) (unsigned long) info, 0, 0 }; memset(info, 0, sizeof(*info)); - strncpy(ifr.ifr_name, bridge, IFNAMSIZ); + xstrncpy(ifr.ifr_name, bridge, IFNAMSIZ); ifr.ifr_data = (char *)args; if (ioctl(TT.sockfd, SIOCDEVPRIVATE, &ifr) < 0) { @@ -118,7 +118,7 @@ void br_addbr(char **argv) #ifdef SIOCBRADDBR xioctl(TT.sockfd, SIOCBRADDBR, argv[0]); #else - strncpy(br, argv[0], IFNAMSIZ); + xstrncpy(br, argv[0], IFNAMSIZ); xioctl(TT.sockfd, SIOCSIFBR, args); #endif } @@ -131,7 +131,7 @@ void br_delbr(char **argv) #ifdef SIOCBRDELBR xioctl(TT.sockfd, SIOCBRDELBR, argv[0]); #else - strncpy(br, argv[0], IFNAMSIZ); + xstrncpy(br, argv[0], IFNAMSIZ); xioctl(TT.sockfd, SIOCSIFBR, args); #endif } @@ -148,7 +148,7 @@ void br_addif(char **argv) xioctl(TT.sockfd, SIOCBRADDIF, &ifr); #else args[1] = index; - strncpy(ifr.ifr_name, argv[0], IFNAMSIZ); + xstrncpy(ifr.ifr_name, argv[0], IFNAMSIZ); ifr.ifr_data = (char *)args; xioctl(TT.sockfd, SIOCDEVPRIVATE, &ifr); #endif @@ -166,7 +166,7 @@ void br_delif(char **argv) xioctl(TT.sockfd, SIOCBRDELIF, &ifr); #else args[1] = index; - strncpy(ifr.ifr_name, argv[0], IFNAMSIZ); + xstrncpy(ifr.ifr_name, argv[0], IFNAMSIZ); ifr.ifr_data = (char *)args; xioctl(TT.sockfd, SIOCDEVPRIVATE, &ifr); #endif @@ -194,7 +194,7 @@ void set_time(char *br, unsigned long cmd, unsigned long val) struct ifreq ifr; unsigned long args[4] = {cmd, val, 0, 0}; - strncpy(ifr.ifr_name, br, IFNAMSIZ); + xstrncpy(ifr.ifr_name, br, IFNAMSIZ); ifr.ifr_data = (char *)args; xioctl(TT.sockfd, SIOCDEVPRIVATE, &ifr); } @@ -270,7 +270,7 @@ void set_cost_prio(char *br, char *port, unsigned long cmd, unsigned long val) } if (i >= MAX_BRIDGES) error_exit("%s not in bridge", port); args[1] = i; - strncpy(ifr.ifr_name, br, IFNAMSIZ); + xstrncpy(ifr.ifr_name, br, IFNAMSIZ); ifr.ifr_data = (char *)args; xioctl(TT.sockfd, SIOCDEVPRIVATE, &ifr); } diff --git a/toys/pending/compress.c b/toys/pending/compress.c index bb4af46..1749ee4 100644 --- a/toys/pending/compress.c +++ b/toys/pending/compress.c @@ -5,7 +5,8 @@ * The inflate/deflate code lives here, so the various things that use it * either live here or call these commands to pipe data through them. * - * Divergence from posix: replace obsolete "compress" with mutiplexer. + * Divergence from posix: replace obsolete/patented "compress" with mutiplexer. + * (gzip already replaces "uncompress".) * * See RFCs 1950 (zlib), 1951 (deflate), and 1952 (gzip) * LSB 4.1 has gzip, gunzip, and zcat @@ -14,8 +15,10 @@ // Accept many different kinds of command line argument. // Leave Lrg at end so flag values line up. -USE_COMPRESS(NEWTOY(compress, "zcd9Lrg[-cd][!zgLr]", TOYFLAG_USR|TOYFLAG_BIN)) -USE_COMPRESS(NEWTOY(zcat, "aLrg[!aLrg]", TOYFLAG_USR|TOYFLAG_BIN)) +USE_COMPRESS(NEWTOY(compress, "zcd9lrg[-cd][!zgLr]", TOYFLAG_USR|TOYFLAG_BIN)) +USE_GZIP(NEWTOY(gzip, USE_GZIP_D("d")"19dcflqStvgLRz[!gLRz]", TOYFLAG_USR|TOYFLAG_BIN)) +USE_ZCAT(NEWTOY(zcat, 0, TOYFLAG_USR|TOYFLAG_BIN)) +USE_GUNZIP(NEWTOY(gunzip, "cflqStv", TOYFLAG_USR|TOYFLAG_BIN)) //zip unzip gzip gunzip zcat @@ -23,38 +26,114 @@ config COMPRESS bool "compress" default n help + usage: compress [-zgLR19] [FILE] + + Compress or decompress file (or stdin) using "deflate" algorithm. + + -1 min compression + -9 max compression (default) + -g gzip (default) + -L zlib + -R raw + -z zip + +config GZIP + bool "gzip" + default y + depends on COMPRESS + help + usage: gzip [-19cfqStvzgLR] [FILE...] + + Compess (deflate) file(s). With no files, compress stdin to stdout. + + On successful decompression, compressed files are replaced with the + uncompressed version. The input file is removed and replaced with + a new file without the .gz extension (with same ownership/permissions). + + -1 Minimal compression (fastest) + -9 Max compression (default) + -c cat to stdout (act as zcat) + -f force (if output file exists, input is tty, unrecognized extension) + -q quiet (no warnings) + -S specify exension (default .*) + -t test compressed file(s) + -v verbose (like -l, but compress files) + + Compression type: + -g gzip (default) -L zlib -R raw -z zip + +config GZIP_D + bool + default y + depends on GZIP && DECOMPRESS + help + usage: gzip [-d] + + -d decompress (act as gunzip) + +config DECOMPRESS + bool "decompress" + default n + help usage: compress [-zglrcd9] [FILE] Compress or decompress file (or stdin) using "deflate" algorithm. - -c compress with -g gzip (default) -L zlib -r raw -z zip + -c compress with -g gzip (default) -l zlib -r raw -z zip -d decompress (autodetects type) + config ZCAT bool "zcat" - default n - depends on COMPRESS + default y + depends on DECOMPRESS help usage: zcat [FILE...] Decompress deflated file(s) to stdout + +config GUNZIP + bool "gunzip" + default y + depends on DECOMPRESS + help + usage: gunzip [-cflqStv] [FILE...] + + Decompess (deflate) file(s). With no files, compress stdin to stdout. + + On successful decompression, compressed files are replaced with the + uncompressed version. The input file is removed and replaced with + a new file without the .gz extension (with same ownership/permissions). + + -c cat to stdout (act as zcat) + -f force (output file exists, input is tty, unrecognized extension) + -l list compressed/uncompressed/ratio/name for each input file. + -q quiet (no warnings) + -S specify exension (default .*) + -t test compressed file(s) + -v verbose (like -l, but decompress files) */ #define FOR_compress #include "toys.h" GLOBALS( - // base offset and extra bits tables (length and distance) + // Huffman codes: base offset and extra bits tables (length and distance) char lenbits[29], distbits[30]; unsigned short lenbase[29], distbase[30]; void *fixdisthuff, *fixlithuff; + // CRC void (*crcfunc)(char *data, int len); - unsigned crc, len; + unsigned crc; + + // Compressed data buffer + char *data; + unsigned pos, len; + int infd, outfd; - char *outbuf; - unsigned outlen; - int outfd; + // Tables only used for deflation + unsigned short *hashhead, *hashchain; ) // little endian bit buffer @@ -66,9 +145,8 @@ struct bitbuf { // malloc a struct bitbuf struct bitbuf *bitbuf_init(int fd, int size) { - struct bitbuf *bb = xmalloc(sizeof(struct bitbuf)+size); + struct bitbuf *bb = xzalloc(sizeof(struct bitbuf)+size); - memset(bb, 0, sizeof(struct bitbuf)); bb->max = size; bb->fd = fd; @@ -125,13 +203,44 @@ unsigned bitbuf_get(struct bitbuf *bb, int bits) return result; } -static void outbuf_crc(char sym) +void bitbuf_flush(struct bitbuf *bb) +{ + if (!bb->bitpos) return; + + xwrite(bb->fd, bb->buf, (bb->bitpos+7)/8); + memset(bb->buf, 0, bb->max); + bb->bitpos = 0; +} + +void bitbuf_put(struct bitbuf *bb, int data, int len) +{ + while (len) { + int click = bb->bitpos >> 3, blow, blen; + + // Flush buffer if necessary + if (click == bb->max) { + bitbuf_flush(bb); + click = 0; + } + blow = bb->bitpos & 7; + blen = 8-blow; + if (blen > len) blen = len; + bb->buf[click] |= data << blow; + bb->bitpos += blen; + data >>= blen; + len -= blen; + } +} + +static void output_byte(char sym) { - TT.outbuf[TT.outlen++ & 32767] = sym; + int pos = TT.pos++ & 32767; - if (!(TT.outlen & 32767)) { - xwrite(TT.outfd, TT.outbuf, 32768); - if (TT.crcfunc) TT.crcfunc(0, 32768); + TT.data[pos] = sym; + + if (!pos) { + xwrite(TT.outfd, TT.data, 32768); + if (TT.crcfunc) TT.crcfunc(TT.data, 32768); } } @@ -146,7 +255,7 @@ struct huff { // Create simple huffman tree from array of bit lengths. -// The symbols in deflate's huffman trees are sorted (first by bit length +// The symbols in the huffman trees are sorted (first by bit length // of the code to reach them, then by symbol number). This means that given // the bit length of each symbol, we can construct a unique tree. static void len2huff(struct huff *huff, char bitlen[], int len) @@ -166,9 +275,9 @@ static void len2huff(struct huff *huff, char bitlen[], int len) } // Fetch and decode next huffman coded symbol from bitbuf. -// This takes advantage of the the sorting to navigate the tree as an array: +// This takes advantage of the sorting to navigate the tree as an array: // each time we fetch a bit we have all the codes at that bit level in -// order with no gaps.. +// order with no gaps. static unsigned huff_and_puff(struct bitbuf *bb, struct huff *huff) { unsigned short *length = huff->length; @@ -185,7 +294,7 @@ static unsigned huff_and_puff(struct bitbuf *bb, struct huff *huff) return huff->symbol[start + offset]; } -// Decompress deflated data from bitbuf to filehandle. +// Decompress deflated data from bitbuf to TT.outfd. static void inflate(struct bitbuf *bb) { TT.crc = ~0; @@ -216,7 +325,7 @@ static void inflate(struct bitbuf *bb) // dump bytes until done or end of current bitbuf contents if (bblen > len) bblen = len; pos = bblen; - while (pos--) outbuf_crc(*(p++)); + while (pos--) output_byte(*(p++)); bitbuf_skip(bb, bblen << 3); len -= bblen; } @@ -276,7 +385,7 @@ static void inflate(struct bitbuf *bb) int sym = huff_and_puff(bb, lithuff); // Literal? - if (sym < 256) outbuf_crc(sym); + if (sym < 256) output_byte(sym); // Copy range? else if (sym > 256) { @@ -286,9 +395,9 @@ static void inflate(struct bitbuf *bb) len = TT.lenbase[sym] + bitbuf_get(bb, TT.lenbits[sym]); sym = huff_and_puff(bb, disthuff); dist = TT.distbase[sym] + bitbuf_get(bb, TT.distbits[sym]); - sym = TT.outlen & 32767; + sym = TT.pos & 32767; - while (len--) outbuf_crc(TT.outbuf[(TT.outlen-dist) & 32767]); + while (len--) output_byte(TT.data[(TT.pos-dist) & 32767]); // End of block } else break; @@ -299,18 +408,62 @@ static void inflate(struct bitbuf *bb) if (final) break; } - if (TT.outlen & 32767) { - xwrite(TT.outfd, TT.outbuf, TT.outlen & 32767); - if (TT.crcfunc) TT.crcfunc(0, TT.outlen & 32767); + if (TT.pos & 32767) { + xwrite(TT.outfd, TT.data, TT.pos & 32767); + if (TT.crcfunc) TT.crcfunc(TT.data, TT.pos & 32767); + } +} + +// Deflate from TT.infd to bitbuf +// For deflate, TT.len = input read, TT.pos = input consumed +static void deflate(struct bitbuf *bb) +{ + char *data = TT.data; + int len, final = 0; + + TT.crc = ~0; + + while (!final) { + // Read next half-window of data if we haven't hit EOF yet. + len = readall(TT.infd, data+(TT.len&32768), 32768); + if (len < 0) perror_exit("read"); // todo: add filename + if (len != 32768) final++; + if (TT.crcfunc) TT.crcfunc(data+(TT.len&32768), len); + // TT.len += len; crcfunc advances len + + // store block as literal + bitbuf_put(bb, final, 1); + bitbuf_put(bb, 0, 1); + + bitbuf_put(bb, 0, (8-bb->bitpos)&7); + bitbuf_put(bb, len, 16); + bitbuf_put(bb, 0xffff & ~len, 16); + + // repeat until spanked + while (TT.pos != TT.len) { + unsigned pos = TT.pos & 65535; + + bitbuf_put(bb, data[pos], 8); + + // need to refill buffer? + if (!(32767 & ++TT.pos) && !final) break; + } } + bitbuf_flush(bb); } -static void init_deflate(void) +// Allocate memory for deflate/inflate. +static void init_deflate(int compress) { int i, n = 1; - // Ye olde deflate window - TT.outbuf = xmalloc(32768); + // compress needs 64k data and 32k each for hashhead and hashchain. + // decompress just needs 32k data. + TT.data = xmalloc(32768*(compress ? 4 : 1)); + if (compress) { + TT.hashhead = (unsigned short *)(TT.data + 65536); + TT.hashchain = (unsigned short *)(TT.data + 65536 + 32768); + } // Calculate lenbits, lenbase, distbits, distbase *TT.lenbase = 3; @@ -365,11 +518,39 @@ void gzip_crc(char *data, int len) unsigned crc, *crc_table = (unsigned *)(toybuf+sizeof(toybuf)-1024); crc = TT.crc; - for (i=0; i<len; i++) crc = crc_table[(crc^TT.outbuf[i])&0xff] ^ (crc>>8); + for (i=0; i<len; i++) crc = crc_table[(crc^data[i])&0xff] ^ (crc>>8); TT.crc = crc; TT.len += len; } +static void do_gzip(int fd, char *name) +{ + struct bitbuf *bb = bitbuf_init(1, sizeof(toybuf)); + + // Header from RFC 1952 section 2.2: + // 2 ID bytes (1F, 8b), gzip method byte (8=deflate), FLAG byte (none), + // 4 byte MTIME (zeroed), Extra Flags (2=maximum compression), + // Operating System (FF=unknown) + + TT.infd = fd; + xwrite(bb->fd, "\x1f\x8b\x08\0\0\0\0\0\x02\xff", 10); + + // Use last 1k of toybuf for little endian crc table + crc_init((unsigned *)(toybuf+sizeof(toybuf)-1024), 1); + TT.crcfunc = gzip_crc; + + deflate(bb); + + // tail: crc32, len32 + + bitbuf_put(bb, 0, (8-bb->bitpos)&7); + bitbuf_put(bb, ~TT.crc, 32); + bitbuf_put(bb, TT.len, 32); + + bitbuf_flush(bb); + free(bb); +} + static void do_zcat(int fd, char *name) { struct bitbuf *bb = bitbuf_init(fd, sizeof(toybuf)); @@ -395,7 +576,8 @@ static void do_zcat(int fd, char *name) void compress_main(void) { - zcat_main(); + // todo: this + printf("hello world"); } //#define CLEANUP_compress @@ -404,7 +586,21 @@ void compress_main(void) void zcat_main(void) { - init_deflate(); + init_deflate(0); loopfiles(toys.optargs, do_zcat); } + +void gunzip_main(void) +{ + init_deflate(0); + + loopfiles(toys.optargs, do_zcat); +} + +void gzip_main(void) +{ + init_deflate(1); + + loopfiles(toys.optargs, do_gzip); +} diff --git a/toys/pending/dd.c b/toys/pending/dd.c index a5c2452..3449104 100644 --- a/toys/pending/dd.c +++ b/toys/pending/dd.c @@ -134,9 +134,9 @@ static void summary() fprintf(stderr,"%llu+%llu records in\n%llu+%llu records out\n", st.in_full, st.in_part, st.out_full, st.out_part); human_readable(toybuf, st.bytes); - fprintf(stderr, "%llu bytes (%s) copied,",st.bytes, toybuf); + fprintf(stderr, "%llu bytes (%s) copied, ",st.bytes, toybuf); human_readable(toybuf, st.bytes/seconds); - fprintf(stderr, "%f seconds, %s/s\n", seconds, toybuf); + fprintf(stderr, "%f s, %s/s\n", seconds, toybuf); } static void sig_handler(int sig) diff --git a/toys/pending/dhcp.c b/toys/pending/dhcp.c index bdaefca..f8226db 100644 --- a/toys/pending/dhcp.c +++ b/toys/pending/dhcp.c @@ -288,7 +288,7 @@ static int get_interface( char *interface, int *ifindex, uint32_t *oip, uint8_t int fd = xsocket(AF_INET, SOCK_RAW, IPPROTO_RAW); req.ifr_addr.sa_family = AF_INET; - strncpy(req.ifr_name, interface, IFNAMSIZ); + xstrncpy(req.ifr_name, interface, IFNAMSIZ); req.ifr_name[IFNAMSIZ-1] = '\0'; xioctl(fd, SIOCGIFFLAGS, &req); @@ -628,7 +628,7 @@ static int mode_app(void) close(state->sockfd); return -1; } - strncpy(ifr.ifr_name, state->iface, IFNAMSIZ); + xstrncpy(ifr.ifr_name, state->iface, IFNAMSIZ); ifr.ifr_name[IFNAMSIZ -1] = '\0'; setsockopt(state->sockfd, SOL_SOCKET, SO_BINDTODEVICE, &ifr, sizeof(ifr)); @@ -884,14 +884,16 @@ static uint8_t *dhcpc_addreqipaddr(struct in_addr *ipaddr, uint8_t *optptr) } // adds hostname to dhcp packet. -static uint8_t *dhcpc_addfdnname(uint8_t *optptr, char *hname) +static uint8_t *dhcpc_addfdnname(uint8_t *optptr, char *hname) { int size = strlen(hname); + *optptr++ = DHCP_OPTION_FQDN; *optptr++ = size + 3; *optptr++ = 0x1; //flags optptr += 2; // two blank bytes - strncpy((char*)optptr, hname, size); // name + strcpy((char*)optptr, hname); // name + return optptr + size; } @@ -1228,16 +1230,14 @@ static void renew(void) // Sends a IP release request. static void release(void) { - int len = sizeof("255.255.255.255\0"); - char buffer[len]; + char buffer[sizeof("255.255.255.255\0")]; struct in_addr temp_addr; mode_app(); // send release packet if (state->status == STATE_BOUND || state->status == STATE_RENEWING || state->status == STATE_REBINDING) { temp_addr.s_addr = htonl(server); - strncpy(buffer, inet_ntoa(temp_addr), sizeof(buffer)); - buffer[len - 1] = '\0'; + xstrncpy(buffer, inet_ntoa(temp_addr), sizeof(buffer)); temp_addr.s_addr = state->ipaddr.s_addr; infomsg( infomode, "Unicasting a release of %s to %s", inet_ntoa(temp_addr), buffer); dhcpc_sendmsg(DHCPRELEASE); diff --git a/toys/pending/dhcpd.c b/toys/pending/dhcpd.c index 7da5194..17f5009 100644 --- a/toys/pending/dhcpd.c +++ b/toys/pending/dhcpd.c @@ -2,19 +2,22 @@ * * Copyright 2013 Madhur Verma <mad.flexi@gmail.com> * Copyright 2013 Kyungwan Han <asura321@gamil.com> + * Copyright 2015 Yeongdeok Suh <skyducks111@gmail.com> * * No Standard -USE_DHCPD(NEWTOY(dhcpd, ">1P#<0>65535=67fS", TOYFLAG_SBIN|TOYFLAG_ROOTONLY)) +USE_DHCPD(NEWTOY(dhcpd, ">1P#<0>65535=67fi:S46[!46]", TOYFLAG_SBIN|TOYFLAG_ROOTONLY)) config DHCPD bool "dhcpd" default n help - usage: dhcpd [-fS] [-P N] [CONFFILE] + usage: dhcpd [-46fS] [-i IFACE] [-P N] [CONFFILE] -f Run in foreground + -i Interface to use -S Log to syslog too - -P N Use port N (default 67) + -P N Use port N (default ipv4 67, ipv6 547) + -4, -6 Run as a DHCPv4 or DHCPv6 server config DEBUG_DHCP bool "debugging messeges ON/OFF" @@ -22,6 +25,15 @@ config DEBUG_DHCP depends on DHCPD */ +/* + * Things to do + * + * - Working as an relay agent + * - Rapid commit option support + * - Additional packet options (commented on the middle of sources) + * - Create common modules + */ + #define FOR_dhcpd #include "toys.h" @@ -30,6 +42,7 @@ config DEBUG_DHCP // Todo: headers not in posix #include <netinet/ip.h> +#include <netinet/ip6.h> #include <netinet/udp.h> #include <netpacket/packet.h> @@ -57,6 +70,20 @@ config DEBUG_DHCP #define DHCPRELEASE 7 #define DHCPINFORM 8 +#define DHCP6SOLICIT 1 +#define DHCP6ADVERTISE 2 // server -> client +#define DHCP6REQUEST 3 +#define DHCP6CONFIRM 4 +#define DHCP6RENEW 5 +#define DHCP6REBIND 6 +#define DHCP6REPLY 7 // server -> client +#define DHCP6RELEASE 8 +#define DHCP6DECLINE 9 +#define DHCP6RECONFIGURE 10 // server -> client +#define DHCP6INFOREQUEST 11 +#define DHCP6RELAYFLOW 12 // relay -> relay/server +#define DHCP6RELAYREPLY 13 // server/relay -> relay + #define DHCP_NUM8 (1<<8) #define DHCP_NUM16 (1<<9) #define DHCP_NUM32 DHCP_NUM16 | DHCP_NUM8 @@ -78,10 +105,39 @@ config DEBUG_DHCP #define DHCP_OPT_PARAM_REQ DHCP_STRING | 0x37 // list of options client wants #define DHCP_OPT_END 0xff +// DHCPv6 option codes (partial). See RFC 3315 +#define DHCP6_OPT_CLIENTID 1 +#define DHCP6_OPT_SERVERID 2 +#define DHCP6_OPT_IA_NA 3 +#define DHCP6_OPT_IA_ADDR 5 +#define DHCP6_OPT_ORO 6 +#define DHCP6_OPT_PREFERENCE 7 +#define DHCP6_OPT_ELAPSED_TIME 8 +#define DHCP6_OPT_RELAY_MSG 9 +#define DHCP6_OPT_STATUS_CODE 13 +#define DHCP6_OPT_IA_PD 25 +#define DHCP6_OPT_IA_PREFIX 26 + +#define DHCP6_STATUS_SUCCESS 0 +#define DHCP6_STATUS_NOADDRSAVAIL 2 + +#define DHCP6_DUID_LLT 1 +#define DHCP6_DUID_EN 2 +#define DHCP6_DUID_LL 3 +#define DHCP6_DUID_UUID 4 + GLOBALS( + char *iface; long port; ); +struct config_keyword { + char *keyword; + int (*handler)(const char *str, void *var); + void *var; + char *def; +}; + typedef struct __attribute__((packed)) dhcp_msg_s { uint8_t op; uint8_t htype; @@ -101,18 +157,39 @@ typedef struct __attribute__((packed)) dhcp_msg_s { uint8_t options[308]; } dhcp_msg_t; +typedef struct __attribute__((packed)) dhcp6_msg_s { + uint8_t msgtype; + uint8_t transaction_id[3]; + uint8_t options[524]; +} dhcp6_msg_t; + typedef struct __attribute__((packed)) dhcp_raw_s { struct iphdr iph; struct udphdr udph; dhcp_msg_t dhcp; } dhcp_raw_t; +typedef struct __attribute__((packed)) dhcp6_raw_s { + struct ip6_hdr iph; + struct udphdr udph; + dhcp6_msg_t dhcp6; +} dhcp6_raw_t; + typedef struct static_lease_s { struct static_lease_s *next; uint32_t nip; int mac[6]; } static_lease; +typedef struct static_lease6_s { + struct static_lease6_s *next; + uint16_t duid_len; + uint16_t ia_type; + uint32_t iaid; + uint32_t nip6[4]; + uint8_t duid[20]; +} static_lease6; + typedef struct { uint32_t expires; uint32_t lease_nip; @@ -121,6 +198,15 @@ typedef struct { uint8_t pad[2]; } dyn_lease; +typedef struct { + uint16_t duid_len; + uint16_t ia_type; + uint32_t expires; + uint32_t iaid; + uint32_t lease_nip6[4]; + uint8_t duid[20]; +} dyn_lease6; + typedef struct option_val_s { char *key; uint16_t code; @@ -128,9 +214,32 @@ typedef struct option_val_s { size_t len; } option_val_t; +struct __attribute__((packed)) optval_duid_llt { + uint16_t type; + uint16_t hwtype; + uint32_t time; + uint8_t *lladdr; +}; + +struct __attribute__((packed)) optval_ia_na { + uint32_t iaid; + uint32_t t1, t2; + uint8_t *optval; +}; +struct __attribute__((packed)) optval_ia_addr { + uint32_t ipv6_addr[4]; + uint32_t pref_lifetime; + uint32_t valid_lifetime; +}; +struct __attribute__((packed)) optval_status_code { + uint16_t status_code; + uint8_t *status_msg; +}; + typedef struct __attribute__((__may_alias__)) server_config_s { char *interface; // interface to use int ifindex; + uint32_t server_nip6[4]; uint32_t server_nip; uint32_t port; uint8_t server_mac[6]; // our MAC address (used only for ARP probing) @@ -138,6 +247,8 @@ typedef struct __attribute__((__may_alias__)) server_config_s { /* start,end are in host order: we need to compare start <= ip <= end*/ uint32_t start_ip; // start address of leases, in host order uint32_t end_ip; // end of leases, in host order + uint32_t start_ip6[4]; // start address of leases, in IPv6 mode + uint32_t end_ip6[4]; // end of leases, in IPv6 mode uint32_t max_lease_sec; // maximum lease time (host order) uint32_t min_lease_sec; // minimum lease time a client can request uint32_t max_leases; // maximum number of leases (including reserved addresses) @@ -149,30 +260,36 @@ typedef struct __attribute__((__may_alias__)) server_config_s { uint32_t offer_time; // how long an offered address is reserved uint32_t siaddr_nip; // "next server" bootp option char *lease_file; + char *lease6_file; char *pidfile; char *notify_file; // what to run whenever leases are written char *sname; // bootp server name char *boot_file; // bootp boot file option + uint32_t pref_lifetime; + uint32_t valid_lifetime; + uint32_t t1,t2; struct static_lease *static_leases; // List of ip/mac pairs to assign static leases } server_config_t; typedef struct __attribute__((__may_alias__)) server_state_s { uint8_t rqcode; int listensock; - dhcp_msg_t rcvd_pkt; + union { + dhcp_msg_t rcvd_pkt; + dhcp6_msg_t rcvd_pkt6; + } rcvd; uint8_t* rqopt; - dhcp_msg_t send_pkt; - static_lease *sleases; + union { + dhcp_msg_t send_pkt; + dhcp6_msg_t send_pkt6; + } send; + union { + static_lease *sleases; + static_lease6 *sleases6; + } leases; struct arg_list *dleases; } server_state_t; -struct config_keyword { - char *keyword; - int (*handler)(const char *str, void *var); - void *var; - char *def; -}; - static option_val_t options_list[] = { {"lease" , DHCP_NUM32 | 0x33, NULL, 0}, {"subnet" , DHCP_IP | 0x01, NULL, 0}, @@ -211,12 +328,34 @@ static server_state_t gstate; static uint8_t infomode; static struct fd_pair sigfd; static int constone = 1; +static sa_family_t addr_version = AF_INET; + +static void htonl6(uint32_t *host_order, uint32_t *network_order) +{ + int i; + if(!host_order) { + error_msg("NULL ipv6 address"); + } else { + for(i=0;i<4;i++) network_order[i] = htonl(host_order[i]); + } +} + +static void ntohl6(uint32_t *network_order, uint32_t *host_order) +{ + int i; + if(!network_order) { + error_msg("NULL ipv6 address"); + } else { + for(i=0;i<4;i++) host_order[i] = ntohl(network_order[i]); + } +} // calculate options size. static int dhcp_opt_size(uint8_t *optionptr) { int i = 0; - for(;optionptr[i] != 0xff; i++) if(optionptr[i] != 0x00) i += optionptr[i + 1] + 2 -1; + for(;optionptr[i] != 0xff; i++) + if(optionptr[i] != 0x00) i += optionptr[i + 1] + 2 -1; return i; } @@ -239,25 +378,61 @@ static uint16_t dhcp_checksum(void *addr, int count) } // gets information of INTERFACE and updates IFINDEX, MAC and IP -static int get_interface(const char *interface, int *ifindex, uint32_t *oip, uint8_t *mac) +static int get_interface(const char *interface, int *ifindex, uint32_t *oip, + uint8_t *mac) { struct ifreq req; struct sockaddr_in *ip; - int fd = xsocket(AF_INET, SOCK_RAW, IPPROTO_RAW); + struct sockaddr_in6 ip6; + int fd = xsocket(addr_version, SOCK_RAW, IPPROTO_RAW); + char ipv6_addr[40] = {0,}; - req.ifr_addr.sa_family = AF_INET; - strncpy(req.ifr_name, interface, IFNAMSIZ); - req.ifr_name[IFNAMSIZ-1] = '\0'; + req.ifr_addr.sa_family = addr_version; + xstrncpy(req.ifr_name, (char *)interface, IFNAMSIZ); xioctl(fd, SIOCGIFFLAGS, &req); - + if (!(req.ifr_flags & IFF_UP)) return -1; - if (oip) { - xioctl(fd, SIOCGIFADDR, &req); - ip = (struct sockaddr_in*) &req.ifr_addr; - dbg("IP %s\n", inet_ntoa(ip->sin_addr)); - *oip = ntohl(ip->sin_addr.s_addr); + + if (addr_version == AF_INET6) { + + FILE *fd6 = fopen("/proc/net/if_inet6", "r"); + int i; + + while(fgets(toybuf, sizeof(toybuf), fd6)) { + if (!strstr(toybuf, interface)) + continue; + + if (sscanf(toybuf, "%32s \n", ipv6_addr) != 1) + continue; + + if (strstr(ipv6_addr, "fe80")) break; + } + fclose(fd6); + + if (oip) { + char *ptr = ipv6_addr+sizeof(ipv6_addr)-1; + + // convert giant hex string into colon-spearated ipv6 address by + // inserting ':' every 4 characters. + for (i = 32; i; i--) + if ((*(ptr--) = ipv6_addr[i])) if (!(i&3)) *(ptr--) = ':'; + + dbg("ipv6 %s\n", ipv6_addr); + if(inet_pton(AF_INET6, ipv6_addr, &ip6.sin6_addr) <= 0) + error_msg("inet : the ipv6 address is not proper"); + else + ntohl6(ip6.sin6_addr.s6_addr32, oip); + } + } else { + if (oip) { + xioctl(fd, SIOCGIFADDR, &req); + ip = (struct sockaddr_in*) &req.ifr_addr; + dbg("IP %s\n", inet_ntoa(ip->sin_addr)); + *oip = ntohl(ip->sin_addr.s_addr); + } } + if (ifindex) { xioctl(fd, SIOCGIFINDEX, &req); dbg("Adapter index %d\n", req.ifr_ifindex); @@ -268,6 +443,7 @@ static int get_interface(const char *interface, int *ifindex, uint32_t *oip, uin memcpy(mac, req.ifr_hwaddr.sa_data, 6); dbg("MAC %02x:%02x:%02x:%02x:%02x:%02x\n", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); } + close(fd); return 0; } @@ -351,6 +527,7 @@ static int strtou32(const char *str, void *var) base = 16; str+=2; } + long ret_val = strtol(str, &endptr, base); if (errno) infomsg(infomode, "config : Invalid num %s",str); else if (endptr && (*endptr!='\0'||endptr == str)) @@ -371,17 +548,15 @@ static int strinvar(const char *str, void *var) // IP String STR to binary data. static int striptovar(const char *str, void *var) { - in_addr_t addr; *((uint32_t*)(var)) = 0; if(!str) { error_msg("config : NULL address string \n"); return -1; } - if((addr = inet_addr(str)) == -1) { - error_msg("config : wrong address %s \n",str ); + if((inet_pton(AF_INET6, str, var)<=0) && (inet_pton(AF_INET, str, var)<=0)) { + error_msg("config : wrong address %s \n", str); return -1; } - *((uint32_t*)(var)) = (uint32_t)addr; return 0; } @@ -527,14 +702,14 @@ static int get_staticlease(const char *str, void *var) } } striptovar(tkip, &sltmp->nip); - sltmp->next = gstate.sleases; - gstate.sleases = sltmp; + sltmp->next = gstate.leases.sleases; + gstate.leases.sleases = sltmp; return 0; } static struct config_keyword keywords[] = { -// keyword handler variable address default +// keyword handler variable address default {"start" , striptovar , (void*)&gconfig.start_ip , "192.168.0.20"}, {"end" , striptovar , (void*)&gconfig.end_ip , "192.168.0.254"}, {"interface" , strinvar , (void*)&gconfig.interface , "eth0"}, @@ -546,6 +721,7 @@ static struct config_keyword keywords[] = { {"conflict_time", strtou32 , (void*)&gconfig.conflict_time, "3600"}, {"offer_time" , strtou32 , (void*)&gconfig.offer_time , "60"}, {"lease_file" , strinvar , (void*)&gconfig.lease_file , "/var/lib/misc/dhcpd.leases"}, //LEASES_FILE + {"lease6_file" , strinvar , (void*)&gconfig.lease6_file , "/var/lib/misc/dhcpd6.leases"}, //LEASES_FILE {"pidfile" , strinvar , (void*)&gconfig.pidfile , "/var/run/dhcpd.pid"}, //DPID_FILE {"siaddr" , striptovar , (void*)&gconfig.siaddr_nip , "0.0.0.0"}, {"option" , strtoopt , (void*)&gconfig.options , ""}, @@ -554,6 +730,12 @@ static struct config_keyword keywords[] = { {"sname" , strinvar , (void*)&gconfig.sname , ""}, {"boot_file" , strinvar , (void*)&gconfig.boot_file , ""}, {"static_lease" , get_staticlease , (void*)&gconfig.static_leases, ""}, + {"start6" , striptovar , (void*)&gconfig.start_ip6 , "2001:620:40b:555::100"}, + {"end6" , striptovar , (void*)&gconfig.end_ip6 , "2001:620:40b:555::200"}, + {"preferred_lifetime" , strtou32 , (void*)&gconfig.pref_lifetime, "3600"}, + {"valid_lifetime" , strtou32 , (void*)&gconfig.valid_lifetime, "7200"}, + {"t1" , strtou32 , (void*)&gconfig.t1 , "3600"}, + {"t2" , strtou32 , (void*)&gconfig.t2 , "5400"}, }; // Parses the server config file and updates the global server config accordingly. @@ -564,7 +746,8 @@ static int parse_server_config(char *config_file, struct config_keyword *confkey int len, linelen, tcount, count, size = ARRAY_LEN(keywords); for (count = 0; count < size; count++) - if (confkey[count].handler) confkey[count].handler(confkey[count].def, confkey[count].var); + if (confkey[count].handler) + confkey[count].handler(confkey[count].def, confkey[count].var); if (!(fs = fopen(config_file, "r"))) perror_msg("%s", config_file); for (len = 0, linelen = 0; fs;) { @@ -608,6 +791,54 @@ free_conf_continue: return 0; } +// opens UDP socket for listen ipv6 packets +static int open_listensock6(void) +{ + struct sockaddr_in6 addr6; + struct ipv6_mreq mreq; + + if (gstate.listensock > 0) close(gstate.listensock); + + dbg("Opening listen socket on *:%d %s\n", gconfig.port, gconfig.interface); + + gstate.listensock = xsocket(PF_INET6, SOCK_DGRAM, 0); + setsockopt(gstate.listensock, SOL_SOCKET, SO_REUSEADDR, &constone, sizeof(constone)); + + if (setsockopt(gstate.listensock, IPPROTO_IPV6, IPV6_RECVPKTINFO, &constone, + sizeof(constone)) == -1) { + error_msg("failed to receive ipv6 packets.\n"); + close(gstate.listensock); + return -1; + } + + setsockopt(gstate.listensock, SOL_SOCKET, SO_BINDTODEVICE, gconfig.interface, strlen(gconfig.interface)+1); + + memset(&addr6, 0, sizeof(addr6)); + addr6.sin6_family = AF_INET6; + addr6.sin6_port = (flag_chk(FLAG_P))?htons(TT.port):htons(gconfig.port); //SERVER_PORT + addr6.sin6_scope_id = if_nametoindex(gconfig.interface); + //Listening for multicast packet + inet_pton(AF_INET6, "ff02::1:2", &addr6.sin6_addr); + + if (bind(gstate.listensock, (struct sockaddr *) &addr6, sizeof(addr6)) == -1) { + close(gstate.listensock); + perror_exit("bind failed"); + } + + memset(&mreq, 0, sizeof(mreq)); + mreq.ipv6mr_interface = if_nametoindex(gconfig.interface); + memcpy(&mreq.ipv6mr_multiaddr, &addr6.sin6_addr, sizeof(addr6.sin6_addr)); + + if(setsockopt(gstate.listensock, IPPROTO_IPV6, IPV6_JOIN_GROUP, &mreq, sizeof(mreq)) == -1) { + error_msg("failed to join a multicast group.\n"); + close(gstate.listensock); + return -1; + } + + dbg("OPEN : success\n"); + return 0; +} + // opens UDP socket for listen static int open_listensock(void) { @@ -620,18 +851,17 @@ static int open_listensock(void) gstate.listensock = xsocket(PF_INET, SOCK_DGRAM, IPPROTO_UDP); setsockopt(gstate.listensock, SOL_SOCKET, SO_REUSEADDR, &constone, sizeof(constone)); if (setsockopt(gstate.listensock, SOL_SOCKET, SO_BROADCAST, &constone, sizeof(constone)) == -1) { - dbg("OPEN : brodcast ioctl failed.\n"); - close(gstate.listensock); - return -1; + error_msg("failed to receive brodcast packets.\n"); + close(gstate.listensock); + return -1; } memset(&ifr, 0, sizeof(ifr)); - strncpy(ifr.ifr_name, gconfig.interface, IFNAMSIZ); - ifr.ifr_name[IFNAMSIZ -1] = '\0'; + xstrncpy(ifr.ifr_name, gconfig.interface, IFNAMSIZ); setsockopt(gstate.listensock, SOL_SOCKET, SO_BINDTODEVICE, &ifr, sizeof(ifr)); memset(&addr, 0, sizeof(addr)); addr.sin_family = AF_INET; - addr.sin_port = (flag_chk(FLAG_P))?htons(TT.port):htons(67); //SERVER_PORT + addr.sin_port = (flag_chk(FLAG_P))?htons(TT.port):htons(gconfig.port); //SERVER_PORT addr.sin_addr.s_addr = INADDR_ANY ; if (bind(gstate.listensock, (struct sockaddr *) &addr, sizeof(addr))) { @@ -642,6 +872,65 @@ static int open_listensock(void) return 0; } +static int send_packet6(uint8_t relay, uint8_t *client_lla, uint16_t optlen) +{ + struct sockaddr_ll dest_sll; + dhcp6_raw_t packet; + unsigned padding; + int fd, result = -1; + uint32_t front, back; + + memset(&packet, 0, sizeof(dhcp6_raw_t)); + memcpy(&packet.dhcp6, &gstate.send.send_pkt6, sizeof(dhcp6_msg_t)); + padding = sizeof(packet.dhcp6.options) - optlen; + + if ((fd = socket(PF_PACKET, SOCK_DGRAM, htons(ETH_P_IPV6))) < 0) { + dbg("SEND : ipv6 socket failed\n"); + return -1; + } + memset(&dest_sll, 0, sizeof(dest_sll)); + dest_sll.sll_family = AF_PACKET; + dest_sll.sll_protocol = htons(ETH_P_IPV6); + dest_sll.sll_ifindex = gconfig.ifindex; + dest_sll.sll_halen = ETH_ALEN; + memcpy(dest_sll.sll_addr, client_lla, sizeof(client_lla)); + + if (bind(fd, (struct sockaddr *) &dest_sll, sizeof(dest_sll)) < 0) { + dbg("SEND : bind failed\n"); + close(fd); + return -1; + } + memcpy(&packet.iph.ip6_src, &gconfig.server_nip6, sizeof(uint32_t)*4); + //HW addr to Link-Local addr + inet_pton(AF_INET6, "fe80::0200:00ff:fe00:0000", &packet.iph.ip6_dst); + ntohl6(packet.iph.ip6_dst.__in6_u.__u6_addr32,packet.iph.ip6_dst.__in6_u.__u6_addr32); + front = ntohl(*(uint32_t*)(client_lla+3) & 0x00ffffff) >> 8; + back = ntohl(*(uint32_t*)(client_lla) & 0x00ffffff); + packet.iph.ip6_dst.__in6_u.__u6_addr32[3] = + packet.iph.ip6_dst.__in6_u.__u6_addr32[3] | front; + packet.iph.ip6_dst.__in6_u.__u6_addr32[2] = + packet.iph.ip6_dst.__in6_u.__u6_addr32[2] | back; + htonl6(packet.iph.ip6_dst.__in6_u.__u6_addr32,packet.iph.ip6_dst.__in6_u.__u6_addr32); + + packet.udph.source = htons(gconfig.port); + packet.udph.dest = htons(546); + packet.udph.len = htons(sizeof(dhcp6_raw_t) - sizeof(struct ip6_hdr) - padding); + packet.iph.ip6_ctlun.ip6_un1.ip6_un1_plen = htons(ntohs(packet.udph.len) + 0x11); + packet.udph.check = dhcp_checksum(&packet, sizeof(dhcp6_raw_t) - padding); + packet.iph.ip6_ctlun.ip6_un1.ip6_un1_flow = htonl(0x60000000); + packet.iph.ip6_ctlun.ip6_un1.ip6_un1_plen = packet.udph.len; + packet.iph.ip6_ctlun.ip6_un1.ip6_un1_nxt = IPPROTO_UDP; + packet.iph.ip6_ctlun.ip6_un1.ip6_un1_hlim = 0x64; + + result = sendto(fd, &packet, sizeof(dhcp6_raw_t)-padding, + 0, (struct sockaddr *) &dest_sll, sizeof(dest_sll)); + + dbg("sendto %d\n", result); + close(fd); + if (result < 0) dbg("PACKET send error\n"); + return result; +} + // Sends data through raw socket. static int send_packet(uint8_t broadcast) { @@ -652,7 +941,7 @@ static int send_packet(uint8_t broadcast) uint8_t bmacaddr[6] = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff}; memset(&packet, 0, sizeof(dhcp_raw_t)); - memcpy(&packet.dhcp, &gstate.send_pkt, sizeof(dhcp_msg_t)); + memcpy(&packet.dhcp, &gstate.send.send_pkt, sizeof(dhcp_msg_t)); if ((fd = socket(PF_PACKET, SOCK_DGRAM, htons(ETH_P_IP))) < 0) { dbg("SEND : socket failed\n"); @@ -663,17 +952,17 @@ static int send_packet(uint8_t broadcast) dest_sll.sll_protocol = htons(ETH_P_IP); dest_sll.sll_ifindex = gconfig.ifindex; dest_sll.sll_halen = 6; - memcpy(dest_sll.sll_addr, (broadcast)?bmacaddr:gstate.rcvd_pkt.chaddr , 6); + memcpy(dest_sll.sll_addr, (broadcast)?bmacaddr:gstate.rcvd.rcvd_pkt.chaddr , 6); if (bind(fd, (struct sockaddr *) &dest_sll, sizeof(dest_sll)) < 0) { dbg("SEND : bind failed\n"); close(fd); return -1; } - padding = 308 - 1 - dhcp_opt_size(gstate.send_pkt.options); + padding = 308 - 1 - dhcp_opt_size(gstate.send.send_pkt.options); packet.iph.protocol = IPPROTO_UDP; packet.iph.saddr = gconfig.server_nip; - packet.iph.daddr = (broadcast || (gstate.rcvd_pkt.ciaddr == 0))?INADDR_BROADCAST:gstate.rcvd_pkt.ciaddr; + packet.iph.daddr = (broadcast || (gstate.rcvd.rcvd_pkt.ciaddr == 0))?INADDR_BROADCAST:gstate.rcvd.rcvd_pkt.ciaddr; packet.udph.source = htons(67);//SERVER_PORT packet.udph.dest = htons(68); //CLIENT_PORT packet.udph.len = htons(sizeof(dhcp_raw_t) - sizeof(struct iphdr) - padding); @@ -694,26 +983,45 @@ static int send_packet(uint8_t broadcast) return result; } +static int read_packet6(void) +{ + int ret; + + memset(&gstate.rcvd.rcvd_pkt6, 0, sizeof(dhcp6_msg_t)); + ret = read(gstate.listensock, &gstate.rcvd.rcvd_pkt6, sizeof(dhcp6_msg_t)); + if (ret < 0) { + dbg("Packet read error, ignoring. \n"); + return ret; // returns -1 + } + if (gstate.rcvd.rcvd_pkt6.msgtype < 1) { + dbg("Bad message type, igroning. \n"); + return -2; + } + + dbg("Received an ipv6 packet. Size : %d \n", ret); + return ret; +} + // Reads from UDP socket static int read_packet(void) { int ret; - memset(&gstate.rcvd_pkt, 0, sizeof(dhcp_msg_t)); - ret = read(gstate.listensock, &gstate.rcvd_pkt, sizeof(dhcp_msg_t)); + memset(&gstate.rcvd.rcvd_pkt, 0, sizeof(dhcp_msg_t)); + ret = read(gstate.listensock, &gstate.rcvd.rcvd_pkt, sizeof(dhcp_msg_t)); if (ret < 0) { dbg("Packet read error, ignoring. \n"); return ret; // returns -1 } - if (gstate.rcvd_pkt.cookie != htonl(DHCP_MAGIC)) { + if (gstate.rcvd.rcvd_pkt.cookie != htonl(DHCP_MAGIC)) { dbg("Packet with bad magic, ignoring. \n"); return -2; } - if (gstate.rcvd_pkt.op != 1) { //BOOTPREQUEST + if (gstate.rcvd.rcvd_pkt.op != 1) { //BOOTPREQUEST dbg("Not a BOOT REQUEST ignoring. \n"); return -2; } - if (gstate.rcvd_pkt.hlen != 6) { + if (gstate.rcvd.rcvd_pkt.hlen != 6) { dbg("hlen != 6 ignoring. \n"); return -2; } @@ -724,16 +1032,24 @@ static int read_packet(void) // Preapres a dhcp packet with defaults and configs static uint8_t* prepare_send_pkt(void) { - memset((void*)&gstate.send_pkt, 0, sizeof(gstate.send_pkt)); - gstate.send_pkt.op = 2; //BOOTPREPLY - gstate.send_pkt.htype = 1; - gstate.send_pkt.hlen = 6; - gstate.send_pkt.xid = gstate.rcvd_pkt.xid; - gstate.send_pkt.cookie = htonl(DHCP_MAGIC); - gstate.send_pkt.nsiaddr = gconfig.server_nip; - memcpy(gstate.send_pkt.chaddr, gstate.rcvd_pkt.chaddr, 16); - gstate.send_pkt.options[0] = DHCP_OPT_END; - return gstate.send_pkt.options; + memset((void*)&gstate.send.send_pkt, 0, sizeof(gstate.send.send_pkt)); + gstate.send.send_pkt.op = 2; //BOOTPREPLY + gstate.send.send_pkt.htype = 1; + gstate.send.send_pkt.hlen = 6; + gstate.send.send_pkt.xid = gstate.rcvd.rcvd_pkt.xid; + gstate.send.send_pkt.cookie = htonl(DHCP_MAGIC); + gstate.send.send_pkt.nsiaddr = gconfig.server_nip; + memcpy(gstate.send.send_pkt.chaddr, gstate.rcvd.rcvd_pkt.chaddr, 16); + gstate.send.send_pkt.options[0] = DHCP_OPT_END; + return gstate.send.send_pkt.options; +} + +static uint8_t* prepare_send_pkt6(uint16_t opt) +{ + memset((void*)&gstate.send.send_pkt6, 0, sizeof(gstate.send.send_pkt6)); + gstate.send.send_pkt6.msgtype = opt; + memcpy(gstate.send.send_pkt6.transaction_id, gstate.rcvd.rcvd_pkt6.transaction_id, 3); + return gstate.send.send_pkt6.options; } // Sets a option value in dhcp packet's option field @@ -748,6 +1064,15 @@ static uint8_t* set_optval(uint8_t *optptr, uint16_t opt, void *var, size_t len) return optptr; } +static uint8_t* set_optval6(uint8_t *optptr, uint16_t opt, void *var, size_t len) +{ + *((uint16_t*)optptr) = htons(opt); + *(uint16_t*)(optptr+2) = htons(len); + memcpy(optptr+4, var, len); + optptr += len+4; + return optptr; +} + // Gets a option value from dhcp packet's option field static uint8_t* get_optval(uint8_t *optptr, uint16_t opt, void *var) { @@ -788,8 +1113,35 @@ static uint8_t* get_optval(uint8_t *optptr, uint16_t opt, void *var) } optptr += len + 2; } - if ((overloaded == 1) | (overloaded == 3)) get_optval((uint8_t*)&gstate.rcvd_pkt.file, opt, var); - if ((overloaded == 2) | (overloaded == 3)) get_optval((uint8_t*)&gstate.rcvd_pkt.sname, opt, var); + if ((overloaded == 1) | (overloaded == 3)) get_optval((uint8_t*)&gstate.rcvd.rcvd_pkt.file, opt, var); + if ((overloaded == 2) | (overloaded == 3)) get_optval((uint8_t*)&gstate.rcvd.rcvd_pkt.sname, opt, var); + return optptr; +} + +static uint8_t* get_optval6(uint8_t *optptr, uint16_t opt, uint16_t *datalen, void **var) +{ + uint16_t optcode; + uint16_t len; + + memcpy(&optcode, optptr, sizeof(uint16_t)); + memcpy(&len, optptr+2, sizeof(uint16_t)); + if(!optcode) { + dbg("Option %d is not exist.\n", opt); + return optptr; + } + optcode = ntohs(optcode); + len = ntohs(len); + + if (opt == optcode) { + *var = xmalloc(len); + memcpy(*var, optptr+4, len); + optptr = optptr + len + 4; + memcpy(datalen, &len, sizeof(uint16_t)); + } + else { + optptr = get_optval6(optptr+len+4, opt, datalen, var); + } + return optptr; } @@ -798,7 +1150,7 @@ static uint8_t get_reqparam(uint8_t **list) { uint8_t len, *optptr; if(*list) free(*list); - for (optptr = gstate.rcvd_pkt.options; + for (optptr = gstate.rcvd.rcvd_pkt.options; *optptr && *optptr!=((DHCP_OPT_PARAM_REQ) & 0x00FF); optptr+=optptr[1]+2); len = *++optptr; *list = xzalloc(len+1); @@ -859,7 +1211,7 @@ static void run_notify(char **argv) dbg("script complete.\n"); } -static int write_leasefile(void) +static void write_leasefile(void) { int fd; uint32_t curr, tmp_time; @@ -867,35 +1219,70 @@ static int write_leasefile(void) struct arg_list *listdls = gstate.dleases; dyn_lease *dls; - if ((fd = open(gconfig.lease_file, O_WRONLY | O_CREAT | O_TRUNC)) < 0) { + if ((fd = open(gconfig.lease_file, O_WRONLY | O_CREAT | O_TRUNC, 0600)) < 0) { perror_msg("can't open %s ", gconfig.lease_file); - return fd; + } else { + curr = timestamp = time(NULL); + timestamp = SWAP_BE64(timestamp); + writeall(fd, ×tamp, sizeof(timestamp)); + + while (listdls) { + dls = (dyn_lease*)listdls->arg; + tmp_time = dls->expires; + dls->expires -= curr; + if ((int32_t) dls->expires < 0) goto skip; + dls->expires = htonl(dls->expires); + writeall(fd, dls, sizeof(dyn_lease)); +skip: + dls->expires = tmp_time; + listdls = listdls->next; + } + close(fd); + if (gconfig.notify_file) { + char *argv[3]; + argv[0] = gconfig.notify_file; + argv[1] = gconfig.lease_file; + argv[2] = NULL; + run_notify(argv); + } } +} - curr = timestamp = time(NULL); - timestamp = SWAP_BE64(timestamp); - writeall(fd, ×tamp, sizeof(timestamp)); +static void write_lease6file(void) +{ + int fd; + uint32_t curr, tmp_time; + int64_t timestamp; + struct arg_list *listdls = gstate.dleases; + dyn_lease6 *dls6; - while (listdls) { - dls = (dyn_lease*)listdls->arg; - tmp_time = dls->expires; - dls->expires -= curr; - if ((int32_t) dls->expires < 0) goto skip; - dls->expires = htonl(dls->expires); - writeall(fd, dls, sizeof(dyn_lease)); + if ((fd = open(gconfig.lease6_file, O_WRONLY | O_CREAT | O_TRUNC, 0600)) < 0) { + perror_msg("can't open %s ", gconfig.lease6_file); + } else { + curr = timestamp = time(NULL); + timestamp = SWAP_BE64(timestamp); + writeall(fd, ×tamp, sizeof(timestamp)); + + while (listdls) { + dls6 = (dyn_lease6*)listdls->arg; + tmp_time = dls6->expires; + dls6->expires -= curr; + if ((int32_t) dls6->expires < 0) goto skip; + dls6->expires = htonl(dls6->expires); + writeall(fd, dls6, sizeof(dyn_lease6)); skip: - dls->expires = tmp_time; - listdls = listdls->next; - } - close(fd); - if (gconfig.notify_file) { - char *argv[3]; - argv[0] = gconfig.notify_file; - argv[1] = gconfig.lease_file; - argv[2] = NULL; - run_notify(argv); + dls6->expires = tmp_time; + listdls = listdls->next; + } + close(fd); + if (gconfig.notify_file) { + char *argv[3]; + argv[0] = gconfig.notify_file; + argv[1] = gconfig.lease6_file; + argv[2] = NULL; + run_notify(argv); + } } - return 0; } // Update max lease time from options. @@ -915,15 +1302,52 @@ static uint32_t get_lease(uint32_t req_exp) { uint32_t now = time(NULL); req_exp = req_exp - now; - if ((req_exp <= 0) || (req_exp > gconfig.max_lease_sec)) - return gconfig.max_lease_sec; + if(addr_version == AF_INET6) { + if ((req_exp <= 0) || req_exp > gconfig.pref_lifetime || + req_exp > gconfig.valid_lifetime) { + if ((gconfig.pref_lifetime > gconfig.valid_lifetime)) { + error_msg("The valid lifetime must be greater than the preferred lifetime, \ + setting to valid lifetime", gconfig.valid_lifetime); + return gconfig.valid_lifetime; + } + return gconfig.pref_lifetime; + } + } else { + if ((req_exp <= 0) || (req_exp > gconfig.max_lease_sec)) + return gconfig.max_lease_sec; - if (req_exp < gconfig.min_lease_sec) - return gconfig.min_lease_sec; + if (req_exp < gconfig.min_lease_sec) + return gconfig.min_lease_sec; + } return req_exp; } +static int verifyip6_in_lease(uint32_t *nip6, uint8_t *duid, uint16_t ia_type, uint32_t iaid) +{ + static_lease6 *sls6; + struct arg_list *listdls; + uint32_t tmpnip6[4] = {0,}; + + for (listdls = gstate.dleases; listdls; listdls = listdls->next) { + if (!memcmp(((dyn_lease6*) listdls->arg)->lease_nip6, nip6, sizeof(uint32_t)*4)) + return -1; + + if (!memcmp(((dyn_lease6*) listdls->arg)->duid, duid, ((dyn_lease6*) listdls->arg)->duid_len) + && ((dyn_lease6*) listdls->arg)->ia_type == ia_type) + return -1; + } + for (sls6 = gstate.leases.sleases6; sls6; sls6 = sls6->next) + if (memcmp(sls6->nip6, nip6, sizeof(uint32_t)*4)==0) return -2; + + ntohl6(nip6, tmpnip6); + if (memcmp(tmpnip6, gconfig.start_ip6, sizeof(tmpnip6)) < 0 || + memcmp(tmpnip6, gconfig.end_ip6, sizeof(tmpnip6)) > 0) + return -3; + + return 0; +} + // Verify ip NIP in current leases ( assigned or not) static int verifyip_in_lease(uint32_t nip, uint8_t mac[6]) { @@ -938,7 +1362,7 @@ static int verifyip_in_lease(uint32_t nip, uint8_t mac[6]) } if (!memcmp(((dyn_lease*) listdls->arg)->lease_mac, mac, 6)) return -1; } - for (sls = gstate.sleases; sls; sls = sls->next) + for (sls = gstate.leases.sleases; sls; sls = sls->next) if (sls->nip == nip) return -2; if ((ntohl(nip) < gconfig.start_ip) || (ntohl(nip) > gconfig.end_ip)) @@ -979,6 +1403,39 @@ static int addip_to_lease(uint32_t assigned_nip, uint8_t mac[6], uint32_t *req_e return 0; } +static int addip6_to_lease(uint32_t *assigned_nip, uint8_t *duid, uint16_t ia_type, uint32_t iaid, uint32_t *lifetime, uint8_t update) +{ + dyn_lease6 *dls6; + struct arg_list *listdls = gstate.dleases; + uint32_t now = time(NULL); + + while (listdls) { + if (!memcmp(((dyn_lease6*) listdls->arg)->duid, duid, ((dyn_lease6*) listdls->arg)->duid_len)) { + if (update) *lifetime = get_lease(*lifetime + ((dyn_lease6*) listdls->arg)->expires); + ((dyn_lease6*) listdls->arg)->expires = *lifetime + now; + return 0; + } + listdls = listdls->next; + } + + dls6 = xzalloc(sizeof(dyn_lease6)); + dls6->duid_len = sizeof(duid); + memcpy(dls6->duid, duid, dls6->duid_len); + dls6->ia_type = ia_type; + dls6->iaid = iaid; + memcpy(dls6->lease_nip6, assigned_nip, sizeof(uint32_t)*4); + + if (update) *lifetime = get_lease(*lifetime + now); + dls6->expires = *lifetime + now; + + listdls = xzalloc(sizeof(struct arg_list)); + listdls->next = gstate.dleases; + listdls->arg = (char*)dls6; + gstate.dleases = listdls; + + return 0; +} + // delete ip assigned_nip from dynamic lease. static int delip_from_lease(uint32_t assigned_nip, uint8_t mac[6], uint32_t del_time) { @@ -998,7 +1455,7 @@ static int delip_from_lease(uint32_t assigned_nip, uint8_t mac[6], uint32_t del_ static uint32_t getip_from_pool(uint32_t req_nip, uint8_t mac[6], uint32_t *req_exp, char *hostname) { uint32_t nip = 0; - static_lease *sls = gstate.sleases; + static_lease *sls = gstate.leases.sleases; struct arg_list *listdls = gstate.dleases, *tmp = NULL; if (req_nip && (!verifyip_in_lease(req_nip, mac))) nip = req_nip; @@ -1042,44 +1499,142 @@ static uint32_t getip_from_pool(uint32_t req_nip, uint8_t mac[6], uint32_t *req_ return nip; } -static int read_leasefile(void) +static uint32_t *getip6_from_pool(uint8_t *duid, uint16_t duid_len, uint16_t ia_type, uint32_t iaid, uint32_t *lifetime) +{ + uint32_t nip6[4] = {0,}; + static_lease6 *sls6 = gstate.leases.sleases6; + struct arg_list *listdls6 = gstate.dleases, *tmp = NULL; + + while(listdls6) { + if (!memcmp(((dyn_lease6*)listdls6->arg)->duid, duid, duid_len)) { + memcpy(nip6, ((dyn_lease6*)listdls6->arg)->lease_nip6, sizeof(nip6)); + if(tmp) tmp->next = listdls6->next; + else gstate.dleases = listdls6->next; + free(listdls6->arg); + free(listdls6); + + if(verifyip6_in_lease(nip6, duid, ia_type, iaid) < 0) + memset(nip6, 0, sizeof(nip6)); + break; + } + tmp = listdls6; + listdls6 = listdls6->next; + } + + if(!nip6[0] && !nip6[1] && !nip6[2] && !nip6[3]) { + while(sls6) { + if(!memcmp(sls6->duid, duid, 6)) { + memcpy(nip6, sls6->nip6, sizeof(nip6)); + break; + } + sls6 = sls6->next; + } + } + + if(!nip6[0] && !nip6[1] && !nip6[2] && !nip6[3]) { + uint32_t tmpip6[4] = {0,}; + int i=3; + memcpy(tmpip6, gconfig.start_ip6, sizeof(tmpip6)); + htonl6(gconfig.start_ip6, nip6); + while(memcmp(tmpip6, gconfig.end_ip6, sizeof(tmpip6))<=0) { + if(!verifyip6_in_lease(nip6, duid, ia_type, iaid)) break; + ntohl6(nip6, tmpip6); + while(i--) { + if (tmpip6[i] == 0xffff) { + tmpip6[i] = 0x0001; + } else { + ++tmpip6[i]; + break; + } + } + htonl6(tmpip6, nip6); + } + + ntohl6(nip6, tmpip6); + if (memcmp(tmpip6, gconfig.end_ip6, sizeof(tmpip6))>0) { + memset(nip6, 0, sizeof(nip6)); + infomsg(infomode, "can't find free IP in IPv6 Pool."); + } + } + + if(nip6[0] && nip6[1] && nip6[2] && nip6[3]) + addip6_to_lease(nip6, duid, ia_type, iaid, lifetime, 1); + return nip6; +} + +static void read_leasefile(void) { uint32_t passed, ip; int32_t tmp_time; int64_t timestamp; dyn_lease *dls; - int ret = -1, fd = open(gconfig.lease_file, O_RDONLY); + int fd = open(gconfig.lease_file, O_RDONLY); - if (fd < 0) return fd; dls = xzalloc(sizeof(dyn_lease)); - if (read(fd, ×tamp, sizeof(timestamp)) != sizeof(timestamp)) goto error_exit; + if (read(fd, ×tamp, sizeof(timestamp)) != sizeof(timestamp)) + goto lease_error_exit; timestamp = SWAP_BE64(timestamp); passed = time(NULL) - timestamp; - if ((uint64_t)passed > 12 * 60 * 60) goto error_exit; + if ((uint64_t)passed > 12 * 60 * 60) goto lease_error_exit; while (read(fd, dls, sizeof(dyn_lease)) == sizeof(dyn_lease)) { ip = ntohl(dls->lease_nip); if (ip >= gconfig.start_ip && ip <= gconfig.end_ip) { tmp_time = ntohl(dls->expires) - passed; if (tmp_time < 0) continue; - addip_to_lease(dls->lease_nip, dls->lease_mac, (uint32_t*)&tmp_time, dls->hostname, 0); + addip_to_lease(dls->lease_nip, dls->lease_mac, + (uint32_t*)&tmp_time, dls->hostname, 0); } } - ret = 0; -error_exit: +lease_error_exit: free(dls); close(fd); - return ret; +} + +static void read_lease6file(void) +{ + uint32_t passed, ip6[4]; + uint32_t tmp_time; + int64_t timestamp; + dyn_lease6 *dls6; + int fd = open(gconfig.lease6_file, O_RDONLY); + + dls6 = xzalloc(sizeof(dyn_lease6)); + + if (read(fd, ×tamp, sizeof(timestamp)) != sizeof(timestamp)) + goto lease6_error_exit; + + timestamp = SWAP_BE64(timestamp); + passed = time(NULL) - timestamp; + if ((uint64_t)passed > 12 * 60 * 60) goto lease6_error_exit; + + while (read(fd, dls6, sizeof(dyn_lease6)) == sizeof(dyn_lease6)) { + ntohl6(dls6->lease_nip6, ip6); + if (memcmp(ip6, gconfig.start_ip6, sizeof(ip6))<0 && + memcmp(ip6, gconfig.end_ip6, sizeof(ip6))>9) { + tmp_time = ntohl(dls6->expires) - passed; + if (tmp_time < 0U) continue; + addip6_to_lease(dls6->lease_nip6, dls6->duid, dls6->ia_type, dls6->iaid, + (uint32_t*)&tmp_time, 0); + } + } + +lease6_error_exit: + free(dls6); + close(fd); } void dhcpd_main(void) { struct timeval tv; - int retval; + int retval, i; uint8_t *optptr, msgtype = 0; + uint16_t optlen = 0; uint32_t waited = 0, serverid = 0, requested_nip = 0; + uint32_t requested_nip6[4] = {0,}; + uint8_t transactionid[3] = {0,}; uint32_t reqested_lease = 0, ip_pool_size = 0; char *hstname = NULL; fd_set rfds; @@ -1094,26 +1649,50 @@ void dhcpd_main(void) infomode |= LOG_SYSTEM; } setlinebuf(stdout); - parse_server_config((toys.optc==1)?toys.optargs[0]:"/etc/dhcpd.conf", keywords); //DHCPD_CONF_FILE + //DHCPD_CONF_FILE + parse_server_config((toys.optc==1)?toys.optargs[0]:"/etc/dhcpd.conf", keywords); infomsg(infomode, "toybox dhcpd started"); - gconfig.start_ip = ntohl(gconfig.start_ip); - gconfig.end_ip = ntohl(gconfig.end_ip); - ip_pool_size = gconfig.end_ip - gconfig.start_ip + 1; + + if (flag_chk(FLAG_6)){ + addr_version = AF_INET6; + ntohl6(gconfig.start_ip6, gconfig.start_ip6); + ntohl6(gconfig.end_ip6, gconfig.end_ip6); + gconfig.t1 = ntohl(gconfig.t1); + gconfig.t2 = ntohl(gconfig.t2); + gconfig.pref_lifetime = ntohl(gconfig.pref_lifetime); + gconfig.valid_lifetime = ntohl(gconfig.valid_lifetime); + for(i=0;i<4;i++) + ip_pool_size += (gconfig.end_ip6[i]-gconfig.start_ip6[i])<<((3-i)*8); + } else { + gconfig.start_ip = ntohl(gconfig.start_ip); + gconfig.end_ip = ntohl(gconfig.end_ip); + ip_pool_size = gconfig.end_ip - gconfig.start_ip + 1; + } + if (gconfig.max_leases > ip_pool_size) { - error_msg("max_leases=%u is too big, setting to %u", (unsigned) gconfig.max_leases, ip_pool_size); + error_msg("max_leases=%u is too big, setting to %u", + (unsigned) gconfig.max_leases, ip_pool_size); gconfig.max_leases = ip_pool_size; } write_pid(gconfig.pidfile); set_maxlease(); read_leasefile(); + if(TT.iface) gconfig.interface = TT.iface; + (addr_version==AF_INET6) ? read_lease6file() : read_leasefile(); - if (get_interface(gconfig.interface, &gconfig.ifindex, &gconfig.server_nip, + if (get_interface(gconfig.interface, &gconfig.ifindex, + (addr_version==AF_INET6)? gconfig.server_nip6 : &gconfig.server_nip, gconfig.server_mac)<0) perror_exit("Failed to get interface %s", gconfig.interface); - gconfig.server_nip = htonl(gconfig.server_nip); - setup_signal(); - open_listensock(); + if (addr_version==AF_INET6) { + htonl6(gconfig.server_nip6, gconfig.server_nip6); + open_listensock6(); + } else { + gconfig.server_nip = htonl(gconfig.server_nip); + open_listensock(); + } + fcntl(gstate.listensock, F_SETFD, FD_CLOEXEC); for (;;) { @@ -1141,10 +1720,14 @@ void dhcpd_main(void) if (!retval) { // Timed out dbg("select wait Timed Out...\n"); waited = 0; - write_leasefile(); - if (get_interface(gconfig.interface, &gconfig.ifindex, &gconfig.server_nip, gconfig.server_mac)<0) - perror_exit("Interface lost %s\n", gconfig.interface); - gconfig.server_nip = htonl(gconfig.server_nip); + (addr_version==AF_INET6)? write_lease6file() : write_leasefile(); + if (get_interface(gconfig.interface, &gconfig.ifindex, + (addr_version==AF_INET6)? gconfig.server_nip6 : &gconfig.server_nip, + gconfig.server_mac)<0) + perror_exit("Failed to get interface %s", gconfig.interface); + if(addr_version == AF_INET6) { + htonl6(gconfig.server_nip6, gconfig.server_nip6); + } else gconfig.server_nip = htonl(gconfig.server_nip); continue; } if (FD_ISSET(sigfd.rd, &rfds)) { // Some Activity on RDFDs : is signal @@ -1154,94 +1737,363 @@ void dhcpd_main(void) continue; } switch (sig) { - case SIGUSR1: - infomsg(infomode, "Received SIGUSR1"); - write_leasefile(); - continue; - case SIGTERM: - infomsg(infomode, "Received SIGTERM"); - write_leasefile(); - unlink(gconfig.pidfile); - exit(0); - break; - default: break; + case SIGUSR1: + infomsg(infomode, "Received SIGUSR1"); + (addr_version==AF_INET6)? write_lease6file() : write_leasefile(); + continue; + case SIGTERM: + infomsg(infomode, "received sigterm"); + (addr_version==AF_INET6)? write_lease6file() : write_leasefile(); + unlink(gconfig.pidfile); + exit(0); + break; + default: break; } } - if (FD_ISSET(gstate.listensock, &rfds)) { // Some Activity on RDFDs : is socket + if (FD_ISSET(gstate.listensock, &rfds)) { // Some Activity on RDFDs : is socke dbg("select listen sock read\n"); - if (read_packet() < 0) { - open_listensock(); - continue; - } - waited += time(NULL) - timestmp; - get_optval((uint8_t*)&gstate.rcvd_pkt.options, DHCP_OPT_MESSAGE_TYPE, &gstate.rqcode); - if (gstate.rqcode == 0 || gstate.rqcode < DHCPDISCOVER - || gstate.rqcode > DHCPINFORM) { - dbg("no or bad message type option, ignoring packet.\n"); - continue; - } - get_optval((uint8_t*) &gstate.rcvd_pkt.options, DHCP_OPT_SERVER_ID, &serverid); - if (serverid && (serverid != gconfig.server_nip)) { - dbg("server ID doesn't match, ignoring packet.\n"); - continue; - } - switch (gstate.rqcode) { - case DHCPDISCOVER: - msgtype = DHCPOFFER; - dbg("Message Type : DHCPDISCOVER\n"); - get_optval((uint8_t*) &gstate.rcvd_pkt.options, DHCP_OPT_REQUESTED_IP, &requested_nip); - get_optval((uint8_t*) &gstate.rcvd_pkt.options, DHCP_OPT_HOST_NAME, &hstname); - reqested_lease = gconfig.offer_time; - get_reqparam(&gstate.rqopt); - optptr = prepare_send_pkt(); - gstate.send_pkt.yiaddr = getip_from_pool(requested_nip, gstate.rcvd_pkt.chaddr, &reqested_lease, hstname); - if(!gstate.send_pkt.yiaddr){ - msgtype = DHCPNAK; + if(addr_version==AF_INET6) { + void *client_duid, *server_duid, *client_ia_na, *server_ia_na, + *client_ia_pd, *server_ia_pd; + uint8_t client_lla[6] = {0,}; + uint16_t client_duid_len = 0, server_duid_len = 0, server_ia_na_len = 0, + client_ia_na_len = 0, client_ia_pd_len = 0; + + if(read_packet6() < 0) { + open_listensock6(); + continue; + } + waited += time(NULL) - timestmp; + + memcpy(&gstate.rqcode, &gstate.rcvd.rcvd_pkt6.msgtype, sizeof(uint8_t)); + memcpy(&transactionid, &gstate.rcvd.rcvd_pkt6.transaction_id, + sizeof(transactionid)); + + if (!gstate.rqcode || gstate.rqcode < DHCP6SOLICIT || + gstate.rqcode > DHCP6RELAYREPLY) { + dbg("no or bad message type option, ignoring packet.\n"); + continue; + } + if (!gstate.rcvd.rcvd_pkt6.transaction_id || + memcmp(gstate.rcvd.rcvd_pkt6.transaction_id, transactionid, 3)) { + dbg("no or bad transaction id, ignoring packet.\n"); + continue; + } + + waited += time(NULL) - timestmp; + switch (gstate.rqcode) { + case DHCP6SOLICIT: + dbg("Message Type: DHCP6SOLICIT\n"); + optptr = prepare_send_pkt6(DHCP6ADVERTISE); + optlen = 0; + + //TODO policy check + //TODO Receive: ORO check (e.g. DNS) + + //Receive: Identity Association for Non-temporary Address + if(get_optval6((uint8_t*)&gstate.rcvd.rcvd_pkt6.options, + DHCP6_OPT_IA_NA, &client_ia_na_len, &client_ia_na)) { + uint16_t ia_addr_len = sizeof(struct optval_ia_addr); + void *ia_addr, *status_code; + char *status_code_msg; + uint16_t status_code_len = 0; + server_ia_na_len = sizeof(struct optval_ia_na)-sizeof(uint8_t*); + + //IA Address + ia_addr = xzalloc(ia_addr_len); + struct optval_ia_addr *ia_addr_p = (struct optval_ia_addr*)ia_addr; + (*ia_addr_p).pref_lifetime = gconfig.pref_lifetime; + (*ia_addr_p).valid_lifetime = gconfig.valid_lifetime; + memcpy(&(*ia_addr_p).ipv6_addr, + getip6_from_pool(client_duid, client_duid_len, + DHCP6_OPT_IA_NA, (*(struct optval_ia_na*) client_ia_na).iaid, + &(*ia_addr_p).pref_lifetime), sizeof(uint32_t)*4); + server_ia_na_len += (ia_addr_len+4); + + //Status Code + if(*(*ia_addr_p).ipv6_addr) { + status_code_msg = xstrdup("Assigned an address."); + status_code_len = strlen(status_code_msg)+1; + status_code = xzalloc(status_code_len); + struct optval_status_code *status_code_p = + (struct optval_status_code*)status_code; + (*status_code_p).status_code = htons(DHCP6_STATUS_SUCCESS); + memcpy(&(*status_code_p).status_msg, status_code_msg, + status_code_len); + server_ia_na_len += (status_code_len+4); + free(status_code_msg); + } else { + status_code_msg = xstrdup("There's no available address."); + status_code_len = strlen(status_code_msg)+1; + status_code = xzalloc(status_code_len); + struct optval_status_code *status_code_p = + (struct optval_status_code*)status_code; + (*status_code_p).status_code = htons(DHCP6_STATUS_NOADDRSAVAIL); + memcpy(&(*status_code_p).status_msg, status_code_msg, + status_code_len); + server_ia_na_len += (status_code_len+4); + server_ia_na_len -= (ia_addr_len+4); + ia_addr_len = 0; + free(ia_addr); + free(status_code_msg); + //TODO send failed status code + break; + } + + //combine options + server_ia_na = xzalloc(server_ia_na_len); + struct optval_ia_na *ia_na_p = (struct optval_ia_na*)server_ia_na; + (*ia_na_p).iaid = (*(struct optval_ia_na*)client_ia_na).iaid; + (*ia_na_p).t1 = gconfig.t1; + (*ia_na_p).t2 = gconfig.t2; + + uint8_t* ia_na_optptr = &(*ia_na_p).optval; + if(ia_addr_len) { + set_optval6(ia_na_optptr, DHCP6_OPT_IA_ADDR, ia_addr, ia_addr_len); + ia_na_optptr += (ia_addr_len + 4); + free(ia_addr); + } + if(status_code_len) { + set_optval6(ia_na_optptr, DHCP6_OPT_STATUS_CODE, status_code, + status_code_len); + ia_na_optptr += (status_code_len); + free(status_code); + } + + //Response: Identity Association for Non-temporary Address + optptr = set_optval6(optptr, DHCP6_OPT_IA_NA, server_ia_na, + server_ia_na_len); + optlen += (server_ia_na_len + 4); + free(client_ia_na);free(server_ia_na); + } + //Receive: Identity Association for Prefix Delegation + else if(get_optval6((uint8_t*)&gstate.rcvd.rcvd_pkt6.options, + DHCP6_OPT_IA_PD, &client_ia_pd_len, &client_ia_pd)) { + + //TODO + //Response: Identity Association for Prefix Delegation + } + + //Receive: Client Identifier (DUID) + get_optval6((uint8_t*)&gstate.rcvd.rcvd_pkt6.options, + DHCP6_OPT_CLIENTID, &client_duid_len, &client_duid); + + //DUID type: link-layer address plus time + if(ntohs((*(struct optval_duid_llt*)client_duid).type) == + DHCP6_DUID_LLT) { + server_duid_len = 8+sizeof(gconfig.server_mac); + server_duid = xzalloc(server_duid_len); + struct optval_duid_llt *server_duid_p = + (struct optval_duid_llt*)server_duid; + (*server_duid_p).type = htons(1); + (*server_duid_p).hwtype = htons(1); + (*server_duid_p).time = htonl((uint32_t) + (time(NULL) - 946684800) & 0xffffffff); + memcpy(&(*server_duid_p).lladdr, gconfig.server_mac, + sizeof(gconfig.server_mac)); + memcpy(&client_lla, &(*(struct optval_duid_llt*)client_duid).lladdr, + sizeof(client_lla)); + + //Response: Server Identifier (DUID) + optptr = set_optval6(optptr, DHCP6_OPT_SERVERID, server_duid, + server_duid_len); + optlen += (server_duid_len + 4); + //Response: Client Identifier + optptr = set_optval6(optptr, DHCP6_OPT_CLIENTID, client_duid, + client_duid_len); + optlen += (client_duid_len + 4); + free(client_duid);free(server_duid); + } + + send_packet6(0, client_lla, optlen); + write_lease6file(); + break; + case DHCP6REQUEST: + dbg("Message Type: DHCP6REQUEST\n"); + optptr = prepare_send_pkt6(DHCP6REPLY); + optlen = 0; + + //Receive: Client Identifier (DUID) + get_optval6((uint8_t*)&gstate.rcvd.rcvd_pkt6.options, + DHCP6_OPT_CLIENTID, &client_duid_len, &client_duid); + optptr = set_optval6(optptr, DHCP6_OPT_CLIENTID, client_duid, + client_duid_len); + optlen += (client_duid_len + 4); + memcpy(&client_lla, &(*(struct optval_duid_llt*)client_duid).lladdr, + sizeof(client_lla)); + + //Receive: Identity Association for Non-temporary Address + if(get_optval6((uint8_t*)&gstate.rcvd.rcvd_pkt6.options, + DHCP6_OPT_IA_NA, &client_ia_na_len, &client_ia_na)) { + uint16_t ia_addr_len = 0, status_code_len = 0; + void *ia_addr, *status_code; + uint16_t server_ia_na_len = + sizeof(struct optval_ia_na)-sizeof(uint8_t*); + char *status_code_msg; + + //Check IA Address + get_optval6((uint8_t*)&(*(struct optval_ia_na*)client_ia_na).optval, + DHCP6_OPT_IA_ADDR, &ia_addr_len, &ia_addr); + struct optval_ia_addr *ia_addr_p = (struct optval_ia_addr*)ia_addr; + if(verifyip6_in_lease((*ia_addr_p).ipv6_addr, client_duid, + DHCP6_OPT_IA_NA, (*(struct optval_ia_na*)client_ia_na).iaid) + == -1) { + server_ia_na_len += (ia_addr_len + 4); + //Add Status Code + status_code_msg = xstrdup("Assigned an address."); + status_code_len = strlen(status_code_msg) + 1; + status_code = xzalloc(status_code_len); + struct optval_status_code *status_code_p = + (struct optval_status_code*)status_code; + (*status_code_p).status_code = htons(DHCP6_STATUS_SUCCESS); + memcpy(&(*status_code_p).status_msg, status_code_msg, + status_code_len); + server_ia_na_len += (status_code_len+4); + } else { + //TODO send failed status code + break; + } + + //combine options + server_ia_na = xzalloc(server_ia_na_len); + struct optval_ia_na *ia_na_p = (struct optval_ia_na*)server_ia_na; + (*ia_na_p).iaid = (*(struct optval_ia_na*)client_ia_na).iaid; + (*ia_na_p).t1 = gconfig.t1; + (*ia_na_p).t2 = gconfig.t2; + + uint8_t* ia_na_optptr = &(*ia_na_p).optval; + ia_na_optptr = set_optval6(ia_na_optptr, DHCP6_OPT_IA_ADDR, + ia_addr, ia_addr_len); + free(ia_addr); + + if(status_code_len) { + ia_na_optptr = set_optval6(ia_na_optptr, DHCP6_OPT_STATUS_CODE, + status_code, status_code_len); + free(status_code); + } + + //Response: Identity Association for Non-temporary Address + //(Status Code added) + optptr = set_optval6(optptr, DHCP6_OPT_IA_NA, + server_ia_na, server_ia_na_len); + optlen += (server_ia_na_len + 4); + free(client_ia_na);free(server_ia_na); + } + + //Receive: Server Identifier (DUID) + get_optval6((uint8_t*)&gstate.rcvd.rcvd_pkt6.options, + DHCP6_OPT_SERVERID, &server_duid_len, &server_duid); + optptr = set_optval6(optptr, DHCP6_OPT_SERVERID, + server_duid, server_duid_len); + optlen += (server_duid_len + 4); + + free(client_duid); free(server_duid); + + send_packet6(0, client_lla, optlen); + write_lease6file(); + break; + case DHCP6RENEW: //TODO + case DHCP6REBIND: //TODO + case DHCP6RELEASE: + dbg("Message Type: DHCP6RELEASE\n"); + optptr = prepare_send_pkt6(DHCP6REPLY); + break; + default: + dbg("Message Type : %u\n", gstate.rqcode); + break; + } + + } else { + if(read_packet() < 0) { + open_listensock(); + continue; + } + waited += time(NULL) - timestmp; + + get_optval((uint8_t*)&gstate.rcvd.rcvd_pkt.options, + DHCP_OPT_MESSAGE_TYPE, &gstate.rqcode); + if (gstate.rqcode == 0 || gstate.rqcode < DHCPDISCOVER + || gstate.rqcode > DHCPINFORM) { + dbg("no or bad message type option, ignoring packet.\n"); + continue; + } + get_optval((uint8_t*) &gstate.rcvd.rcvd_pkt.options, + DHCP_OPT_SERVER_ID, &serverid); + if (serverid && (serverid != gconfig.server_nip)) { + dbg("server ID doesn't match, ignoring packet.\n"); + continue; + } + + waited += time(NULL) - timestmp; + switch (gstate.rqcode) { + case DHCPDISCOVER: + msgtype = DHCPOFFER; + dbg("Message Type : DHCPDISCOVER\n"); + get_optval((uint8_t*) &gstate.rcvd.rcvd_pkt.options, + DHCP_OPT_REQUESTED_IP, &requested_nip); + get_optval((uint8_t*) &gstate.rcvd.rcvd_pkt.options, + DHCP_OPT_HOST_NAME, &hstname); + reqested_lease = gconfig.offer_time; + get_reqparam(&gstate.rqopt); + optptr = prepare_send_pkt(); + gstate.send.send_pkt.yiaddr = getip_from_pool(requested_nip, + gstate.rcvd.rcvd_pkt.chaddr, &reqested_lease, hstname); + if(!gstate.send.send_pkt.yiaddr){ + msgtype = DHCPNAK; + optptr = set_optval(optptr, DHCP_OPT_MESSAGE_TYPE, &msgtype, 1); + send_packet(1); + break; + } + get_optval((uint8_t*) &gstate.rcvd.rcvd_pkt.options, + DHCP_OPT_LEASE_TIME, &reqested_lease); + reqested_lease = htonl(get_lease(reqested_lease + time(NULL))); optptr = set_optval(optptr, DHCP_OPT_MESSAGE_TYPE, &msgtype, 1); + optptr = set_optval(optptr, DHCP_OPT_SERVER_ID, &gconfig.server_nip, 4); + optptr = set_optval(optptr, DHCP_OPT_LEASE_TIME, &reqested_lease, 4); + optptr = set_reqparam(optptr, gstate.rqopt); send_packet(1); break; - } - get_optval((uint8_t*) &gstate.rcvd_pkt.options, DHCP_OPT_LEASE_TIME, &reqested_lease); - reqested_lease = htonl(get_lease(reqested_lease + time(NULL))); - optptr = set_optval(optptr, DHCP_OPT_MESSAGE_TYPE, &msgtype, 1); - optptr = set_optval(optptr, DHCP_OPT_SERVER_ID, &gconfig.server_nip, 4); - optptr = set_optval(optptr, DHCP_OPT_LEASE_TIME, &reqested_lease, 4); - optptr = set_reqparam(optptr, gstate.rqopt); - send_packet(1); - break; - case DHCPREQUEST: - msgtype = DHCPACK; - dbg("Message Type : DHCPREQUEST\n"); - optptr = prepare_send_pkt(); - get_optval((uint8_t*) &gstate.rcvd_pkt.options, DHCP_OPT_REQUESTED_IP, &requested_nip); - get_optval((uint8_t*) &gstate.rcvd_pkt.options, DHCP_OPT_LEASE_TIME, &reqested_lease); - get_optval((uint8_t*) &gstate.rcvd_pkt.options, DHCP_OPT_HOST_NAME, &hstname); - gstate.send_pkt.yiaddr = getip_from_pool(requested_nip, gstate.rcvd_pkt.chaddr, &reqested_lease, hstname); - if (!serverid) reqested_lease = gconfig.max_lease_sec; - if (!gstate.send_pkt.yiaddr) { - msgtype = DHCPNAK; + case DHCPREQUEST: + msgtype = DHCPACK; + dbg("Message Type : DHCPREQUEST\n"); + optptr = prepare_send_pkt(); + get_optval((uint8_t*) &gstate.rcvd.rcvd_pkt.options, + DHCP_OPT_REQUESTED_IP, &requested_nip); + get_optval((uint8_t*) &gstate.rcvd.rcvd_pkt.options, + DHCP_OPT_LEASE_TIME, &reqested_lease); + get_optval((uint8_t*) &gstate.rcvd.rcvd_pkt.options, + DHCP_OPT_HOST_NAME, &hstname); + gstate.send.send_pkt.yiaddr = getip_from_pool(requested_nip, + gstate.rcvd.rcvd_pkt.chaddr, &reqested_lease, hstname); + if (!serverid) reqested_lease = gconfig.max_lease_sec; + if (!gstate.send.send_pkt.yiaddr) { + msgtype = DHCPNAK; + optptr = set_optval(optptr, DHCP_OPT_MESSAGE_TYPE, &msgtype, 1); + send_packet(1); + break; + } optptr = set_optval(optptr, DHCP_OPT_MESSAGE_TYPE, &msgtype, 1); + optptr = set_optval(optptr, DHCP_OPT_SERVER_ID, &gconfig.server_nip, 4); + reqested_lease = htonl(reqested_lease); + optptr = set_optval(optptr, DHCP_OPT_LEASE_TIME, &reqested_lease, 4); send_packet(1); + write_leasefile(); break; - } - optptr = set_optval(optptr, DHCP_OPT_MESSAGE_TYPE, &msgtype, 1); - optptr = set_optval(optptr, DHCP_OPT_SERVER_ID, &gconfig.server_nip, 4); - reqested_lease = htonl(reqested_lease); - optptr = set_optval(optptr, DHCP_OPT_LEASE_TIME, &reqested_lease, 4); - send_packet(1); - write_leasefile(); - break; - case DHCPDECLINE:// FALL THROUGH - case DHCPRELEASE: - dbg("Message Type : DHCPDECLINE or DHCPRELEASE \n"); - get_optval((uint8_t*) &gstate.rcvd_pkt.options, DHCP_OPT_SERVER_ID, &serverid); - if (serverid != gconfig.server_nip) break; - get_optval((uint8_t*) &gstate.rcvd_pkt.options, DHCP_OPT_REQUESTED_IP, &requested_nip); - delip_from_lease(requested_nip, gstate.rcvd_pkt.chaddr, (gstate.rqcode==DHCPRELEASE)?0:gconfig.decline_time); - break; - default: - dbg("Message Type : %u\n", gstate.rqcode); - break; + case DHCPDECLINE:// FALL THROUGH + case DHCPRELEASE: + dbg("Message Type : DHCPDECLINE or DHCPRELEASE \n"); + get_optval((uint8_t*) &gstate.rcvd.rcvd_pkt.options, + DHCP_OPT_SERVER_ID, &serverid); + if (serverid != gconfig.server_nip) break; + get_optval((uint8_t*) &gstate.rcvd.rcvd_pkt.options, + DHCP_OPT_REQUESTED_IP, &requested_nip); + delip_from_lease(requested_nip, gstate.rcvd.rcvd_pkt.chaddr, + (gstate.rqcode==DHCPRELEASE)?0:gconfig.decline_time); + break; + default: + dbg("Message Type : %u\n", gstate.rqcode); + break; + } } } } diff --git a/toys/pending/diff.c b/toys/pending/diff.c index 1b3f80b..8023861 100644 --- a/toys/pending/diff.c +++ b/toys/pending/diff.c @@ -768,7 +768,6 @@ void diff_main(void) struct stat st[2]; int j = 0, k = 1, start[2] = {1, 1}; char *files[2]; - struct dirtree *root; for (j = 0; j < 2; j++) { files[j] = toys.optargs[j]; @@ -799,8 +798,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)); - root = dirtree_add_node(0, files[j], 1); - if (root) dirtree_handle_callback(root, list_dir); + dirtree_handle_callback(dirtree_start(files[j], 1), 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/dumpleases.c b/toys/pending/dumpleases.c index fec3955..86cb4e7 100644 --- a/toys/pending/dumpleases.c +++ b/toys/pending/dumpleases.c @@ -41,7 +41,7 @@ void dumpleases_main(void) int64_t written_time , current_time, exp; int i, fd; - if(!(toys.optflags & FLAG_f)) TT.file = "/var/lib/misc/udhcpd.leases"; //DEF_LEASE_FILE + if(!(toys.optflags & FLAG_f)) TT.file = "/var/lib/misc/dhcpd.leases"; //DEF_LEASE_FILE fd = xopen(TT.file, O_RDONLY); xprintf("Mac Address IP Address Host Name Expires %s\n", (toys.optflags & FLAG_a) ? "at" : "in"); xread(fd, &written_time, sizeof(written_time)); diff --git a/toys/pending/expr.c b/toys/pending/expr.c index dd27d58..763ad02 100644 --- a/toys/pending/expr.c +++ b/toys/pending/expr.c @@ -3,6 +3,9 @@ * Copyright 2013 Daniel Verkamp <daniel@drv.nu> * * http://pubs.opengroup.org/onlinepubs/9699919799/utilities/expr.html + * + * The web standard is incomplete (precedence grouping missing), see: + * http://permalink.gmane.org/gmane.comp.standards.posix.austin.general/10141 USE_EXPR(NEWTOY(expr, NULL, TOYFLAG_USR|TOYFLAG_BIN)) @@ -10,15 +13,31 @@ config EXPR bool "expr" default n help - usage: expr args + usage: expr ARG1 OPERATOR ARG2... + + Evaluate expression and print result. For example, "expr 1 + 2". + + The supported operators are (grouped from highest to lowest priority): - Evaluate expression and print result. + ( ) : * / % + - != <= < >= > = & | - The supported operators, in order of increasing precedence, are: + Each constant and operator must be a separate command line argument. + All operators are infix, meaning they expect a constant (or expression + that resolves to a constant) on each side of the operator. Operators of + the same priority (within each group above) are evaluated left to right. + Parentheses may be used (as separate arguments) to elevate the priority + of expressions. - | & = > >= < <= != + - * / % + Calling expr from a command shell requires a lot of \( or '*' escaping + to avoid interpreting shell control characters. - In addition, parentheses () are supported for grouping. + The & and | operators are logical (not bitwise) and may operate on + strings (a blank string is "false"). Comparison operators may also + operate on strings (alphabetical sort). + + Constants may be strings or integers. Comparison, logical, and regex + operators may operate on strings (a blank string is "false"), other + operators require integers. */ // TODO: int overflow checking @@ -39,44 +58,10 @@ struct value { long long i; }; -static void parse_expr(struct value *ret, struct value *v); - -static void get_value(struct value *v) -{ - char *endp, *arg; - - if (TT.argidx == toys.optc) { - v->i = 0; - v->s = ""; // signal end of expression - return; - } - - if (TT.argidx >= toys.optc) { - error_exit("syntax error"); - } - - arg = toys.optargs[TT.argidx++]; - - v->i = strtoll(arg, &endp, 10); - v->s = *endp ? arg : NULL; -} - - -// check if v matches a token, and consume it if so -static int match(struct value *v, const char *tok) -{ - if (v->s && !strcmp(v->s, tok)) { - get_value(v); - return 1; - } - - return 0; -} - // check if v is the integer 0 or the empty string -static int is_zero(const struct value *v) +static int is_zero(struct value *v) { - return ((v->s && *v->s == '\0') || v->i == 0); + return v->s ? !*v->s : !v->i; } static char *num_to_str(long long num) @@ -86,103 +71,106 @@ static char *num_to_str(long long num) return num_buf; } -static int cmp(const struct value *lhs, const struct value *rhs) +static int cmp(struct value *lhs, struct value *rhs) { if (lhs->s || rhs->s) { // at least one operand is a string char *ls = lhs->s ? lhs->s : num_to_str(lhs->i); char *rs = rhs->s ? rhs->s : num_to_str(rhs->i); return strcmp(ls, rs); - } else { - return lhs->i - rhs->i; - } + } else return lhs->i - rhs->i; } - -// operators - -struct op { - const char *tok; - - // calculate "lhs op rhs" (e.g. lhs + rhs) and store result in lhs - void (*calc)(struct value *lhs, const struct value *rhs); -}; - - -static void re(struct value *lhs, const struct value *rhs) +static void re(struct value *lhs, struct value *rhs) { - error_exit("regular expression match not implemented"); + regex_t rp; + regmatch_t rm[2]; + + xregcomp(&rp, rhs->s, 0); + if (!regexec(&rp, lhs->s, 2, rm, 0) && rm[0].rm_so == 0) { + if (rp.re_nsub > 0 && rm[1].rm_so >= 0) + lhs->s = xmprintf("%.*s", rm[1].rm_eo - rm[1].rm_so, lhs->s+rm[1].rm_so); + else { + lhs->i = rm[0].rm_eo; + lhs->s = 0; + } + } else { + if (!rp.re_nsub) { + lhs->i = 0; + lhs->s = 0; + } else lhs->s = ""; + } } -static void mod(struct value *lhs, const struct value *rhs) +static void mod(struct value *lhs, struct value *rhs) { if (lhs->s || rhs->s) error_exit("non-integer argument"); if (is_zero(rhs)) error_exit("division by zero"); lhs->i %= rhs->i; } -static void divi(struct value *lhs, const struct value *rhs) +static void divi(struct value *lhs, struct value *rhs) { if (lhs->s || rhs->s) error_exit("non-integer argument"); if (is_zero(rhs)) error_exit("division by zero"); lhs->i /= rhs->i; } -static void mul(struct value *lhs, const struct value *rhs) +static void mul(struct value *lhs, struct value *rhs) { if (lhs->s || rhs->s) error_exit("non-integer argument"); lhs->i *= rhs->i; } -static void sub(struct value *lhs, const struct value *rhs) +static void sub(struct value *lhs, struct value *rhs) { if (lhs->s || rhs->s) error_exit("non-integer argument"); lhs->i -= rhs->i; } -static void add(struct value *lhs, const struct value *rhs) +static void add(struct value *lhs, struct value *rhs) { if (lhs->s || rhs->s) error_exit("non-integer argument"); lhs->i += rhs->i; } -static void ne(struct value *lhs, const struct value *rhs) +static void ne(struct value *lhs, struct value *rhs) { lhs->i = cmp(lhs, rhs) != 0; lhs->s = NULL; } -static void lte(struct value *lhs, const struct value *rhs) +static void lte(struct value *lhs, struct value *rhs) { lhs->i = cmp(lhs, rhs) <= 0; lhs->s = NULL; } -static void lt(struct value *lhs, const struct value *rhs) +static void lt(struct value *lhs, struct value *rhs) { lhs->i = cmp(lhs, rhs) < 0; lhs->s = NULL; } -static void gte(struct value *lhs, const struct value *rhs) +static void gte(struct value *lhs, struct value *rhs) { lhs->i = cmp(lhs, rhs) >= 0; lhs->s = NULL; } -static void gt(struct value *lhs, const struct value *rhs) +static void gt(struct value *lhs, struct value *rhs) { lhs->i = cmp(lhs, rhs) > 0; lhs->s = NULL; } -static void eq(struct value *lhs, const struct value *rhs) +static void eq(struct value *lhs, struct value *rhs) { - lhs->i = cmp(lhs, rhs) == 0; + lhs->i = !cmp(lhs, rhs); lhs->s = NULL; } -static void and(struct value *lhs, const struct value *rhs) +static void and(struct value *lhs, struct value *rhs) { if (is_zero(lhs) || is_zero(rhs)) { lhs->i = 0; @@ -190,51 +178,71 @@ static void and(struct value *lhs, const struct value *rhs) } } -static void or(struct value *lhs, const struct value *rhs) +static void or(struct value *lhs, struct value *rhs) { - if (is_zero(lhs)) { - *lhs = *rhs; - } + if (is_zero(lhs)) *lhs = *rhs; } +static void get_value(struct value *v) +{ + char *endp, *arg; -// operators in order of increasing precedence -static const struct op ops[] = { - {"|", or }, - {"&", and }, - {"=", eq }, - {">", gt }, - {">=", gte }, - {"<", lt }, - {"<=", lte }, - {"!=", ne }, - {"+", add }, - {"-", sub }, - {"*", mul }, - {"/", divi}, - {"%", mod }, - {":", re }, - {"(", NULL}, // special case - must be last -}; + if (TT.argidx == toys.optc) { + v->i = 0; + v->s = ""; // signal end of expression + return; + } + +// can't happen, the increment is after the == test +// if (TT.argidx >= toys.optc) error_exit("syntax error"); + arg = toys.optargs[TT.argidx++]; + + v->i = strtoll(arg, &endp, 10); + v->s = *endp ? arg : NULL; +} -static void parse_parens(struct value *ret, struct value *v) +// check if v matches a token, and consume it if so +static int match(struct value *v, char *tok) { - if (match(v, "(")) { - parse_expr(ret, v); - if (!match(v, ")")) error_exit("syntax error"); // missing closing paren - } else { - // v is a string or integer - return it and get the next token - *ret = *v; + if (v->s && !strcmp(v->s, tok)) { get_value(v); + return 1; } + + return 0; } -static void parse_op(struct value *lhs, struct value *tok, const struct op *op) +// operators in order of increasing precedence +static struct op { + char *tok; + + // calculate "lhs op rhs" (e.g. lhs + rhs) and store result in lhs + void (*calc)(struct value *lhs, struct value *rhs); +} ops[] = { + {"|", or }, {"&", and }, {"=", eq }, {"==", eq }, {">", gt }, + {">=", gte }, {"<", lt }, {"<=", lte }, {"!=", ne }, {"+", add }, + {"-", sub }, {"*", mul }, {"/", divi}, {"%", mod }, {":", re }, + {"(", NULL}, // special case - must be last +}; + +// "|,&,= ==> >=< <= !=,+-,*/%,:" + +static void parse_op(struct value *lhs, struct value *tok, struct op *op) { + if (!op) op = ops; + // special case parsing for parentheses if (*op->tok == '(') { - parse_parens(lhs, tok); + if (match(tok, "(")) { + parse_op(lhs, tok, 0); + if (!match(tok, ")")) error_exit("syntax error"); // missing closing paren + } else { + // tok is a string or integer - return it and get the next token + *lhs = *tok; + get_value(tok); + } + return; } @@ -247,11 +255,6 @@ static void parse_op(struct value *lhs, struct value *tok, const struct op *op) } } -static void parse_expr(struct value *ret, struct value *v) -{ - parse_op(ret, v, ops); // start at the top of the ops table -} - void expr_main(void) { struct value tok, ret = {0}; @@ -261,9 +264,10 @@ void expr_main(void) TT.argidx = 0; get_value(&tok); // warm up the parser with the initial value - parse_expr(&ret, &tok); + parse_op(&ret, &tok, 0); - if (!tok.s || *tok.s) error_exit("syntax error"); // final token should be end of expression + // final token should be end of expression + if (!tok.s || *tok.s) error_exit("syntax error"); if (ret.s) printf("%s\n", ret.s); else printf("%lld\n", ret.i); diff --git a/toys/pending/fold.c b/toys/pending/fold.c index 80bb241..0face64 100644 --- a/toys/pending/fold.c +++ b/toys/pending/fold.c @@ -4,7 +4,7 @@ * * See http://pubs.opengroup.org/onlinepubs/9699919799/utilities/fold.html -USE_FOLD(NEWTOY(fold, "bsuw#", TOYFLAG_USR|TOYFLAG_BIN)) +USE_FOLD(NEWTOY(fold, "bsuw#<1", TOYFLAG_USR|TOYFLAG_BIN)) config FOLD bool "fold" diff --git a/toys/pending/ftpget.c b/toys/pending/ftpget.c index 2a81a34..a144713 100644 --- a/toys/pending/ftpget.c +++ b/toys/pending/ftpget.c @@ -6,7 +6,7 @@ * No Standard. * USE_FTPGET(NEWTOY(ftpget, "<2cvu:p:P#<0=21>65535", TOYFLAG_BIN)) -USE_FTPGET(OLDTOY(ftpput,ftpget, "<2vu:p:P#<0=21>65535", TOYFLAG_BIN)) +USE_FTPGET(OLDTOY(ftpput, ftpget, TOYFLAG_BIN)) config FTPGET bool "ftpget/ftpput" diff --git a/toys/pending/groupadd.c b/toys/pending/groupadd.c index 7df0a5c..615c12f 100644 --- a/toys/pending/groupadd.c +++ b/toys/pending/groupadd.c @@ -6,7 +6,7 @@ * See http://refspecs.linuxfoundation.org/LSB_4.1.0/LSB-Core-generic/LSB-Core-generic/groupadd.html USE_GROUPADD(NEWTOY(groupadd, "<1>2g#<0S", TOYFLAG_NEEDROOT|TOYFLAG_SBIN)) -USE_GROUPADD(OLDTOY(addgroup, groupadd, OPTSTR_groupadd, TOYFLAG_NEEDROOT|TOYFLAG_SBIN)) +USE_GROUPADD(OLDTOY(addgroup, groupadd, TOYFLAG_NEEDROOT|TOYFLAG_SBIN)) config GROUPADD bool "groupadd" diff --git a/toys/pending/groupdel.c b/toys/pending/groupdel.c index 834e113..483ac59 100644 --- a/toys/pending/groupdel.c +++ b/toys/pending/groupdel.c @@ -6,7 +6,7 @@ * See http://refspecs.linuxfoundation.org/LSB_4.1.0/LSB-Core-generic/LSB-Core-generic/groupdel.html USE_GROUPDEL(NEWTOY(groupdel, "<1>2", TOYFLAG_NEEDROOT|TOYFLAG_SBIN)) -USE_GROUPDEL(OLDTOY(delgroup, groupdel, OPTSTR_groupdel, TOYFLAG_NEEDROOT|TOYFLAG_SBIN)) +USE_GROUPDEL(OLDTOY(delgroup, groupdel, TOYFLAG_NEEDROOT|TOYFLAG_SBIN)) config GROUPDEL bool "groupdel" diff --git a/toys/pending/ip.c b/toys/pending/ip.c index 42d3a73..9a01d95 100644 --- a/toys/pending/ip.c +++ b/toys/pending/ip.c @@ -8,11 +8,11 @@ * No Standard. * USE_IP(NEWTOY(ip, NULL, TOYFLAG_SBIN)) -USE_IP(OLDTOY(ipaddr, ip, NULL, TOYFLAG_SBIN)) -USE_IP(OLDTOY(iplink, ip, NULL, TOYFLAG_SBIN)) -USE_IP(OLDTOY(iproute, ip, NULL, TOYFLAG_SBIN)) -USE_IP(OLDTOY(iprule, ip, NULL, TOYFLAG_SBIN)) -USE_IP(OLDTOY(iptunnel, ip, NULL, TOYFLAG_SBIN)) +USE_IP(OLDTOY(ipaddr, ip, TOYFLAG_SBIN)) +USE_IP(OLDTOY(iplink, ip, TOYFLAG_SBIN)) +USE_IP(OLDTOY(iproute, ip, TOYFLAG_SBIN)) +USE_IP(OLDTOY(iprule, ip, TOYFLAG_SBIN)) +USE_IP(OLDTOY(iptunnel, ip, TOYFLAG_SBIN)) config IP bool "ip" @@ -646,7 +646,7 @@ static int link_set(char **argv) memset(&req, 0, sizeof(req)); if (!*argv) error_exit("\"dev\" missing"); - strncpy(req.ifr_name, *argv, IF_NAMESIZE); + xstrncpy(req.ifr_name, *argv, IF_NAMESIZE); fd = xsocket(AF_INET, SOCK_DGRAM, 0); xioctl(fd, SIOCGIFINDEX, &req); for (++argv; *argv;) { @@ -674,9 +674,9 @@ static int link_set(char **argv) ++argv; break; case 5: - strncpy(req.ifr_ifru.ifru_newname, *argv, IF_NAMESIZE); + xstrncpy(req.ifr_ifru.ifru_newname, *argv, IF_NAMESIZE); xioctl(fd, SIOCSIFNAME, &req); - strncpy(req.ifr_name, *argv++, IF_NAMESIZE); + xstrncpy(req.ifr_name, *argv++, IF_NAMESIZE); xioctl(fd, SIOCGIFINDEX, &req); break; case 6: @@ -839,7 +839,7 @@ static int get_link_info(struct nlmsghdr* h,struct linkdata* link,char **argv) link->next = link->prev = 0; link->iface_type = iface->ifi_type; if (!lname) error_exit("Invalid link."); - strncpy(link->type, lname, IFNAMSIZ); + xstrncpy(link->type, lname, IFNAMSIZ); free(lname); link->iface_idx = iface->ifi_index; link->flags = iface->ifi_flags; @@ -886,7 +886,7 @@ static int get_link_info(struct nlmsghdr* h,struct linkdata* link,char **argv) {"DORMANT", 5}, {"UP", 6}, {NULL, -1}}; if (!(lname = get_flag_string(flags, *((int*)(RTA_DATA(attr))), 0))) error_exit("Invalid state."); - strncpy(link->state, lname,IFNAMSIZ); + xstrncpy(link->state, lname,IFNAMSIZ); free(lname); } break; @@ -974,7 +974,7 @@ static int print_addrinfo(struct nlmsghdr *h, int flag_l) if (flag_l && addrinfo.label && ifa->ifa_family == AF_INET6) return 0; if ((rta_tb[IFA_LABEL])) { - strncpy(label, RTA_DATA(rta_tb[IFA_LABEL]), 256); + xstrncpy(label, RTA_DATA(rta_tb[IFA_LABEL]), 256); label[255] = '\0'; if (addrinfo.label && fnmatch(addrinfo.label, label, 0)) return 0; @@ -2330,8 +2330,8 @@ static int tnl_ioctl(char *dev, int rtype, struct ip_tunnel_parm *ptnl) int fd, ret = 0; if ((rtype == SIOCCHGTUNNEL || rtype == SIOCDELTUNNEL) && *ptnl->name) - strncpy(req.ifr_name, ptnl->name, IF_NAMESIZE); - else strncpy(req.ifr_name, dev, IF_NAMESIZE); + xstrncpy(req.ifr_name, ptnl->name, IF_NAMESIZE); + else xstrncpy(req.ifr_name, dev, IF_NAMESIZE); if (rtype != SIOCGIFHWADDR) req.ifr_ifru.ifru_data = (void*)ptnl; fd = xsocket(AF_INET, SOCK_DGRAM, 0); @@ -2448,7 +2448,7 @@ static void parse_iptunnel_args(struct ip_tunnel_parm *ptnl, char **argv, // frag_off is measured in units of 8 octets (64 bits) ptnl->iph.frag_off = htons(IP_DF); if (*argv && ipt_opt_idx <= 2 && string_to_idx(*argv, opts) == -1) { - strncpy(ptnl->name, *argv, IF_NAMESIZE); + xstrncpy(ptnl->name, *argv, IF_NAMESIZE); if (ipt_opt_idx == 1) { struct ip_tunnel_parm iptnl_old; @@ -2545,7 +2545,7 @@ static void parse_iptunnel_args(struct ip_tunnel_parm *ptnl, char **argv, struct ifreq req; int fd; - strncpy(req.ifr_name, *argv, IFNAMSIZ); + xstrncpy(req.ifr_name, *argv, IFNAMSIZ); fd = xsocket(AF_INET, SOCK_DGRAM, 0); xioctl(fd, SIOCGIFINDEX, &req); close(fd); @@ -2578,12 +2578,12 @@ static void parse_iptunnel_args(struct ip_tunnel_parm *ptnl, char **argv, if (*ptnl->name) error_exit("invalid tunnel"); else { if (!*++argv) error_exit("name is missing"); - strncpy(ptnl->name, *argv, IF_NAMESIZE); + xstrncpy(ptnl->name, *argv, IF_NAMESIZE); } break; default: if (*ptnl->name) error_exit("invalid tunnel"); - strncpy(ptnl->name, *argv, IF_NAMESIZE); + xstrncpy(ptnl->name, *argv, IF_NAMESIZE); break; } } diff --git a/toys/pending/mdev.c b/toys/pending/mdev.c index 2d98c25..0c49633 100644 --- a/toys/pending/mdev.c +++ b/toys/pending/mdev.c @@ -31,34 +31,53 @@ config MDEV_CONF #include "toys.h" -// todo, open() block devices to trigger partition scanning. - // mknod in /dev based on a path like "/sys/block/hda/hda1" static void make_device(char *path) { - char *device_name, *s, *temp; + char *device_name = NULL, *s, *temp; int major, minor, type, len, fd; int mode = 0660; uid_t uid = 0; gid_t gid = 0; - // Try to read major/minor string - - temp = strrchr(path, '/'); - fd = open(path, O_RDONLY); - *temp=0; - temp = toybuf; - len = read(fd, temp, 64); - close(fd); - if (len<1) return; - temp[len] = 0; - - // Determine device name, type, major and minor - - device_name = strrchr(path, '/') + 1; - type = path[5]=='c' ? S_IFCHR : S_IFBLK; - major = minor = 0; - sscanf(temp, "%u:%u", &major, &minor); + if (path) { + // Try to read major/minor string + + temp = strrchr(path, '/'); + fd = open(path, O_RDONLY); + *temp=0; + temp = toybuf; + len = read(fd, temp, 64); + close(fd); + if (len<1) return; + temp[len] = 0; + + // Determine device type, major and minor + + type = path[5]=='c' ? S_IFCHR : S_IFBLK; + major = minor = 0; + sscanf(temp, "%u:%u", &major, &minor); + } else { + // if (!path), do hotplug + + if (!(temp = getenv("SUBSYSTEM"))) + return; + type = strcmp(temp, "block") ? S_IFCHR : S_IFBLK; + major = minor = 0; + if (!(temp = getenv("MAJOR"))) + return; + sscanf(temp, "%u", &major); + if (!(temp = getenv("MINOR"))) + return; + sscanf(temp, "%u", &minor); + path = getenv("DEVPATH"); + device_name = getenv("DEVNAME"); + if (!path) + return; + temp = toybuf; + } + if (!device_name) + device_name = strrchr(path, '/') + 1; // If we have a config file, look up permissions for this device @@ -167,9 +186,20 @@ found_device: } sprintf(temp, "/dev/%s", device_name); + + if (getenv("ACTION") && !strcmp(getenv("ACTION"), "remove")) { + unlink(temp); + return; + } + + if (strchr(device_name, '/')) + mkpathat(AT_FDCWD, temp, 0, 2); if (mknod(temp, mode | type, makedev(major, minor)) && errno != EEXIST) perror_exit("mknod %s failed", temp); + + if (type == S_IFBLK) close(open(temp, O_RDONLY)); // scan for partitions + if (CFG_MDEV_CONF) mode=chown(temp, uid, gid); } @@ -202,7 +232,7 @@ void mdev_main(void) if (toys.optflags) { dirtree_read("/sys/class", callback); dirtree_read("/sys/block", callback); + } else { // hotplug support + make_device(NULL); } - - // hotplug support goes here } diff --git a/toys/pending/mix.c b/toys/pending/mix.c deleted file mode 100644 index 4a51fb9..0000000 --- a/toys/pending/mix.c +++ /dev/null @@ -1,64 +0,0 @@ -/* mix.c - A very basic mixer. - * - * Copyright 2014 Brad Conroy, dedicated to the Public Domain. - * - -USE_MIX(NEWTOY(mix, "m:d:l#r#", TOYFLAG_USR|TOYFLAG_BIN)) -config MIX - bool "mix" - default n - help - usage: mix [-m mixer] [-d device] [-l level / left level] [-r right level] - - Lists/sets mixer devices/levels. -*/ - -#define FOR_mix -#include <linux/soundcard.h> -#include "toys.h" - - -GLOBALS( - int right; - int level; - char *device; - char *mixer; -) - -void mix_main(void) -{ - const char *devices[SOUND_MIXER_NRDEVICES] = SOUND_DEVICE_NAMES; - char *mixer_name=(toys.optflags & FLAG_m)?TT.mixer:"/dev/mixer"; - int i, mask, device=-1, level, - mixer=xopen(mixer_name, O_RDWR|O_NONBLOCK); - - xioctl(mixer, SOUND_MIXER_READ_DEVMASK,&mask); - - if (!(toys.optflags & FLAG_d)){ - for (i = 0; i < SOUND_MIXER_NRDEVICES; ++i) - if (1<<i & mask) printf("%s\n",devices[i]); - return; - }else{ - for (i = 0; i < SOUND_MIXER_NRDEVICES; ++i){ - if ((1<<i & mask) && !strcmp(devices[i], TT.device)){ - device=i; - break; - } - } - if (-1==device) return; //with error - } - - if (!(toys.optflags & FLAG_l)){ - xioctl(mixer, MIXER_READ(device),&level); - if (0xFF < level) printf("%s:%s = left:%d\t right:%d\n", mixer_name, - devices[device], level>>8, level & 0xFF); - else printf("%s:%s = %d\n",mixer_name, devices[device], level); - return; - } - - level=TT.level; - if (!(toys.optflags & FLAG_r)) level = TT.right | (level<<8); - - xioctl(mixer, MIXER_WRITE(device),&level); - close(mixer); -} diff --git a/toys/pending/more.c b/toys/pending/more.c index 9d89626..f0e7907 100644 --- a/toys/pending/more.c +++ b/toys/pending/more.c @@ -17,7 +17,6 @@ config MORE #define FOR_more #include "toys.h" -#include <signal.h> GLOBALS( struct termios inf; @@ -33,31 +32,58 @@ static void signal_handler(int sig) _exit(sig | 128); } +static void show_file_header(const char *name) +{ + printf(":::::::::::::::::::::::\n%s\n:::::::::::::::::::::::\n", name); +} + +static int prompt(FILE *cin, const char* fmt, ...) +{ + int input_key; + va_list ap; + + printf("\33[7m"); // Reverse video before printing the prompt. + + va_start(ap, fmt); + vfprintf(stdout, fmt, ap); + va_end(ap); + + while (1) { + fflush(NULL); + input_key = tolower(getc(cin)); + printf("\33[0m\33[1K\r"); // Reset all attributes, erase to start of line. + if (strchr(" \nrq", input_key)) { + fflush(NULL); + return input_key; + } + printf("\33[7m(Enter:Next line Space:Next page Q:Quit R:Show the rest)"); + } +} + static void do_cat_operation(int fd, char *name) { char *buf = NULL; - if(toys.optc > 1) printf(":::::::::::::::::::::::\n" - "%s\n:::::::::::::::::::::::\n",name); + if (toys.optc > 1) show_file_header(name); for (; (buf = get_line(fd)); free(buf)) printf("%s\n", buf); } void more_main() { - int ch, lines, input_key = 0, disp_more, more_msg_len; - unsigned rows = 24, cols = 80; + int ch, input_key = 0, show_prompt; + unsigned rows = 24, cols = 80, row = 0, col = 0; struct stat st; struct termios newf; FILE *fp, *cin; if (!isatty(STDOUT_FILENO) || !(cin = fopen("/dev/tty", "r"))) { loopfiles(toys.optargs, do_cat_operation); - toys.exitval = 0; return; } TT.cin_fd = fileno(cin); - tcgetattr(TT.cin_fd,&TT.inf); + tcgetattr(TT.cin_fd, &TT.inf); + //Prepare terminal for input memcpy(&newf, &TT.inf, sizeof(struct termios)); newf.c_lflag &= ~(ICANON | ECHO); @@ -70,52 +96,54 @@ void more_main() do { fp = stdin; if (*toys.optargs && !(fp = fopen(*toys.optargs, "r"))) { - perror_msg("'%s'", *toys.optargs); - continue; + perror_msg("%s", *toys.optargs); + goto next_file; } - st.st_size = disp_more = more_msg_len = lines = 0; + st.st_size = show_prompt = col = row = 0; fstat(fileno(fp), &st); terminal_size(&cols, &rows); rows--; - if(toys.optc > 1) { - printf(":::::::::::::::::::::::\n" - "%s\n:::::::::::::::::::::::\n",*toys.optargs); - rows -= 3; + + if (toys.optc > 1) { + show_file_header(*toys.optargs); + row += 3; } while ((ch = getc(fp)) != EOF) { - if (input_key != 'r' && disp_more) { - more_msg_len = printf("--More-- "); - if (st.st_size) - more_msg_len += printf("(%d%% of %lld bytes)", - (int) (100 * ( (double) ftell(fp) / (double) st.st_size)), - st.st_size); - fflush(NULL); - - while (1) { - input_key = getc(cin); - input_key = tolower(input_key); - printf("\r%*s\r", more_msg_len, ""); // Remove previous msg - if (input_key == ' ' || input_key == '\n' || input_key == 'q' - || input_key == 'r') break; - more_msg_len = printf("(Enter:Next line Space:Next page Q:Quit R:Show the rest)"); - } - more_msg_len = lines = disp_more = 0; + if (input_key != 'r' && show_prompt) { + if (st.st_size) + input_key = prompt(cin, "--More--(%d%% of %lld bytes)", + (int) (100 * ( (double) ftell(fp) / (double) st.st_size)), + (long long)st.st_size); + else + input_key = prompt(cin, "--More--"); if (input_key == 'q') goto stop; + + col = row = show_prompt = 0; terminal_size(&cols, &rows); rows--; } - if (ch == '\n') - if (++lines >= rows || input_key == '\n') disp_more = 1; putchar(ch); + if (ch == '\t') col = (col | 0x7) + 1; else col++; + if (col == cols) putchar(ch = '\n'); + if (ch == '\n') { + col = 0; + if (++row >= rows || input_key == '\n') show_prompt = 1; + } } fclose(fp); - fflush(NULL); - } while (*toys.optargs && *++toys.optargs); + +next_file: + if (*toys.optargs && *++toys.optargs) { + input_key = prompt(cin, "--More--(Next file: %s)", *toys.optargs); + if (input_key == 'q') goto stop; + } + } while (*toys.optargs); stop: tcsetattr(TT.cin_fd, TCSANOW, &TT.inf); fclose(cin); + // Even if optarg not found, exit value still 0 toys.exitval = 0; } diff --git a/toys/pending/netstat.c b/toys/pending/netstat.c index 5935563..fbb9eb1 100644 --- a/toys/pending/netstat.c +++ b/toys/pending/netstat.c @@ -76,15 +76,6 @@ static const char *get_basename(char *name) if (c) return c + 1; return name; } -/* - * copy string from src to dest -> only number of bytes. - */ -static char *safe_strncpy(char *dst, char *src, size_t size) -{ - if(!size) return dst; - dst[--size] = '\0'; - return strncpy(dst, src, size); -} /* * locate character in string. @@ -410,7 +401,7 @@ static void add2list(long inode, char *progname) } PID_LIST *new = (PID_LIST *)xzalloc(sizeof(PID_LIST)); new->inode = inode; - safe_strncpy(new->name, progname, PROGNAME_LEN-1); + xstrncpy(new->name, progname, PROGNAME_LEN); new->next = pid_list; pid_list = new; } diff --git a/toys/pending/pgrep.c b/toys/pending/pgrep.c index 77b6ced..59767b9 100644 --- a/toys/pending/pgrep.c +++ b/toys/pending/pgrep.c @@ -5,7 +5,7 @@ * USE_PGREP(NEWTOY(pgrep, "?P# s# xvonlf[!sP]", TOYFLAG_USR|TOYFLAG_BIN)) -USE_PGREP(OLDTOY(pkill, pgrep, OPTSTR_pgrep, TOYFLAG_USR|TOYFLAG_BIN)) +USE_PGREP(OLDTOY(pkill, pgrep, TOYFLAG_USR|TOYFLAG_BIN)) config PGREP bool "pgrep" diff --git a/toys/pending/printf.c b/toys/pending/printf.c deleted file mode 100644 index 0970c8c..0000000 --- a/toys/pending/printf.c +++ /dev/null @@ -1,269 +0,0 @@ -/* printf.c - Format and Print the data. - * - * Copyright 2014 Sandeep Sharma <sandeep.jack2756@gmail.com> - * Copyright 2014 Kyungwan Han <asura321@gmail.com> - * - * See http://pubs.opengroup.org/onlinepubs/9699919799/utilities/printf.html - -USE_PRINTF(NEWTOY(printf, "<1", TOYFLAG_USR|TOYFLAG_BIN)) - -config PRINTF - bool "printf" - default n - help - usage: printf Format [Arguments..] - - Format and print ARGUMENT(s) according to FORMAT. - Format is 'C' control output as 'C' printf. -*/ -#define FOR_printf -#include "toys.h" - -GLOBALS( - char *hv_w; - char *hv_p; - int encountered; -) - -// Calculate width and precision from format string -static int get_w_p() -{ - char *ptr, *str = *toys.optargs; - - errno = 0; - if (*str == '-') str++; - long value = strtol(str, &ptr, 10); - if (errno || (ptr && (*ptr != '\0' || ptr == str))) - perror_msg("Invalid num %s", *toys.optargs); - if (*--str == '-') return (int)(-1 * value); - return value; -} - -// Add ll and L to Interger and floating point formats respectively. -static char *get_format(char *f) -{ - int len = strlen(f); - char last = f[--len], *post = ""; - - f[len] = '\0'; - if (strchr("diouxX", last)) post = "ll"; // add ll to integer modifier. - else if (strchr("feEgG", last)) post = "L"; // add L to float modifier. - return xmprintf("%s%s%c", f, post, last); -} - -// Print arguments with corresponding conversion and width and precision. -static void print(char *fmt, int w, int p, int l) -{ - char *ptr = (fmt+l-1), *ep = NULL, *format = NULL; - long long val; - long double dval; - - errno = 0; - switch (*ptr) { - case 'd': /*FALL_THROUGH*/ - case 'i': /*FALL_THROUGH*/ - case 'o': /*FALL_THROUGH*/ - case 'u': /*FALL_THROUGH*/ - case 'x': /*FALL_THROUGH*/ - case 'X': - if (!*toys.optargs) val = 0; - else { - if (**toys.optargs == '\'' || **toys.optargs == '"') - val = *((*toys.optargs) + 1); - else { - val = strtoll(*toys.optargs, &ep, 0); - if (errno || (ep && (*ep != '\0' || ep == *toys.optargs))) { - perror_msg("Invalid num %s", *toys.optargs); - val = 0; - } - } - } - format = get_format(fmt); - TT.hv_w ? (TT.hv_p ? printf(format, w, p, val) : printf(format, w, val)) - : (TT.hv_p ? printf(format, p, val) : printf(format, val)); - break; - case 'g': /*FALL_THROUGH*/ - case 'e': /*FALL_THROUGH*/ - case 'E': /*FALL_THROUGH*/ - case 'G': /*FALL_THROUGH*/ - case 'f': - if (*toys.optargs) { - dval = strtold(*toys.optargs, &ep); - if (errno || (ep && (*ep != '\0' || ep == *toys.optargs))) { - perror_msg("Invalid num %s", *toys.optargs); - dval = 0; - } - } else dval = 0; - format = get_format(fmt); - TT.hv_w ? (TT.hv_p ? printf(format, w, p, dval):printf(format, w, dval)) - : (TT.hv_p ? printf(format, p, dval) : printf(format, dval)); - break; - case 's': - { - char *str = (*toys.optargs ? *toys.optargs : ""); - TT.hv_w ? (TT.hv_p ? printf(fmt,w,p,str): printf(fmt, w, str)) - : (TT.hv_p ? printf(fmt, p, str) : printf(fmt, str)); - } - break; - case 'c': - printf(fmt, (*toys.optargs ? **toys.optargs : '\0')); - break; - } - if (format) free(format); -} - -// Handle the escape sequences. -static int handle_slash(char **esc_val) -{ - char *ptr = *esc_val; - int esc_length = 0; - unsigned base = 0, num = 0, result = 0, count = 0; - - /* - * Hex escape sequence have only 1 or 2 digits, xHH. Oct escape sequence - * have 1,2 or 3 digits, xHHH. Leading "0" (\0HHH) we are ignoring. - */ - if (*ptr == 'x') { - ptr++; - esc_length++; - base = 16; - } else if (isdigit(*ptr)) base = 8; - - while (esc_length < 3 && base) { - num = tolower(*ptr) - '0'; - if (num > 10) num += ('0' - 'a' + 10); - if (num >= base) { - if (base == 16) { - esc_length--; - if (!esc_length) {// Invalid hex value eg. /xvd, print as it is /xvd - result = '\\'; - ptr--; - } - } - break; - } - esc_length++; - count = result = (count * base) + num; - ptr++; - } - if (base) { - ptr--; - *esc_val = ptr; - return (char)result; - } else { - switch (*ptr) { - case 'n': result = '\n'; break; - case 't': result = '\t'; break; - case 'e': result = (char)27; break; - case 'b': result = '\b'; break; - case 'a': result = '\a'; break; - case 'f': result = '\f'; break; - case 'v': result = '\v'; break; - case 'r': result = '\r'; break; - case '\\': result = '\\'; break; - default : - result = '\\'; - ptr--; // Let pointer pointing to / we will increment after returning. - break; - } - } - *esc_val = ptr; - return (char)result; -} - -// Handle "%b" option with '\' interpreted. -static void print_esc_str(char *str) -{ - for (; *str; str++) { - if (*str == '\\') { - str++; - xputc(handle_slash(&str)); //print corresponding char - } else xputc(*str); - } -} - -// Prase the format string and print. -static void parse_print(char *f) -{ - char *start, *p, format_specifiers[] = "diouxXfeEgGcs"; - int len = 0, width = 0, prec = 0; - - while (*f) { - switch (*f) { - case '%': - start = f++; - len++; - if (*f == '%') { - xputc('%'); - break; - } - if (*f == 'b') { - if (*toys.optargs) { - print_esc_str(*toys.optargs++); - TT.encountered = 1; - } else print_esc_str(""); - break; - } - if (strchr("-+# ", *f)) f++, len++; - if (*f == '*') { - f++, len++; - if (*toys.optargs) { - width = get_w_p(); - toys.optargs++; - } - } else { - while (isdigit(*f)) f++, len++; - } - if (*f == '.') { - f++, len++; - if (*f == '*') { - f++, len++; - if (*toys.optargs) { - prec = get_w_p(); - toys.optargs++; - } - } else { - while (isdigit(*f)) f++, len++; - } - } - if (!(p = strchr(format_specifiers, *f))) - perror_exit("Missing OR Invalid format specifier"); - else { - len++; - TT.hv_p = strstr(start, ".*"); - TT.hv_w = strchr(start, '*'); - //pitfall: handle diff b/w * and .* - if ((TT.hv_w-1) == TT.hv_p) TT.hv_w = NULL; - memcpy((p = xzalloc(len+1)), start, len); - print(p, width, prec, len); - if (*toys.optargs) toys.optargs++; - free(p); - p = NULL; - } - TT.encountered = 1; - break; - case '\\': - if (f[1]) { - if (*++f == 'c') exit(0); //Got '\c', so no further output - xputc(handle_slash(&f)); - } else xputc(*f); - break; - default: - xputc(*f); - break; - } - f++; - len = 0; - } -} - -void printf_main(void) -{ - char *format = *toys.optargs++; - - TT.encountered = 0; - parse_print(format); //printf acc. to format. - //Re-use FORMAT arg as necessary to convert all given ARGS. - while (*toys.optargs && TT.encountered) parse_print(format); - xflush(); -} diff --git a/toys/pending/ps.c b/toys/pending/ps.c index f6680bf..cb0f32c 100644 --- a/toys/pending/ps.c +++ b/toys/pending/ps.c @@ -1,426 +1,358 @@ -/* ps.c - Show running process statistics. +/* ps.c - show process list * - * Copyright 2013 Sandeep Sharma <sandeep.jack2756@gmail.com> - * Copyright 2013 Kyungwan Han <asura321@gmail.com> + * Copyright 2015 Rob Landley <rob@landley.net> * * See http://pubs.opengroup.org/onlinepubs/9699919799/utilities/ps.html + * And http://kernel.org/doc/Documentation/filesystems/proc.txt Table 1-4 + * And linux kernel source fs/proc/array.c function do_task_stat() + * + * Deviations from posix: no -n to specify an alternate /etc/passwd (??!?) + * Posix says default output should have field named "TTY" but if you "-o tty" + * the same field should be called "TT" which is _INSANE_ and I'm not doing it. + * It also says that -o "args" and "comm" should behave differently but use + * the same title, which is not the same title as the default output. No. -USE_PS(NEWTOY(ps, ">0o*T", TOYFLAG_BIN)) +USE_PS(NEWTOY(ps, "aAdeflo*[!ol][+Ae]", TOYFLAG_USR|TOYFLAG_BIN)) config PS bool "ps" default n help - usage: ps [-o COL1,COL2=HEADER] [-T] - - Show list of processes - - -o COL1,COL2=HEADER Select columns for display - -T Show threads + usage: ps [-Aade] [-fl] [-gG GROUP] [-o FIELD] [-p PID] [-t TTY] [-u USER] + + List processes. + + -A All processes + -a Processes with terminals, except session leaders + -d Processes that aren't session leaders + -e Same as -A + -f Full listing + -l Long listing + + -g Processes belonging to these session leaders + -G Processes with these real group IDs + -o Show FIELDS for each process + -p select by PID + -t select by TTY + -u select by USER + -U select by USER + + GROUP, FIELD, PID, TTY, and USER are comma separated lists. + + OUTPUT (-o) FIELDS: + + "UID", "PID", "PPID", "C", "PRI", "NI", "ADDR", "SZ", + "WCHAN", "STIME", "TTY", "TIME", "CMD", "COMMAND", "ELAPSED", "GROUP", + "%CPU", "PGID", "RGROUP", "RUSER", "USER", "VSZ" + + C Processor utilization for scheduling + F Process flags (PF_*) from linux source file include/sched.h + (in octal rather than hex because posix) + S Process state: + R (running) S (sleeping) D (disk sleep) T (stopped) t (tracing stop) + Z (zombie) X (dead) x (dead) K (wakekill) W (waking) + PID Process id + PPID Parent process id + PRI Priority + UID User id of process owner + + Default output is -o PID,TTY,TIME,CMD + With -f USER=UID,PID,PPID,C,STIME,TTY,TIME,CMD + With -l F,S,UID,PID,PPID,C,PRI,NI,ADDR,SZ,WCHAN,TTY,TIME,CMD */ #define FOR_ps #include "toys.h" GLOBALS( - struct arg_list *llist_o; - unsigned screen_width; + struct arg_list *o; + + unsigned width; + dev_t tty; + void *fields; + long uptime; ) -#define BUFF_SIZE 1024 -struct header_list { - char *name; - char *header; - char *format; - int width; - int position; - struct header_list *next; -}; +/* + l l fl a fl fl l l l l l f a a a + F S UID PID PPID C PRI NI ADDR SZ WCHAN STIME TTY TIME CMD + ruser user rgroup group pid ppid pgid pcpu vsz nice etime time tty comm args -struct header_list *o_list = NULL; //List of Header attributes. + todo: thread support /proc/$d/task/%d/stat + man page: F flags mean... +*/ -/* - * create list of header attributes taking care of -o (-o ooid=MOM..) - * and width of attributes. - */ -static void list_add(struct header_list **list, struct header_list *data, char *c_data) -{ - struct header_list *temp = *list, *new = xzalloc(sizeof(struct header_list)); - - new->name = data->name; - if (c_data) new->header = c_data; - else new->header = xstrdup(data->header); - if (c_data && (strlen(c_data) > data->width)) new->width = strlen(c_data); - else new->width = data->width; - new->format = data->format; - new->position = data->position; - - if (temp) { - while (temp->next) temp = temp->next; - temp->next = new; - } else *list = new; -} +struct strawberry { + struct strawberry *next, *prev; + short which, len; + char title[]; +}; -//print the default header OR header with -o args -static void print_header(struct header_list *hdr, int hdr_len) +// dirtree callback. +// toybuf used as: 1024 /proc/$PID/stat, 1024 slot[], 2048 /proc/$PID/cmdline +static int do_ps(struct dirtree *new) { - int i = 0; - char *ptr = NULL, *str, *temp; - struct arg_list *node = TT.llist_o; - - // Default pid, user, time, comm - if (!node) { - list_add(&o_list, hdr+4, 0); - list_add(&o_list, hdr, 0); - list_add(&o_list, hdr+11, 0); - list_add(&o_list, hdr+3, 0); - } + struct strawberry *field; + long long *slot = (void *)(toybuf+1024); + char *name, *s, state; + int nlen, i, fd, len, width = TT.width; + + if (!new->parent) return DIRTREE_RECURSE; + if (!(*slot = atol(new->name))) return 0; + + // name field limited to 256 bytes by VFS, plus 40 fields * max 20 chars: + // 1000-ish total, but some forced zero so actually there's headroom. + sprintf(toybuf, "%lld/stat", *slot); + if (!readfileat(dirtree_parentfd(new), toybuf, toybuf, 1024)) return 0; + + // parse oddball fields (name and state) + if (!(s = strchr(toybuf, '('))) return 0; + for (name = ++s; *s != ')'; s++) if (!*s) return 0; + nlen = s++-name; + if (1>sscanf(++s, " %c%n", &state, &i)) return 0; + + // parse numeric fields + for (len = 1; len<100; len++) + if (1>sscanf(s += i, " %lld%n", slot+len, &i)) break; + + // skip entries we don't care about. + if ((toys.optflags&(FLAG_a|FLAG_d)) && getsid(*slot)==*slot) return 0; + if ((toys.optflags&FLAG_a) && !slot[4]) return 0; + if (!(toys.optflags&(FLAG_a|FLAG_d|FLAG_A|FLAG_e)) && TT.tty!=slot[4]) + return 0; + + for (field = TT.fields; field; field = field->next) { + char *out = toybuf+2048; + + // Default: unsupported (5 "C") + sprintf(out, "-"); + + // PID, PPID, PRI, NI, ADDR, SZ + if (-1 != (i = stridx((char[]){3,4,6,7,8,9,0}, field->which))) + sprintf(out, ((1<<i)&0x10) ? "%llx" : "%lld", + slot[((char[]){0,2,16,17,22})[i]]>>(((1<<i)&0x20) ? 12 : 0)); + // F + else if (!(i = field->which)) sprintf(out, "%llo", slot[7]); + // S + else if (i == 1) + sprintf(out, "%c", state); + // UID and USER + else if (i == 2 || i == 22) { + sprintf(out, "%d", new->st.st_uid); + if (i == 22) { + struct passwd *pw = getpwuid(new->st.st_uid); + + if (pw) out = pw->pw_name; + } + // WCHAN + } else if (i==10) { + sprintf(toybuf+512, "%lld/wchan", *slot); + readfileat(dirtree_parentfd(new), toybuf+512, out, 2047); + + // STIME + // TTY + } else if (i==12) { + + // Can we readlink() our way to a name? + for (i=0; i<3; i++) { + struct stat st; + + sprintf(toybuf+512, "%lld/fd/%i", *slot, i); + fd = dirtree_parentfd(new); + if (!fstatat(fd, toybuf+512, &st, 0) && S_ISCHR(st.st_mode) + && st.st_rdev == slot[4] + && 0<(len = readlinkat(fd, toybuf+512, out, 2047))) + { + out[len] = 0; + if (!strncmp(out, "/dev/", 5)) out += 5; - while (node) { - char *s = str = xstrdup(node->arg); - - i = 0; - while (str) { - if ((ptr = strsep(&str, ","))) { //seprate list - if ((temp = strchr(ptr, '='))) { // Handle ppid = MOM - *temp = 0; - temp++; - while (hdr[i].name) { - // search from default header - if (!(strcmp(hdr[i].name, ptr))) { - //handle condition like ppid = M,OM - if (str) ptr = xmprintf("%s,%s", temp, str); - else ptr = xmprintf("%s", temp); - list_add(&o_list, &hdr[i], ptr); - break; - } - i++; - } - if (!hdr[i].name) perror_exit("Invalid arg for -o option"); break; - } else { - while (hdr[i].name) { - if (!(strcmp(hdr[i].name, ptr))) { - list_add(&o_list, &hdr[i], 0); - break; - } - i++; - } - if (!hdr[i].name) error_exit("bad -o"); - i = 0; } } + + // Couldn't find it, show major:minor + if (i==3) { + i = slot[4]; + sprintf(out, "%d:%d", (i>>8)&0xfff, ((i>>12)&0xfff00)|(i&0xff)); + } + + // TIME ELAPSED + } else if (i==13 || i==16) { + long seconds = (i==16) ? slot[20] : slot[11]+slot[12], ll = 60*60*24; + + seconds /= sysconf(_SC_CLK_TCK); + if (i==16) seconds = TT.uptime-seconds; + for (s = out, i = 0; i<4; i++) { + if (i>1 || seconds > ll) + s += sprintf(s, (i==3) ? "%02ld" : "%ld%c", seconds/ll, "-::"[i]); + seconds %= ll; + ll /= i ? 60 : 24; + } + +//16 "ELAPSED", "GROUP", "%CPU", "PGID", "RGROUP", +//21 "RUSER", -, "VSZ" + + // Command line limited to 2k displayable. We could dynamically malloc, but + // it'd almost never get used, querying length of a proc file is awkward, + // fixed buffer is nommu friendly... Wait for somebody to complain. :) + } else if (i == 14 || i == 15) { + int fd; + + len = 0; + sprintf(out, "%lld/cmdline", *slot); + fd = openat(dirtree_parentfd(new), out, O_RDONLY); + + if (fd != -1) { + if (0<(len = read(fd, out, 2047))) { + if (!out[len-1]) len--; + else out[len] = 0; + for (i = 0; i<len; i++) if (out[i] < ' ') out[i] = ' '; + } + close(fd); + } + if (len<1) sprintf(out, "[%.*s]", nlen, name); } - free(s); - node = node->next; - } - for (hdr = o_list; hdr; hdr = hdr->next) - printf(hdr->format , hdr->width, hdr->header); + i = width<field->len ? width : field->len; + width -= printf(" %*.*s", i, field->next ? i : width, out); + } xputc('\n'); + + return 0; } -//get uid/gid for processes. -static void get_uid_gid(char *p, char *id_str, unsigned *id) +void ps_main(void) { - FILE *f; - - if(!p) return; - f = xfopen(p, "r"); - while (fgets(toybuf, BUFF_SIZE, f)) { - if (!strncmp(toybuf, id_str, strlen(id_str))) { - sscanf(toybuf, "%*s %u", id); - break; - } + struct strawberry *field; + // Octal output code followed by header name + char widths[] = {1,1,5,5,5,2,3,3,4,5,6,5,8,8,27,27,11,8,4,5,8,8,8,6}, + *typos[] = { + "F", "S", "UID", "PID", "PPID", "C", "PRI", "NI", "ADDR", "SZ", + "WCHAN", "STIME", "TTY", "TIME", "CMD", "COMMAND", "ELAPSED", "GROUP", + "%CPU", "PGID", "RGROUP", "RUSER", "USER", "VSZ" + }; + int i, fd = -1; + + TT.width = 80; + terminal_size(&TT.width, 0); + TT.width--; + + // find controlling tty, falling back to /dev/tty if none + for (i = fd = 0; i < 4; i++) { + struct stat st; + + if (i != 3 || -1 != (i = fd = open("/dev/tty", O_RDONLY))) { + if (isatty(i) && !fstat(i, &st)) { + TT.tty = st.st_rdev; + break; + } + } } - fclose(f); -} + if (fd != -1) close(fd); -//get etime for processes. -void get_etime(unsigned long s_time) -{ - unsigned long min; - unsigned sec; - struct sysinfo info; - char *temp; - - sysinfo(&info); - min = s_time/sysconf(_SC_CLK_TCK); - min = info.uptime - min; - sec = min % 60; - min = min / 60; - temp = xmprintf("%3lu:%02u", min,sec); - xprintf("%*.*s",7,7,temp); - free(temp); -} + sysinfo((void *)toybuf); + // Because "TT.uptime = *(long *)toybuf;" triggers a bug in gcc. + { + long *sigh = (long *)toybuf; + TT.uptime = *sigh; + } -//get time attributes for processes. -void get_time(unsigned long s_time, unsigned long u_time) -{ - unsigned long min; - unsigned sec; - char *temp; - - min = (s_time + u_time)/sysconf(_SC_CLK_TCK); - sec = min % 60; - min = min / 60; - temp = xmprintf("%3lu:%02u", min,sec); - xprintf("%*.*s",6,6,temp); - free(temp); -} + // Manual field selection via -o + if (toys.optflags&FLAG_o) { + struct arg_list *ol; + int length; -/* - * read command line taking care of in between NUL's - * in command line - */ -static void read_cmdline(int fd, char *cmd_ptr) -{ - int size = read(fd, cmd_ptr, BUFF_SIZE); //sizeof(cmd_buf) + for (ol = TT.o; ol; ol = ol->next) { + char *width, *type, *title, *end, *arg = ol->arg; - cmd_ptr[size] = '\0'; - while (--size > 0 && cmd_ptr[size] == '\0'); //reach to last char + // Set title, length of title, type, end of type, and display width + while ((type = comma_iterate(&arg, &length))) { + if ((end = strchr(type, '=')) && length<(end-type)) { + title = end+1; + length = (end-type)-1; + } else { + end = type+length; + title = 0; + } - while (size >= 0) { - if ((unsigned char)cmd_ptr[size] < ' ') cmd_ptr[size] = ' '; - size--; - } -} + // If changing display width, trim title at the : + if ((width = strchr(type, ':')) && width<end) { + if (!title) length = width-type; + } else width = 0; -/* - * get the processes stats and print the stats - * corresponding to header attributes. - */ -static void do_ps_line(int pid, int tid) -{ - char *stat_buff = toybuf + BUFF_SIZE, *cmd_buff = toybuf + (2*BUFF_SIZE); - char state[4] = {0,}; - int tty, tty_major, tty_minor, fd, n, nice, width_counter = 0; - struct stat stats; - struct passwd *pw; - struct group *gr; - char *name, *user, *group, *ruser, *rgroup, *ptr; - long rss; - unsigned long stime, utime, start_time, vsz; - unsigned ppid, ruid, rgid, pgid; - struct header_list *p = o_list; - - sprintf(stat_buff, "/proc/%d", pid); - if(stat(stat_buff, &stats)) return; - - if (tid) { - if (snprintf(stat_buff, BUFF_SIZE, "/proc/%d/task/%d/stat", pid, tid) >= BUFF_SIZE) return; - if (snprintf(cmd_buff, BUFF_SIZE, "/proc/%d/task/%d/cmdline", pid, tid) >= BUFF_SIZE) return; - } else { - if (snprintf(stat_buff, BUFF_SIZE, "/proc/%d/stat", pid) >= BUFF_SIZE) return; - if (snprintf(cmd_buff, BUFF_SIZE, "/proc/%d/cmdline", pid) >= BUFF_SIZE) return; - } + // Allocate structure, copy title + field = xmalloc(sizeof(struct strawberry)+length+1); + if (title) { + memcpy(field->title, title, length); + field->title[length] = 0; + } + field->len = length; - fd = xopen(stat_buff, O_RDONLY); - n = readall(fd, stat_buff, BUFF_SIZE); - xclose(fd); - if (n < 0) return; - stat_buff[n] = 0; //Null terminate the buffer. - ptr = strchr(stat_buff, '('); - ptr++; - name = ptr; - ptr = strrchr(stat_buff, ')'); - *ptr = '\0'; //unecessary if? - name = xmprintf("[%s]", name); - ptr += 2; // goto STATE - n = sscanf(ptr, "%c %u %u %*u %d %*s %*s %*s %*s %*s %*s " - "%lu %lu %*s %*s %*s %d %*s %*s %lu %lu %ld", - &state[0],&ppid, &pgid, &tty, &utime, &stime, - &nice,&start_time, &vsz,&rss); - - if (tid) pid = tid; - vsz >>= 10; //Convert into KB - rss = rss * 4; //Express in pages - tty_major = (tty >> 8) & 0xfff; - tty_minor = (tty & 0xff) | ((tty >> 12) & 0xfff00); - - if (vsz == 0 && state[0] != 'Z') state[1] = 'W'; - else state[1] = ' '; - if (nice < 0 ) state[2] = '<'; - else if (nice) state[2] = 'N'; - else state[2] = ' '; - - if (tid) { - if (snprintf(stat_buff, BUFF_SIZE, "/proc/%d/task/%d/status", pid, tid) >= BUFF_SIZE) - goto clean; - } else { - if (snprintf(stat_buff, BUFF_SIZE, "/proc/%d/status", pid) >= BUFF_SIZE) - goto clean; - } + if (width) { + field->len = strtol(++width, &title, 10); + if (!isdigit(*width) || title != end) + error_exit("bad : in -o %s@%ld", ol->arg, title-ol->arg); + end = width; + } - fd = -1; - while (p) { - int width; - width = p->width; - width_counter += (width + 1); //how much screen we hv filled, +1, extra space b/w headers - switch (p->position) { - case 0: - pw = getpwuid(stats.st_uid); - if (!pw) user = xmprintf("%d",(int)stats.st_uid); - else user = xmprintf("%s", pw->pw_name); - printf("%-*.*s", width, width, user); - free(user); - break; - case 1: - gr = getgrgid(stats.st_gid); - if (!gr) group = xmprintf("%d",(int)stats.st_gid); - else group = xmprintf("%s", gr->gr_name); - printf("%-*.*s", width, width, group); - free(group); - break; - case 2: - name[strlen(name) - 1] = '\0'; - printf("%-*.*s", width,width, name + 1); - name[strlen(name)] = ']'; //Refill it for further process. - break; - case 3: - { - int j = 0; - width_counter -= width; - if(p->next) j = width; //is args is in middle. ( -o pid,args,ppid) - else j = (TT.screen_width - width_counter % TT.screen_width); //how much screen left. - if (fd == -1) fd = open(cmd_buff, O_RDONLY); //don't want to die - else xlseek(fd, 0, SEEK_SET); - if (fd < 0) cmd_buff[0] = 0; - else read_cmdline(fd, cmd_buff); - if (cmd_buff[0]) printf("%-*.*s", j, j, cmd_buff); - else printf("%-*.*s", j, j, name); - width_counter += width; - break; + // Find type (reuse width as temp because we're done with it) + for (i = 0; i<ARRAY_LEN(typos) && !field->which; i++) { + int j, k; + char *s; + + field->which = i; + for (j = 0; j < 2; j++) { + if (!j) s = typos[i]; + // posix requires alternate names for some fields + else if (-1 == (k = stridx((char []){7, 14, 15, 16}, i))) break; + else s = ((char *[]){"nice", "args", "comm", "etime"})[k]; + + if (!strncasecmp(type, s, end-type) && strlen(s)==end-type) break; + } + if (j!=2) break; } - case 4: - printf("%*d", width, pid); - break; - case 5: - printf("%*d", width, ppid); - break; - case 6: - printf("%*d", width, pgid); - break; - case 7: - get_etime(start_time); - break; - case 8: - printf("%*d", width, nice); - break; - case 9: - get_uid_gid(stat_buff, "Gid:", &rgid); - gr = getgrgid(rgid); - if (!gr) rgroup = xmprintf("%d",(int)stats.st_gid); - else rgroup = xmprintf("%s", gr->gr_name); - printf("%-*.*s", width,width, rgroup); - free(rgroup); - break; - case 10: - get_uid_gid(stat_buff, "Uid:", &ruid); - pw = getpwuid(ruid); - if (!pw) ruser = xmprintf("%d",(int)stats.st_uid); - else ruser = xmprintf("%s", pw->pw_name); - printf("%-*.*s", width, width, ruser); - free(ruser); - break; - case 11: - get_time(utime, stime); - break; - case 12: - if (tty_major) { - char *temp = xmprintf("%d,%d", tty_major,tty_minor); - printf("%-*s", width, temp); - free(temp); - } else printf("%-*s", width, "?"); - break; - case 13: - printf("%*lu", width, vsz); - break; - case 14: - printf("%-*s", width, state); - break; - case 15: - printf("%*lu", width, rss); - break; + if (i == ARRAY_LEN(typos)) error_exit("bad -o %.*s", end-type, type); + if (!field->title) strcpy(field->title, typos[field->which]); + dlist_add_nomalloc((void *)&TT.fields, (void *)field); + } } - p = p->next; - xputc(' '); //space char - } - if (fd >= 0) xclose(fd); - xputc('\n'); -clean: - free(name); -} -//Do stats for threads (for -T option) -void do_ps_threads(int pid) -{ - DIR *d; - int tid; - struct dirent *de; - char *tmp = xmprintf("/proc/%d/task",pid); - - if (!(d = opendir(tmp))) { - free(tmp); - return; - } - while ((de = readdir(d))) { - if (isdigit(de->d_name[0])) { - tid = atoi(de->d_name); - if (tid == pid) continue; - do_ps_line(pid, tid); - } - } - closedir(d); - free(tmp); -} + // Default fields (also with -f and -l) + } else { + unsigned short def = 0x7008; -void ps_main(void) -{ - DIR *dp; - struct dirent *entry; - int pid; - struct header_list def_header[] = { - {"user", "USER", "%-*s ", 8, 0, NULL}, - {"group", "GROUP", "%-*s ", 8, 1, NULL}, - {"comm", "COMMAND", "%-*s ",16, 2, NULL}, - {"args", "COMMAND", "%-*s ",30, 3, NULL}, - {"pid", "PID", "%*s ", 5, 4, NULL}, - {"ppid","PPID", "%*s ", 5, 5, NULL}, - {"pgid", "PGID", "%*s ", 5, 6, NULL}, - {"etime","ELAPSED", "%*s ", 7, 7, NULL}, - {"nice", "NI", "%*s ", 5, 8, NULL}, - {"rgroup","RGROUP", "%-*s ", 8, 9, NULL}, - {"ruser","RUSER", "%-*s ", 8, 10, NULL}, - {"time", "TIME", "%*s ", 6, 11, NULL}, - {"tty", "TT", "%-*s ", 6, 12, NULL}, - {"vsz","VSZ", "%*s ", 7, 13, NULL}, - {"stat", "STAT", "%-*s ", 4, 14, NULL}, - {"rss", "RSS", "%*s ", 4, 15, NULL}, -{0,0,0,0,0,0} - }; - - TT.screen_width = 80; //default width - terminal_size(&TT.screen_width, NULL); - print_header(def_header, ARRAY_LEN(def_header)); - - if (!(dp = opendir("/proc"))) perror_exit("opendir"); - while ((entry = readdir(dp))) { - if (isdigit(*entry->d_name)) { - pid = atoi(entry->d_name); - do_ps_line(pid, 0); - if (toys.optflags & FLAG_T) - do_ps_threads(pid); + if (toys.optflags&FLAG_f) def = 0x783c; + if (toys.optflags&FLAG_l) def = 0x77ff; + + // order of fields[] matches posix STDOUT section, so add enabled XSI + // defaults according to bitmask + + for (i=0; def>>i; i++) { + if (!((def>>i)&1)) continue; + + field = xmalloc(sizeof(struct strawberry)+strlen(typos[i])+1); + field->which = i; + field->len = widths[i]; + strcpy(field->title, typos[i]); + dlist_add_nomalloc((void *)&TT.fields, (void *)field); } } - closedir(dp); - if (CFG_TOYBOX_FREE) { - struct header_list *temp = o_list; - while(temp) { - o_list = o_list->next; - free(temp->header); - free(temp); - temp = o_list; - } + dlist_terminate(TT.fields); + + // Print padded headers. (Numbers are right justified, everyting else left. + // time and pcpu count as numbers, tty does not) + for (field = TT.fields; field; field = field->next) { + + // right justify F, UID, PID, PPID, PRI, NI, ADDR SZ, TIME, ELAPSED, %CPU + // todo: STIME? C? + if (!((1<<field->which)&0x523dd)) field->len *= -1; + printf(" %*s", field->len, field->title); + + // -f prints USER but calls it UID (but "ps -o uid -f" is numeric...?) + if ((toys.optflags&(FLAG_f|FLAG_o))==FLAG_f && field->which==2) + field->which = 22; } + xputc('\n'); + + dirtree_read("/proc", do_ps); } diff --git a/toys/pending/reset.c b/toys/pending/reset.c deleted file mode 100644 index a12f0b6..0000000 --- a/toys/pending/reset.c +++ /dev/null @@ -1,34 +0,0 @@ -/* reset.c - A program to reset the terminal. - * - * Copyright 2014 Ashwini Kumar <ak.ashwini@gmail.com> - * Copyright 2014 Kyungwan Han <asura321@gmail.com> - * - * No Standard. - -USE_RESET(NEWTOY(reset, NULL, TOYFLAG_USR|TOYFLAG_BIN)) - -config RESET - bool "reset" - default n - help - usage: reset - - A program to reset the terminal. -*/ -#define FOR_reset -#include "toys.h" - -void reset_main(void) -{ - char *args[] = {"stty", "sane", NULL}; - - /* \033c - reset the terminal with default setting - * \033(B - set the G0 character set (B=US) - * \033[2J - clear the whole screen - * \033[0m - Reset all attributes - */ - if (isatty(1)) xprintf("\033c\033(B\033[0m\033[J\033[?25h"); - fflush(stdout); - // set the terminal to sane settings - xexec(args); -} diff --git a/toys/pending/route.c b/toys/pending/route.c index 163ce6b..ac1bbef 100644 --- a/toys/pending/route.c +++ b/toys/pending/route.c @@ -411,7 +411,7 @@ static void setroute_inet6(char **argv) if (dev_name) { char ifre_buf[sizeof(struct ifreq)] = {0,}; struct ifreq *ifre = (struct ifreq*)ifre_buf; - strncpy(ifre->ifr_name, dev_name, IFNAMSIZ-1); + xstrncpy(ifre->ifr_name, dev_name, IFNAMSIZ); xioctl(sockfd, SIOGIFINDEX, ifre); rt.rtmsg_ifindex = ifre->ifr_ifindex; } diff --git a/toys/pending/sh.c b/toys/pending/sh.c index 81f91a7..499c5db 100644 --- a/toys/pending/sh.c +++ b/toys/pending/sh.c @@ -24,8 +24,8 @@ USE_SH(NEWTOY(cd, NULL, TOYFLAG_NOFORK)) USE_SH(NEWTOY(exit, NULL, TOYFLAG_NOFORK)) -USE_SH(NEWTOY(sh, "c:"USE_SH_INTERACTIVE("i"), TOYFLAG_BIN)) -USE_SH(OLDTOY(toysh, sh, OPTSTR_sh, TOYFLAG_BIN)) +USE_SH(NEWTOY(sh, "c:i", TOYFLAG_BIN)) +USE_SH(OLDTOY(toysh, sh, TOYFLAG_BIN)) config SH bool "sh (toysh)" @@ -37,16 +37,6 @@ config SH and responds to it. -c command line to execute - -config SH_INTERACTIVE - bool "Interactive shell" - default n - depends on SH - help - This shell supports terminal control (so the shell isn't killed by CTRL-C), - job control (fg, bg, jobs), and reads /etc/profile and ~/.profile when - running interactively. - -i interactive mode (default when STDIN is a tty) config EXIT @@ -248,7 +238,7 @@ static char *parse_pipeline(char *cmdline, struct pipeline *line) if (!cmdline) return 0; - if (CFG_SH_INTERACTIVE) line->cmdline = cmdline; + line->cmdline = cmdline; // Parse command into argv[] for (;;) { @@ -257,7 +247,7 @@ static char *parse_pipeline(char *cmdline, struct pipeline *line) // Skip leading whitespace and detect end of line. while (isspace(*start)) start++; if (!*start || *start=='#') { - if (CFG_SH_INTERACTIVE) line->cmdlinelen = start-cmdline; + line->cmdlinelen = start-cmdline; return 0; } @@ -281,7 +271,7 @@ static char *parse_pipeline(char *cmdline, struct pipeline *line) start = end; } - if (CFG_SH_INTERACTIVE) line->cmdlinelen = start-cmdline; + line->cmdlinelen = start-cmdline; return start; } @@ -375,7 +365,7 @@ void sh_main(void) FILE *f; // Set up signal handlers and grab control of this tty. - if (CFG_SH_INTERACTIVE && isatty(0)) toys.optflags |= FLAG_i; + if (isatty(0)) toys.optflags |= FLAG_i; f = *toys.optargs ? xfopen(*toys.optargs, "r") : NULL; if (TT.command) handle(TT.command); diff --git a/toys/pending/syslogd.c b/toys/pending/syslogd.c index 7fb297f..450bd72 100644 --- a/toys/pending/syslogd.c +++ b/toys/pending/syslogd.c @@ -300,6 +300,10 @@ static int write_rotate(struct logfile *tf, int len) unlink(tf->filename); close(tf->logfd); tf->logfd = open(tf->filename, O_CREAT | O_WRONLY | O_APPEND, 0666); + if (tf->logfd < 0) { + perror_msg("can't open %s", tf->filename); + return -1; + } } ftruncate(tf->logfd, 0); } diff --git a/toys/pending/tar.c b/toys/pending/tar.c index 5b060c6..c8b6dff 100644 --- a/toys/pending/tar.c +++ b/toys/pending/tar.c @@ -32,6 +32,7 @@ config TAR X File with names to exclude T File with names to include */ + #define FOR_tar #include "toys.h" @@ -94,7 +95,7 @@ static void copy_in_out(int src, int dst, off_t size) static void itoo(char *str, int len, off_t val) { char *t, tmp[sizeof(off_t)*3+1]; - int cnt = sprintf(tmp, "%0*llo", len, val); + int cnt = sprintf(tmp, "%0*llo", len, (unsigned long long)val); t = tmp + cnt - len; if (*t == '0') t++; @@ -130,11 +131,11 @@ static void write_longname(struct archive_handler *tar, char *name, char type) memset(&tmp, 0, sizeof(tmp)); strcpy(tmp.name, "././@LongLink"); - sprintf(tmp.mode, "%0*d", sizeof(tmp.mode)-1, 0); - sprintf(tmp.uid, "%0*d", sizeof(tmp.uid)-1, 0); - sprintf(tmp.gid, "%0*d", sizeof(tmp.gid)-1, 0); - sprintf(tmp.size, "%0*d", sizeof(tmp.size)-1, 0); - sprintf(tmp.mtime, "%0*d", sizeof(tmp.mtime)-1, 0); + sprintf(tmp.mode, "%0*d", (int)sizeof(tmp.mode)-1, 0); + sprintf(tmp.uid, "%0*d", (int)sizeof(tmp.uid)-1, 0); + sprintf(tmp.gid, "%0*d", (int)sizeof(tmp.gid)-1, 0); + sprintf(tmp.size, "%0*d", (int)sizeof(tmp.size)-1, 0); + sprintf(tmp.mtime, "%0*d", (int)sizeof(tmp.mtime)-1, 0); itoo(tmp.size, sizeof(tmp.size), sz); tmp.type = type; memset(tmp.chksum, ' ', 8); @@ -184,12 +185,12 @@ static void add_file(struct archive_handler *tar, char **nam, struct stat *st) while ((c = strstr(hname, "../"))) hname = c + 3; if (warn && hname != name) { printf("removing leading '%.*s' " - "from member names\n",hname-name, name); + "from member names\n", (int)(hname-name), name); warn = 0; } memset(&hdr, 0, sizeof(hdr)); - strncpy(hdr.name, hname, sizeof(hdr.name)); + xstrncpy(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); @@ -202,13 +203,14 @@ static void add_file(struct archive_handler *tar, char **nam, struct stat *st) hdr.type = '1'; if (strlen(node->arg) > sizeof(hdr.link)) write_longname(tar, hname, 'K'); //write longname LINK - strncpy(hdr.link, node->arg, sizeof(hdr.link)); + xstrncpy(hdr.link, node->arg, sizeof(hdr.link)); } else if (S_ISREG(st->st_mode)) { hdr.type = '0'; if (st->st_size <= (off_t)0777777777777LL) itoo(hdr.size, sizeof(hdr.size), st->st_size); else { - error_msg("can't store file '%s' of size '%d'\n", hname, st->st_size); + error_msg("can't store file '%s' of size '%lld'\n", + hname, (unsigned long long)st->st_size); return; } } else if (S_ISLNK(st->st_mode)) { @@ -219,7 +221,7 @@ static void add_file(struct archive_handler *tar, char **nam, struct stat *st) } if (strlen(lnk) > sizeof(hdr.link)) write_longname(tar, hname, 'K'); //write longname LINK - strncpy(hdr.link, lnk, sizeof(hdr.link)); + xstrncpy(hdr.link, lnk, sizeof(hdr.link)); free(lnk); } else if (S_ISDIR(st->st_mode)) hdr.type = '5'; @@ -229,7 +231,7 @@ static void add_file(struct archive_handler *tar, char **nam, struct stat *st) itoo(hdr.major, sizeof(hdr.major), major(st->st_rdev)); itoo(hdr.minor, sizeof(hdr.minor), minor(st->st_rdev)); } else { - error_msg("unknown file type '%s'"); + error_msg("unknown file type '%o'", st->st_mode & S_IFMT); return; } if (strlen(hname) > sizeof(hdr.name)) @@ -432,7 +434,8 @@ COPY: if (pw) u = pw->pw_uid; if (gr) g = gr->gr_gid; } - chown(file_hdr->name, u, g); + if (chown(file_hdr->name, u, g)) + perror_msg("chown %d:%d '%s'", u, g, file_hdr->name);; } if (toys.optflags & FLAG_p) // || !(toys.optflags & FLAG_no_same_permissions)) @@ -744,15 +747,10 @@ SKIP: void tar_main(void) { struct archive_handler *tar_hdl; - int fd = 0, flags = O_RDONLY; + int fd = 0; struct arg_list *tmp; char **args = toys.optargs; - if (!toys.argv[1]) { - toys.exithelp++; - error_exit(NULL); - } - if (!geteuid()) toys.optflags |= FLAG_p; for (tmp = TT.exc; tmp; tmp = tmp->next) @@ -764,16 +762,16 @@ void tar_main(void) if (toys.optflags & FLAG_c) { if (!TT.inc) error_exit("empty archive"); - fd = 1, flags = O_WRONLY|O_CREAT|O_TRUNC; + fd = 1; } if ((toys.optflags & FLAG_f) && strcmp(TT.fname, "-")) - fd = xcreate(TT.fname, flags, 0666); + fd = xcreate(TT.fname, fd*(O_WRONLY|O_CREAT|O_TRUNC), 0666); if (toys.optflags & FLAG_C) xchdir(TT.dir); tar_hdl = init_handler(); tar_hdl->src_fd = fd; - if (toys.optflags & FLAG_x || toys.optflags & FLAG_t) { + if ((toys.optflags & FLAG_x) || (toys.optflags & FLAG_t)) { if (toys.optflags & FLAG_O) tar_hdl->extract_handler = extract_to_stdout; if (toys.optflags & FLAG_to_command) { signal(SIGPIPE, SIG_IGN); //will be using pipe between child & parent @@ -790,9 +788,8 @@ void tar_main(void) for (tmp = TT.inc; tmp; tmp = tmp->next) { TT.handle = tar_hdl; //recurse thru dir and add files to archive - struct dirtree *root = dirtree_add_node(0,tmp->arg,toys.optflags & FLAG_h); - - if (root) dirtree_handle_callback(root, add_to_tar); + dirtree_handle_callback(dirtree_start(tmp->arg, toys.optflags & FLAG_h), + add_to_tar); } memset(toybuf, 0, 1024); writeall(tar_hdl->src_fd, toybuf, 1024); diff --git a/toys/pending/tcpsvd.c b/toys/pending/tcpsvd.c index d7e1f6c..16110e5 100644 --- a/toys/pending/tcpsvd.c +++ b/toys/pending/tcpsvd.c @@ -7,7 +7,7 @@ * No Standard. USE_TCPSVD(NEWTOY(tcpsvd, "^<3c#=30<1C:b#=20<0u:l:hEv", TOYFLAG_USR|TOYFLAG_BIN)) -USE_TCPSVD(OLDTOY(udpsvd, tcpsvd, OPTSTR_tcpsvd, TOYFLAG_USR|TOYFLAG_BIN)) +USE_TCPSVD(OLDTOY(udpsvd, tcpsvd, TOYFLAG_USR|TOYFLAG_BIN)) config TCPSVD bool "tcpsvd" @@ -392,7 +392,7 @@ void tcpsvd_main(void) close(1); dup2(newfd, 0); dup2(newfd, 1); - xexec_optargs(2); //skip IP PORT + xexec(toys.optargs+2); //skip IP PORT } else { insert(&pids, pid, addr); xclose(newfd); //close and reopen for next client. diff --git a/toys/pending/top.c b/toys/pending/top.c index 700baba..fd7e879 100644 --- a/toys/pending/top.c +++ b/toys/pending/top.c @@ -60,11 +60,6 @@ struct cpu_info { unsigned long long total; }; -enum CODE{ - KEY_UP = 0x100, KEY_DOWN, KEY_HOME, - KEY_END, KEY_PAGEUP, KEY_PAGEDN, -}; - struct keycode_map_s { char *key; int code; @@ -208,8 +203,8 @@ static int get_key_code(char *ch, int i) }; static struct keycode_map_s type3[] = { - {"[1~", KEY_HOME}, {"[4~", KEY_END}, {"[5~", KEY_PAGEUP}, - {"[6~", KEY_PAGEDN}, {"[7~", KEY_HOME}, {"[8~", KEY_END}, + {"[1~", KEY_HOME}, {"[4~", KEY_END}, {"[5~", KEY_PGUP}, + {"[6~", KEY_PGDN}, {"[7~", KEY_HOME}, {"[8~", KEY_END}, {NULL, 0} }; struct keycode_map_s *table, *keytable[3] = {type2, type3, NULL}; @@ -833,10 +828,10 @@ void top_main(void ) case KEY_END: TT.scroll_offset = TT.num_new_procs - TT.rows/2; break; - case KEY_PAGEUP: + case KEY_PGUP: TT.scroll_offset -= TT.rows/2; break; - case KEY_PAGEDN: + case KEY_PGDN: TT.scroll_offset += TT.rows/2; break; } diff --git a/toys/pending/traceroute.c b/toys/pending/traceroute.c index 9250993..830331a 100644 --- a/toys/pending/traceroute.c +++ b/toys/pending/traceroute.c @@ -8,7 +8,7 @@ * No Standard USE_TRACEROUTE(NEWTOY(traceroute, "<1>2i:f#<1>255=1z#<0>86400=0g*w#<0>86400=5t#<0>255=0s:q#<1>255=3p#<1>65535=33434m#<1>255=30rvndlIUF64", TOYFLAG_STAYROOT|TOYFLAG_USR|TOYFLAG_BIN)) -USE_TRACEROUTE(OLDTOY(traceroute6,traceroute, OPTSTR_traceroute, TOYFLAG_STAYROOT|TOYFLAG_USR|TOYFLAG_BIN)) +USE_TRACEROUTE(OLDTOY(traceroute6,traceroute, TOYFLAG_STAYROOT|TOYFLAG_USR|TOYFLAG_BIN)) config TRACEROUTE bool "traceroute" default n diff --git a/toys/pending/useradd.c b/toys/pending/useradd.c index 4f2bcc6..78f083b 100644 --- a/toys/pending/useradd.c +++ b/toys/pending/useradd.c @@ -6,7 +6,7 @@ * See http://refspecs.linuxfoundation.org/LSB_4.1.0/LSB-Core-generic/LSB-Core-generic/useradd.html USE_USERADD(NEWTOY(useradd, "<1>2u#<0G:s:g:h:SDH", TOYFLAG_NEEDROOT|TOYFLAG_UMASK|TOYFLAG_SBIN)) -USE_USERADD(OLDTOY(adduser, useradd, OPTSTR_useradd, TOYFLAG_NEEDROOT|TOYFLAG_UMASK|TOYFLAG_SBIN)) +USE_USERADD(OLDTOY(adduser, useradd, TOYFLAG_NEEDROOT|TOYFLAG_UMASK|TOYFLAG_SBIN)) config USERADD bool "useradd" diff --git a/toys/pending/userdel.c b/toys/pending/userdel.c index 8619a62..9c93a21 100644 --- a/toys/pending/userdel.c +++ b/toys/pending/userdel.c @@ -5,7 +5,7 @@ * See http://refspecs.linuxfoundation.org/LSB_4.1.0/LSB-Core-generic/LSB-Core-generic/userdel.html USE_USERDEL(NEWTOY(userdel, "<1>1r", TOYFLAG_NEEDROOT|TOYFLAG_SBIN)) -USE_USERDEL(OLDTOY(deluser, userdel, OPTSTR_userdel, TOYFLAG_NEEDROOT|TOYFLAG_SBIN)) +USE_USERDEL(OLDTOY(deluser, userdel, TOYFLAG_NEEDROOT|TOYFLAG_SBIN)) config USERDEL bool "userdel" diff --git a/toys/posix/cat.c b/toys/posix/cat.c index 3644c4f..3aae4a1 100644 --- a/toys/posix/cat.c +++ b/toys/posix/cat.c @@ -3,8 +3,12 @@ * Copyright 2006 Rob Landley <rob@landley.net> * * See http://opengroup.org/onlinepubs/9699919799/utilities/cat.html + * + * And "Cat -v considered harmful" at + * http://cm.bell-labs.com/cm/cs/doc/84/kp.ps.gz -USE_CAT(NEWTOY(cat, "u", TOYFLAG_BIN)) +USE_CAT(NEWTOY(cat, "u"USE_CAT_V("vte"), TOYFLAG_BIN)) +USE_CATV(NEWTOY(catv, USE_CATV("vte"), TOYFLAG_USR|TOYFLAG_BIN)) config CAT bool "cat" @@ -16,19 +20,73 @@ config CAT Filename "-" is a synonym for stdin. -u Copy one byte at a time (slow). + +config CAT_V + bool "cat -etv" + default n + depends on CAT + help + usage: cat [-evt] + + -e Mark each newline with $ + -t Show tabs as ^I + -v Display nonprinting characters as escape sequences. Use M-x for + high ascii characters (>127), and ^x for other nonprinting chars. + +config CATV + bool "catv" + default y + help + usage: catv [-evt] [filename...] + + Display nonprinting characters as escape sequences. Use M-x for + high ascii characters (>127), and ^x for other nonprinting chars. + + -e Mark each newline with $ + -t Show tabs as ^I + -v Don't use ^x or M-x escapes. */ +#define FOR_cat +#define FORCE_FLAGS #include "toys.h" static void do_cat(int fd, char *name) { - int len, size=toys.optflags ? 1 : sizeof(toybuf); + int i, len, size=(toys.optflags & FLAG_u) ? 1 : sizeof(toybuf); - for (;;) { + for(;;) { len = read(fd, toybuf, size); - if (len<0) perror_msg("%s",name); - if (len<1) break; - xwrite(1, toybuf, len); + if (len < 0) { + toys.exitval = EXIT_FAILURE; + perror_msg("%s", name); + } + if (len < 1) break; + if ((CFG_CAT_V || CFG_CATV) && (toys.optflags&~FLAG_u)) { + for (i=0; i<len; i++) { + char c=toybuf[i]; + + if (c > 126 && (toys.optflags & FLAG_v)) { + if (c > 127) { + printf("M-"); + c -= 128; + } + if (c == 127) { + printf("^?"); + continue; + } + } + if (c < 32) { + if (c == 10) { + if (toys.optflags & FLAG_e) xputc('$'); + } else if (toys.optflags & (c==9 ? FLAG_t : FLAG_v)) { + printf("^%c", c+'@'); + continue; + } + } + xputc(c); + } + } else xwrite(1, toybuf, len); } } @@ -36,3 +94,9 @@ void cat_main(void) { loopfiles(toys.optargs, do_cat); } + +void catv_main(void) +{ + toys.optflags ^= FLAG_v; + loopfiles(toys.optargs, do_cat); +} diff --git a/toys/posix/chgrp.c b/toys/posix/chgrp.c index 3aa2514..6b95c6a 100644 --- a/toys/posix/chgrp.c +++ b/toys/posix/chgrp.c @@ -4,20 +4,17 @@ * * See http://opengroup.org/onlinepubs/9699919799/utilities/chown.html * See http://opengroup.org/onlinepubs/9699919799/utilities/chgrp.html - * - * TODO: group only one of [HLP] -USE_CHGRP(NEWTOY(chgrp, "<2hPLHRfv", TOYFLAG_BIN)) -USE_CHGRP(OLDTOY(chown, chgrp, OPTSTR_chgrp, TOYFLAG_BIN)) +USE_CHGRP(NEWTOY(chgrp, "<2hPLHRfv[-HLP]", TOYFLAG_BIN)) +USE_CHOWN(OLDTOY(chown, chgrp, TOYFLAG_BIN)) config CHGRP - bool "chgrp/chown" + bool "chgrp" default y help - usage: chown [-RHLP] [-fvh] [owner][:group] file... - usage: chgrp [-RHLP] [-fvh] group file... + usage: chgrp/chown [-RHLP] [-fvh] group file... - Change ownership of one or more files. + Change group of one or more files. -f suppress most error messages. -h change symlinks instead of what they point to @@ -26,9 +23,16 @@ config CHGRP -L with -R change target of symlink, follow all symlinks -P with -R change symlink, do not follow symlinks (default) -v verbose output. + +config CHOWN + bool "chown" + default y + help + see: chgrp */ #define FOR_chgrp +#define FORCE_FLAGS #include "toys.h" GLOBALS( @@ -45,12 +49,11 @@ static int do_chgrp(struct dirtree *node) // Depth first search if (!dirtree_notdotdot(node)) return 0; if ((flags & FLAG_R) && !node->again && S_ISDIR(node->st.st_mode)) - return DIRTREE_COMEAGAIN|((flags&FLAG_L) ? DIRTREE_SYMFOLLOW : 0); + return DIRTREE_COMEAGAIN|(DIRTREE_SYMFOLLOW*!!(flags&FLAG_L)); fd = dirtree_parentfd(node); ret = fchownat(fd, node->name, TT.owner, TT.group, - (flags&(FLAG_L|FLAG_H)) || !(flags&(FLAG_h|FLAG_R)) - ? 0 : AT_SYMLINK_NOFOLLOW); + AT_SYMLINK_NOFOLLOW*(!(flags&(FLAG_L|FLAG_H)) && (flags&(FLAG_h|FLAG_R)))); if (ret || (flags & FLAG_v)) { char *path = dirtree_path(node, 0); @@ -70,42 +73,34 @@ static int do_chgrp(struct dirtree *node) void chgrp_main(void) { - int ischown = toys.which->name[2] == 'o', hl = toys.optflags&(FLAG_H|FLAG_L); + int ischown = toys.which->name[2] == 'o'; char **s, *own; + TT.owner = TT.group = -1; + // Distinguish chown from chgrp if (ischown) { char *grp; - struct passwd *p; own = xstrdup(*toys.optargs); if ((grp = strchr(own, ':')) || (grp = strchr(own, '.'))) { *(grp++) = 0; TT.group_name = grp; } - if (*own) { - TT.owner_name = own; - p = getpwnam(own); - // TODO: trailing garbage? - if (!p && isdigit(*own)) p=getpwuid(atoi(own)); - if (!p) error_exit("no user '%s'", own); - TT.owner = p->pw_uid; - } + if (*own) TT.owner = xgetpwnamid(TT.owner_name = own)->pw_uid; } else TT.group_name = *toys.optargs; - if (TT.group_name) { - struct group *g; - g = getgrnam(TT.group_name); - if (!g) g=getgrgid(atoi(TT.group_name)); - if (!g) error_exit("no group '%s'", TT.group_name); - TT.group = g->gr_gid; - } + if (TT.group_name && *TT.group_name) + TT.group = xgetgrnamid(TT.group_name)->gr_gid; - for (s=toys.optargs+1; *s; s++) { - struct dirtree *new = dirtree_add_node(0, *s, hl); - if (new) dirtree_handle_callback(new, do_chgrp); - else toys.exitval = 1; - } + for (s=toys.optargs+1; *s; s++) + dirtree_handle_callback(dirtree_start(*s, toys.optflags&(FLAG_H|FLAG_L)), + do_chgrp);; if (CFG_TOYBOX_FREE && ischown) free(own); } + +void chown_main() +{ + chgrp_main(); +} diff --git a/toys/posix/cp.c b/toys/posix/cp.c index 07a8f05..d5e92f2 100644 --- a/toys/posix/cp.c +++ b/toys/posix/cp.c @@ -4,13 +4,12 @@ * * Posix says "cp -Rf dir file" shouldn't delete file, but our -f does. -// This is subtle: MV options must be in same order (right to left) as CP -// for FLAG_X macros to work out right. +// options shared between mv/cp must be in same order (right to left) +// for FLAG macros to work out right in shared infrastructure. -USE_CP(NEWTOY(cp, "<2RHLPp"USE_CP_MORE("rdaslvnF")"fi[-HLPd]"USE_CP_MORE("[-ni]"), TOYFLAG_BIN)) -USE_CP_MV(OLDTOY(mv, cp, "<2"USE_CP_MORE("vnF")"fi"USE_CP_MORE("[-ni]"), TOYFLAG_BIN)) +USE_CP(NEWTOY(cp, "<2"USE_CP_PRESERVE("(preserve):;")"RHLPp"USE_CP_MORE("rdaslvnF(remove-destination)")"fi[-HLP"USE_CP_MORE("d")"]"USE_CP_MORE("[-ni]"), TOYFLAG_BIN)) +USE_MV(NEWTOY(mv, "<2"USE_CP_MORE("vnF")"fi"USE_CP_MORE("[-ni]"), TOYFLAG_BIN)) USE_INSTALL(NEWTOY(install, "<1cdDpsvm:o:g:", TOYFLAG_USR|TOYFLAG_BIN)) -* config CP bool "cp" @@ -22,7 +21,7 @@ config CP be a directory. -f delete destination files we can't write to - -F delete any existing destination file first (breaks hardlinks) + -F delete any existing destination file first (--remove-destination) -i interactive, prompt before overwriting existing DEST -p preserve timestamps, ownership, and permissions -R recurse into subdirectories (DEST must be a directory) @@ -45,7 +44,22 @@ config CP_MORE -s symlink instead of copy -v verbose -config CP_MV +config CP_PRESERVE + bool "cp --preserve support" + default y + depends on CP_MORE + help + usage: cp [--preserve=mota] + + --preserve takes either a comma separated list of attributes, or the first + letter(s) of: + + mode - permissions (ignore umask for rwx, copy suid and sticky bit) + ownership - user and group + timestamps - file creation, modification, and access times. + all - all of the above + +config MV bool "mv" default y depends on CP @@ -55,10 +69,10 @@ config CP_MV -f force copy by deleting destination file -i interactive, prompt before overwriting existing DEST -config CP_MV_MORE +config MV_MORE bool default y - depends on CP_MV && CP_MORE + depends on MV && CP_MORE help usage: mv [-vn] @@ -88,16 +102,24 @@ config INSTALL #include "toys.h" GLOBALS( - // install's options - char *group; - char *user; - char *mode; + union { + struct { + // install's options + char *group; + char *user; + char *mode; + } i; + struct { + char *preserve; + } c; + }; char *destname; struct stat top; int (*callback)(struct dirtree *try); uid_t uid; gid_t gid; + int pflags; ) // Callback from dirtree_read() for each file/directory under a source dir. @@ -208,7 +230,7 @@ int cp_node(struct dirtree *try) if (*or->name == '/') dotdots = 0; if (dotdots) { - char *s2 = xmprintf("% *c%s", 3*dotdots, ' ', s); + char *s2 = xmprintf("%*c%s", 3*dotdots, ' ', s); free(s); s = s2; while(dotdots--) { @@ -246,43 +268,55 @@ int cp_node(struct dirtree *try) if (fdin < 0) { catch = try->name; break; - } else { - fdout = openat(cfd, catch, O_RDWR|O_CREAT|O_TRUNC, try->st.st_mode); - if (fdout >= 0) { - xsendfile(fdin, fdout); - err = 0; - } - close(fdin); } + fdout = openat(cfd, catch, O_RDWR|O_CREAT|O_TRUNC, try->st.st_mode); + if (fdout >= 0) { + xsendfile(fdin, fdout); + err = 0; + } + close(fdin); } } while (err && (flags & (FLAG_f|FLAG_n)) && !unlinkat(cfd, catch, 0)); } + // Did we make a thing? if (fdout != -1) { - if (flags & (FLAG_a|FLAG_p)) { - struct timespec times[2]; + int rc; - // Inability to set these isn't fatal, some require root access. + // Inability to set --preserve isn't fatal, some require root access. - times[0] = try->st.st_atim; - times[1] = try->st.st_mtim; + // ownership + if (TT.pflags & 2) { + // permission bits already correct for mknod and don't apply to symlink // If we can't get a filehandle to the actual object, use racy functions - if (fdout == AT_FDCWD) { - fchownat(cfd, catch, try->st.st_uid, try->st.st_gid, - AT_SYMLINK_NOFOLLOW); - utimensat(cfd, catch, times, AT_SYMLINK_NOFOLLOW); - // permission bits already correct for mknod, don't apply to symlink - } else { - fchown(fdout, try->st.st_uid, try->st.st_gid); - futimens(fdout, times); - fchmod(fdout, try->st.st_mode); + if (fdout == AT_FDCWD) + rc = fchownat(cfd, catch, try->st.st_uid, try->st.st_gid, + AT_SYMLINK_NOFOLLOW); + else rc = fchown(fdout, try->st.st_uid, try->st.st_gid); + if (rc) { + char *pp; + + perror_msg("chown '%s'", pp = dirtree_path(try, 0)); + free(pp); } } - if (fdout != AT_FDCWD) xclose(fdout); + // timestamp + if (TT.pflags & 4) { + struct timespec times[] = {try->st.st_atim, try->st.st_mtim}; + + if (fdout == AT_FDCWD) utimensat(cfd, catch, times, AT_SYMLINK_NOFOLLOW); + else futimens(fdout, times); + } + + // mode comes last because other syscalls can strip suid bit + if (fdout != AT_FDCWD) { + if (TT.pflags & 1) fchmod(fdout, try->st.st_mode); + xclose(fdout); + } - if (CFG_CP_MV && toys.which->name[0] == 'm') + if (CFG_MV && toys.which->name[0] == 'm') if (unlinkat(tfd, try->name, S_ISDIR(try->st.st_mode) ? AT_REMOVEDIR :0)) err = "%s"; } @@ -293,17 +327,41 @@ int cp_node(struct dirtree *try) void cp_main(void) { - char *destname = toys.optargs[--toys.optc]; + char *destname = toys.optargs[--toys.optc], + *preserve[] = {"mode", "ownership", "timestamps"}; int i, destdir = !stat(destname, &TT.top) && S_ISDIR(TT.top.st_mode); if (toys.optc>1 && !destdir) error_exit("'%s' not directory", destname); - if (toys.which->name[0] == 'm') toys.optflags |= FLAG_d|FLAG_p|FLAG_R; - if (toys.optflags & (FLAG_a|FLAG_p)) umask(0); + if (toys.optflags & (FLAG_a|FLAG_p)) { + TT.pflags = 7; // preserve=mot + umask(0); + } + if (CFG_CP_PRESERVE && TT.c.preserve) { + char *pre = xstrdup(TT.c.preserve), *s; + + if (comma_scan(pre, "all", 1)) TT.pflags = ~0; + for (i=0; i<ARRAY_LEN(preserve); i++) + if (comma_scan(pre, preserve[i], 1)) TT.pflags |= 1<<i; + if (*pre) { + + // Try to interpret as letters, commas won't set anything this doesn't. + for (s = TT.c.preserve; *s; s++) { + for (i=0; i<ARRAY_LEN(preserve); i++) + if (*s == *preserve[i]) TT.pflags |= 1<<i; + if (i == ARRAY_LEN(preserve)) { + if (*s == 'a') TT.pflags = ~0; + else break; + } + } + + if (*s) error_exit("bad --preserve=%s", pre); + } + free(pre); + } if (!TT.callback) TT.callback = cp_node; // Loop through sources - for (i=0; i<toys.optc; i++) { struct dirtree *new; char *src = toys.optargs[i]; @@ -313,7 +371,7 @@ void cp_main(void) else TT.destname = destname; errno = EXDEV; - if (CFG_CP_MV && toys.which->name[0] == 'm') { + if (CFG_MV && toys.which->name[0] == 'm') { if (!(toys.optflags & FLAG_f)) { struct stat st; @@ -333,9 +391,8 @@ void cp_main(void) // Skip nonexistent sources if (rc) { - int symfollow = toys.optflags & (FLAG_H|FLAG_L); - - if (errno != EXDEV || !(new = dirtree_add_node(0, src, symfollow))) + if (errno!=EXDEV || + !(new = dirtree_start(src, toys.optflags&(FLAG_H|FLAG_L)))) perror_msg("bad '%s'", src); else dirtree_handle_callback(new, TT.callback); } @@ -343,15 +400,22 @@ void cp_main(void) } } +void mv_main(void) +{ + toys.optflags |= FLAG_d|FLAG_p|FLAG_R; + + cp_main(); +} + #define CLEANUP_cp #define FOR_install #include <generated/flags.h> static int install_node(struct dirtree *try) { - if (TT.mode) try->st.st_mode = string_to_mode(TT.mode, try->st.st_mode); - if (TT.group) try->st.st_gid = TT.gid; - if (TT.user) try->st.st_uid = TT.uid; + if (TT.i.mode) try->st.st_mode = string_to_mode(TT.i.mode, try->st.st_mode); + if (TT.i.group) try->st.st_gid = TT.gid; + if (TT.i.user) try->st.st_uid = TT.uid; // Always returns 0 because no -r cp_node(try); @@ -378,6 +442,7 @@ void install_main(void) } if (toys.optflags & FLAG_D) { + TT.destname = toys.optargs[toys.optc-1]; if (mkpathat(AT_FDCWD, TT.destname, 0, 2)) perror_exit("-D '%s'", TT.destname); if (toys.optc == 1) return; @@ -389,8 +454,8 @@ void install_main(void) if (flags & FLAG_v) toys.optflags |= 8; // cp's FLAG_v if (flags & (FLAG_p|FLAG_o|FLAG_g)) toys.optflags |= 512; // cp's FLAG_p - if (TT.user) TT.uid = xgetpwnam(TT.user)->pw_uid; - if (TT.group) TT.gid = xgetgrnam(TT.group)->gr_gid; + if (TT.i.user) TT.uid = xgetpwnamid(TT.i.user)->pw_uid; + if (TT.i.group) TT.gid = xgetgrnamid(TT.i.group)->gr_gid; TT.callback = install_node; cp_main(); diff --git a/toys/posix/cpio.c b/toys/posix/cpio.c index 2a0f7d9..312bb93 100644 --- a/toys/posix/cpio.c +++ b/toys/posix/cpio.c @@ -256,7 +256,7 @@ void cpio_main(void) xwrite(afd, toybuf, nlen); } llen = st.st_size & 3; - if (llen) write(afd, &zero, 4-llen); + if (llen) xwrite(afd, &zero, 4-llen); } close(fd); } diff --git a/toys/posix/cut.c b/toys/posix/cut.c index 7f10c5e..bb2b22d 100644 --- a/toys/posix/cut.c +++ b/toys/posix/cut.c @@ -3,9 +3,11 @@ * Copyright 2012 Ranjan Kumar <ranjankumar.bth@gmail.com> * Copyright 2012 Kyungwan Han <asura321@gmail.com> * - * http://pubs.opengroup.org/onlinepubs/9699919799/utilities/cut.html + * http://pubs.opengroup.org/onlinepubs/9699919799/utilities/cut.html + * + * TODO: cleanup -USE_CUT(NEWTOY(cut, "b:|c:|f:|d:sn[!cbf]", TOYFLAG_BIN)) +USE_CUT(NEWTOY(cut, "b:|c:|f:|d:sn[!cbf]", TOYFLAG_USR|TOYFLAG_BIN)) config CUT bool "cut" @@ -111,7 +113,7 @@ static void get_data(void) else { int fd = open(*argv, O_RDONLY, 0); if(fd < 0) {//if file not present then continue with other files. - perror_msg(*argv); + perror_msg("%s", *argv); continue; } TT.do_cut(fd); diff --git a/toys/posix/df.c b/toys/posix/df.c index afb296b..0bec17f 100644 --- a/toys/posix/df.c +++ b/toys/posix/df.c @@ -4,7 +4,7 @@ * * See http://opengroup.org/onlinepubs/9699919799/utilities/df.html -USE_DF(NEWTOY(df, "Pkt*a", TOYFLAG_USR|TOYFLAG_SBIN)) +USE_DF(NEWTOY(df, "Pkt*a[-Pk]", TOYFLAG_SBIN)) config DF bool "df" @@ -16,17 +16,9 @@ config DF each filesystem listed on the command line, or all currently mounted filesystems. - -t type Display only filesystems of this type. - -config DF_PEDANTIC - bool "options -P and -k" - default y - depends on DF - help - usage: df [-Pk] - -P The SUSv3 "Pedantic" option -k Sets units back to 1024 bytes (the default without -P) + -t type Display only filesystems of this type. Pedantic provides a slightly less useful output format dictated by Posix, and sets the units to 512 bytes instead of the default 1024 bytes. @@ -68,8 +60,7 @@ static void show_mt(struct mtab_list *mt) block = mt->statvfs.f_bsize ? mt->statvfs.f_bsize : 1; size = (block * mt->statvfs.f_blocks) / TT.units; used = (block * (mt->statvfs.f_blocks-mt->statvfs.f_bfree)) / TT.units; - avail = (block * (getuid() ? mt->statvfs.f_bavail : mt->statvfs.f_bfree)) - / TT.units; + avail = (block*(getuid()?mt->statvfs.f_bavail:mt->statvfs.f_bfree))/TT.units; if (!(used+avail)) percent = 0; else { percent = (used*100)/(used+avail); @@ -82,13 +73,8 @@ static void show_mt(struct mtab_list *mt) // Figure out appropriate spacing len = 25 - strlen(device); if (len < 1) len = 1; - if (CFG_DF_PEDANTIC && (toys.optflags & FLAG_P)) { - xprintf("%s %lld %lld %lld %lld%% %s\n", device, size, used, avail, - percent, mt->dir); - } else { - xprintf("%s% *lld % 10lld % 9lld % 3lld%% %s\n", device, len, - size, used, avail, percent, mt->dir); - } + xprintf("%s% *lld % 10lld % 10lld % *lld%% %s\n", device, len, + size, used, avail, (toys.optflags & FLAG_P) ? 7 : 3, percent, mt->dir); if (device != mt->device) free(device); } @@ -96,15 +82,12 @@ static void show_mt(struct mtab_list *mt) void df_main(void) { struct mtab_list *mt, *mtstart, *mtend; + int p = toys.optflags & FLAG_P; - // Handle -P and -k - TT.units = 1024; - if (CFG_DF_PEDANTIC && (toys.optflags & FLAG_P)) { - // Units are 512 bytes if you select "pedantic" without "kilobytes". - if ((toys.optflags&(FLAG_P|FLAG_k)) == FLAG_P) TT.units = 512; - printf("Filesystem %ld-blocks Used Available Capacity Mounted on\n", - TT.units); - } else puts("Filesystem\t1K-blocks\tUsed Available Use% Mounted on"); + // Units are 512 bytes if you select "pedantic" without "kilobytes". + TT.units = p ? 512 : 1024; + xprintf("Filesystem%8s-blocks\tUsed Available %s Mounted on\n", + p ? "512" : "1K", p ? "Capacity" : "Use%"); if (!(mtstart = xgetmountlist(0))) return; mtend = dlist_terminate(mtstart); @@ -118,14 +101,16 @@ void df_main(void) // Stat it (complain if we can't). if(stat(*next, &st)) { - perror_msg("`%s'", *next); + perror_msg("'%s'", *next); continue; } // Find and display this filesystem. Use _last_ hit in case of // overmounts (which is first hit in the reversed list). for (mt = mtend; mt; mt = mt->prev) { - if (st.st_dev == mt->stat.st_dev) { + if (st.st_dev == mt->stat.st_dev + || (st.st_rdev && (st.st_rdev == mt->stat.st_dev))) + { show_mt(mt); break; } @@ -143,7 +128,7 @@ void df_main(void) mt3 = mt; for (mt2 = mt->prev; mt2; mt2 = mt2->prev) { if (mt->stat.st_dev == mt2->stat.st_dev) { - // For --bind mounts, take show earliest mount + // For --bind mounts, show earliest mount if (!strcmp(mt->device, mt2->device)) { if (!toys.optflags & FLAG_a) mt3->stat.st_dev = 0; mt3 = mt2; diff --git a/toys/posix/du.c b/toys/posix/du.c index 22a26d3..4302997 100644 --- a/toys/posix/du.c +++ b/toys/posix/du.c @@ -3,6 +3,8 @@ * Copyright 2012 Ashwini Kumar <ak.ashwini@gmail.com> * * See http://opengroup.org/onlinepubs/9699919799/utilities/du.html + * + * TODO: cleanup USE_DU(NEWTOY(du, "d#<0hmlcaHkKLsx[-HL][-kKmh]", TOYFLAG_USR|TOYFLAG_BIN)) @@ -104,12 +106,22 @@ static int seen_inode(void **list, struct stat *st) // dirtree callback, comput/display size of node static int do_du(struct dirtree *node) { - if (node->parent && !dirtree_notdotdot(node)) return 0; + if (!node->parent) TT.st_dev = node->st.st_dev; + else if (!dirtree_notdotdot(node)) return 0; // detect swiching filesystems if ((toys.optflags & FLAG_x) && (TT.st_dev != node->st.st_dev)) return 0; + // Don't loop endlessly on recursive directory symlink + if (toys.optflags & FLAG_L) { + struct dirtree *try = node; + + while ((try = try->parent)) + if (node->st.st_dev==try->st.st_dev && node->st.st_ino==try->st.st_ino) + return 0; + } + // Don't count hard links twice if (!(toys.optflags & FLAG_l) && !node->again) if (seen_inode(&TT.inodes, &node->st)) return 0; @@ -118,7 +130,7 @@ static int do_du(struct dirtree *node) if (S_ISDIR(node->st.st_mode)) { if (!node->again) { TT.depth++; - return DIRTREE_COMEAGAIN | (DIRTREE_SYMFOLLOW*!!(toys.optflags & FLAG_L)); + return DIRTREE_COMEAGAIN|(DIRTREE_SYMFOLLOW*!!(toys.optflags&FLAG_L)); } else TT.depth--; } @@ -137,21 +149,12 @@ static int do_du(struct dirtree *node) void du_main(void) { - char *noargs[] = {".", 0}; - struct dirtree *root; - - if (!toys.optc) toys.optargs = noargs; + char *noargs[] = {".", 0}, **args; // Loop over command line arguments, recursing through children - while (*toys.optargs) { - root = dirtree_add_node(0, *toys.optargs, toys.optflags & (FLAG_H|FLAG_L)); - - if (root) { - TT.st_dev = root->st.st_dev; - dirtree_handle_callback(root, do_du); - } - toys.optargs++; - } + for (args = toys.optc ? toys.optargs : noargs; *args; args++) + dirtree_handle_callback(dirtree_start(*args, toys.optflags&(FLAG_H|FLAG_L)), + do_du); if (toys.optflags & FLAG_c) print(TT.total*512, 0); if (CFG_TOYBOX_FREE) seen_inode(TT.inodes, 0); diff --git a/toys/posix/env.c b/toys/posix/env.c index 4e819f2..f20517e 100644 --- a/toys/posix/env.c +++ b/toys/posix/env.c @@ -24,28 +24,17 @@ extern char **environ; void env_main(void) { char **ev; - char **command = NULL; char *del = "="; if (toys.optflags) clearenv(); for (ev = toys.optargs; *ev != NULL; ev++) { - char *env, *val = NULL; - - env = strtok(*ev, del); - - if (env) val = strtok(NULL, del); + char *env = strtok(*ev, del), *val = 0; + if (env) val = strtok(0, del); if (val) setenv(env, val, 1); - else { - command = ev; - break; - } + else xexec(ev); } - if (!command) { - char **ep; - if (environ) for (ep = environ; *ep; ep++) xputs(*ep); - } else xexec_optargs(command - toys.optargs); - + if (environ) for (ev = environ; *ev; ev++) xputs(*ev); } diff --git a/toys/posix/find.c b/toys/posix/find.c index caec80e..a11a910 100644 --- a/toys/posix/find.c +++ b/toys/posix/find.c @@ -32,6 +32,7 @@ config FIND -ctime N created N days ago -mtime N modified N days ago -newer FILE newer mtime than FILE -mindepth # at least # dirs down -depth ignore contents of dir -maxdepth # at most # dirs down + -inum N inode number N -type [bcdflps] (block, char, dir, file, symlink, pipe, socket) Numbers N may be prefixed by a - (less than) or + (greater than): @@ -72,7 +73,7 @@ static int flush_exec(struct dirtree *new, struct exec_range *aa) { struct double_list **dl; char **newargs; - int rc; + int rc = 0; if (!aa->namecount) return 0; @@ -82,8 +83,13 @@ static int flush_exec(struct dirtree *new, struct exec_range *aa) // switch to directory for -execdir, or back to top if we have an -execdir // _and_ a normal -exec, or are at top of tree in -execdir - if (aa->dir && new->parent) fchdir(new->parent->data); - else if (TT.topdir != -1) fchdir(TT.topdir); + if (aa->dir && new->parent) rc = fchdir(new->parent->data); + else if (TT.topdir != -1) rc = fchdir(TT.topdir); + if (rc) { + perror_msg("%s", new->name); + + return rc; + } // execdir: accumulated execs in this directory's children. newargs = xmalloc(sizeof(char *)*(aa->arglen+aa->namecount+1)); @@ -160,7 +166,7 @@ char *strlower(char *s) // encode back to utf8, something is wrong with your libc. But just // in case somebody finds an exploit... len = wcrtomb(new, c, 0); - if (len < 1) error_exit("bad utf8 %x", c); + if (len < 1) error_exit("bad utf8 %x", (int)c); new += len; } } @@ -179,7 +185,7 @@ static int do_find(struct dirtree *new) struct double_list *argdata = TT.argdata; char *s, **ss; - recurse = DIRTREE_COMEAGAIN|((toys.optflags&FLAG_L) ? DIRTREE_SYMFOLLOW : 0); + recurse = DIRTREE_COMEAGAIN|(DIRTREE_SYMFOLLOW*!!(toys.optflags&FLAG_L)); // skip . and .. below topdir, handle -xdev and -depth if (new) { @@ -336,6 +342,9 @@ static int do_find(struct dirtree *new) test = compare_numsign(new->st.st_size, 512, ss[1]); } else if (!strcmp(s, "links")) { if (check) test = compare_numsign(new->st.st_nlink, 0, ss[1]); + } else if (!strcmp(s, "inum")) { + if (check) + test = compare_numsign(new->st.st_ino, 0, ss[1]); } else if (!strcmp(s, "mindepth") || !strcmp(s, "maxdepth")) { if (check) { struct dirtree *dt = new; @@ -366,8 +375,8 @@ static int do_find(struct dirtree *new) udl = xmalloc(sizeof(*udl)); dlist_add_nomalloc(&TT.argdata, (void *)udl); - if (*s == 'u') udl->u.uid = xgetpwnam(ss[1])->pw_uid; - else if (*s == 'g') udl->u.gid = xgetgrnam(ss[1])->gr_gid; + if (*s == 'u') udl->u.uid = xgetpwnamid(ss[1])->pw_uid; + else if (*s == 'g') udl->u.gid = xgetgrnamid(ss[1])->gr_gid; else { struct stat st; @@ -430,7 +439,7 @@ static int do_find(struct dirtree *new) if (aa->dir && TT.topdir == -1) TT.topdir = xopen(".", 0); // collect names and execute commands - } else if (check) { + } else { char *name, *ss1 = ss[1]; struct double_list **ddl; @@ -438,11 +447,12 @@ static int do_find(struct dirtree *new) aa = (void *)llist_pop(&argdata); ss += aa->arglen + 1; + if (!check) goto cont; // name is always a new malloc, so we can always free it. name = aa->dir ? xstrdup(new->name) : dirtree_path(new, 0); // Mark entry so COMEAGAIN can call flush_exec() in parent. - // This is never a valid pointer valud for prev to have otherwise + // This is never a valid pointer value for prev to have otherwise if (aa->dir) aa->prev = (void *)1; if (*s == 'o') { @@ -525,12 +535,9 @@ void find_main(void) do_find(0); // Loop through paths - for (i = 0; i < len; i++) { - struct dirtree *new; - - new = dirtree_add_node(0, ss[i], toys.optflags&(FLAG_H|FLAG_L)); - if (new) dirtree_handle_callback(new, do_find); - } + for (i = 0; i < len; i++) + dirtree_handle_callback(dirtree_start(ss[i], toys.optflags&(FLAG_H|FLAG_L)), + do_find); if (CFG_TOYBOX_FREE) { close(TT.topdir); diff --git a/toys/posix/grep.c b/toys/posix/grep.c index aba7087..ffc920c 100644 --- a/toys/posix/grep.c +++ b/toys/posix/grep.c @@ -5,8 +5,8 @@ * See http://pubs.opengroup.org/onlinepubs/9699919799/utilities/grep.html USE_GREP(NEWTOY(grep, "ZzEFHabhinorsvwclqe*f*m#x[!wx][!EFw]", TOYFLAG_BIN)) -USE_GREP(OLDTOY(egrep, grep, OPTSTR_grep, TOYFLAG_BIN)) -USE_GREP(OLDTOY(fgrep, grep, OPTSTR_grep, TOYFLAG_BIN)) +USE_EGREP(OLDTOY(egrep, grep, TOYFLAG_BIN)) +USE_FGREP(OLDTOY(fgrep, grep, TOYFLAG_BIN)) config GREP bool "grep" @@ -36,6 +36,16 @@ config GREP output prefix (default: filename if checking more than 1 file) -H force filename -b byte offset of match -h hide filename -n line number of match + +config EGREP + bool + default y + depends on GREP + +config FGREP + bool + default y + depends on GREP */ #define FOR_grep @@ -259,7 +269,7 @@ static int do_grep_r(struct dirtree *new) void grep_main(void) { - char **ss; + char **ss = toys.optargs; // Handle egrep and fgrep if (*toys.which->name == 'e' || (toys.optflags & FLAG_w)) @@ -267,9 +277,9 @@ void grep_main(void) if (*toys.which->name == 'f') toys.optflags |= FLAG_F; if (!TT.e && !TT.f) { - if (!*toys.optargs) error_exit("no REGEX"); + if (!*ss) error_exit("no REGEX"); TT.e = xzalloc(sizeof(struct arg_list)); - TT.e->arg = *(toys.optargs++); + TT.e->arg = *(ss++); toys.optc--; } @@ -284,9 +294,9 @@ void grep_main(void) } if (toys.optflags & FLAG_r) { - for (ss=toys.optargs; *ss; ss++) { + for (ss = *ss ? ss : (char *[]){".", 0}; *ss; ss++) { if (!strcmp(*ss, "-")) do_grep(0, *ss); else dirtree_read(*ss, do_grep_r); } - } else loopfiles_rw(toys.optargs, O_RDONLY, 0, 1, do_grep); + } else loopfiles_rw(ss, O_RDONLY, 0, 1, do_grep); } diff --git a/toys/posix/head.c b/toys/posix/head.c index e8517d4..5867dfb 100644 --- a/toys/posix/head.c +++ b/toys/posix/head.c @@ -4,7 +4,7 @@ * * See http://opengroup.org/onlinepubs/9699919799/utilities/head.html -USE_HEAD(NEWTOY(head, "n#<0=10", TOYFLAG_BIN)) +USE_HEAD(NEWTOY(head, "?n#<0=10", TOYFLAG_USR|TOYFLAG_BIN)) config HEAD bool "head" @@ -50,5 +50,12 @@ static void do_head(int fd, char *name) void head_main(void) { - loopfiles(toys.optargs, do_head); + char *arg = *toys.optargs; + + // handle old "-42" style arguments + if (arg && *arg == '-' && arg[1]) { + TT.lines = atolx(arg+1); + toys.optc--; + } else arg = 0; + loopfiles(toys.optargs+!!arg, do_head); } diff --git a/toys/posix/id.c b/toys/posix/id.c index 000d7b4..aa43072 100644 --- a/toys/posix/id.c +++ b/toys/posix/id.c @@ -6,10 +6,10 @@ * * See http://opengroup.org/onlinepubs/9699919799/utilities/id.html -USE_ID(NEWTOY(id, ">1nGgru[!Ggu]", TOYFLAG_BIN)) -USE_GROUPS(OLDTOY(groups, id, NULL, TOYFLAG_USR|TOYFLAG_BIN)) -USE_LOGNAME(OLDTOY(logname, id, ">0", TOYFLAG_BIN)) -USE_WHOAMI(OLDTOY(whoami, id, ">0", TOYFLAG_BIN)) +USE_ID(NEWTOY(id, ">1"USE_ID_Z("Z")"nGgru[!"USE_ID_Z("Z")"Ggu]", TOYFLAG_USR|TOYFLAG_BIN)) +USE_GROUPS(NEWTOY(groups, NULL, TOYFLAG_USR|TOYFLAG_BIN)) +USE_LOGNAME(NEWTOY(logname, ">0", TOYFLAG_USR|TOYFLAG_BIN)) +USE_WHOAMI(OLDTOY(whoami, logname, TOYFLAG_USR|TOYFLAG_BIN)) config ID bool "id" @@ -25,6 +25,15 @@ config ID -r Show real ID instead of effective ID -u Show only the effective user ID +config ID_Z + bool + default y + depends on ID && !TOYBOX_LSM_NONE + help + usage: id [-Z] + + -Z Show only security context + config GROUPS bool "groups" default y @@ -51,15 +60,16 @@ config WHOAMI */ #define FOR_id +#define FORCE_FLAGS #include "toys.h" GLOBALS( - int do_u, do_n, do_G, is_groups; + int is_groups; ) static void s_or_u(char *s, unsigned u, int done) { - if (TT.do_n) printf("%s", s); + if (toys.optflags&FLAG_n) printf("%s", s); else printf("%u", u); if (done) { xputc('\n'); @@ -92,12 +102,12 @@ void do_id(char *username) i = flags & FLAG_r; pw = xgetpwuid(i ? uid : euid); - if (TT.do_u) s_or_u(pw->pw_name, pw->pw_uid, 1); + if (toys.optflags&FLAG_u) s_or_u(pw->pw_name, pw->pw_uid, 1); grp = xgetgrgid(i ? gid : egid); if (flags & FLAG_g) s_or_u(grp->gr_name, grp->gr_gid, 1); - if (!TT.do_G) { + if (!(toys.optflags&(FLAG_g|FLAG_Z))) { showid("uid=", pw->pw_uid, pw->pw_name); showid(" gid=", grp->gr_gid, grp->gr_name); @@ -115,33 +125,55 @@ void do_id(char *username) showid(" groups=", grp->gr_gid, grp->gr_name); } - groups = (gid_t *)toybuf; - i = sizeof(toybuf)/sizeof(gid_t); - ngroups = username ? getgrouplist(username, gid, groups, &i) - : getgroups(i, groups); - if (ngroups<0) perror_exit(0); - - for (i = 0; i<ngroups; i++) { - if (i || !TT.do_G) xputc(' '); - if (!(grp = getgrgid(groups[i]))) perror_msg(0); - else if (TT.do_G) s_or_u(grp->gr_name, grp->gr_gid, 0); - else if (grp->gr_gid != egid) showid("", grp->gr_gid, grp->gr_name); + if (!(toys.optflags&FLAG_Z)) { + groups = (gid_t *)toybuf; + i = sizeof(toybuf)/sizeof(gid_t); + ngroups = username ? getgrouplist(username, gid, groups, &i) + : getgroups(i, groups); + if (ngroups<0) perror_exit(0); + + int show_separator = !(toys.optflags&FLAG_G); + for (i = 0; i<ngroups; i++) { + if (show_separator) xputc((toys.optflags&FLAG_G) ? ' ' : ','); + show_separator = 1; + if (!(grp = getgrgid(groups[i]))) perror_msg(0); + else if (toys.optflags&FLAG_G) s_or_u(grp->gr_name, grp->gr_gid, 0); + else if (grp->gr_gid != egid) showid("", grp->gr_gid, grp->gr_name); + else show_separator = 0; // Because we didn't show anything this time. + } + if (toys.optflags&FLAG_G) { + xputc('\n'); + exit(0); + } + } + + if (!CFG_TOYBOX_LSM_NONE) { + if (lsm_enabled()) { + char *context = lsm_context(); + + printf(" context=%s"+!!(toys.optflags&FLAG_Z), context); + if (CFG_TOYBOX_FREE) free(context); + } else if (toys.optflags&FLAG_Z) error_exit("%s disabled", lsm_name()); } + xputc('\n'); } void id_main(void) { - // FLAG macros can be 0 if "id" command not enabled, so snapshot them here. - if (FLAG_u) TT.do_u = toys.optflags & FLAG_u; - if (FLAG_n) TT.do_n = toys.optflags & FLAG_n; - if (FLAG_G) TT.do_G = toys.optflags & FLAG_G; - - // And set the variables for non-id commands. - TT.is_groups = toys.which->name[0] == 'g'; - if (TT.is_groups) TT.do_G = TT.do_n = 1; - else if (toys.which->name[0] != 'i') TT.do_u = TT.do_n = 1; - if (toys.optc) while(*toys.optargs) do_id(*toys.optargs++); else do_id(NULL); } + +void groups_main(void) +{ + TT.is_groups = 1; + toys.optflags = FLAG_G|FLAG_n; + id_main(); +} + +void logname_main(void) +{ + toys.optflags = FLAG_u|FLAG_n; + id_main(); +} diff --git a/toys/posix/ln.c b/toys/posix/ln.c index 04b4f29..06700dd 100644 --- a/toys/posix/ln.c +++ b/toys/posix/ln.c @@ -40,7 +40,7 @@ void ln_main(void) if (((toys.optflags&FLAG_n) ? lstat : stat)(dest, &buf) || !S_ISDIR(buf.st_mode)) { - if (toys.optc>1) error_exit("'%s' not a directory"); + if (toys.optc>1) error_exit("'%s' not a directory", dest); buf.st_mode = 0; } diff --git a/toys/posix/ls.c b/toys/posix/ls.c index d1a26ee..44915fa 100644 --- a/toys/posix/ls.c +++ b/toys/posix/ls.c @@ -5,13 +5,13 @@ * * See http://opengroup.org/onlinepubs/9699919799/utilities/ls.html -USE_LS(NEWTOY(ls, USE_LS_COLOR("(color):;")"goACFHLRSacdfiklmnpqrstux1[-1Cglmnox][-cu][-ftS][-HL]", TOYFLAG_BIN|TOYFLAG_LOCALE)) +USE_LS(NEWTOY(ls, USE_LS_COLOR("(color):;")"ZgoACFHLRSacdfiklmnpqrstux1[-Cxm1][-Cxml][-Cxmo][-Cxmg][-cu][-ftS][-HL]", TOYFLAG_BIN|TOYFLAG_LOCALE)) config LS bool "ls" default y help - usage: ls [-ACFHLRSacdfiklmnpqrstux1] [directory...] + usage: ls [-ACFHLRSZacdfiklmnpqrstux1] [directory...] list files what to show: @@ -22,6 +22,7 @@ config LS -u use access time for timestamps -A list all files but . and .. -H follow command line symlinks -L follow symlinks -R recursively list files in subdirs -F append /dir *exe @sym |FIFO + -Z security context output formats: -1 list one file per line -C columns (sorted vertically) @@ -54,7 +55,7 @@ config LS_COLOR GLOBALS( char *color; - struct dirtree *files; + struct dirtree *files, *singledir; unsigned screen_width; int nl_title; @@ -86,19 +87,6 @@ int strwidth(char *s) return total; } -void dlist_to_dirtree(struct dirtree *parent) -{ - // Turn double_list into dirtree - struct dirtree *dt = parent->child; - if (dt) { - dt->parent->next = NULL; - while (dt) { - dt->parent = parent; - dt = dt->next; - } - } -} - static char endtype(struct stat *st) { mode_t mode = st->st_mode; @@ -128,6 +116,11 @@ static char *getgroupname(gid_t gid) return gr ? gr->gr_name : TT.gid_buf; } +static int numlen(long long ll) +{ + return snprintf(0, 0, "%llu", ll); +} + // Figure out size of printable entry fields for display indent/wrap static void entrylen(struct dirtree *dt, unsigned *len) @@ -139,21 +132,21 @@ static void entrylen(struct dirtree *dt, unsigned *len) if (endtype(st)) ++*len; if (flags & FLAG_m) ++*len; - if (flags & FLAG_i) *len += (len[1] = numlen(st->st_ino)); + len[1] = (flags & FLAG_i) ? numlen(st->st_ino) : 0; if (flags & (FLAG_l|FLAG_o|FLAG_n|FLAG_g)) { unsigned fn = flags & FLAG_n; len[2] = numlen(st->st_nlink); - len[3] = fn ? snprintf(0, 0, "%u", (unsigned)st->st_uid) - : strwidth(getusername(st->st_uid)); - len[4] = fn ? snprintf(0, 0, "%u", (unsigned)st->st_gid) - : strwidth(getgroupname(st->st_gid)); + len[3] = fn ? numlen(st->st_uid) : strwidth(getusername(st->st_uid)); + len[4] = fn ? numlen(st->st_gid) : strwidth(getgroupname(st->st_gid)); if (S_ISBLK(st->st_mode) || S_ISCHR(st->st_mode)) { // cheating slightly here: assuming minor is always 3 digits to avoid // tracking another column len[5] = numlen(major(st->st_rdev))+5; } else len[5] = numlen(st->st_size); } - if (flags & FLAG_s) *len += (len[6] = numlen(st->st_blocks)); + + len[6] = (flags & FLAG_s) ? numlen(st->st_blocks) : 0; + len[7] = (flags & FLAG_Z) ? strwidth((char *)dt->extra) : 0; } static int compare(void *a, void *b) @@ -186,6 +179,42 @@ static int filter(struct dirtree *new) return 0; } + if (flags & FLAG_Z) { + if (!CFG_TOYBOX_LSM_NONE) { + int fd; + + // Why not just openat(O_PATH|(O_NOFOLLOW*!!(toys.optflags&FLAG_L))) and + // lsm_fget_context() on that filehandle? Because the kernel is broken, + // and won't let us read this "metadata" from the filehandle unless we + // have permission to read the data. We _can_ read the same data in + // by path, we just can't do it through an O_PATH filehandle, because + // reasons. So as a bug workaround for the broken kernel, we do it + // both ways. + // + // The O_NONBLOCK is there to avoid triggering automounting (there's + // a rush of nostalgia for you) on directories we don't descend into, + // which O_PATH would have done for us but see "the kernel is broken". + if (S_ISSOCK(new->st.st_mode) || + (S_ISLNK(new->st.st_mode) && !(toys.optflags & FLAG_L)) || + -1 == (fd = openat(dirtree_parentfd(new), new->name, + O_RDONLY|O_NONBLOCK|O_NOATIME))) + { + char *path; + + // Wouldn't it be nice if the lsm functions worked like openat(), + // fchmodat(), mknodat(), readlinkat() so we could do this without + // even O_PATH? But no, this is 1990's tech. + path = dirtree_path(new, 0); + lsm_lget_context(path, (char **)&new->extra); + free(path); + } else { + lsm_fget_context(fd, (char **)&new->extra); + close(fd); + } + } + if (CFG_TOYBOX_LSM_NONE || !new->extra) new->extra = (long)xstrdup("?"); + } + if (flags & FLAG_u) new->st.st_mtime = new->st.st_atime; if (flags & FLAG_c) new->st.st_mtime = new->st.st_ctime; if (flags & FLAG_k) new->st.st_blocks = (new->st.st_blocks + 1) / 2; @@ -257,44 +286,46 @@ int color_from_mode(mode_t mode) static void listfiles(int dirfd, struct dirtree *indir) { - struct dirtree *dt, **sort = 0; - unsigned long dtlen = 0, ul = 0; - unsigned width, flags = toys.optflags, totals[7], len[7], + struct dirtree *dt, **sort; + unsigned long dtlen, ul = 0; + unsigned width, flags = toys.optflags, totals[8], len[8], totpad = 0, *colsizes = (unsigned *)(toybuf+260), columns = (sizeof(toybuf)-260)/4; memset(totals, 0, sizeof(totals)); - // Silently descend into single directory listed by itself on command line. - // In this case only show dirname/total header when given -R. + // Top level directory was already populated by main() if (!indir->parent) { - if (!(dt = indir->child)) return; - if (S_ISDIR(dt->st.st_mode) && !dt->next && !(flags & FLAG_d)) { - dt->extra = 1; - listfiles(open(dt->name, 0), dt); + // Silently descend into single directory listed by itself on command line. + // In this case only show dirname/total header when given -R. + dt = indir->child; + if (dt && S_ISDIR(dt->st.st_mode) && !dt->next && !(flags&(FLAG_d|FLAG_R))) + { + listfiles(open(dt->name, 0), TT.singledir = dt); + return; } + + // Do preprocessing (Dirtree didn't populate, so callback wasn't called.) + for (;dt; dt = dt->next) filter(dt); + if (flags == (FLAG_1|FLAG_f)) return; } else { // Read directory contents. We dup() the fd because this will close it. + // This reads/saves contents to display later, except for in "ls -1f" mode. indir->data = dup(dirfd); - dirtree_recurse(indir, filter, (flags&FLAG_L) ? DIRTREE_SYMFOLLOW : 0); + dirtree_recurse(indir, filter, DIRTREE_SYMFOLLOW*!!(flags&FLAG_L)); } // Copy linked list to array and sort it. Directories go in array because - // we visit them in sorted order. - - for (;;) { - for (dt = indir->child; dt; dt = dt->next) { + // we visit them in sorted order too. (The nested loops let us measure and + // fill with the same inner loop.) + for (sort = 0;;sort = xmalloc(dtlen*sizeof(void *))) { + for (dtlen = 0, dt = indir->child; dt; dt = dt->next, dtlen++) if (sort) sort[dtlen] = dt; - dtlen++; - } - if (sort) break; - sort = xmalloc(dtlen * sizeof(void *)); - dtlen = 0; - continue; + if (sort || !dtlen) break; } // Label directory if not top of tree, or if -R - if (indir->parent && (!indir->extra || (flags & FLAG_R))) + if (indir->parent && (TT.singledir!=indir || (flags&FLAG_R))) { char *path = dirtree_path(indir, 0); @@ -303,7 +334,21 @@ static void listfiles(int dirfd, struct dirtree *indir) free(path); } - if (!(flags & FLAG_f)) qsort(sort, dtlen, sizeof(void *), (void *)compare); + // Measure each entry to work out whitespace padding and total blocks + if (!(flags & FLAG_f)) { + unsigned long long blocks = 0; + + qsort(sort, dtlen, sizeof(void *), (void *)compare); + for (ul = 0; ul<dtlen; ul++) { + entrylen(sort[ul], len); + for (width = 0; width<8; width++) + if (len[width]>totals[width]) totals[width] = len[width]; + blocks += sort[ul]->st.st_blocks; + } + totpad = totals[1]+!!totals[1]+totals[6]+!!totals[6]+totals[7]+!!totals[7]; + if ((flags&(FLAG_l|FLAG_o|FLAG_n|FLAG_g|FLAG_s)) && indir->parent) + xprintf("total %llu\n", blocks); + } // Find largest entry in each field for display alignment if (flags & (FLAG_C|FLAG_x)) { @@ -320,29 +365,18 @@ static void listfiles(int dirfd, struct dirtree *indir) memset(colsizes, 0, columns*sizeof(unsigned)); for (ul=0; ul<dtlen; ul++) { entrylen(sort[next_column(ul, dtlen, columns, &c)], len); + *len += totpad; if (c == columns) break; - // Does this put us over budget? + // Expand this column if necessary, break if that puts us over budget if (*len > colsizes[c]) { - totlen += *len-colsizes[c]; + totlen += (*len)-colsizes[c]; colsizes[c] = *len; if (totlen > TT.screen_width) break; } } - // If it fit, stop here + // If everything fit, stop here if (ul == dtlen) break; } - } else if (flags & (FLAG_l|FLAG_o|FLAG_n|FLAG_g|FLAG_s)) { - unsigned long blocks = 0; - - for (ul = 0; ul<dtlen; ul++) - { - entrylen(sort[ul], len); - for (width=0; width<6; width++) - if (len[width] > totals[width]) totals[width] = len[width]; - blocks += sort[ul]->st.st_blocks; - } - - if (indir->parent) xprintf("total %lu\n", blocks); } // Loop through again to produce output. @@ -375,41 +409,46 @@ static void listfiles(int dirfd, struct dirtree *indir) } width += *len; - if (flags & FLAG_i) xprintf("% *lu ", len[1], (unsigned long)st->st_ino); - if (flags & FLAG_s) xprintf("% *lu ", len[6], (unsigned long)st->st_blocks); + if (flags & FLAG_i) + xprintf("%*lu ", totals[1], (unsigned long)st->st_ino); + if (flags & FLAG_s) + xprintf("%*lu ", totals[6], (unsigned long)st->st_blocks); if (flags & (FLAG_l|FLAG_o|FLAG_n|FLAG_g)) { struct tm *tm; - char perm[11], thyme[64], *usr, *upad, *grp, *grpad; + char perm[11], thyme[64], *ss; + // (long) is to coerce the st types into something we know we can print. mode_to_string(mode, perm); + printf("%s% *ld", perm, totals[2]+1, (long)st->st_nlink); - if (flags&FLAG_o) grp = grpad = toybuf+256; - else { - if (flags&FLAG_n) sprintf(grp = thyme, "%u", (unsigned)st->st_gid); - else strwidth(grp = getgroupname(st->st_gid)); - grpad = toybuf+256-(totals[4]-len[4]); + // print group + if (!(flags&FLAG_o)) { + if (flags&FLAG_n) sprintf(ss = thyme, "%u", (unsigned)st->st_gid); + else strwidth(ss = getgroupname(st->st_gid)); + printf(" %*s", (int)totals[4], ss); } - if (flags&FLAG_g) usr = upad = toybuf+256; - else { - upad = toybuf+255-(totals[3]-len[3]); - if (flags&FLAG_n) sprintf(usr = TT.uid_buf, "%u", (unsigned)st->st_uid); - else strwidth(usr = getusername(st->st_uid)); + if (!(flags&FLAG_g)) { + if (flags&FLAG_n) sprintf(ss = thyme, "%u", (unsigned)st->st_uid); + else strwidth(ss = getusername(st->st_uid)); + printf(" %*s", (int)totals[3], ss); } - // Coerce the st types into something we know we can print. - printf("%s% *ld %s%s%s%s", perm, totals[2]+1, (long)st->st_nlink, - usr, upad, grp, grpad); + if (flags & FLAG_Z) + printf(" %*s", -(int)totals[7], (char *)sort[next]->extra); + // print major/minor if (S_ISCHR(st->st_mode) || S_ISBLK(st->st_mode)) printf("% *d,% 4d", totals[5]-4, major(st->st_rdev),minor(st->st_rdev)); - else printf("% *"PRId64, totals[5]+1, (int64_t)st->st_size); + else printf("% *lld", totals[5]+1, (long long)st->st_size); + // print time, always in --time-style=long-iso tm = localtime(&(st->st_mtime)); strftime(thyme, sizeof(thyme), "%F %H:%M", tm); xprintf(" %s ", thyme); - } + } else if (flags & FLAG_Z) + printf("%*s ", (int)totals[7], (char *)sort[next]->extra); if (flags & FLAG_color) { color = color_from_mode(st->st_mode); @@ -441,7 +480,7 @@ static void listfiles(int dirfd, struct dirtree *indir) // Pad columns if (flags & (FLAG_C|FLAG_x)) { - curcol = colsizes[curcol] - *len; + curcol = colsizes[curcol]-(*len)-totpad; if (curcol < 255) xprintf("%s", toybuf+255-curcol); } } @@ -451,12 +490,12 @@ static void listfiles(int dirfd, struct dirtree *indir) // Free directory entries, recursing first if necessary. for (ul = 0; ul<dtlen; free(sort[ul++])) { - if ((flags & FLAG_d) || !S_ISDIR(sort[ul]->st.st_mode) - || !dirtree_notdotdot(sort[ul])) continue; + if ((flags & FLAG_d) || !S_ISDIR(sort[ul]->st.st_mode)) continue; // Recurse into dirs if at top of the tree or given -R - if (!indir->parent || (flags & FLAG_R)) + if (!indir->parent || ((flags&FLAG_R) && dirtree_notdotdot(sort[ul]))) listfiles(openat(dirfd, sort[ul]->name, 0), sort[ul]); + free((void *)sort[ul]->extra); } free(sort); if (dirfd != AT_FDCWD) close(dirfd); @@ -485,22 +524,19 @@ void ls_main(void) // Iterate through command line arguments, collecting directories and files. // Non-absolute paths are relative to current directory. - TT.files = dirtree_add_node(0, 0, 0); + TT.files = dirtree_start(0, 0); for (s = *toys.optargs ? toys.optargs : noargs; *s; s++) { - dt = dirtree_add_node(0, *s, !(toys.optflags & (FLAG_l|FLAG_d|FLAG_F)) - || (toys.optflags & (FLAG_L|FLAG_H))); - - if (!dt) { - toys.exitval = 1; - continue; - } + dt = dirtree_start(*s, !(toys.optflags&(FLAG_l|FLAG_d|FLAG_F)) || + (toys.optflags&(FLAG_L|FLAG_H))); - // Typecast means double_list->prev temporarirly goes in dirtree->parent - dlist_add_nomalloc((void *)&TT.files->child, (struct double_list *)dt); + // note: double_list->prev temporarirly goes in dirtree->parent + if (dt) dlist_add_nomalloc((void *)&TT.files->child, (void *)dt); + else toys.exitval = 1; } - // Turn double_list into dirtree - dlist_to_dirtree(TT.files); + // Convert double_list into dirtree. + dlist_terminate(TT.files->child); + for (dt = TT.files->child; dt; dt = dt->next) dt->parent = TT.files; // Display the files we collected listfiles(AT_FDCWD, TT.files); diff --git a/toys/posix/mkdir.c b/toys/posix/mkdir.c index 739f961..95bdf33 100644 --- a/toys/posix/mkdir.c +++ b/toys/posix/mkdir.c @@ -4,7 +4,7 @@ * * See http://opengroup.org/onlinepubs/9699919799/utilities/mkdir.html -USE_MKDIR(NEWTOY(mkdir, "<1vpm:", TOYFLAG_BIN|TOYFLAG_UMASK)) +USE_MKDIR(NEWTOY(mkdir, "<1"USE_MKDIR_Z("Z:")"vpm:", TOYFLAG_BIN|TOYFLAG_UMASK)) config MKDIR bool "mkdir" @@ -17,6 +17,15 @@ config MKDIR -m set permissions of directory to mode. -p make parent directories as needed. -v verbose + +config MKDIR_Z + bool + default y + depends on MKDIR && !TOYBOX_LSM_NONE + help + usage: [-Z context] + + -Z set security context */ #define FOR_mkdir @@ -24,6 +33,7 @@ config MKDIR GLOBALS( char *arg_mode; + char *arg_context; ) void mkdir_main(void) @@ -31,12 +41,15 @@ void mkdir_main(void) char **s; mode_t mode = (0777&~toys.old_umask); + if (CFG_MKDIR_Z && (toys.optflags&FLAG_Z)) + if (0>lsm_set_create(TT.arg_context)) + perror_exit("-Z '%s' failed", TT.arg_context); if (TT.arg_mode) mode = string_to_mode(TT.arg_mode, 0777); // Note, -p and -v flags line up with mkpathat() flags - - for (s=toys.optargs; *s; s++) + for (s=toys.optargs; *s; s++) { if (mkpathat(AT_FDCWD, *s, mode, toys.optflags|1)) perror_msg("'%s'", *s); + } } diff --git a/toys/posix/mkfifo.c b/toys/posix/mkfifo.c index 15fab70..9fc3829 100644 --- a/toys/posix/mkfifo.c +++ b/toys/posix/mkfifo.c @@ -4,15 +4,24 @@ * * See http://opengroup.org/onlinepubs/9699919799/utilities/mkfifo.html -USE_MKFIFO(NEWTOY(mkfifo, "<1m:", TOYFLAG_BIN)) +USE_MKFIFO(NEWTOY(mkfifo, "<1"USE_MKFIFO_Z("Z:")"m:", TOYFLAG_USR|TOYFLAG_BIN)) config MKFIFO bool "mkfifo" default y help - usage: mkfifo [fifo_name...] + usage: mkfifo [NAME...] Create FIFOs (named pipes). + +config MKFIFO_Z + bool + default y + depends on MKFIFO && !TOYBOX_LSM_NONE + help + usage: mkfifo [-Z CONTEXT] + + -Z Security context */ #define FOR_mkfifo @@ -20,6 +29,8 @@ config MKFIFO GLOBALS( char *m_string; + char *Z; + mode_t mode; ) @@ -30,6 +41,10 @@ void mkfifo_main(void) TT.mode = 0666; if (toys.optflags & FLAG_m) TT.mode = string_to_mode(TT.m_string, 0); + if (CFG_MKFIFO_Z && (toys.optflags&FLAG_Z)) + if (0>lsm_set_create(TT.Z)) + perror_exit("-Z '%s' failed", TT.Z); + for (s = toys.optargs; *s; s++) if (mknod(*s, S_IFIFO | TT.mode, 0) < 0) perror_msg("%s", *s); } diff --git a/toys/posix/nice.c b/toys/posix/nice.c index bc25d35..4b587ee 100644 --- a/toys/posix/nice.c +++ b/toys/posix/nice.c @@ -34,5 +34,5 @@ void nice_main(void) errno = 0; if (nice(TT.priority)==-1 && errno) perror_exit("Can't set priority"); - xexec_optargs(0); + xexec(toys.optargs); } diff --git a/toys/posix/nohup.c b/toys/posix/nohup.c index df264da..4d6d59f 100644 --- a/toys/posix/nohup.c +++ b/toys/posix/nohup.c @@ -21,7 +21,7 @@ config NOHUP void nohup_main(void) { - signal(SIGHUP, SIG_IGN); + xsignal(SIGHUP, SIG_IGN); if (isatty(1)) { close(1); if (-1 == open("nohup.out", O_CREAT|O_APPEND|O_WRONLY, @@ -38,5 +38,5 @@ void nohup_main(void) close(0); open("/dev/null", O_RDONLY); } - xexec_optargs(0); + xexec(toys.optargs); } diff --git a/toys/posix/printf.c b/toys/posix/printf.c new file mode 100644 index 0000000..365b8f3 --- /dev/null +++ b/toys/posix/printf.c @@ -0,0 +1,141 @@ +/* printf.c - Format and Print the data. + * + * Copyright 2014 Sandeep Sharma <sandeep.jack2756@gmail.com> + * Copyright 2014 Kyungwan Han <asura321@gmail.com> + * + * See http://pubs.opengroup.org/onlinepubs/9699919799/utilities/printf.html + * + * todo: *m$ ala printf("%1$d:%2$.*3$d:%4$.*3$d\n", hour, min, precision, sec); + +USE_PRINTF(NEWTOY(printf, "<1?^", TOYFLAG_USR|TOYFLAG_BIN)) + +config PRINTF + bool "printf" + default y + help + usage: printf FORMAT [ARGUMENT...] + + Format and print ARGUMENT(s) according to FORMAT, using C printf syntax + (% escapes for cdeEfgGiosuxX, \ escapes for abefnrtv0 or \OCTAL or \xHEX). +*/ + +#define FOR_printf +#include "toys.h" + +// Detect matching character (return true/false) and advance pointer if match. +static int eat(char **s, char c) +{ + int x = (**s == c); + + if (x) ++*s; + + return x; +} + +// Parse escape sequences. +static int handle_slash(char **esc_val) +{ + char *ptr = *esc_val; + int len, base = 0; + unsigned result = 0, num; + + if (*ptr == 'c') xexit(); + + // 0x12 hex escapes have 1-2 digits, \123 octal escapes have 1-3 digits. + if (eat(&ptr, 'x')) base = 16; + else if (*ptr >= '0' && *ptr <= '8') base = 8; + len = (char []){0,3,2}[base/8]; + + // Not a hex or octal escape? (This catches trailing \) + if (!len) { + if (!(result = unescape(*ptr))) result = '\\'; + else ++*esc_val; + + return result; + } + + while (len) { + num = tolower(*ptr) - '0'; + if (num >= 'a'-'0') num += '0'-'a'+10; + if (num >= base) { + // Don't parse invalid hex value ala "\xvd", print it verbatim + if (base == 16 && len == 2) { + ptr--; + result = '\\'; + } + break; + } + result = (result*base)+num; + ptr++; + len--; + } + *esc_val = ptr; + + return result; +} + +void printf_main(void) +{ + char **arg = toys.optargs+1; + + // Repeat format until arguments consumed + for (;;) { + int seen = 0; + char *f = *toys.optargs; + + // Loop through characters in format + while (*f) { + if (eat(&f, '\\')) putchar(handle_slash(&f)); + else if (!eat(&f, '%') || *f == '%') putchar(*f++); + + // Handle %escape + else { + char c, *end = 0, *aa, *to = toybuf; + int wp[] = {0,-1}, i = 0; + + // Parse width.precision between % and type indicator. + *to++ = '%'; + while (strchr("-+# '0", *f) && (to-toybuf)<10) *to++ = *f++; + for (;;) { + if (eat(&f, '*')) { + if (*arg) wp[i] = atolx(*arg++); + } else while (*f >= '0' && *f <= '9') wp[i] = (wp[i]*10)+(*f++)-'0'; + if (i++ || !eat(&f, '.')) break; + wp[1] = 0; + } + c = *f++; + seen = sprintf(to, "*.*%c", c);; + errno = 0; + aa = *arg ? *arg++ : ""; + + // Output %esc using parsed format string + if (c == 'b') { + while (*aa) putchar(eat(&aa, '\\') ? handle_slash(&aa) : *aa++); + + continue; + } else if (c == 'c') printf(toybuf, wp[0], wp[1], *aa); + else if (c == 's') printf(toybuf, wp[0], wp[1], aa); + else if (strchr("diouxX", c)) { + long ll; + + if (*aa == '\'' || *aa == '"') ll = aa[1]; + else ll = strtoll(aa, &end, 0); + + sprintf(to, "*.*ll%c", c); + printf(toybuf, wp[0], wp[1], ll); + } else if (strchr("feEgG", c)) { + long double ld = strtold(aa, &end); + + sprintf(to, "*.*L%c", c); + printf(toybuf, wp[0], wp[1], ld); + } else error_exit("bad %%%c@%ld", c, (long)(f-*toys.optargs)); + + if (end && (errno || *end)) perror_msg("bad %%%c %s", c, aa); + } + } + + // Posix says to keep looping through format until we consume all args. + // This only works if the format actually consumed at least one arg. + if (!seen || !*arg) break; + } +} diff --git a/toys/posix/renice.c b/toys/posix/renice.c index 8c20644..489eb13 100644 --- a/toys/posix/renice.c +++ b/toys/posix/renice.c @@ -4,7 +4,7 @@ * * See http://pubs.opengroup.org/onlinepubs/9699919799/utilities/renice.html -USE_RENICE(NEWTOY(renice, "<1gpun#|", TOYFLAG_BIN)) +USE_RENICE(NEWTOY(renice, "<1gpun#|", TOYFLAG_USR|TOYFLAG_BIN)) config RENICE bool "renice" diff --git a/toys/posix/rm.c b/toys/posix/rm.c index 9561a67..5523a98 100644 --- a/toys/posix/rm.c +++ b/toys/posix/rm.c @@ -47,13 +47,13 @@ static int do_rm(struct dirtree *try) // handle directory recursion if (dir) { + using = AT_REMOVEDIR; // Handle chmod 000 directories when -f - if (faccessat(fd, try->name, R_OK, AT_SYMLINK_NOFOLLOW)) { + if (faccessat(fd, try->name, R_OK, 0)) { if (toys.optflags & FLAG_f) wfchmodat(fd, try->name, 0700); else goto skip; } if (!try->again) return DIRTREE_COMEAGAIN; - using = AT_REMOVEDIR; if (try->symlink) goto skip; if (flags & FLAG_i) { char *s = dirtree_path(try, 0); diff --git a/toys/pending/sed.c b/toys/posix/sed.c index b98da05..2097532 100644 --- a/toys/pending/sed.c +++ b/toys/posix/sed.c @@ -4,16 +4,16 @@ * * See http://pubs.opengroup.org/onlinepubs/9699919799/utilities/sed.html * - * TODO: lines > 2G could signed int wrap length counters. Not just getline() + * TODO: lines > 2G could wrap signed int length counters. Not just getline() * but N and s/// -USE_SED(NEWTOY(sed, "(version)e*f*inr", TOYFLAG_USR|TOYFLAG_BIN|TOYFLAG_LOCALE)) +USE_SED(NEWTOY(sed, "(version)e*f*inEr[+Er]", TOYFLAG_USR|TOYFLAG_BIN|TOYFLAG_LOCALE)) config SED bool "sed" - default n + default y help - usage: sed [-inr] [-e SCRIPT]...|SCRIPT [-f SCRIPT_FILE]... [FILE...] + usage: sed [-inrE] [-e SCRIPT]...|SCRIPT [-f SCRIPT_FILE]... [FILE...] Stream editor. Apply one or more editing SCRIPTs to each line of input (from FILE or stdin) producing output (by default to stdout). @@ -23,6 +23,7 @@ config SED -i Edit each file in place. -n No default output. (Use the p command to output matched lines.) -r Use extended regular expression syntax. + -E Alias for -r. -s Treat input files separately (implied by -i) A SCRIPT is a series of one or more COMMANDs separated by newlines or @@ -83,9 +84,9 @@ config SED H Remember this line (appending to remembered line, if any) - l Print this line, escaping \abfrtv (but leaving \n as a newline), - using octal escapes for other nonprintable characters, and - wrapping lines to terminal width with a backslash and newline + l Print line, escaping \abfrtv (but not newline), octal escaping other + nonprintable characters, wrapping lines to terminal width with a + backslash, and appending $ to actual end of line. n Print default output and read next line, replacing current line (If no next line available, quit processing script) @@ -175,6 +176,7 @@ GLOBALS( void *restart, *lastregex; long nextlen, rememberlen, count; int fdout, noeol; + unsigned xx; ) struct step { @@ -254,7 +256,7 @@ static char *extend_string(char **old, char *new, int oldlen, int newlen) memcpy(s+oldlen, new, newlen); s[oldlen+newlen] = 0; - return s+oldlen+newlen; + return s+oldlen+newlen+1; } // An empty regex repeats the previous one @@ -296,7 +298,9 @@ static void walk_pattern(char **pline, long plen) if (line[len-1] == '\n') line[--len] = eol++; TT.count++; - logrus = TT.restart ? TT.restart : (void *)TT.pattern; + // 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; TT.restart = 0; while (logrus) { @@ -326,11 +330,17 @@ static void walk_pattern(char **pline, long plen) if (line && !ghostwheel(rm, line, len, 0, 0, 0)) logrus->hit++; } else if (lm == TT.count || (lm == -1 && !pline)) logrus->hit++; + + if (!logrus->lmatch[1] && !logrus->rmatch[1]) miss = 1; } // Didn't match? - if (!(logrus->hit ^ logrus->not)) { + lm = !(logrus->hit ^ logrus->not); + + // Deferred disable from regex end match + if (miss || logrus->lmatch[1] == TT.count) logrus->hit = 0; + if (lm) { // Handle skipping curly bracket command group if (c == '{') { int curly = 1; @@ -344,8 +354,6 @@ static void walk_pattern(char **pline, long plen) logrus = logrus->next; continue; } - // Deferred disable from regex end match - if (miss) logrus->hit = 0; } // A deleted line can still update line match state for later commands @@ -359,7 +367,7 @@ static void walk_pattern(char **pline, long plen) if (c=='a' || c=='r') { struct append *a = xzalloc(sizeof(struct append)); a->str = logrus->arg1+(char *)logrus; - a->file = c== 'r'; + a->file = c=='r'; dlist_add_nomalloc((void *)&append, (void *)a); } else if (c=='b' || c=='t' || c=='T') { int t = tea; @@ -375,22 +383,30 @@ static void walk_pattern(char **pline, long plen) } } else if (c=='c') { str = logrus->arg1+(char *)logrus; - if (!logrus->hit || (!logrus->lmatch[1] && !logrus->rmatch[1])) - emit(str, strlen(str), 1); - goto done; + if (!logrus->hit) emit(str, strlen(str), 1); + free(line); + line = 0; + continue; } else if (c=='d') { free(line); line = 0; continue; } else if (c=='D') { // Delete up to \n or end of buffer - for (str = line; !*str || *str=='\n'; str++); + str = line; + while ((str-line)<len) if (*(str++) == '\n') break; len -= str - line; memmove(line, str, len); - line[len] = 0; - // restart script - logrus = (void *)TT.pattern; + // if "delete" blanks line, disable further processing + // otherwise trim and restart script + if (!len) { + free(line); + line = 0; + } else { + line[len] = 0; + logrus = (void *)TT.pattern; + } continue; } else if (c=='g') { free(line); @@ -413,17 +429,40 @@ static void walk_pattern(char **pline, long plen) } else if (c=='i') { str = logrus->arg1+(char *)logrus; emit(str, strlen(str), 1); -// } else if (c=='l') { -// error_exit("todo: l"); + } else if (c=='l') { + int i, x, off; + + if (!TT.xx) { + terminal_size(&TT.xx, 0); + if (!TT.xx) TT.xx = 80; + if (TT.xx > sizeof(toybuf)-10) TT.xx = sizeof(toybuf)-10; + if (TT.xx > 4) TT.xx -= 4; + } + + for (i = off = 0; i<len; i++) { + if (off >= TT.xx) { + toybuf[off++] = '\\'; + emit(toybuf, off, 1); + off = 0; + } + x = stridx("\\\a\b\f\r\t\v", line[i]); + if (x != -1) { + toybuf[off++] = '\\'; + toybuf[off++] = "\\abfrtv"[x]; + } else if (line[i] >= ' ') toybuf[off++] = line[i]; + else off += sprintf(toybuf+off, "\\%03o", line[i]); + } + toybuf[off++] = '$'; + emit(toybuf, off, 1); } else if (c=='n') { - TT.restart = logrus->next; + TT.restart = logrus->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; + TT.restart = logrus->next+1; extend_string(&line, TT.nextline, len, -TT.nextlen); free(TT.nextline); TT.nextline = line; @@ -492,35 +531,36 @@ static void walk_pattern(char **pline, long plen) // place because backrefs may refer to text after it's overwritten.) len += newlen-mlen; swap = xmalloc(len+1); - rswap = swap+(rline-line); + rswap = swap+(rline-line)+match[0].rm_so; memcpy(swap, line, (rline-line)+match[0].rm_so); - memcpy(rswap+match[0].rm_so+newlen, rline+match[0].rm_eo, - (rlen -= match[0].rm_eo)+1); + memcpy(rswap+newlen, rline+match[0].rm_eo, (rlen -= match[0].rm_eo)+1); // copy in new replacement text - rswap += match[0].rm_so; for (off = mlen = 0; new[off]; off++) { int cc = 0, ll; - if ((rswap[mlen++] = new[off]) == '\\') { + if (new[off] == '\\') { cc = new[++off] - '0'; if (cc<0 || cc>9) { - if (!(rswap[mlen-1] = unescape(new[off]))) + if (!(rswap[mlen++] = unescape(new[off]))) rswap[mlen-1] = new[off]; continue; } else if (match[cc].rm_so == -1) error_exit("no s//\\%d/", cc); - } else if (new[off] != '&') continue; + } else if (new[off] != '&') { + rswap[mlen++] = new[off]; + + continue; + } ll = match[cc].rm_eo-match[cc].rm_so; - memcpy(rswap+(--mlen), rline+match[cc].rm_so, ll); + memcpy(rswap+mlen, rline+match[cc].rm_so, ll); mlen += ll; } rline = rswap+newlen; free(line); line = swap; - len = rlen+(rline-line); // Stop after first substitution unless we have flag g if (!(logrus->sflags & 2)) break; @@ -576,7 +616,7 @@ writenow: } else if (c=='=') { sprintf(toybuf, "%ld", TT.count); emit(toybuf, strlen(toybuf), 1); - } else if (!strchr(":{}", c)) error_exit("todo: %c", c); + } logrus = logrus->next; } @@ -590,12 +630,15 @@ done: struct append *a = append->next; if (append->file) { - int fd = xopen(append->str, O_RDONLY); + int fd = open(append->str, O_RDONLY); // Force newline if noeol pending - emit(0, 0, 0); - xsendfile(fd, TT.fdout); - close(fd); + if (fd != -1) { + if (TT.noeol) xwrite(TT.fdout, "\n", 1); + TT.noeol = 0; + xsendfile(fd, TT.fdout); + close(fd); + } } else emit(append->str, strlen(append->str), 1); free(append); append = a; @@ -635,7 +678,7 @@ static void do_sed(int fd, char *name) struct step *primal; if (!fd && *name=='-') { - error_msg("no -i on stdin"); + error_msg("-i on stdin"); return; } TT.fdout = copy_tempfile(fd, name, &tmp); @@ -653,89 +696,79 @@ static void do_sed(int fd, char *name) } } -// Note: removing backslash escapes and null terminating edits the source -// string, which could be from the environment space via -e, which could -// screw up what "ps" sees, and I'm ok with that. (Modifying the environment -// space like that means sed is very, very not reentrant.) - -// Ok, what happens if we xexec() sed with constant arguments then? -// TODO: ^^^ that -// also screws up error reporting for bad patterns - -// returns length of processed string, *pstr advances to next unused char, -// if delim (or *delim) is 0 uses starting char as delimiter, otherwise -// parses and saves delimiter from first character(s) -// if rexex, ignore delimiter in [ranges] -static int unescape_delimited_string(char **pstr, char *delim, int regex) +// Copy chunk of string between two delimiters, converting printf escapes. +// returns processed copy of string (0 if error), *pstr advances to next +// unused char. if delim (or *delim) is 0 uses/saves starting char as delimiter +// if regxex, ignore delimiter in [ranges] +static char *unescape_delimited_string(char **pstr, char *delim, int regex) { - char *to, *from, d; - int rc; + char *to, *from, mode = 0, d; to = from = *pstr; if (!delim || !*delim) { - if (!(d = *(from++))) return -1; + if (!(d = *(from++))) return 0; if (d == '\\') d = *(from++); - if (!d || d == '\\') return -1; + if (!d || d == '\\') return 0; if (delim) *delim = d; } else d = *delim; + to = delim = xmalloc(strlen(*pstr)+1); - while (*from != d) { - if (!*from) return -1; + while (mode || *from != d) { + if (!*from) return 0; // delimiter in regex character range doesn't count if (*from == '[') { - int len = 1; - - if (from[len] == ']') len++; - while (from[len] != ']') if (!from[len++]) return -1; - memmove(to, from, ++len); - to += len; - from += len; - continue; - } - if (*from == '\\') { - if (!from[1]) return -1; + mode = '['; + if (from[1] == ']') *(to++) = *(from++); + } else if (mode && *from == ']') mode = 0; + else if (*from == '\\') { + if (!from[1]) return 0; // Check escaped end delimiter before printf style escapes. if (from[1] == d) from++; - else if (from[1]!='\\') { + else if (from[1]=='\\') *(to++) = *(from++); + else { char c = unescape(from[1]); if (c) { *(to++) = c; from+=2; continue; - } + } else *(to++) = *(from++); } } *(to++) = *(from++); } - rc = to-*pstr; *to = 0; *pstr = from+1; - return rc; + return delim; } // Translate primal pattern into walkable form. static void jewel_of_judgement(char **pline, long len) { struct step *corwin = (void *)TT.pattern; - char *line = *pline, *reg, c; + 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) { + if (!*pline) error_exit("unfinished %c", corwin->prev->c);; // Remove half-finished entry from list so remalloc() doesn't confuse it TT.pattern = TT.pattern->prev; corwin = dlist_pop(&TT.pattern); - corwin->hit = 0; c = corwin->c; reg = (char *)corwin; reg += corwin->arg1 + strlen(reg + corwin->arg1); - // Resume parsing - goto append; + // Resume parsing for 'a' or 's' command + if (corwin->hit < 256) goto resume_s; + else goto resume_a; } // Loop through commands in line @@ -744,9 +777,14 @@ static void jewel_of_judgement(char **pline, long len) for (;;) { if (corwin) dlist_add_nomalloc(&TT.pattern, (void *)corwin); - while (isspace(*line) || *line == ';') line++; - if (!*line || *line == '#') return; + for (;;) { + while (isspace(*line) || *line == ';') line++; + if (*line == '#') while (*line && *line != '\n') line++; + else break; + } + if (!*line) return; + errstart = line; memset(toybuf, 0, sizeof(struct step)); corwin = (void *)toybuf; reg = toybuf + sizeof(struct step); @@ -763,13 +801,14 @@ static void jewel_of_judgement(char **pline, long len) } else if (*line == '/' || *line == '\\') { char *s = line; - if (-1 == unescape_delimited_string(&line, 0, 1)) goto brand; + if (!(s = unescape_delimited_string(&line, 0, 1))) goto brand; if (!*s) corwin->rmatch[i] = 0; else { xregcomp((void *)reg, s, (toys.optflags & FLAG_r)*REG_EXTENDED); corwin->rmatch[i] = reg-toybuf; reg += sizeof(regex_t); } + free(s); } else break; } @@ -796,34 +835,57 @@ static void jewel_of_judgement(char **pline, long len) else if (c == '}') { if (!TT.nextlen--) break; } else if (c == 's') { - char *merlin, *fiona, delim = 0; + char *fiona, 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. + // get pattern (just record, we parse it later) - corwin->arg1 = reg - (char *)corwin; - merlin = line; - if (-1 == unescape_delimited_string(&line, &delim, 1)) goto brand; + corwin->arg2 = reg - (char *)corwin; + if (!(TT.remember = unescape_delimited_string(&line, &delim, 1))) + goto brand; + reg += sizeof(regex_t); + corwin->arg1 = reg-(char *)corwin; + corwin->hit = delim; +resume_s: // get replacement - don't replace escapes because \1 and \& need // processing later, after we replace \\ with \ we can't tell \\1 from \1 fiona = line; - while (*line != delim) { - if (!*line) goto brand; - if (*line == '\\') { - if (!line[1]) goto brand; - line += 2; - } else line++; + while (*fiona != corwin->hit) { + if (!*fiona) goto brand; + if (*fiona++ == '\\') { + if (!*fiona || *fiona == '\n') { + fiona[-1] = '\n'; + break; + } + fiona++; + } + } + + reg = extend_string((void *)&corwin, line, reg-(char *)corwin,fiona-line); + line = fiona; + // line continuation? (note: '\n' can't be a valid delim). + if (*line == corwin->hit) corwin->hit = 0; + else { + if (!*line) continue; + reg--; + line++; + goto resume_s; } - corwin->arg2 = corwin->arg1 + sizeof(regex_t); - reg = extend_string((void *)&corwin, fiona, corwin->arg2, line-fiona)+1; + // swap arg1/arg2 so they're back in order arguments occur. + i = corwin->arg1; + corwin->arg1 = corwin->arg2; + corwin->arg2 = i; // get flags for (line++; *line; line++) { long l; - if (isspace(*line)) continue; + 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))) { @@ -834,9 +896,11 @@ static void jewel_of_judgement(char **pline, long len) // We deferred actually parsing the regex until we had the s///i flag // allocating the space was done by extend_string() above - if (!*merlin) corwin->arg1 = 0; - else xregcomp((void *)(corwin->arg1 + (char *)corwin), merlin, + 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)); + free(TT.remember); + TT.remember = 0; if (*line == 'w') { line++; goto writenow; @@ -873,46 +937,65 @@ writenow: line = cc; if (delim) line += 2; } else if (c == 'y') { - char *s = line, delim = 0; - int len1, len2; + char *s, delim = 0; + int len; - if (-1 == (len1 = unescape_delimited_string(&line, &delim, 0))) - goto brand; + if (!(s = unescape_delimited_string(&line, &delim, 0))) goto brand; corwin->arg1 = reg-(char *)corwin; - reg = extend_string((void *)&corwin, s, reg-(char *)corwin, len1); - s = line; + len = strlen(s); + reg = extend_string((void *)&corwin, s, reg-(char *)corwin, len); + free(s); corwin->arg2 = reg-(char *)corwin; - if (-1 == (len2 = unescape_delimited_string(&line, &delim, 0))) - goto brand; - if (len1 != len2) goto brand; - reg = extend_string((void *)&corwin, s, reg-(char*)corwin, len2); + if (!(s = unescape_delimited_string(&line, &delim, 0))) goto brand; + if (len != strlen(s)) goto brand; + reg = extend_string((void *)&corwin, s, reg-(char*)corwin, len); + free(s); } else if (strchr("abcirtTw:", c)) { - int end, class; + int end; - // Trim whitespace from "b ;" and ": blah " but only first space in "w x " + while (isspace(*line) && *line != '\n') line++; - while (isspace(*line)) line++; -append: - class = !strchr("btT:", c); - end = strcspn(line, class ? "\n" : "; \t\r\n\v\f"); + // 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; - if (!end) { - if (!strchr("btT", c)) break; - continue; + // Trim whitespace from "b ;" and ": blah " but only first space in "w x " + if (!(end = strcspn(line, strchr("btT:", c) ? "; \t\r\n\v\f" : "\n"))) { + if (strchr("btT", c)) continue; + else if (!corwin->arg1) break; } // Extend allocation to include new string. We use offsets instead of - // pointers so realloc() moving stuff doesn't break things. Do it - // here instead of toybuf so there's no maximum size. + // 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; - reg = extend_string((void *)&corwin, line, reg - (char *)corwin, end); - line += end; - - // Line continuation? - if (class && reg[-1] == '\\') { - reg[-1] = 0; - corwin->hit++; - } + else if ((corwin+1) != (void *)reg) *(reg++) = '\n'; + reg = extend_string((void *)&corwin, line, reg - (char *)corwin, end); + + // Recopy data to remove escape sequences and handle line continuation. + if (strchr("aci", c)) { + reg -= end+1; + for (i = end; i; i--) { + if ((*reg++ = *line++)=='\\') { + + // escape at end of line: resume if -e escaped literal newline, + // else request callback and resume with next line + if (!--i) { + *--reg = 0; + if (*line) { + line++; + goto resume_a; + } + corwin->hit = 256; + break; + } + if (!(reg[-1] = unescape(*line))) reg[-1] = *line; + line++; + } + } + *reg = 0; + } else line += end; // Commands that take no arguments } else if (!strchr("{dDgGhHlnNpPqx=", c)) break; @@ -920,7 +1003,7 @@ append: brand: // Reminisce about chestnut trees. - error_exit("bad pattern '%s'@%ld (%c)", *pline, line-*pline+1, *line); + error_exit("bad pattern '%s'@%ld (%c)", errstart, line-errstart+1L, *line); } void sed_main(void) @@ -949,6 +1032,7 @@ void sed_main(void) 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); dlist_terminate(TT.pattern); if (TT.nextlen) error_exit("no }"); diff --git a/toys/posix/sort.c b/toys/posix/sort.c index c9f887d..ed7c36c 100644 --- a/toys/posix/sort.c +++ b/toys/posix/sort.c @@ -14,9 +14,9 @@ config SORT Sort all lines of text from input files (or stdin) to stdout. - -r reverse - -u unique lines only - -n numeric order (instead of alphabetical) + -r reverse + -u unique lines only + -n numeric order (instead of alphabetical) config SORT_BIG bool "SuSv3 options (Support -ktcsbdfiozM)" @@ -25,18 +25,18 @@ config SORT_BIG help usage: sort [-bcdfiMsz] [-k#[,#[x]] [-t X]] [-o FILE] - -b ignore leading blanks (or trailing blanks in second part of key) - -c check whether input is sorted - -d dictionary order (use alphanumeric and whitespace chars only) - -f force uppercase (case insensitive sort) - -i ignore nonprinting characters - -M month sort (jan, feb, etc). - -x Hexadecimal numerical sort - -s skip fallback sort (only sort with keys) - -z zero (null) terminated input - -k sort by "key" (see below) - -t use a key separator other than whitespace - -o output to FILE instead of stdout + -b ignore leading blanks (or trailing blanks in second part of key) + -c check whether input is sorted + -d dictionary order (use alphanumeric and whitespace chars only) + -f force uppercase (case insensitive sort) + -i ignore nonprinting characters + -M month sort (jan, feb, etc). + -x Hexadecimal numerical sort + -s skip fallback sort (only sort with keys) + -z zero (null) terminated lines + -k sort by "key" (see below) + -t use a key separator other than whitespace + -o output to FILE instead of stdout Sorting by key looks at a subset of the words on each line. -k2 uses the second word to the end of the line, -k2,2 looks at only @@ -46,15 +46,13 @@ config SORT_BIG (such as -2,2n) applies only to sorting that key. config SORT_FLOAT - bool "Floating point (-g)" + bool default y - depends on SORT_BIG + depends on SORT_BIG && TOYBOX_FLOAT help usage: sort [-g] -g general numeric sort (double precision with nan and inf) - - This version of sort requires floating point. */ #define FOR_sort @@ -386,9 +384,11 @@ void sort_main(void) // Output result for (idx = 0; idx<TT.linecount; idx++) { char *s = TT.lines[idx]; - xwrite(fd, s, strlen(s)); + unsigned i = strlen(s); + + if (!(toys.optflags&FLAG_z)) s[i] = '\n'; + xwrite(fd, s, i+1); if (CFG_TOYBOX_FREE) free(s); - xwrite(fd, "\n", 1); } exit_now: diff --git a/toys/posix/split.c b/toys/posix/split.c index d9a556b..aabf931 100644 --- a/toys/posix/split.c +++ b/toys/posix/split.c @@ -100,7 +100,7 @@ void split_main(void) if (!TT.bytes && !TT.lines) TT.lines = 1000; // Allocate template for output filenames - TT.outfile = xmprintf("%s% *c", (toys.optc == 2) ? toys.optargs[1] : "x", + TT.outfile = xmprintf("%s%*c", (toys.optc == 2) ? toys.optargs[1] : "x", (int)TT.suflen, ' '); // We only ever use one input, but this handles '-' or no input for us. diff --git a/toys/posix/tail.c b/toys/posix/tail.c index e92c044..80556e2 100644 --- a/toys/posix/tail.c +++ b/toys/posix/tail.c @@ -4,7 +4,7 @@ * * See http://opengroup.org/onlinepubs/9699919799/utilities/tail.html -USE_TAIL(NEWTOY(tail, "fc-n-[-cn]", TOYFLAG_BIN)) +USE_TAIL(NEWTOY(tail, "?fc-n-[-cn]", TOYFLAG_USR|TOYFLAG_BIN)) config TAIL bool "tail" @@ -17,7 +17,7 @@ config TAIL -n output the last NUMBER lines (default 10), +X counts from start. -c output the last NUMBER bytes, +NUMBER counts from start - -f follow FILE(s), waiting for more data to be appended + #-f follow FILE(s), waiting for more data to be appended [TODO] config TAIL_SEEK bool "tail seek support" @@ -213,10 +213,22 @@ static void do_tail(int fd, char *name) void tail_main(void) { - // if nothing specified, default -n to -10 - if (!(toys.optflags&(FLAG_n|FLAG_c))) TT.lines = -10; + char **args = toys.optargs; - loopfiles(toys.optargs, do_tail); + if (!(toys.optflags&(FLAG_n|FLAG_c))) { + char *arg = *args; + + // handle old "-42" style arguments + if (arg && *arg == '-' && arg[1]) { + TT.lines = atolx(*(args++)); + toys.optc--; + } + + // if nothing specified, default -n to -10 + TT.lines = -10; + } + + loopfiles(args, do_tail); // do -f stuff } diff --git a/toys/posix/tee.c b/toys/posix/tee.c index 0388510..d5591b6 100644 --- a/toys/posix/tee.c +++ b/toys/posix/tee.c @@ -4,7 +4,7 @@ * * See http://opengroup.org/onlinepubs/9699919799/utilities/tee.html -USE_TEE(NEWTOY(tee, "ia", TOYFLAG_BIN)) +USE_TEE(NEWTOY(tee, "ia", TOYFLAG_USR|TOYFLAG_BIN)) config TEE bool "tee" @@ -45,7 +45,7 @@ static void do_tee_open(int fd, char *name) void tee_main(void) { - if (toys.optflags & FLAG_i) signal(SIGINT, SIG_IGN); + if (toys.optflags & FLAG_i) xsignal(SIGINT, SIG_IGN); // Open output files loopfiles_rw(toys.optargs, diff --git a/toys/posix/time.c b/toys/posix/time.c index 0159205..70d2997 100644 --- a/toys/posix/time.c +++ b/toys/posix/time.c @@ -9,6 +9,7 @@ USE_TIME(NEWTOY(time, "<1^p", TOYFLAG_USR|TOYFLAG_BIN)) config TIME bool "time" default y + depends on TOYBOX_FLOAT help usage: time [-p] COMMAND [ARGS...] @@ -27,7 +28,7 @@ void time_main(void) struct timeval tv, tv2; gettimeofday(&tv, NULL); - if (!(pid = xfork())) xexec_optargs(0); + if (!(pid = xfork())) xexec(toys.optargs); else { int stat; struct rusage ru; diff --git a/toys/posix/touch.c b/toys/posix/touch.c index 71ddc43..052448b 100644 --- a/toys/posix/touch.c +++ b/toys/posix/touch.c @@ -2,21 +2,24 @@ * * Copyright 2012 Choubey Ji <warior.linux@gmail.com> * - * See http://pubs.opengroup.org/onlinepubs/9699919799/utilities/touch.html + * See http://pubs.opengroup.org/onlinepubs/9699919799/utilities/touch.html + * + * TODO: have another go at merging the -t and -d stanzas -USE_TOUCH(NEWTOY(touch, "acd:mr:t:[!dtr]", TOYFLAG_BIN)) +USE_TOUCH(NEWTOY(touch, "acd:mr:t:h[!dtr]", TOYFLAG_BIN)) config TOUCH bool "touch" default y help - usage: touch [-amc] [-d DATE] [-t TIME] [-r FILE] FILE... + usage: touch [-amch] [-d DATE] [-t TIME] [-r FILE] FILE... Update the access and modification times of each FILE to the current time. -a change access time -m change modification time -c don't create file + -h change symlink -d set time to DATE (in YYYY-MM-DDThh:mm:SS[.frac][tz] format) -t set time to TIME (in [[CC]YY]MMDDhhmm[.ss][frac] format) -r set time same as reference FILE @@ -31,41 +34,20 @@ GLOBALS( char *date; ) -// Fetch access and/or modification time of a file -int fetch(char *file, struct timeval *tv, unsigned flags) -{ - struct stat st; - - if (stat(file, &st)) return 1; - - if (flags & FLAG_a) { - tv[0].tv_sec = st.st_atime; - tv[0].tv_usec = st.st_atim.tv_nsec/1000; - } - if (flags & FLAG_m) { - tv[1].tv_sec = st.st_mtime; - tv[1].tv_usec = st.st_mtim.tv_nsec/1000; - } - - return 0; -} - void touch_main(void) { - struct timeval tv[2]; + struct timespec ts[2]; char **ss; - int flag, fd, i; - - // Set time from clock? - - gettimeofday(tv, NULL); + int fd, i; + // use current time if no -t or -d + ts[0].tv_nsec = UTIME_NOW; if (toys.optflags & (FLAG_t|FLAG_d)) { char *s, *date; struct tm tm; - int len; + int len = 0; - localtime_r(&(tv->tv_sec), &tm); + localtime_r(&(ts->tv_sec), &tm); // Set time from -d? @@ -77,14 +59,13 @@ void touch_main(void) if (toupper(date[i-1])=='Z') { date[i-1] = 0; setenv("TZ", "UTC0", 1); - localtime_r(&(tv->tv_sec), &tm); + localtime_r(&(ts->tv_sec), &tm); } s = strptime(date, "%Y-%m-%dT%T", &tm); - if (s && *s=='.') { - sscanf(s, ".%d%n", &i, &len); - s += len; - tv->tv_usec = i; - } + ts->tv_nsec = 0; + if (s && *s=='.' && isdigit(s[1])) + sscanf(s, ".%lu%n", &ts->tv_nsec, &len); + else len = 0; } else s = 0; // Set time from -t? @@ -92,40 +73,54 @@ void touch_main(void) } else { strcpy(toybuf, "%Y%m%d%H%M"); date = TT.time; + i = ((s = strchr(date, '.'))) ? s-date : strlen(date); + if (i < 8 || i%2) error_exit("bad '%s'", date); for (i=0;i<3;i++) { s = strptime(date, toybuf+(i&2), &tm); if (s) break; toybuf[1]='y'; } - if (s && *s=='.') { - int count = sscanf(s, ".%2d%u%n", &(tm.tm_sec), &i, &len); - - if (count==2) tv->tv_usec = i; - s += len; - } + ts->tv_nsec = 0; + if (s && *s=='.' && sscanf(s, ".%2u%n", &(tm.tm_sec), &len) == 1) { + sscanf(s += len, "%lu%n", &ts->tv_nsec, &len); + len++; + } else len = 0; + } + if (len) { + s += len; + if (ts->tv_nsec > 999999999) s = 0; + else while (len++ < 10) ts->tv_nsec *= 10; } errno = 0; - tv->tv_sec = mktime(&tm); + ts->tv_sec = mktime(&tm); if (!s || *s || errno == EOVERFLOW) perror_exit("bad '%s'", date); } - tv[1]=tv[0]; + ts[1]=ts[0]; // Set time from -r? - if (TT.file && fetch(TT.file, tv, FLAG_a|FLAG_m)) - perror_exit("-r '%s'", TT.file); + if (TT.file) { + struct stat st; - // Ok, we've got a time. Flip -am flags so now it's the ones we _keep_. + xstat(TT.file, &st); + ts[0] = st.st_atim; + ts[1] = st.st_mtim; + } - flag = (~toys.optflags) & (FLAG_m|FLAG_a); + // Which time(s) should we actually change? + i = toys.optflags & (FLAG_a|FLAG_m); + if (i && i!=(FLAG_a|FLAG_m)) ts[i==FLAG_m].tv_nsec = UTIME_OMIT; // Loop through files on command line - for (ss=toys.optargs; *ss;) { - if ((flag == (FLAG_m|FLAG_a) || !fetch(*ss, tv, flag)) && !utimes(*ss, tv)) - ss++; + for (ss = toys.optargs; *ss;) { + + // cheat: FLAG_h is rightmost flag, so its value is 1 + if (!utimensat(AT_FDCWD, *ss, ts, + (toys.optflags & FLAG_h)*AT_SYMLINK_NOFOLLOW)) ss++; else if (toys.optflags & FLAG_c) ss++; - else if (-1 != (fd = open(*ss, O_CREAT, 0666))) close(fd); + else if (access(*ss, F_OK) && (-1!=(fd = open(*ss, O_CREAT, 0666)))) + close(fd); else perror_msg("'%s'", *ss++); } } diff --git a/toys/posix/true.c b/toys/posix/true.c index b22b7ac..0fbb178 100644 --- a/toys/posix/true.c +++ b/toys/posix/true.c @@ -5,7 +5,7 @@ * See http://opengroup.org/onlinepubs/9699919799/utilities/true.html USE_TRUE(NEWTOY(true, NULL, TOYFLAG_BIN)) -USE_TRUE(OLDTOY(:, true, 0, TOYFLAG_NOFORK)) +USE_TRUE(OLDTOY(:, true, TOYFLAG_NOFORK)) config TRUE bool "true" diff --git a/toys/posix/uniq.c b/toys/posix/uniq.c index 3cfdb94..c127cfe 100644 --- a/toys/posix/uniq.c +++ b/toys/posix/uniq.c @@ -4,7 +4,7 @@ * * See http://opengroup.org/onlinepubs/9699919799/utilities/uniq.html -USE_UNIQ(NEWTOY(uniq, "f#s#w#zicdu", TOYFLAG_BIN)) +USE_UNIQ(NEWTOY(uniq, "f#s#w#zicdu", TOYFLAG_USR|TOYFLAG_BIN)) config UNIQ bool "uniq" diff --git a/toys/posix/uuencode.c b/toys/posix/uuencode.c index 2323c98..34ca701 100644 --- a/toys/posix/uuencode.c +++ b/toys/posix/uuencode.c @@ -22,22 +22,13 @@ config UUENCODE void uuencode_main(void) { - char *p, *name = toys.optargs[toys.optc-1], buf[(76/4)*3]; + char *name = toys.optargs[toys.optc-1], buf[(76/4)*3]; int i, m = toys.optflags & FLAG_m, fd = 0; if (toys.optc > 1) fd = xopen(toys.optargs[0], O_RDONLY); - // base64 table - - p = toybuf; - for (i = 'A'; i != ':'; i++) { - if (i == 'Z'+1) i = 'a'; - if (i == 'z'+1) i = '0'; - *(p++) = i; - } - *(p++) = '+'; - *(p++) = '/'; + base64_init(toybuf); xprintf("begin%s 744 %s\n", m ? "-base64" : "", name); for (;;) { diff --git a/toys/posix/who.c b/toys/posix/who.c index 876a562..414cdfc 100644 --- a/toys/posix/who.c +++ b/toys/posix/who.c @@ -9,7 +9,7 @@ * Posix says to support many options (-abdHlmpqrstTu) but this * isn't aimed at minicomputers with modem pools. -USE_WHO(NEWTOY(who, "a", TOYFLAG_BIN)) +USE_WHO(NEWTOY(who, "a", TOYFLAG_USR|TOYFLAG_BIN)) config WHO bool "who" diff --git a/toys/posix/xargs.c b/toys/posix/xargs.c index cde71f6..8178bf0 100644 --- a/toys/posix/xargs.c +++ b/toys/posix/xargs.c @@ -3,6 +3,8 @@ * Copyright 2011 Rob Landley <rob@landley.net> * * See http://opengroup.org/onlinepubs/9699919799/utilities/xargs.html + * + * TODO: Rich's whitespace objection, env size isn't fixed anymore. USE_XARGS(NEWTOY(xargs, "^I:E:L#ptxrn#<1s#0", TOYFLAG_USR|TOYFLAG_BIN)) diff --git a/www/about.html b/www/about.html index 1426221..61c57a6 100755 --- a/www/about.html +++ b/www/about.html @@ -73,133 +73,21 @@ kind of nice to have.</p> <b><h2><a name="downloads" />Download</h2></b> -<p>This project is maintained as a mercurial archive. To get a copy of the -current development version, either use mercurial (hg clone -http://landley.net/hg/toybox) or click on one of the zip/gz/bz2 links -at the top of the <a href=/hg/toybox>mercurial archive browser</a> page to get -an archive of the appropriate version. Click -<a href="/hg/toybox?cmd=tags">tags</a> to see all the tagged release -versions ("tip" is the current development version).</p> +<p>This project is maintained as a <a href=https://github.com/landley/toybox>git +archive</a>, and also offers <a href=http://landley.net/toybox/downloads>source +tarballs</a> and <a href=http://landley.net/toybox/bin>static binaries</a> +of the release versions.</p> <p>The maintainer's <a href=/notes.html>development log</a> and the project's <a href=http://lists.landley.net/listinfo.cgi/toybox-landley.net>mailing list</a> are also good ways to track what's going on with the project.</p> -<!-- -<b><h2><a name="why">Why do toybox?</h2></b> - -<p>Because smart phones are replacing the PC, and Android must become -self-hosting to beat the iPhone in establishing the new standard.</p> - -<p>This is the third such major transition in computer history: -(mainframe->minicomputer->microcomputer->smartphone). -The mainframe was replaced by the minicomputer, which was replaced by -the microcomputer (renamed the "personal" computer to make clear you could -access porn through it), which is being replaced by the smartphone. Nobody -needed to wait for printouts from a big computer in another building when they -could use a little one down the hall. Then nobody needed the big computer -down the hall when they had a little one on their desk. Now nobody needs the -big computer on their desk when they have a little one in their pocket.</p> - -<p>The new platform displaces the old when it becomes natively self hosting. -Often they leverage existing technology: just as early microcmputers used -teletypes and televisions for output, phones can use -<a href=http://us.toshiba.com/accessory/PA3575U-1PRP>USB docking stations</a> -to access a bigger screen, mouse, keyboard, speakers, etc. Plugging a phone into -USB even charges the battery. But to use the phone as a development -workstation, it needs more software, such as a Posix command line, a native -compiler, and drivers for the USB peripherals.</p> - -<p>The new platform also eventually weans itself off of its dominant language. -Dalvik is to Android what ROM Basic was to the PC: something it must -eventually outgrow. Thus toybox is native C code, not Java.</p> - -<b><h3>So why aren't self-hosting smartphones attracting more attention?</h3></b> - -<p>Because most people are focusing on the legacy platforms, not on the new -stuff. Existing multi-billion dollar industries are getting evicted from their -decades-old established niche, and are trying to spin the transition as an -opportunity instead of a forced march onto reservations. When elephants run -from mice, it's easier to notice the elephants.</p> - -<p>History's our guide here: the previous technology always gets kicked up into -the "server space", moving from "the thing you stood in front of waiting for -your printout" to "that thing you sometimes accessed remotely via the new -computer". This time around they're calling it "the cloud" and pretending it's -a big deal; it's really just a beowulf cluster with a layer of -virtualization/containerization software implementing hotplug hardware and -live migration to provide cheap -commodity processing power that dominant players (like amazon) literally -give away for free. These old machines become secondary, only -accessed through the new machines users now directly interact with.</p> - -<p>Since there's only one server space, the mainframe ate the minicomputer in -the 1980's (when DEC went under), and this time around "the cloud" seems to be -eating the mainframe (IBM ain't happy). The inevitable consolidation leads -to drama, but doesn't mean much in the long run.</p> - -<p><a href=http://landley.net/notes-2012.html#12-07-2012>For more -on this topic...</a></p> - -<b><h3><a name="why_android">Why is Android important?</h2></b> - -<p>Major hardware transitions introduce -<a href=http://landley.net/notes-2011.html#26-06-2011>new software -standards</a> which are extremely sticky once -established, due to network effects.</p> - -<p>Last time around, the PC was stuck with -a proprietary operating system (DOS/Windows) which is still dominant on that -hardware platform's descendants 30 years later. This time around, the choice -is between Android (a Linux derivative) and iPhone (a closed BSD fork ala -SunOS, put out by a company already engaged in multiple aggressive IP lawsuits). -The main difference between Apple and Microsoft is that Apple is competent.</p> - -<p>And yes, it has to be Android, it won't be vanilla Linux any time soon, -for three reasons. 1) <a href=http://landley.net/notes-2010.html#13-08-2010>Open -Source can't do user interfaces</a> for about the same reason wikipedia can't -write a novel, 2) it's too late to the -party (a 5 year headstart is forever in computers), 3) preinstalls matter -(GPLv3 spooked all the hardware vendors, Android has a "no GPL in -userspace" policy which is rigidly enforced).</p> - -<p>And "any time soon" is important: attempting to displace an existing -entrenched de-facto standard is what linux has spent the last 20 -years trying (and failing) to do on the desktop. Spending another 20 -years fighting for less than 1% of the phone market would just be sad.</p> - -<b><h3><a name="how_google">How is Google less evil than Apple?</h3></b> - -<p>Because Android isn't Google's core business, attaching advertising to large -scale data searches is. Android and Chrome and such are Google's way of -"commoditizing their co-factors" to drive down the price of ingredients -to their core business.</p> - -<p>Thus Google is pursuing a commodity market and encouring as many vendors as -possible to participate, not to control the new space but to hold it open, -so that its search products are widely available without requiring the -permission of some other monopoly gatekeeper. Apple is attemping to corner the -smartphone market and extract monopoly rents, excluding all -vendors except itself.</p> - -<p>So if Google wins we get a commodity market in smartphone/tablet software, -and may be able to open it further in future. If Apple wins we get a proprietary -smartphone/tablet OS with a single monopoly vendor, which is likely to close it -further.</p> - -<b><h3>Why not just use BusyBox?</h3></b> - -<p>Android can't. Busybox predates android -by many years; if they were ever going to ship it they'd have done so by -now. Android has had a "No GPL in Userspace" policy ever since GPLv3 -came out (before the first Android phone shipped), and they mean it.</p> - -<p>Toybox also has a better design and simpler code. I did both -and this is the one I enjoy banging on; I tried to contribute a few things -to busybox and it was like crawling through a thornbush of #ifdefs. Busybox -development is just no fun anymore.</p> - ---> +<b><h2><a name="why" />Why is toybox?</h2></b> + +<p>The <a href=http://landley.net/talks/celf-2015.txt>2015 toybox talk</a> +starts with links to three previous talks on the history and motivation of +the project: "Why Toybox", "Why Public Domain", and "Why did I do +Aboriginal Linux (which led me here)?".</p> <b><h2><a name="toycans" />What's the toybox logo image?</h2></b> diff --git a/www/cleanup.html b/www/cleanup.html index 5919994..9a9695a 100644 --- a/www/cleanup.html +++ b/www/cleanup.html @@ -139,7 +139,7 @@ cleanup. The final version is about 1/3 the size of the original.</p> <ul> <li>old total: <a href=/hg/toybox/file/841/toys/pending/ifconfig.c>1504 lines (44268 bytes) in 38 functions</a></li> -<li>new total: <a href=/hg/toybox/file/1113/toys/other/ifconfig.c>521 lines (15963 bytes) in 4 functions</a></li> +<li>new total: <a href=/hg/toybox/file/1133/toys/other/ifconfig.c>521 lines (15963 bytes) in 4 functions</a></li> </ul> <p>This was the first command I started cleaning up with the intent of diff --git a/www/code.html b/www/code.html index 7603c84..a1c08ef 100644 --- a/www/code.html +++ b/www/code.html @@ -233,6 +233,18 @@ where execution of your command starts. Your command line options are already sorted into this.optflags, this.optargs, this.optc, and the GLOBALS() as appropriate by the time this function is called. (See <a href="#lib_args">get_optflags()</a> for details.)</p></li> + +<li><p>Switch on TOYBOX_DEBUG in menuconfig (toybox global settings menu) +the first time you build and run your new command. If anything is wrong +with your option string, that will give you error messages.</p> + +<p>Otherwise it'll just segfault without +explanation when it falls off the end because it didn't find a matching +end parantheses for a longopt, or you put a nonexistent option in a square +bracket grouping... Since these kind of errors can only be caused by a +developer, not by end users, we don't normally want runtime checks for +them. Once you're happy with your option string, you can switch TOYBOX_DEBUG +back off.</p></li> </ul> <a name="headers" /><h2><a href="#headers">Headers.</a></h2> @@ -877,20 +889,21 @@ a double_list, dlist_add() your entries, and then break the circle with <a name="lib_args"><h3>lib/args.c</h3> <p>Toybox's main.c automatically parses command line options before calling the -command's main function. Option parsing starts in get_optflags(), which stores +command's main function. Option parsing starts in get_optflags(), which stores results in the global structures "toys" (optflags and optargs) and "this".</p> <p>The option parsing infrastructure stores a bitfield in toys.optflags to -indicate which options the current command line contained. Arguments +indicate which options the current command line contained, and defines FLAG +macros code can use to check whether each argument's bit is set. Arguments attached to those options are saved into the command's global structure -("this"). Any remaining command line arguments are collected together into -the null-terminated array toys.optargs, with the length in toys.optc. (Note +("this"). Any remaining command line arguments are collected together into +the null-terminated array toys.optargs, with the length in toys.optc. (Note that toys.optargs does not contain the current command name at position zero, -use "toys.which->name" for that.) The raw command line arguments get_optflags() +use "toys.which->name" for that.) The raw command line arguments get_optflags() parsed are retained unmodified in toys.argv[].</p> <p>Toybox's option parsing logic is controlled by an "optflags" string, using -a format reminiscent of getopt's optargs but has several important differences. +a format reminiscent of getopt's optargs but with several important differences. Toybox does not use the getopt() function out of the C library, get_optflags() is an independent implementation which doesn't permute the original arguments (and thus doesn't change how the @@ -904,14 +917,14 @@ command line arguments to look for, and what to do with them. If a command has no option definition string (I.E. the argument is NULL), option parsing is skipped for that command, which must look at the raw data in toys.argv to parse its -own arguments. (If no currently enabled command uses option parsing, +own arguments. (If no currently enabled command uses option parsing, get_optflags() is optimized out of the resulting binary by the compiler's --gc-sections option.)</p> <p>You don't have to free the option strings, which point into the environment -space (I.E. the string data is not copied). A TOYFLAG_NOFORK command +space (I.E. the string data is not copied). A TOYFLAG_NOFORK command that uses the linked list type "*" should free the list objects but not -the data they point to, via "llist_free(TT.mylist, NULL);". (If it's not +the data they point to, via "llist_free(TT.mylist, NULL);". (If it's not NOFORK, exit() will free all the malloced data anyway unless you want to implement a CONFIG_TOYBOX_FREE cleanup for it.)</p> @@ -932,7 +945,7 @@ available to command_main(): <ul> <li><p>In <b>struct toys</b>: <ul> -<li>toys.optflags = 13; // -a = 8 | -b = 4 | -d = 1</li> +<li>toys.optflags = 13; // FLAG_a = 8 | FLAG_b = 4 | FLAG_d = 1</li> <li>toys.optargs[0] = "walrus"; // leftover argument</li> <li>toys.optargs[1] = NULL; // end of list</li> <li>toys.optc = 1; // there was 1 leftover argument</li> @@ -958,6 +971,7 @@ GLOBALS( long a; ) </pre></blockquote> + <p>That would mean TT.c == NULL, TT.b == "fruit", and TT.a == 42. (Remember, each entry that receives an argument must be a long or pointer, to line up with the array position. Right to left in the optflags string corresponds to @@ -976,19 +990,39 @@ toys.optflags, with the same value as a corresponding binary digit. The rightmost argument is (1<<0), the next to last is (1<<1) and so on. If the option isn't encountered while parsing argv[], its bit remains 0.</p> +<p>Each option -x has a FLAG_x macro for the command letter. Bare --longopts +with no corresponding short option have a FLAG_longopt macro for the long +optionname. Commands enable these macros by #defining FOR_commandname before +#including <toys.h>. When multiple commands are implemented in the same +source file, you can switch flag contexts later in the file by +#defining CLEANUP_oldcommand and #defining FOR_newcommand, then +#including <generated/flags.h>.</p> + +<p>Options disabled in the current configuration (wrapped in +a USE_BLAH() macro for a CONFIG_BLAH that's switched off) have their +corresponding FLAG macro set to zero, so code checking them ala +if (toys.optargs & FLAG_x) gets optimized out via dead code elimination. +#defining FORCE_FLAGS when switching flag context disables this +behavior: the flag is never zero even if the config is disabled. This +allows code shared between multiple commands to use the same flag +values, as long as the common flags match up right to left in both option +strings.</p> + <p>For example, the optflags string "abcd" would parse the command line argument "-c" to set optflags to 2, "-a" would set optflags to 8, "-bd" would set optflags to -6 (I.E. 4|2), and "-a -c" would set optflags to 10 (2|8).</p> +6 (I.E. 4|2), and "-a -c" would set optflags to 10 (2|8). To check if -c +was encountered, code could test: if (toys.optflags & FLAG_c) printf("yup"); +(See the toys/examples directory for more.)</p> <p>Only letters are relevant to optflags, punctuation is skipped: in the -string "a*b:c#d", d=1, c=2, b=4, a=8. The punctuation after a letter +string "a*b:c#d", d=1, c=2, b=4, a=8. The punctuation after a letter usually indicate that the option takes an argument.</p> -<p>Since toys.optflags is an unsigned int, it only stores 32 bits. (Which is +<p>Since toys.optflags is an unsigned int, it only stores 32 bits. (Which is the amount a long would have on 32-bit platforms anyway; 64 bit code on 32 bit platforms is too expensive to require in common code used by almost -all commands.) Bit positions beyond the 1<<31 aren't recorded, but +all commands.) Bit positions beyond the 1<<31 aren't recorded, but parsing higher options can still set global variables.</p> <p><b>Automatically setting global variables from arguments (union this)</b></p> @@ -1010,15 +1044,6 @@ argument letter, indicating the option takes an additional argument:</p> </ul> </ul> -<p>A note about "." and CFG_TOYBOX_FLOAT: option parsing only understands <>= -after . when CFG_TOYBOX_FLOAT -is enabled. (Otherwise the code to determine where floating point constants -end drops out; it requires floating point). When disabled, it can reserve a -global data slot for the argument (so offsets won't change in your -GLOBALS[] block), but will never fill it out. You can handle -this by using the USE_BLAH() macros with C string concatenation, ala: -"abc." USE_TOYBOX_FLOAT("<1.23>4.56=7.89") "def"</p> - <p><b>GLOBALS</b></p> <p>Options which have an argument fill in the corresponding slot in the global @@ -1033,7 +1058,7 @@ in the same order they're declared, and that padding won't be inserted between consecutive variables of register size. Thus the first few entries can be longs or pointers corresponding to the saved arguments.</p> -<p>See toys/other/hello.c for a longer example of parsing options into the +<p>See toys/example/*.c for longer examples of parsing options into the GLOBALS block.</p> <p><b>char *toys.optargs[]</b></p> @@ -1087,7 +1112,7 @@ optflag, but letters are never control characters.)</p> <p>Option parsing only understands <>= after . when CFG_TOYBOX_FLOAT is enabled. (Otherwise the code to determine where floating point constants end drops out. When disabled, it can reserve a global data slot for the -argument so offsets won't change, but will never fill it out.). You can handle +argument so offsets won't change, but will never fill it out.) You can handle this by using the USE_BLAH() macros with C string concatenation, ala:</p> <blockquote>"abc." USE_TOYBOX_FLOAT("<1.23>4.56=7.89") "def"</blockquote> @@ -1095,13 +1120,13 @@ this by using the USE_BLAH() macros with C string concatenation, ala:</p> <p><b>--longopts</b></p> <p>The optflags string can contain long options, which are enclosed in -parentheses. They may be appended to an existing option character, in +parentheses. They may be appended to an existing option character, in which case the --longopt is a synonym for that option, ala "a:(--fred)" which understands "-a blah" or "--fred blah" as synonyms.</p> <p>Longopts may also appear before any other options in the optflags string, in which case they have no corresponding short argument, but instead set -their own bit based on position. So for "(walrus)#(blah)xy:z" "command +their own bit based on position. So for "(walrus)#(blah)xy:z", "command --walrus 42" would set toys.optflags = 16 (-z = 1, -y = 2, -x = 4, --blah = 8) and would assign this[1] = 42;</p> @@ -1109,6 +1134,17 @@ and would assign this[1] = 42;</p> each "bare longopt" (ala "(one)(two)abc" before any option characters) always sets its own bit (although you can group them with +X).</p> +<p>Only bare longopts have a FLAG_ macro with the longopt name +(ala --fred would #define FLAG_fred). Other longopts use the short +option's FLAG macro to test the toys.optflags bit.</p> + +<p>Options with a semicolon ";" after their data type can only set their +corresponding GLOBALS() entry via "--longopt=value". For example, option +string "x(boing): y" would set TT.x if it saw "--boing=value", but would +treat "--boing value" as setting FLAG_x in toys.optargs, leaving TT.x NULL, +and keeping "value" in toys.optargs[]. (This lets "ls --color" and +"ls --color=auto" both work.)</p> + <p><b>[groups]</b></p> <p>At the end of the option string, square bracket groups can define diff --git a/www/design.html b/www/design.html index c9944e3..4996f33 100755 --- a/www/design.html +++ b/www/design.html @@ -262,7 +262,8 @@ are always the same size (on both 32 and 64 bit). Pointer and int are _not_ the same size on 64 bit systems, but pointer and long are.</p> <p>This is guaranteed by the LP64 memory model, a Unix standard (which Linux -and MacOS X both implement). See +and MacOS X both implement, and which modern 64 bit processors such as +x86-64 were <a href=http://www.pagetable.com/?p=6>designed for</a>). See <a href=http://www.unix.org/whitepapers/64bit.html>the LP64 standard</a> and <a href=http://www.unix.org/version2/whatsnew/lp64_wp.html>the LP64 rationale</a> for details.</p> diff --git a/www/header.html b/www/header.html index 2d0d564..56396d5 100644 --- a/www/header.html +++ b/www/header.html @@ -22,8 +22,7 @@ </ul> <b>Download</b> <ul> - <li><a href="/hg/toybox">Mercurial Repository</a></li> - <li><a href="https://github.com/gfto/toybox">Git mirror</a></li> + <li><a href="https://github.com/landley/toybox">Source control</a></li> <li><a href="downloads">Releases</a></li> <li><a href="bin">Binaries</a></li> </ul> @@ -31,8 +30,9 @@ <ul> <li><a href="design.html">Design</a></li> <li><a href="code.html">Source walkthrough</a></li> - <li><a href="http://lists.landley.net/listinfo.cgi/toybox-landley.net">Mailing List</a></li> + <li><a href="http://lists.landley.net/listinfo.cgi/toybox-landley.net">Mailing List</a> (<a href=http://news.gmane.org/gmane.linux.toybox>backup archive</a>)</li> <li>IRC #toybox on freenode.net</li> + <li><a href=https://github.com/landley/toybox/commits/master.atom>Commit RSS feed</a></li> <li><a href="/notes.html">Maintainer's Blog</a></li> <li><a href=cleanup.html>Cleanup</a></li> <li><a href=http://www.ohloh.net/p/toybox-landley>Statistics</a></li> diff --git a/www/license.html b/www/license.html index 5fcb94f..15104ed 100755 --- a/www/license.html +++ b/www/license.html @@ -1,7 +1,7 @@ <html><head><title>Toybox License</title> <!--#include file="header.html" --> -<h2>Toybox is released under the following "zero clause" BSD license:</h2>, +<h2>Toybox is released under the following "zero clause" BSD license:</h2> <blockquote> <p>Copyright (C) 2006 by Rob Landley <rob@landley.net> @@ -18,7 +18,33 @@ ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.</p> </blockquote> -<p>You can treat it as a license if you like, but this variant is functionally -equivalent to placing the code in the public domain.</p> +<p>The text of the above license is included in the file LICENSE in the source.</p> +<h2>Why 0BSD?</h2> + +<p>As with <a href=https://creativecommons.org/publicdomain/zero/1.0/>CC0</a>, +<a href=http://unlicense.org>unlicense</a>, and <a href=http://wtfpl.net/>wtfpl</a>, +the intent is to place the licensed material into the public domain, +which after decades of FUD (such as the time OSI's ex-lawyer compared +<a href=http://www.cod5.org/archive/>placing code into the public domain</a> to +<a href=http://www.linuxjournal.com/article/6225>abandoning trash by the +side of a highway</a>) is considered somehow unsafe. But if some random third +party +<a href=https://github.com/mkj/dropbear/blob/master/libtomcrypt/LICENSE>takes +public domain code</a> and slaps <a href=http://www.opensource.apple.com/source/gnuzip/gnuzip-25/gzip/gzip.c>some other license on it</a>, then it's fine.</p> + +<p>To work around this perception, the above license is a standard 2-clause BSD +license <a href=https://github.com/landley/toybox/commit/ee86b1d8e25cb0ca9d418b33eb0dc5e7716ddc1e>minus the half sentence</a> +requiring text copied verbatim into derived works. If 2BSD is +ok, the 0BSD should be ok, despite being equivalent to placing code in the +public domain.</p> + +<p>Modifying the license in this way avoids the hole android toolbox fell into where +<a href=https://github.com/android/platform_system_core/blob/fd4c6b0a3a25921a9fe24691a695d715aecb6afe/toolbox/NOTICE>33 copies of BSD license text</a> +were concatenated together when copyright dates changed, or the strange +solution the busybox developers used to resolve tension between GPLv2's "no +additional restrictions" and BSD's "you must include this large hunk of text" +by sticking the two licenses at +<a href=http://git.busybox.net/busybox/tree/networking/ping.c?id=887a1ad57fe978cd320be358effbe66df8a068bf>opposite ends of the file</a> and hoping nobody +noticed.</a> <!--#include file="footer.html" --> diff --git a/www/news.html b/www/news.html index b820d52..084d753 100755 --- a/www/news.html +++ b/www/news.html @@ -8,6 +8,244 @@ a development environment. See the links on the left for details.</p> <h2>News</h2> +<hr><b>April 5, 2015</b> +<p>Since <a href=https://android.googlesource.com/platform/external/toybox/>android</a> and +<a href=https://git.tizen.org/cgit/platform/upstream/toybox.git>tizen</a> +and <a href=https://github.com/kraj/meta-musl/tree/master/recipes-core/toybox>openembedded</a> +and <a href=https://packages.gentoo.org/package/sys-apps/toybox>gentoo</a> +and so on have all been using Georgi Chorbadzhiyski's git mirror rather +than the mercurial repository, I bit the bullet and switched the project's repo +<a href=https://github.com/landley/toybox>to git</a>. Georgi's +<a href=https://github.com/gfto/toybox>mirror</a> is now pulling from that.</p> + +<hr><b>February 25, 2015</b> +<blockquote><p>"A common mistake that people make when trying to design +something completely foolproof is to underestimate the ingenuity of +complete fools."</p><p>- The Hitchhiker's Guide to the Galaxy.</p></blockquote> + +<p><a href=downloads/toybox-0.5.2.tar.gz>Toybox 0.5.2</a> +(<a href=/hg/toybox/shortlog/1702>commit 1702</a>) is out.</p> + +<p>New promoted commands: sed (finally fixed enough it builds Linux From +Scratch), printf (cleaned up and promoted), shred and +base64 (the Tizen guys wanted them), getenforce, setenforce, and chcon (android), +mix (promoted with fixes from Isaac Dunham), nsenter (from +Andy Lutomirski, merged into unshare).</p> + +<p>Elliott Hughes submited a bunch of patches to support Android (to +both toybox and Bionic libc, which he maintains). On toybox's end this +involved a lot of fixups to portability.[ch] and fixes to over a dozen +commands, plus several new ones. Other portability fixes included working +with buildroot's uclibc fork and building for nommu targets.</p> + +<p>The new "make change" target builds each toybox command as a standalone +binary. Rather a lot of commands that didn't build by themselves (mv depending +on cp and so on) were hit with a large rock until they built standalone. +This involved rewriting bits of option parsing, more elaborate dependency +generation, making each command have its own config +symbol and main() function (even when it's just a wrapper calling another +command's main()), and so on. Also, some commands can't be built standalone +at a conceptual level: "help" describes other enabled commands and "sh" +has a number of bulitin commands (cd, exit, set) that require the +multiplexer infrastructure, so "make change" filters them out.</p> + +<p>The mailing list's web archive is still screwed up. Dreamhost has +been trying to fix it since approximately September. There are +<a href=http://www.mail-archive.com/toybox@lists.landley.net/>two</a> +<a href=http://news.gmane.org/gmane.linux.toybox>other</a> less broken +archives, but neither has quite the same UI as mailman.</p> + +<h3>Bugfixes and tweaks</h3> + +<p>Cynt Rynt sent in tests for ifconfig, +Robert Thompson taught factor to accept whitespace separated arguments, +Hyejin Kim pointed out that some of mktemp's longopts were attached to +the wrong short options, +Luis Felipe Strano Moraes fixed a wrong free() call in bootchartd in pending. +Patches from Ashwini Sharma to make "df /dev/node" work, prevent du from +looping endlessly following symlinks, and to make expr.c +(in pending) understand == and regex matches. (Speaking of expr, it gets +priority groupings wrong but the bug was actually in the posix spec's +HTML conversion. They fixed the posix spec upstream for us. Still need +to fix the expr code, but it's in pending for a reason...)</p> + +<p>Some commands grew new option flags, such as cp --remove-destination +and touch -h.</p> + +<p>The parallel build has better error reporting now. When toybox needs to +re-exec itself to regain suid root permissions and hasn't got the suid bit, +it now gives the right error message ("not root" instead of "no such command"). + +<p>Added a test to "mount" to not mount the same device/directory combination +over itself (the OS catches this for block devices, but not for tmpfs). +Make blkid distinguish ext3 from ext4. Added catv back into cat (because +the Android guys wanted it, and they have historical usage on their side, +so...). Handle nanoseconds in touch.</p> + +<p>Fixed a segfault when CP_MORE was disabled (the resulting option flag list +no longer defined -d but still had it in option groups at the end). +Workaround for glibc redefining dirname() and basename() to random non-posix +semantics because gnu. (They could have created dirname_r() but didn't want +to.)</p> + +<p>Fix an ifconfig test that was preventing assigning an ipv4 address to +interface aliases. Several cleanup passes on hwclock but not quite +promoted out of pending yet.<p> + +<p>Fixed a wrong error message in rm (if you had a chmod 000 directory and +did rm -r on it without -f, after the prompt it would complain it was a +directory, which was not the problem).</p> + +<p>The gzip compression code now does "store only" output to stdout, for +what that's worth.</p> + +<p>Cleanup mountpoint and expand, and remove them from toys/pending/README +(a list of commands that predate the toys/pending directory but needed +another pass).</p> + +<h3>Library and infrastructure:</h3> + +<p>Reworked the option parsing infrastructure so more commands build +standalone (via scripts/single.sh or "make change"). The option flag bit +values are no longer packed, it leaves spaces where currently disabled +flags go, and you can #define FORCE_FLAGS so disabled flags aren't zeroed. +This allows multiple commands to more easily share infrastructure, even if +your current flag context is for a disabled command (switched off in config), +you can force them to stay on and as long as the flags read the same right +to left they'll have the same values.</p> + +<p>We've started removing use of strncpy() because it's a hugely broken +standard C function: the length is the maximum length to _append_, not +the size of the destination buffer. It memsets the remaining space it didn't +copy ala "memset(dest+strlen(dest), 0, len);" so +if you think len is the size of dest you're guaranteed to stomp memory off the +end). And if it runs out of space it won't null terminate because reasons. +(Meanwhile sprintf("%*s", len, str) is counting wide characters in your current +locale, so if you set a locale other than "C" it will also go past your +allocated buffer size. Whoever is maintining the C library standards is really +bad at strings.) +Instead we have xstrncat() which will error_exit() if src+dest+1 doesn't +fit in the buffer. (Because randomly truncating input data isn't necessarily +an improvement.) And there's always xmprintf().</p> + +<p>Similarly, strtol() doesn't return an error indicator on overflow, +you have to clear and then check errno. So new xstrtol() that cares +about overflow.</p> + +<p>The bionic and musl guys agree faccessat(AT_SYMLINK_NOFOLLOW) is not +supported, so stop using it.</p> + +<p>Fixed toy_exec() to detect when argc is in optargs, so we don't +need a separate xexec_optargs().</p> + +<hr><b>February 18, 2015</b> +<p>Dreamhost continues to be unable to make mailing list archives work, so +here's <a href=http://www.mail-archive.com/toybox@lists.landley.net/>another +list archive</a> with a less awkward interface than gmane.</p> + +<p>(Neither gives you the convenient historical monthly views of mailman, +but I still have hopes dreamhost will someday figure out what they're doing +wrong. They've only been trying since October. Last month they did a +<a href=http://www.dreamhoststatus.com/2015/01/14/discussion-list-hardware-maintenance/>hardware upgrade to fix a software problem</a>, and the stale +data loads much faster now, so that's something.)</p> + +<p>Update (Feb 19): the archive started updating again, by discarding +all the pending data. So there are now _two_ giant holes in Dreamhost's +web archive, from Dec 15-Jan 3, and then another hole from Jan 16-Feb 18. +The relevant messages are in both of the other archives. Here's hoping +the chronic archive constipation problem won't happen a sixth time.</p> + +<hr><b>December 30, 2014</b> +<p>Due to Dreamhost's <a href=http://landley.net/dreamhost.txt>ongoing</a> +<a href=http://landley.net/dreamhost2.txt>inability</a> to make mailman +work reliably, I've added a link to a backup web archive at +<a href=http://news.gmane.org/gmane.linux.toybox>gmane</a> to the nav bar +on the left.</p> + +<p>You still subscribe to the list through +<a href=http://lists.landley.net/listinfo.cgi/toybox-landley.net>the first link</a>.</p> + +<p>Update (January 27, 2015): they're <a href=https://twitter.com/landley/status/558428839462703104>still working on it</a>.</p> + +<hr><b>November 19, 2014</b> + +<blockquote><p>"This time it was right, it would work, and no one would have to get nailed to anything." - The Hitchhiker's Guide to the Galaxy.</p></blockquote> + +<p><a href=downloads/toybox-0.5.1.tar.bz2>Toybox 0.5.1</a> +(<a href=/hg/toybox/shortlog/1566>commit 1566</a>) is out.</p> + +<p>It's an interim release, mostly bugfixes. There are several new commands, +but they're all in pending.</p> + +<h3>Development</h3> + +<p>Finally implemented sed, which is still in pending because although +it's feature complete according to posix, and even passes the parts of +Busybox's sed test suite that aren't explicitly testing for gnu bugs we +don't want to copy, it's not yet good enough to build Linux From Scratch. +(The ./configure stages use very long sed scripts. 20 commits worth of +implementation and debugging, just under 1000 lines of code, and there's +still more to do. We're definitely up to some of the "fiddly" commands now. +Did you know "echo hello | sed p - -" segfaults gnu sed in Ubuntu 12.04? +Yeah...)</p> + +<p>Talked with the Tizen developers to follow up on their desire to +make toybox a part of the base Tizen system, and got a list of commands +to add to the roadmap. The tizen todo list is:</p> + +<blockquote><p> +wget, sha256*, gzip, gunzip, bunzip2, rsync, zdiff*, +less, ar, arch, base64, csplit, dir, fmt, join, +nproc, shred, shuf, stdbuf, stty, test, tr, unexpand, +users, vdir, diff3, sdiff, dosfsck (fsck.vfat), awk, fdisk +</p></blockquote> + +<p>(Most of which was already on the todo list, but it helps prioritize.)</p> + +<p>Fixed md5sum and sha1sum on big endian systems (reported by James McMechan). +Andy Lutomirski fixed unshare's help text and option parsing, +and submitted nsenter (a tool to use setns(2)) to pending. +Isaac Dunham implemented acpi -ctV options, and spotted the bug that ls -d +was inappropraitely following command line symlinks without -H or -L (it +should act like ls -l does), and ls -F handles symlinks wrong too. +Lukasz Szpakowski sent in two bugfixes to tail.c. Cynt Rynt spotted an +unnecessary assignment in lib/password.c.</p> + +<p>Ashwini Sharma's team was as busy as usual, submitting tr, crontab, and +ipcrm, and hwclock to pending, more features to the pending ip.c, and a +pile of bugfixes (to chgrp, killall, ifconfig, insmod, +losetup, comm, cp, id, xwrap, netcat, modprobe, nohup...) mostly found by +static analysis. (These fixes are mostly to seldom-used codepaths like the +TOYBOX_FREE config option, but test coverage is always appreciated.) Ashwini +also suggested upgrading ln -f to leave the original target alone if link +creation fails, and reported that mv -f and -i weren't implemented (now fixed).</p> + +<p>New config option: TOYBOX_NORECURSE prevents xexec() from making internal +function calls (for nommu systems with a finite stack).</p> + +<p>The "toybox" multiplexer command no longer adds a trailing space to each +line of command names, so things like "./toybox | tr ' \n' '|'" to create +a grep pattern snippet are easier to do. (Why you'd want to is your business, +but the output is tidier now.)</p> + +<h3>Infrastructure</h3> + +<p>Isaac Dunham added Android support to portability.h, including compile +probes for functions missing from bionic-libc, and annotated the commands that +use those functions. We haven't really tested building against bionic, +but in theory it's possible now.</p> + +<p>Running the test suite now color codes the PASS/SKIP/FAIL notifications +if output is to a tty. (And in case you missed it last time, VERBOSE=fail +to stop at the first failure is really useful.)</p> + +<p>In loopfiles_rw() use O_CLOEXEC instead of O_RDONLY to request the loop +function close filehandles for us. (Otherwise the callback function must +close each supplied filehandle itself.)</p> + +<p>The printf-style escape parsing ("\n" and friends) got factored out into +a new unescape() function.</p> + <hr><b>October 2, 2014</b> <blockquote><p>"There is an art, it says, or rather, a knack to flying. The knack lies in learning how to throw yourself at the ground and miss... diff --git a/www/roadmap.html b/www/roadmap.html index 2415711..bba08b1 100755 --- a/www/roadmap.html +++ b/www/roadmap.html @@ -33,6 +33,7 @@ and progress towards implementing it.</p> <li><a href=#sigh>Linux "Standard" Base</a></li> <li><a href=#dev_env>Development Environment</a></li> <li><a href=#android>Android Toolbox</a></li> +<li><a href=#tizen>Tizen Core</a></li> <li>Miscelaneous: <a href=#klibc>klibc</a>, <a href=#glibc>glibc</a>, <a href=#sash>sash</a>, <a href=#sbase>sbase</a>, <a href=#s6>s6</a>...</li> </ul> @@ -203,7 +204,7 @@ when called under the name "bash".</p> <p>The <a href=http://landley.net/aboriginal>Aboriginal Linux</a> self-bootstrapping build still uses the following busybox commands, -not yet supplied by toybox:</p. +not yet supplied by toybox:</p> <blockquote><p> ash awk bunzip2 bzip2 dd diff expr fdisk ftpd ftpget ftpput gunzip @@ -220,73 +221,48 @@ commands are vi, awk, and ash.</p> <p>Android has a policy against GPL in userspace, so even though BusyBox predates Android by many years, they couldn't use it. Instead they grabbed an old version of ash and implemented their own command line utility set -called "toolbox".</p> +called "toolbox". ash was later replaced by +<a href="https://www.mirbsd.org/mksh.htm">mksh</a>; toolbox is being +replaced by toybox.</p> <p>Toolbox doesn't have its own repository, instead it's part of Android's <a href=https://android.googlesource.com/platform/system/core>system/core -git repository</a> (this analysis looked at commit 51ccef27cab58).</p> +git repository</a>.</p> <h3>Toolbox commands:</h3> -<p>According to core/toolbox/Android.mk the toolbox directory builds the +<p>According to <a href=https://android.googlesource.com/platform/system/core/+/master/toolbox/Android.mk> +system/core/toolbox/Android.mk</a> the toolbox directory builds the following commands:</p> <blockquote><b> -ls mount cat ps kill ln insmod rmmod lsmod ifconfig setconsole -rm mkdir rmdir reboot getevent sendevent date wipe sync umount -start stop notify cmp dmesg route hd dd df getprop setprop watchprops -log sleep renice printenv smd chmod chown newfs_msdos netstat ioctl -mv schedtop top iftop id uptime vmstat nandread ionice touch lsof md5 r -cp du grep watchdogd +dd du df getevent iftop ioctl ionice log ls +lsof mount nandread newfs_msdos ps prlimit renice +sendevent start stop top uptime watchprops </b></blockquote> -<p>If selinux is enabled, you also get:</p> -<blockquote><b> -getenforce setenforce chcon restorecon runcon getsebool setsebool load_policy -</b></blockquote> - -<p>The Android.mk file also refers to dynarray.c and toolbox.c as library -code. This leaves the following apparently unused C files in toolbox/*.c, each -of which has a command_main() function and seems to implement a standalone -command:</p> - -<blockquote><b> -alarm exists lsusb readtty rotatefb setkey syren -</b></blockquote> - -<h3>Command shell (ash)</h3> - -<p>The core/sh subdirectory contains a fork of ash 1.17, and sucks in -liblinenoise to provide command line history/editing.</p> - <h3>Other Android core commands</h3> -<p>Other than the toolbox and sh directories, the currently interesting -subdirectories in the core repository are fs_mgr, gpttool, init, -logcat, logwrapper, mkbootimg, netcfg, run-as, and sdcard.</p> +<p>Other than the toolbox directory, the currently interesting +subdirectories in the core repository are gpttool, init, +logcat, logwrapper, mkbootimg, reboot, and run-as.</p> <ul> -<li><b>fs_mgr</b> - subset of mount</li> <li><b>gpttool</b> - subset of fdisk</li> <li><b>init</b> - Android's PID 1</li> <li><b>logcat</b> - read android log format</li> <li><b>logwrapper</b> - redirect stdio to android log</li> <li><b>mkbootimg</b> - create signed boot image</li> -<li><b>netcfg</b> - network configuration (sucks in libnetutils)</li> +<li><b>reboot</b> - Android's reboot(1)</li> <li><b>run-as</b> - subset of sudo</li> -<li><b>sdcard</b> - FUSE wrapper to squash UID/GID/permissions to what FAT supports.</li> </ul> <p>Almost all of these reinvent an existing wheel with less functionality and a different user interface. We may want to provide that interface, but -implementing the full commands (mount, fdisk, init, ifconfig with dhcp, -and sudo) come first.</p> +implementing the full commands (fdisk, init, and sudo) come first.</p> -<p>Although logcat/logwrapper also reinvent a wheel, Android did so in the -kernel and these provide an interface to that.</p> - -<p>Also, gpttool and mkbootimg are install tools, and sdcard looks like a -testing tool. These aren't a priority if android wants to use its own +<p>Also, gpttool and mkbootimg are install tools. +These aren't a priority if android wants to use its own bespoke code to install itself.</p> <h3>Analysis</h3> @@ -294,51 +270,57 @@ bespoke code to install itself.</p> <p>For reference, combining everything listed above, we get:</p> <blockquote><b> -alarm ash cat chcon chmod chown cmp cp date dd df dmesg du exists fs_mgr -getenforce -getevent getprop getsebool gpttool grep hd id ifconfig iftop init insmod ioctl -ionice kill ln load_policy log logcat logwrapper ls lsmod lsof lsusb md5 -mkbootimg mkdir mount mv nandread netcfg netstat newfs_msdos notify printenv -ps r readtty reboot renice restorecon rm rmdir rmmod rotatefb route run-as -runcon schedtop sdcard sendevent setconsole setenforce setkey setprop setsebool -sleep smd start stop sync syren top touch umount uptime vmstat watchdogd -watchprops wipe +dd du df getevent gpttool iftop init ioctl ionice +log logcat logwrapper ls lsof mkbootimg mount nandread +newfs_msdos ps prlimit reboot renice run-as +sendevent start stop top uptime watchprops </b></blockquote> <p>We may eventually implement all of that, but for toybox 1.0 we need to -focus a bit. For our first pass, let's ignore selinux, strip out the "unlisted" -commands except lsusb, and grab just logcat and logwrapper from the "core" +focus a bit. For our first pass, let's ignore selinux [note: the android +guys submitted selinux code to us and we merged it], +and grab just logcat and logwrapper from the "core" commands (since the rest have some full/standard version providing that functionality, which we can implement a shim interface for later).</p> -<p>This means toybox should implement:</p> +<p>This means toybox should implement (or finish implementing):</p> <blockquote><b> <span id=toolbox> -cat chmod chown cmp cp date dd df dmesg du getevent getprop grep hd id ifconfig -iftop insmod ioctl ionice kill ln log logcat logwrapper ls lsmod lsof lsusb md5 -mkdir mount mv nandread -netstat newfs_msdos notify printenv ps r reboot renice rm rmdir rmmod route -schedtop sendevent setconsole setprop sleep smd start stop sync top touch -umount uptime vmstat watchprops watchdogd wipe +dd du df getevent iftop ioctl ionice log logcat logwrapper ls lsof +mount nandread newfs_msdos ps prlimit renice schedtop sendevent +smd start stop top uptime watchprops </span> </b></blockquote> -<p>The following Toolbox commands are already covered in previous -sections of this analysis:</p> +<hr /> +<h2><a name=tizen /><a href="#tizen">Use case: Tizen Core</a></h2> + +<p>The Tizen project has expressed a desire to eliminate GPLv3 software +from its core system, and is installing toybox as +<a href=https://wiki.tizen.org/wiki/Toybox>part of this process</a>.</p> + +<p>They have a fairly long list of new commands they'd like to see in toybox:</p> <blockquote><b> -cat chmod chown cmp cp date dd df dmesg du grep id ifconfig insmod kill ln ls -lsmod mkdir mount mv ps renice rm rmdir rmmod route sleep sync top touch umount +<span id=tizen> +arch base64 users dir vdir unexpand shred join csplit +hostid nproc runcon sha224 sha256 sha384 sha512 sha3 mkfs.vfat fsck.vfat +dosfslabel uname stdbuf pinky diff3 sdiff zcmp zdiff zegrep zfgrep zless zmore +</span> </b></blockquote> -<p>Which leaves the following commands as new from Toolbox:</p> +<p>In addition, they'd like to use several commands currently in pending:</p> <blockquote><b> -getevent getprop hd iftop ioctl ionice log lsof nandread netstat -newfs_msdos notify printenv r reboot schedtop sendevent setconsole -setprop smd start stop top uptime vmstat watchprops watchdogd wipe +<span id=tizen> +tar diff printf wget rsync fdisk vi less tr test stty fold expr dd +</span> </b></blockquote> +<p>Also, tizen uses a different Linux Security Module called SMACK, so +many of the SELinux options ala ls -Z need smack alternatives in an +if/else setup.</p> + <hr /><a name=klibc /> <h2>klibc:</h2> @@ -402,7 +384,7 @@ from it into a /proc file, something the kernel is capable of doing itself. <a href=http://www.zytor.com/pipermail/klibc/2006-June/001748.html>attempted to remove</a> that capability from the kernel, current kernel/power/hibernate.c still parses "resume=" on the command line). And yet various distros seem to -make use of klibc for this> +make use of klibc for this. Given the history of swsusp/hibernate (and <a href=http://lwn.net/Articles/333007>TuxOnIce</a> and <a href=http://lwn.net/Articles/242107>kexec jump</a>) I've lost track @@ -746,7 +728,7 @@ arp makedevs sysctl killall5 crond crontab deluser last mkpasswd watch ipaddr iplink iproute blockdev rpm2cpio arping brctl dumpleases fsck tcpsvd tftpd factor fallocate fsfreeze inotifyd lspci nbd-client partprobe strings - +base64 mix </span> </b></blockquote> diff --git a/www/status.html b/www/status.html index d32e130..8d50080 100755 --- a/www/status.html +++ b/www/status.html @@ -2,56 +2,8 @@ <!--#include file="header.html" --> <title>Toybox Status</title> -<h1>How are we doing on implementing stuff so far?</h1> - -<p>Legend: [posix] <lsb> (development) {android} =klibc= #sash# @sbase@ -*beastiebox* +request+ other <strike>implemented</strike></p> - <!--#include file="status.gen" --> -<h1>The current status of toybox (as of 0.5.0 release):</h1> - -<h3><u>These commands are reasonably finished (in defconfig)</u>:</h3> -<blockquote><b> -<span id=ready> -acpi basename blkid blockdev bzcat cal cat catv chattr chgrp chmod -chown chvt cksum clear cmp comm count cp cpio date df dirname -dmesg dos2unix echo egrep eject env factor fallocate false -fgrep find free freeramdisk fsfreeze fstype grep groups halt head -help hostname id ifconfig inotifyd insmod install kill killall killall5 -link ln logname losetup ls lsattr lsmod lspci lsusb makedevs -md5sum mkdir mkfifo mknod mkpasswd mkswap mktemp mount -mv nbd-client nc netcat nice nl nohup od oneit partprobe passwd paste -patch pidof pivot_root pmap poweroff printenv pwd pwdx readahead readlink -realpath reboot renice rev rfkill rm rmdir rmmod seq setsid sha1sum -sleep sort split stat strings su swapoff swapon switch_root sync sysctl -tac tail taskset tee time timeout true truncate tty umount uname -uniq unix2dos unlink unshare uptime usleep uudecode uuencode -w wc which who whoami yes -</span> -</b></blockquote> - -<h3><u>These commands are at least partially implemented (in toys/pending) -but have todo items remaining:</u></h3> -<blockquote><b> -<span id=pending> -ar arp arping bc bootchartd brctl compress crond dhcp dhcpd diff dumpleases -expr fdisk fold fsck ftpget getty groupadd groupdel host iconv init ip ipcs -kexec klogd last logger mdev mix mke2fs modprobe more netstat openvt p9d pgrep -ping printf ps reset route sh sulogin syslogd tar tcpsvd telnet telnetd test -tftpd top traceroute tr useradd userdel watch xzcat -</span> -</b></blockquote> - -<h3><u>These commands predate the pending directory but also have unfinished -todo items:</u></h3> - -<blockquote><b> -<span id=pending> -vmstat login du vconfig mountpoint chroot cut touch modinfo expand xargs -</span> -</b></blockquote> - <p>There is also <a href="todo.txt">a todo list</a>, but development's moved on a bit since it was written.</p> |