diff options
Diffstat (limited to 'toys')
128 files changed, 4680 insertions, 2170 deletions
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)) |