diff options
Diffstat (limited to 'toys/posix')
43 files changed, 4611 insertions, 0 deletions
diff --git a/toys/posix/basename.c b/toys/posix/basename.c new file mode 100644 index 00000000..9f228b41 --- /dev/null +++ b/toys/posix/basename.c @@ -0,0 +1,45 @@ +/* vi: set sw=4 ts=4: + * + * basename.c - Return non-directory portion of a pathname + * + * Copyright 2012 Tryn Mirell <tryn@mirell.org> + * + * See http://opengroup.org/onlinepubs/9699919799/utilities/basename.html + + +USE_BASENAME(NEWTOY(basename, "<1>2", TOYFLAG_USR|TOYFLAG_BIN)) + +config BASENAME + bool "basename" + default y + help + usage: basename string [suffix] + + Return non-directory portion of a pathname removing suffix +*/ + +#include "toys.h" + +void basename_main(void) +{ + char *arg = toys.optargs[0], *suffix = toys.optargs[1], *base; + + while ((base = strrchr(arg, '/'))) { + if (base == arg) break; + if (!base[1]) *base = 0; + else { + base++; + break; + } + } + + if (!base) base = arg; + + // chop off the suffix if provided + if (suffix) { + arg = base + strlen(base) - strlen(suffix); + if (arg > base && !strcmp(arg, suffix)) *arg = 0; + } + + puts(base); +} diff --git a/toys/posix/cal.c b/toys/posix/cal.c new file mode 100644 index 00000000..1c018e2f --- /dev/null +++ b/toys/posix/cal.c @@ -0,0 +1,134 @@ +/* vi: set sw=4 ts=4: + * + * cal.c - show calendar. + * + * Copyright 2011 Rob Landley <rob@landley.net> + * + * See http://opengroup.org/onlinepubs/9699919799/utilities/cal.html + +USE_CAL(NEWTOY(cal, ">2", TOYFLAG_USR|TOYFLAG_BIN)) + +config CAL + bool "cal" + default y + help + usage: cal [[month] year] + Print a calendar. + + With one argument, prints all months of the specified year. + With two arguments, prints calendar for month and year. +*/ + +#include "toys.h" + +// Write calendar into buffer: each line is 20 chars wide, end indicated +// by empty string. + +static char *calstrings(char *buf, struct tm *tm) +{ + char temp[21]; + int wday, mday, start, len, line; + + // header + len = strftime(temp, 21, "%B %Y", tm); + len += (20-len)/2; + buf += sprintf(buf, "%*s%*s ", len, temp, 20-len, ""); + buf++; + buf += sprintf(buf, "Su Mo Tu We Th Fr Sa "); + buf++; + + // What day of the week does this month start on? + if (tm->tm_mday>1) + start = (36+tm->tm_wday-tm->tm_mday)%7; + else start = tm->tm_wday; + + // What day does this month end on? Alas, libc doesn't tell us... + len = 31; + if (tm->tm_mon == 1) { + int year = tm->tm_year; + len = 28; + if (!(year & 3) && !((year&100) && !(year&400))) len++; + } else if ((tm->tm_mon+(tm->tm_mon>6 ? 1 : 0)) & 1) len = 30; + + for (mday=line=0;line<6;line++) { + for (wday=0; wday<7; wday++) { + char *pat = " "; + if (!mday ? wday==start : mday<len) { + pat = "%2d "; + mday++; + } + buf += sprintf(buf, pat, mday); + } + buf++; + } + + return buf; +} + +void xcheckrange(long val, long low, long high) +{ + char *err = "%ld %s than %ld"; + + if (val < low) error_exit(err, val, "less", low); + if (val > high) error_exit(err, val, "greater", high); +} + +// Worst case scenario toybuf usage: sizeof(struct tm) plus 21 bytes/line +// plus 8 lines/month plus 12 months, comes to a bit over 2k of our 4k buffer. + +void cal_main(void) +{ + struct tm *tm; + char *buf = toybuf; + + if (toys.optc) { + // Conveniently starts zeroed + tm = (struct tm *)toybuf; + buf += sizeof(struct tm); + + // Last argument is year, one before that (if any) is month. + xcheckrange(tm->tm_year = atol(toys.optargs[--toys.optc]),1,9999); + tm->tm_year -= 1900; + tm->tm_mday = 1; + tm->tm_hour = 12; // noon to avoid timezone weirdness + if (toys.optc) { + xcheckrange(tm->tm_mon = atol(toys.optargs[--toys.optc]),1,12); + tm->tm_mon--; + + // Print 12 months of the year + + } else { + char *bufs[12]; + int i, j, k; + + for (i=0; i<12; i++) { + tm->tm_mon=i; + mktime(tm); + buf = calstrings(bufs[i]=buf, tm); + } + + // 4 rows, 6 lines each, 3 columns + for (i=0; i<4; i++) { + for (j=0; j<8; j++) { + for(k=0; k<3; k++) { + char **b = bufs+(k+i*3); + *b += printf("%s ", *b); + } + puts(""); + } + } + return; + } + + // What day of the week does that start on? + mktime(tm); + + } else { + time_t now; + time(&now); + tm = localtime(&now); + } + + calstrings(buf, tm); + while (*buf) buf += printf("%s\n", buf); +} diff --git a/toys/posix/cat.c b/toys/posix/cat.c new file mode 100644 index 00000000..81bfcafd --- /dev/null +++ b/toys/posix/cat.c @@ -0,0 +1,42 @@ +/* vi: set sw=4 ts=4: + * + * cat.c - copy inputs to stdout. + * + * Copyright 2006 Rob Landley <rob@landley.net> + * + * See http://www.opengroup.org/onlinepubs/009695399/utilities/cat.html + +USE_CAT(NEWTOY(cat, "u", TOYFLAG_BIN)) + +config CAT + bool "cat" + default y + help + usage: cat [-u] [file...] + Copy (concatenate) files to stdout. If no files listed, copy from stdin. + Filename "-" is a synonym for stdin. + + -u Copy one byte at a time (slow). +*/ + +#include "toys.h" + +static void do_cat(int fd, char *name) +{ + int len, size=toys.optflags ? 1 : sizeof(toybuf); + + for (;;) { + len = read(fd, toybuf, size); + if (len<0) { + perror_msg("%s",name); + toys.exitval = EXIT_FAILURE; + } + if (len<1) break; + xwrite(1, toybuf, len); + } +} + +void cat_main(void) +{ + loopfiles(toys.optargs, do_cat); +} diff --git a/toys/posix/chgrp.c b/toys/posix/chgrp.c new file mode 100644 index 00000000..ad4e351a --- /dev/null +++ b/toys/posix/chgrp.c @@ -0,0 +1,124 @@ +/* vi: set sw=4 ts=4: + * + * chown.c - Change ownership + * + * Copyright 2012 Georgi Chorbadzhiyski <georgi@unixsol.org> + * + * See http://pubs.opengroup.org/onlinepubs/009695399/utilities/chown.html + * See http://pubs.opengroup.org/onlinepubs/009695399/utilities/chgrp.html + * + * TODO: group only one of [HLP] + +USE_CHGRP(NEWTOY(chgrp, "<2hPLHRfv", TOYFLAG_BIN)) +USE_CHGRP(OLDTOY(chown, chgrp, "<2hPLHRfv", TOYFLAG_BIN)) + +config CHGRP + bool "chgrp/chown" + default y + help + usage: chown [-RHLP] [-fvh] [owner][:group] file... + usage: chgrp [-RHLP] [-fvh] group file... + + Change ownership of one or more files. + + -f suppress most error messages. + -h change symlinks instead of what they point to + -R recurse into subdirectories (implies -h). + -H with -R change target of symlink, follow command line symlinks + -L with -R change target of symlink, follow all symlinks + -P with -R change symlink, do not follow symlinks (default) + -v verbose output. +*/ + +#include "toys.h" + +#define FLAG_v 1 +#define FLAG_f 2 +#define FLAG_R 4 +#define FLAG_H 8 +#define FLAG_L 16 +#define FLAG_P 32 +#define FLAG_h 64 + +DEFINE_GLOBALS( + uid_t owner; + gid_t group; + char *owner_name, *group_name; + int symfollow; +) + +#define TT this.chgrp + +static int do_chgrp(struct dirtree *node) +{ + int fd, ret, flags = toys.optflags; + + // Depth first search + if (!dirtree_notdotdot(node)) return 0; + if ((flags & FLAG_R) && node->data != -1 && S_ISDIR(node->st.st_mode)) + return DIRTREE_COMEAGAIN|((flags&FLAG_L) ? DIRTREE_SYMFOLLOW : 0); + + 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); + + if (ret || (flags & FLAG_v)) { + char *path = dirtree_path(node, 0); + if (flags & FLAG_v) + xprintf("%s %s%s%s %s\n", toys.which->name, + TT.owner_name ? TT.owner_name : "", + toys.which->name[2]=='o' && TT.group_name ? ":" : "", + TT.group_name ? TT.group_name : "", path); + if (ret == -1 && !(toys.optflags & FLAG_f)) + perror_msg("changing owner:group of '%s' to '%s:%s'", path, + TT.owner_name, TT.group_name); + free(path); + } + toys.exitval |= ret; + + return 0; +} + +void chgrp_main(void) +{ + int ischown = toys.which->name[2] == 'o'; + char **s, *own; + + // 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; + } + } 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; + } + + for (s=toys.optargs+1; *s; s++) { + struct dirtree *new = dirtree_add_node(AT_FDCWD, *s, + toys.optflags&(FLAG_H|FLAG_L)); + if (new) handle_callback(new, do_chgrp); + else toys.exitval = 1; + } + + if (CFG_TOYBOX_FREE) free(own); +} diff --git a/toys/posix/chmod.c b/toys/posix/chmod.c new file mode 100644 index 00000000..e41598c1 --- /dev/null +++ b/toys/posix/chmod.c @@ -0,0 +1,72 @@ +/* vi: set sw=4 ts=4: + * + * chmod.c - Change file mode bits + * + * Copyright 2012 Rob Landley <rob@landley.net> + * + * See http://pubs.opengroup.org/onlinepubs/009695399/utilities/chmod.html + * + +USE_CHMOD(NEWTOY(chmod, "<2?vR", TOYFLAG_BIN)) + +config CHMOD + bool "chmod" + default y + help + usage: chmod [-R] MODE FILE... + + Change mode of listed file[s] (recursively with -R). + + MODE can be (comma-separated) stanzas: [ugoa][+-=][rwxstXugo] + + Stanzas are applied in order: For each category (u = user, + g = group, o = other, a = all three, if none specified default is a), + set (+), clear (-), or copy (=), r = read, w = write, x = execute. + s = u+s = suid, g+s = sgid, o+s = sticky. (+t is an alias for o+s). + suid/sgid: execute as the user/group who owns the file. + sticky: can't delete files you don't own out of this directory + X = x for directories or if any category already has x set. + + Or MODE can be an octal value up to 7777 ug uuugggooo top + + bit 1 = o+x, bit 1<<8 = u+w, 1<<11 = g+1 sstrwxrwxrwx bottom + + Examples: + chmod u+w file - allow owner of "file" to write to it. + chmod 744 file - user can read/write/execute, everyone else read only +*/ + +#include "toys.h" + +DEFINE_GLOBALS( + char *mode; +) + +#define TT this.chmod + +#define FLAG_R 1 +#define FLAG_v 2 + +int do_chmod(struct dirtree *try) +{ + mode_t mode; + + if (!dirtree_notdotdot(try)) return 0; + + mode = string_to_mode(TT.mode, try->st.st_mode); + if (toys.optflags & FLAG_v) { + char *s = dirtree_path(try, 0); + printf("chmod '%s' to %04o\n", s, mode); + free(s); + } + wfchmodat(dirtree_parentfd(try), try->name, mode); + + return (toys.optflags & FLAG_R) ? DIRTREE_RECURSE : 0; +} + +void chmod_main(void) +{ + TT.mode = *toys.optargs; + char **file; + + for (file = toys.optargs+1; *file; file++) dirtree_read(*file, do_chmod); +} diff --git a/toys/posix/cksum.c b/toys/posix/cksum.c new file mode 100644 index 00000000..213bc74a --- /dev/null +++ b/toys/posix/cksum.c @@ -0,0 +1,87 @@ +/* vi: set sw=4 ts=4: + * + * cksum.c - produce crc32 checksum value for each input + * + * Copyright 2008 Rob Landley <rob@landley.net> + * + * See http://www.opengroup.org/onlinepubs/009695399/utilities/cksum.html + +USE_CKSUM(NEWTOY(cksum, "IPLN", TOYFLAG_BIN)) + +config CKSUM + bool "cksum" + default y + help + usage: cksum [-IPLN] [file...] + + For each file, output crc32 checksum value, length and name of file. + If no files listed, copy from stdin. Filename "-" is a synonym for stdin. + + -L Little endian (defaults to big endian) + -P Pre-inversion + -I Skip post-inversion + -N Do not include length in CRC calculation +*/ + +#include "toys.h" + +DEFINE_GLOBALS( + unsigned crc_table[256]; +) + +#define TT this.cksum + +static unsigned cksum_be(unsigned crc, unsigned char c) +{ + return (crc<<8)^TT.crc_table[(crc>>24)^c]; +} + +static unsigned cksum_le(unsigned crc, unsigned char c) +{ + return TT.crc_table[(crc^c)&0xff] ^ (crc>>8); +} + +static void do_cksum(int fd, char *name) +{ + unsigned crc = (toys.optflags&4) ? 0xffffffff : 0; + uint64_t llen = 0, llen2; + unsigned (*cksum)(unsigned crc, unsigned char c); + + + cksum = (toys.optflags&2) ? cksum_le : cksum_be; + // CRC the data + + for (;;) { + int len, i; + + len = read(fd, toybuf, sizeof(toybuf)); + if (len<0) { + perror_msg("%s",name); + toys.exitval = EXIT_FAILURE; + } + if (len<1) break; + + llen += len; + for (i=0; i<len; i++) crc=cksum(crc, toybuf[i]); + } + + // CRC the length + + llen2 = llen; + if (!(toys.optflags&1)) { + while (llen) { + crc = cksum(crc, llen); + llen >>= 8; + } + } + + printf("%u %"PRIu64, (toys.optflags&8) ? crc : ~crc, llen2); + if (strcmp("-", name)) printf(" %s", name); + xputc('\n'); +} + +void cksum_main(void) +{ + crc_init(TT.crc_table, toys.optflags&2); + loopfiles(toys.optargs, do_cksum); +} diff --git a/toys/posix/cmp.c b/toys/posix/cmp.c new file mode 100644 index 00000000..0afca381 --- /dev/null +++ b/toys/posix/cmp.c @@ -0,0 +1,92 @@ +/* vi: set sw=4 ts=4: + * + * cmp.c - Compare two files. + * + * Copyright 2012 Timothy Elliott <tle@holymonkey.com> + * + * See http://pubs.opengroup.org/onlinepubs/9699919799/utilities/cmp.html + +USE_CMP(NEWTOY(cmp, "<2>2ls", TOYFLAG_USR|TOYFLAG_BIN)) + +config CMP + bool "cmp" + default y + help + usage: cmp [-l] [-s] FILE1 FILE2 + + Compare the contents of two files. + + -l show all differing bytes + -s silent +*/ + +#include "toys.h" + +#define FLAG_s 1 +#define FLAG_l 2 + +DEFINE_GLOBALS( + int fd; + char *name; +) + +#define TT this.cmp + +// This handles opening the file and + +void do_cmp(int fd, char *name) +{ + int i, len1, len2, min_len, size = sizeof(toybuf)/2; + long byte_no = 1, line_no = 1; + char *buf2 = toybuf+size; + + // First time through, cache the data and return. + if (!TT.fd) { + TT.name = name; + // On return the old filehandle is closed, and this assures that even + // if we were called with stdin closed, the new filehandle != 0. + TT.fd = dup(fd); + return; + } + + for (;;) { + len1 = readall(TT.fd, toybuf, size); + len2 = readall(fd, buf2, size); + + min_len = len1 < len2 ? len1 : len2; + for (i=0; i<min_len; i++) { + if (toybuf[i] != buf2[i]) { + toys.exitval = 1; + if (toys.optflags & FLAG_l) + printf("%ld %o %o\n", byte_no, toybuf[i], buf2[i]); + else { + if (!(toys.optflags & FLAG_s)) { + printf("%s %s differ: char %ld, line %ld\n", + TT.name, name, byte_no, line_no); + toys.exitval++; + } + goto out; + } + } + byte_no++; + if (toybuf[i] == '\n') line_no++; + } + if (len1 != len2) { + if (!(toys.optflags & FLAG_s)) { + fprintf(stderr, "cmp: EOF on %s\n", + len1 < len2 ? TT.name : name); + } + toys.exitval = 1; + break; + } + if (len1 < 1) break; + } +out: + if (CFG_TOYBOX_FREE) close(TT.fd); +} + +void cmp_main(void) +{ + loopfiles_rw(toys.optargs, O_RDONLY, 0, toys.optflags&FLAG_s, do_cmp); +} + diff --git a/toys/posix/comm.c b/toys/posix/comm.c new file mode 100644 index 00000000..1c2267ac --- /dev/null +++ b/toys/posix/comm.c @@ -0,0 +1,85 @@ +/* vi: set sw=4 ts=4: + * + * comm.c - select or reject lines common to two files + * + * Copyright 2012 Ilya Kuzmich <ikv@safe-mail.net> + * + * See http://pubs.opengroup.org/onlinepubs/009695399/utilities/comm.html + +// <# and ># take single digit, so 321 define flags +USE_COMM(NEWTOY(comm, "<2>2321", TOYFLAG_USR|TOYFLAG_BIN)) + +config COMM + bool "comm" + default y + help + usage: comm [-123] FILE1 FILE2 + + Reads FILE1 and FILE2, which should be ordered, and produces three text + columns as output: lines only in FILE1; lines only in FILE2; and lines + in both files. Filename "-" is a synonym for stdin. + + -1 suppress the output column of lines unique to FILE1 + -2 suppress the output column of lines unique to FILE2 + -3 suppress the output column of lines duplicated in FILE1 and FILE2 +*/ + +#include "toys.h" + +#define FLAG_1 1 +#define FLAG_2 2 +#define FLAG_3 4 + +static void writeline(const char *line, int col) +{ + if (col == 0 && toys.optflags & FLAG_1) return; + else if (col == 1) { + if (toys.optflags & FLAG_2) return; + if (!(toys.optflags & FLAG_1)) putchar('\t'); + } else if (col == 2) { + if (toys.optflags & FLAG_3) return; + if (!(toys.optflags & FLAG_1)) putchar('\t'); + if (!(toys.optflags & FLAG_2)) putchar('\t'); + } + puts(line); +} + +void comm_main(void) +{ + int file[2]; + char *line[2]; + int i; + + if (toys.optflags == 7) return; + + for (i = 0; i < 2; i++) { + file[i] = strcmp("-", toys.optargs[i]) ? xopen(toys.optargs[i], O_RDONLY) : 0; + line[i] = get_line(file[i]); + } + + while (line[0] && line[1]) { + int order = strcmp(line[0], line[1]); + + if (order == 0) { + writeline(line[0], 2); + for (i = 0; i < 2; i++) { + free(line[i]); + line[i] = get_line(file[i]); + } + } else { + i = order < 0 ? 0 : 1; + writeline(line[i], i); + free(line[i]); + line[i] = get_line(file[i]); + } + } + + /* print rest of the longer file */ + for (i = line[0] ? 0 : 1; line[i];) { + writeline(line[i], i); + free(line[i]); + line[i] = get_line(file[i]); + } + + if (CFG_TOYBOX_FREE) for (i = 0; i < 2; i--) xclose(file[i]); +} diff --git a/toys/posix/cp.c b/toys/posix/cp.c new file mode 100644 index 00000000..bc922b3d --- /dev/null +++ b/toys/posix/cp.c @@ -0,0 +1,224 @@ +/* vi: set sw=4 ts=4: + * + * cp.c - Copy files. + * + * Copyright 2008 Rob Landley <rob@landley.net> + * + * See http://www.opengroup.org/onlinepubs/009695399/utilities/cp.html + * + * "R+ra+d+p+r" +USE_CP(NEWTOY(cp, "<2vslrRdpaHLPif", TOYFLAG_BIN)) + +config CP + bool "cp (broken by dirtree changes)" + default n + help + usage: cp -fiprdal SOURCE... DEST + + Copy files from SOURCE to DEST. If more than one SOURCE, DEST must + be a directory. + + -f force copy by deleting destination file + -i interactive, prompt before overwriting existing DEST + -p preserve timestamps, ownership, and permissions + -r recurse into subdirectories (DEST must be a directory) + -d don't dereference symlinks + -a same as -dpr + -l hard link instead of copying + -v verbose +*/ + +#include "toys.h" + +#define FLAG_f 1 +#define FLAG_i 2 +#define FLAG_P 4 // todo +#define FLAG_L 8 // todo +#define FLAG_H 16 // todo +#define FLAG_a 32 +#define FLAG_p 64 +#define FLAG_d 128 // todo +#define FLAG_R 256 +#define FLAG_r 512 +#define FLAG_l 1024 // todo +#define FLAG_s 2048 // todo +#define FLAG_v 4098 + +DEFINE_GLOBALS( + char *destname; + int destisdir; + int destisnew; + int keep_symlinks; +) + +#define TT this.cp + +// Copy an individual file or directory to target. + +void cp_file(char *src, char *dst, struct stat *srcst) +{ + int fdout = -1; + + // -i flag is specified and dst file exists. + if ((toys.optflags&FLAG_i) && !access(dst, R_OK) + && !yesno("cp: overwrite", 1)) + return; + + if (toys.optflags & FLAG_v) + printf("'%s' -> '%s'\n", src, dst); + + // Copy directory or file to destination. + + if (S_ISDIR(srcst->st_mode)) { + struct stat st2; + + // Always make directory writeable to us, so we can create files in it. + // + // Yes, there's a race window between mkdir() and open() so it's + // possible that -p can be made to chown a directory other than the one + // we created. The closest we can do to closing this is make sure + // that what we open _is_ a directory rather than something else. + + if ((mkdir(dst, srcst->st_mode | 0200) && errno != EEXIST) + || 0>(fdout=open(dst, 0)) || fstat(fdout, &st2) + || !S_ISDIR(st2.st_mode)) + { + perror_exit("mkdir '%s'", dst); + } + } else if (TT.keep_symlinks && S_ISLNK(srcst->st_mode)) { + char *link = xreadlink(src); + + // Note: -p currently has no effect on symlinks. How do you get a + // filehandle to them? O_NOFOLLOW causes the open to fail. + if (!link || symlink(link, dst)) perror_msg("link '%s'", dst); + free(link); + return; + } else if (toys.optflags & FLAG_l) { + if (link(src, dst)) perror_msg("link '%s'"); + return; + } else { + int fdin, i; + + fdin = xopen(src, O_RDONLY); + for (i=2 ; i; i--) { + fdout = open(dst, O_RDWR|O_CREAT|O_TRUNC, srcst->st_mode); + if (fdout>=0 || !(toys.optflags & FLAG_f)) break; + unlink(dst); + } + if (fdout<0) perror_exit("%s", dst); + xsendfile(fdin, fdout); + close(fdin); + } + + // Inability to set these isn't fatal, some require root access. + // Can't do fchmod() etc here because -p works on mkdir, too. + + if (toys.optflags & (FLAG_p|FLAG_a)) { + int mask = umask(0); + struct utimbuf ut; + + (void) fchown(fdout,srcst->st_uid, srcst->st_gid); + ut.actime = srcst->st_atime; + ut.modtime = srcst->st_mtime; + utime(dst, &ut); + umask(mask); + } + xclose(fdout); +} + +// Callback from dirtree_read() for each file/directory under a source dir. + +int cp_node(struct dirtree *node) +{ + char *path = dirtree_path(node, 0); // TODO: use openat() instead + char *s = path+strlen(path); + struct dirtree *n; + + // Find appropriate chunk of path for destination. + + n = node; + if (!TT.destisdir) n = n->parent; + for (;;n = n->parent) { + while (s!=path) { + if (*(--s)=='/') break; + } + if (!n) break; + } + if (s != path) s++; + + s = xmsprintf("%s/%s", TT.destname, s); + cp_file(path, s, &(node->st)); + free(s); + free(path); // redo this whole darn function. + + return 0; +} + +void cp_main(void) +{ + struct stat st; + int i; + + // Grab target argument. (Guaranteed to be there due to "<2" above.) + + TT.destname = toys.optargs[--toys.optc]; + + // If destination doesn't exist, are we ok with that? + + if (stat(TT.destname, &st)) { + if (toys.optc>1) goto error_notdir; + TT.destisnew++; + + // If destination exists... + + } else { + if (S_ISDIR(st.st_mode)) TT.destisdir++; + else if (toys.optc > 1) goto error_notdir; + } + + // Handle sources + + for (i=0; i<toys.optc; i++) { + char *src = toys.optargs[i]; + char *dst; + + // Skip src==dest (TODO check inodes to catch "cp blah ./blah"). + + if (!strcmp(src, TT.destname)) continue; + + // Skip nonexistent sources. + + TT.keep_symlinks = toys.optflags & (FLAG_d|FLAG_a); + if (TT.keep_symlinks ? lstat(src, &st) : stat(src, &st)) + { + perror_msg("'%s'", src); + toys.exitval = 1; + continue; + } + + // Copy directory or file. + + if (TT.destisdir) { + dst = strrchr(src, '/'); + if (dst) dst++; + else dst=src; + dst = xmsprintf("%s/%s", TT.destname, dst); + } else dst = TT.destname; + if (S_ISDIR(st.st_mode)) { + if (toys.optflags & (FLAG_r|FLAG_R|FLAG_a)) { + cp_file(src, dst, &st); + + TT.keep_symlinks++; + strncpy(toybuf, src, sizeof(toybuf)-1); + toybuf[sizeof(toybuf)-1]=0; + dirtree_read(toybuf, cp_node); + } else error_msg("Skipped dir '%s'", src); + } else cp_file(src, dst, &st); + if (TT.destisdir) free(dst); + } + + return; + +error_notdir: + error_exit("'%s' isn't a directory", TT.destname); +} diff --git a/toys/posix/date.c b/toys/posix/date.c new file mode 100644 index 00000000..925565a2 --- /dev/null +++ b/toys/posix/date.c @@ -0,0 +1,95 @@ +/* vi: set sw=4 ts=4: + * + * date.c - set/get the date + * + * Copyright 2012 Andre Renaud <andre@bluewatersys.com> + * + * See http://pubs.opengroup.org/onlinepubs/9699919799/utilities/date.html + +USE_DATE(NEWTOY(date, "r:u", TOYFLAG_BIN)) + +config DATE + bool "date" + default y + help + usage: date [-u] [-r file] [+format] | mmddhhmm[[cc]yy] + + Set/get the current date/time +*/ + +#include "toys.h" + +DEFINE_GLOBALS( + char *file; +) + +#define TT this.date + +#define FLAG_u 1 +#define FLAG_r 2 + +void date_main(void) +{ + const char *format_string = "%a %b %e %H:%M:%S %Z %Y"; + time_t now = time(NULL); + struct tm tm; + + if (TT.file) { + struct stat st; + + xstat(TT.file, &st); + now = st.st_mtim.tv_sec; + } + ((toys.optflags & FLAG_u) ? gmtime_r : localtime_r)(&now, &tm); + + // Display the date? + if (!toys.optargs[0] || toys.optargs[0][0] == '+') { + if (toys.optargs[0]) format_string = toys.optargs[0]+1; + if (!strftime(toybuf, sizeof(toybuf), format_string, &tm)) + perror_msg("bad format `%s'", format_string); + + puts(toybuf); + + // Set the date + } else { + struct timeval tv; + char *s = *toys.optargs; + int len = strlen(s); + + if (len < 8 || len > 12 || (len & 1)) error_msg("bad date `%s'", s); + + // Date format: mmddhhmm[[cc]yy] + memset(&tm, 0, sizeof(tm)); + len = sscanf(s, "%2u%2u%2u%2u", &tm.tm_mon, &tm.tm_mday, &tm.tm_hour, + &tm.tm_min); + tm.tm_mon--; + + // If year specified, overwrite one we fetched earlier + if (len > 8) { + sscanf(s, "%u", &tm.tm_year); + if (len == 12) tm.tm_year -= 1900; + /* 69-99 = 1969-1999, 0 - 68 = 2000-2068 */ + else if (tm.tm_year < 69) tm.tm_year += 100; + } + + if (toys.optflags & FLAG_u) { + // Get the UTC version of a struct tm + char *tz = CFG_TOYBOX_FREE ? getenv("TZ") : 0; + setenv("TZ", "UTC", 1); + tzset(); + tv.tv_sec = mktime(&tm); + if (CFG_TOYBOX_FREE) { + if (tz) setenv("TZ", tz, 1); + else unsetenv("TZ"); + tzset(); + } + } else tv.tv_sec = mktime(&tm); + + if (tv.tv_sec == (time_t)-1) error_msg("bad `%s'", toys.optargs[0]); + tv.tv_usec = 0; + if (!strftime(toybuf, sizeof(toybuf), format_string, &tm)) + perror_msg("bad format `%s'", format_string); + puts(toybuf); + if (settimeofday(&tv, NULL) < 0) perror_msg("cannot set date"); + } +} diff --git a/toys/posix/df.c b/toys/posix/df.c new file mode 100644 index 00000000..72c15195 --- /dev/null +++ b/toys/posix/df.c @@ -0,0 +1,154 @@ +/* vi: set sw=4 ts=4: + * + * df.c - report free disk space. + * + * Copyright 2006 Rob Landley <rob@landley.net> + * + * See http://www.opengroup.org/onlinepubs/009695399/utilities/df.html + +USE_DF(NEWTOY(df, "Pkt*a", TOYFLAG_USR|TOYFLAG_SBIN)) + +config DF + bool "df (disk free)" + default y + help + usage: df [-t type] [FILESYSTEM ...] + + The "disk free" command, df shows total/used/available disk space for + 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 + + Provides a slightly less useful output format dictated by + the Single Unix Specification version 3, and sets the + units to 512 bytes instead of the default 1024 bytes. + + -k Sets units back to 1024 bytes (the default without -P) +*/ + +#include "toys.h" + +DEFINE_GLOBALS( + struct arg_list *fstype; + + long units; +) + +#define TT this.df + +static void show_mt(struct mtab_list *mt) +{ + int len; + long size, used, avail, percent; + uint64_t block; + + // Return if it wasn't found (should never happen, but with /etc/mtab...) + if (!mt) return; + + // If we have -t, skip other filesystem types + if (TT.fstype) { + struct arg_list *al; + + for (al = TT.fstype; al; al = al->next) { + if (!strcmp(mt->type, al->arg)) break; + } + if (!al) return; + } + + // If we don't have -a, skip synthetic filesystems + if (!(toys.optflags & 1) && !mt->statvfs.f_blocks) return; + + // Figure out how much total/used/free space this filesystem has, + // forcing 64-bit math because filesystems are big now. + block = mt->statvfs.f_bsize ? mt->statvfs.f_bsize : 1; + size = (long)((block * mt->statvfs.f_blocks) / TT.units); + used = (long)((block * (mt->statvfs.f_blocks-mt->statvfs.f_bfree)) + / TT.units); + avail = (long)((block + * (getuid() ? mt->statvfs.f_bavail : mt->statvfs.f_bfree)) + / TT.units); + percent = size ? 100-(long)((100*(uint64_t)avail)/size) : 0; + + // Figure out appropriate spacing + len = 25 - strlen(mt->device); + if (len < 1) len = 1; + if (CFG_DF_PEDANTIC && (toys.optflags & 8)) { + printf("%s %ld %ld %ld %ld%% %s\n", mt->device, size, used, avail, + percent, mt->dir); + } else { + printf("%s% *ld % 10ld % 9ld % 3ld%% %s\n",mt->device, len, + size, used, avail, percent, mt->dir); + } +} + +void df_main(void) +{ + struct mtab_list *mt, *mt2, *mtlist; + + // Handle -P and -k + TT.units = 1024; + if (CFG_DF_PEDANTIC && (toys.optflags & 8)) { + // Units are 512 bytes if you select "pedantic" without "kilobytes". + if ((toys.optflags&3) == 1) 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"); + + mtlist = getmountlist(1); + + // If we have a list of filesystems on the command line, loop through them. + if (*toys.optargs) { + char **next; + + for(next = toys.optargs; *next; next++) { + struct stat st; + + // Stat it (complain if we can't). + if(stat(*next, &st)) { + perror_msg("`%s'", *next); + toys.exitval = 1; + continue; + } + + // Find and display this filesystem. Use _last_ hit in case of + // -- bind mounts. + mt2 = NULL; + for (mt = mtlist; mt; mt = mt->next) + if (st.st_dev == mt->stat.st_dev) mt2 = mt; + show_mt(mt2); + } + } else { + // Get and loop through mount list. + + for (mt = mtlist; mt; mt = mt->next) { + struct mtab_list *mt2, *mt3; + + if (!mt->stat.st_dev) continue; + + // Filter out overmounts. + mt3 = mt; + for (mt2 = mt->next; mt2; mt2 = mt2->next) { + if (mt->stat.st_dev == mt2->stat.st_dev) { + // For --bind mounts, take last match + if (!strcmp(mt->device, mt2->device)) mt3 = mt2; + // Filter out overmounts + mt2->stat.st_dev = 0; + } + } + show_mt(mt3); + } + } + + if (CFG_TOYBOX_FREE) llist_traverse(mtlist, free); +} diff --git a/toys/posix/dirname.c b/toys/posix/dirname.c new file mode 100644 index 00000000..5dc60181 --- /dev/null +++ b/toys/posix/dirname.c @@ -0,0 +1,25 @@ +/* vi: set sw=4 ts=4: + * + * dirname.c - show directory portion of path + * + * Copyright 2011 Rob Landley <rob@landley.net> + * + * See http://opengroup.org/onlinepubs/9699919799/utilities/dirname.html + +USE_DIRNAME(NEWTOY(dirname, "<1", TOYFLAG_USR|TOYFLAG_BIN)) + +config DIRNAME + bool "dirname" + default y + help + usage: dirname PATH + + Show directory portion of path. +*/ + +#include "toys.h" + +void dirname_main(void) +{ + puts(dirname(*toys.optargs)); +} diff --git a/toys/posix/echo.c b/toys/posix/echo.c new file mode 100644 index 00000000..6fb9e43d --- /dev/null +++ b/toys/posix/echo.c @@ -0,0 +1,97 @@ +/* vi: set sw=4 ts=4: + * + * echo.c - echo supporting -n and -e. + * + * Copyright 2007 Rob Landley <rob@landley.net> + * + * See http://www.opengroup.org/onlinepubs/009695399/utilities/echo.html + +USE_ECHO(NEWTOY(echo, "^?en", TOYFLAG_BIN)) + +config ECHO + bool "echo" + default y + help + usage: echo [-ne] [args...] + + Write each argument to stdout, with one space between each, followed + by a newline. + + -n No trailing newline. + -e Process the following escape sequences: + \\ backslash + \0NNN octal values (1 to 3 digits) + \a alert (beep/flash) + \b backspace + \c stop output here (avoids trailing newline) + \f form feed + \n newline + \r carriage return + \t horizontal tab + \v vertical tab + \xHH hexadecimal values (1 to 2 digits) +*/ + +#define THIS echo +#include "toys.h" + +#define FLAG_e (1<<1) +#define FLAG_n (1<<0) + +void echo_main(void) +{ + int i = 0, out; + char *arg, *from = "\\abfnrtv", *to = "\\\a\b\f\n\r\t\v", *c; + + for (;;) { + arg = toys.optargs[i]; + if (!arg) break; + if (i++) xputc(' '); + + // Should we output arg verbatim? + + if (!(toys.optflags&FLAG_e)) { + xprintf("%s", arg); + continue; + } + + // Handle -e + + for (c=arg;;) { + if (!(out = *(c++))) break; + + // handle \escapes + if (out == '\\' && *c) { + int n = 0, slash = *(c++); + char *found = strchr(from, slash); + if (found) out = to[found-from]; + else if (slash == 'c') goto done; + else if (slash == '0') { + out = 0; + while (*c>='0' && *c<='7' && n++<3) + out = (out*8)+*(c++)-'0'; + } else if (slash == 'x') { + out = 0; + while (n++<2) { + if (*c>='0' && *c<='9') + out = (out*16)+*(c++)-'0'; + else { + int temp = tolower(*c); + if (temp>='a' && temp<='f') { + out = (out*16)+temp-'a'+10; + c++; + } else break; + } + } + // Slash in front of unknown character, print literal. + } else c--; + } + xputc(out); + } + } + + // Output "\n" if no -n + if (!(toys.optflags&FLAG_n)) xputc('\n'); +done: + xflush(); +} diff --git a/toys/posix/env.c b/toys/posix/env.c new file mode 100644 index 00000000..7cda85f9 --- /dev/null +++ b/toys/posix/env.c @@ -0,0 +1,52 @@ +/* vi: set sw=4 ts=4: + * + * env.c - Set the environment for command invocation. + * + * Copyright 2012 Tryn Mirell <tryn@mirell.org> + * env.c + +USE_ENV(NEWTOY(env, "^i", TOYFLAG_USR|TOYFLAG_BIN)) + +config ENV + bool "env" + default y + help + usage: env [-i] [NAME=VALUE...] [command [option...]] + + Set the environment for command invocation. + + -i Clear existing environment. +*/ + +#include "toys.h" + +extern char **environ; + +void env_main(void) +{ + char **ev; + char **command = NULL; + char *del = "="; + + if (toys.optflags & 1) clearenv(); + + for (ev = toys.optargs; *ev != NULL; ev++) { + char *env, *val = NULL; + + env = strtok(*ev, del); + + if (env) val = strtok(NULL, del); + + if (val) setenv(env, val, 1); + else { + command = ev; + break; + } + } + + if (!command) { + char **ep; + for (ep = environ; *ep; ep++) xputs(*ep); + return; + } else xexec(command); +} diff --git a/toys/posix/false.c b/toys/posix/false.c new file mode 100644 index 00000000..7b570bd2 --- /dev/null +++ b/toys/posix/false.c @@ -0,0 +1,23 @@ +/* vi: set sw=4 ts=4: + * + * false.c - Return nonzero. + * + * Copyright 2007 Rob Landley <rob@landley.net> + * + * See http://www.opengroup.org/onlinepubs/009695399/utilities/false.html + +USE_FALSE(NEWTOY(false, NULL, TOYFLAG_BIN)) + +config FALSE + bool "false" + default y + help + Return nonzero. +*/ + +#include "toys.h" + +void false_main(void) +{ + toys.exitval = 1; +} diff --git a/toys/posix/head.c b/toys/posix/head.c new file mode 100644 index 00000000..1d1e54a3 --- /dev/null +++ b/toys/posix/head.c @@ -0,0 +1,61 @@ +/* vi: set sw=4 ts=4: + * + * head.c - copy first lines from input to stdout. + * + * Copyright 2006 Timothy Elliott <tle@holymonkey.com> + * + * See http://www.opengroup.org/onlinepubs/009695399/utilities/head.html + +USE_HEAD(NEWTOY(head, "n#<0=10", TOYFLAG_BIN)) + +config HEAD + bool "head" + default y + help + usage: head [-n number] [file...] + + Copy first lines from files to stdout. If no files listed, copy from + stdin. Filename "-" is a synonym for stdin. + + -n Number of lines to copy. +*/ + +#include "toys.h" + +DEFINE_GLOBALS( + long lines; + int file_no; +) + +#define TT this.head + +static void do_head(int fd, char *name) +{ + int i, len, lines=TT.lines, size=sizeof(toybuf); + + if (toys.optc > 1) { + // Print an extra newline for all but the first file + if (TT.file_no++) xprintf("\n"); + xprintf("==> %s <==\n", name); + xflush(); + } + + while (lines) { + len = read(fd, toybuf, size); + if (len<0) { + perror_msg("%s",name); + toys.exitval = EXIT_FAILURE; + } + if (len<1) break; + + for(i=0; i<len;) + if (toybuf[i++] == '\n' && !--lines) break; + + xwrite(1, toybuf, i); + } +} + +void head_main(void) +{ + loopfiles(toys.optargs, do_head); +} diff --git a/toys/posix/id.c b/toys/posix/id.c new file mode 100644 index 00000000..973cda5a --- /dev/null +++ b/toys/posix/id.c @@ -0,0 +1,119 @@ +/* vi: set sw=4 ts=4: + * + * id.c - print real and effective user and group IDs + * + * Copyright 2012 Sony Network Entertainment, Inc. + * + * by Tim Bird <tim.bird@am.sony.com> + * + * See http://www.opengroup.org/onlinepubs/009695399/utilities/id.html + +USE_ID(NEWTOY(id, "nGgru", TOYFLAG_BIN)) + +config ID + bool "id" + default y + help + usage: id [-nGgru] + + Print user and group ID. + + -n print names instead of numeric IDs (to be used with -Ggu) + -G Show only the group IDs + -g Show only the effective group ID + -r Show real ID instead of effective ID + -u Show only the effective user ID +*/ + +#include "toys.h" + +#define FLAG_n (1<<4) +#define FLAG_G (1<<3) +#define FLAG_g (1<<2) +#define FLAG_r (1<<1) +#define FLAG_u 1 + +static void s_or_u(char *s, unsigned u, int done) +{ + if (toys.optflags & FLAG_n) printf("%s", s); + else printf("%u", u); + if (done) { + xputc('\n'); + exit(0); + } +} + +static void showid(char *header, unsigned u, char *s) +{ + printf("%s%u(%s)", header, u, s); +} + +struct passwd *xgetpwuid(uid_t uid) +{ + struct passwd *pwd = getpwuid(uid); + if (!pwd) error_exit(NULL); + return pwd; +} + +struct group *xgetgrgid(gid_t gid) +{ + struct group *group = getgrgid(gid); + if (!group) error_exit(NULL); + return group; +} + +void id_main(void) +{ + int flags = toys.optflags, i, ngroups; + struct passwd *pw; + struct group *grp; + uid_t uid = getuid(), euid = geteuid(); + gid_t gid = getgid(), egid = getegid(), *groups; + + /* check if a username is given */ + if (*toys.optargs) { + if (!(pw = getpwnam(*toys.optargs))) + error_exit("no such user '%s'", *toys.optargs); + uid = euid = pw->pw_uid; + gid = egid = pw->pw_gid; + } + + i = toys.optflags & FLAG_r; + pw = xgetpwuid(i ? uid : euid); + if (flags & 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 (!(flags & FLAG_G)) { + showid("uid=", pw->pw_uid, pw->pw_name); + showid(" gid=", grp->gr_gid, grp->gr_name); + + if (!i) { + if (uid != euid) { + pw = xgetpwuid(euid); + showid(" euid=", pw->pw_uid, pw->pw_name); + } + if (gid != egid) { + grp = xgetgrgid(egid); + showid(" egid=", grp->gr_gid, grp->gr_name); + } + } + + showid(" groups=", grp->gr_gid, grp->gr_name); + } + + + groups = (gid_t *)toybuf; + if (0 >= (ngroups = getgroups(sizeof(toybuf)/sizeof(gid_t), groups))) + perror_exit(0); + + for (i = 0; i < ngroups; i++) { + xputc(' '); + if (!(grp = getgrgid(groups[i]))) perror_msg(0); + else if (flags & 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); + } + xputc('\n'); +} diff --git a/toys/posix/kill.c b/toys/posix/kill.c new file mode 100644 index 00000000..e64a146b --- /dev/null +++ b/toys/posix/kill.c @@ -0,0 +1,74 @@ +/* vi: set sw=4 ts=4: + * + * kill.c - a program to send signals to processes + * + * Copyright 2012 Daniel Walter <d.walter@0x90.at> + * + * See http://opengroup.org/onlinepubs/9699919799/utilities/kill.html + +USE_KILL(NEWTOY(kill, "?s: l", TOYFLAG_BIN)) + +config KILL + bool "kill" + default y + help + usage: kill [-l [SIGNAL] | -s SIGNAL | -SIGNAL] pid... + + Send a signal to a process + +*/ + +#include "toys.h" + +DEFINE_GLOBALS( + char *signame; +) + +#define TT this.kill + +void kill_main(void) +{ + int signum; + char *tmp, **args = toys.optargs; + pid_t pid; + + // list signal(s) + if (toys.optflags & 1) { + if (*args) { + int signum = sig_to_num(*args); + char *s = NULL; + + if (signum>=0) s = num_to_sig(signum&127); + puts(s ? s : "UNKNOWN"); + } else sig_to_num(NULL); + return; + } + + // signal must come before pids, so "kill -9 -1" isn't confusing. + + if (!TT.signame && *args && **args=='-') TT.signame=*(args++)+1; + if (TT.signame) { + char *arg; + int i = strtol(TT.signame, &arg, 10); + if (!*arg) arg = num_to_sig(i); + else arg = TT.signame; + + if (!arg || -1 == (signum = sig_to_num(arg))) + error_exit("Unknown signal '%s'", arg); + } else signum = SIGTERM; + + if (!*args) { + toys.exithelp++; + error_exit("missing argument"); + } + + while (*args) { + char *arg = *(args++); + + pid = strtol(arg, &tmp, 10); + if (*tmp || kill(pid, signum) < 0) { + error_msg("unknown pid '%s'", arg); + toys.exitval = EXIT_FAILURE; + } + } +} diff --git a/toys/posix/link.c b/toys/posix/link.c new file mode 100644 index 00000000..69a9a60f --- /dev/null +++ b/toys/posix/link.c @@ -0,0 +1,27 @@ +/* vi: set sw=4 ts=4: + * + * link.c - hardlink a file + * + * Copyright 2011 Rob Landley <rob@landley.net> + * + * See http://opengroup.org/onlinepubs/9699919799/utilities/link.html + +USE_LINK(NEWTOY(link, "<2>2", TOYFLAG_USR|TOYFLAG_BIN)) + +config LINK + bool "link" + default y + help + usage: link FILE NEWLINK + + Create hardlink to a file. +*/ + +#include "toys.h" + +void link_main(void) +{ + if (link(toys.optargs[0], toys.optargs[1])) + perror_exit("couldn't link '%s' to '%s'", toys.optargs[1], + toys.optargs[0]); +} diff --git a/toys/posix/ln.c b/toys/posix/ln.c new file mode 100644 index 00000000..6f55971a --- /dev/null +++ b/toys/posix/ln.c @@ -0,0 +1,70 @@ +/* vi: set sw=4 ts=4: + * + * ln.c - Create filesystem links + * + * Copyright 2012 Andre Renaud <andre@bluewatersys.com> + * + * See http://pubs.opengroup.org/onlinepubs/9699919799/utilities/ln.html + +USE_LN(NEWTOY(ln, "<1nfs", TOYFLAG_BIN)) + +config LN + bool "ln" + default y + help + usage: ln [-sf] [FROM...] TO + + Create a link between FROM and TO. + With only one argument, create link in current directory. + + -s Create a symbolic link + -f Force the creation of the link, even if TO already exists + -n Symlink at destination treated as file +*/ + +#include "toys.h" + +#define FLAG_s 1 +#define FLAG_f 2 +#define FLAG_n 4 + +void ln_main(void) +{ + char *dest = toys.optargs[--toys.optc], *new; + struct stat buf; + int i; + + // With one argument, create link in current directory. + if (!toys.optc) { + toys.optc++; + dest="."; + } + + // Is destination a directory? + if (((toys.optflags&FLAG_n) ? lstat : stat)(dest, &buf) + || !S_ISDIR(buf.st_mode)) + { + if (toys.optc>1) error_exit("'%s' not a directory"); + buf.st_mode = 0; + } + + for (i=0; i<toys.optc; i++) { + int rc; + char *try = toys.optargs[i]; + + if (S_ISDIR(buf.st_mode)) { + new = strrchr(try, '/'); + if (!new) new = try; + new = xmsprintf("%s/%s", dest, new); + } else new = dest; + /* Silently unlink the existing target. If it doesn't exist, + * then we just move on */ + if (toys.optflags & FLAG_f) unlink(new); + + rc = (toys.optflags & FLAG_s) ? symlink(try, new) : link(try, new); + if (rc) + perror_exit("cannot create %s link from '%s' to '%s'", + (toys.optflags & FLAG_s) ? "symbolic" : "hard", try, new); + if (new != dest) free(new); + } +} diff --git a/toys/posix/logname.c b/toys/posix/logname.c new file mode 100644 index 00000000..b93e3366 --- /dev/null +++ b/toys/posix/logname.c @@ -0,0 +1,29 @@ +/* vi: set sw=4 ts=4: + * + * logname.c - Print user's login name. + * + * Copyright 2012 Elie De Brauwer <eliedebrauwer@gmail.com> + * + * See http://pubs.opengroup.org/onlinepubs/9699919799/utilities/logname.html + +USE_LOGNAME(NEWTOY(logname, ">0", TOYFLAG_BIN)) + +config LOGNAME + bool "logname" + default y + help + usage: logname + + Prints the calling user's name or an error when this cannot be + determined. +*/ + +#include "toys.h" + +void logname_main(void) +{ + if (getlogin_r(toybuf, sizeof(toybuf))){ + error_exit("no login name"); + } + xputs(toybuf); +} diff --git a/toys/posix/ls.c b/toys/posix/ls.c new file mode 100644 index 00000000..1881df71 --- /dev/null +++ b/toys/posix/ls.c @@ -0,0 +1,493 @@ +/* vi: set sw=4 ts=4: + * + * ls.c - list files + * + * Copyright 2012 Andre Renaud <andre@bluewatersys.com> + * Copyright 2012 Rob Landley <rob@landley.net> + * + * See http://pubs.opengroup.org/onlinepubs/9699919799/utilities/ls.html + +// "[-Cl]" +USE_LS(NEWTOY(ls, "goACFHLRSacdfiklmnpqrstux1", TOYFLAG_BIN)) + +config LS + bool "ls" + default y + help + usage: ls [-ACFHLRSacdfiklmnpqrstux1] [directory...] + list files + + what to show: + -a all files including .hidden + -c use ctime for timestamps + -d directory, not contents + -i inode number + -k block sizes in kilobytes + -p put a '/' after directory names + -q unprintable chars as '?' + -s size (in blocks) + -u use access time for timestamps + -A list all files except . and .. + -H follow command line symlinks + -L follow symlinks + -R recursively list files in subdirectories + -F append file type indicator (/=dir, *=exe, @=symlink, |=FIFO) + + output formats: + -1 list one file per line + -C columns (sorted vertically) + -g like -l but no owner + -l long (show full details for each file) + -m comma separated + -n like -l but numeric uid/gid + -o like -l but no group + -x columns (sorted horizontally) + + sorting (default is alphabetical): + -f unsorted + -r reverse + -t timestamp + -S size +*/ + +#include "toys.h" + +#define FLAG_1 (1<<0) +#define FLAG_x (1<<1) +#define FLAG_u (1<<2) +#define FLAG_t (1<<3) +#define FLAG_s (1<<4) +#define FLAG_r (1<<5) +#define FLAG_q (1<<6) +#define FLAG_p (1<<7) +#define FLAG_n (1<<8) +#define FLAG_m (1<<9) +#define FLAG_l (1<<10) +#define FLAG_k (1<<11) +#define FLAG_i (1<<12) +#define FLAG_f (1<<13) +#define FLAG_d (1<<14) +#define FLAG_c (1<<15) +#define FLAG_a (1<<16) +#define FLAG_S (1<<17) +#define FLAG_R (1<<18) +#define FLAG_L (1<<19) +#define FLAG_H (1<<20) +#define FLAG_F (1<<21) +#define FLAG_C (1<<22) +#define FLAG_A (1<<23) +#define FLAG_o (1<<24) +#define FLAG_g (1<<25) + +// test sst output (suid/sticky in ls flaglist) + +// ls -lR starts .: then ./subdir: + +DEFINE_GLOBALS( + struct dirtree *files; + + unsigned screen_width; + int nl_title; + + // group and user can make overlapping use of the utoa() buf, so move it + char uid_buf[12]; +) + +#define TT this.ls + +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; + if ((toys.optflags&(FLAG_F|FLAG_p)) && S_ISDIR(mode)) return '/'; + if (toys.optflags & FLAG_F) { + if (S_ISLNK(mode)) return '@'; + if (S_ISREG(mode) && (mode&0111)) return '*'; + if (S_ISFIFO(mode)) return '|'; + if (S_ISSOCK(mode)) return '='; + } + return 0; +} + +static char *getusername(uid_t uid) +{ + struct passwd *pw = getpwuid(uid); + utoa_to_buf(uid, TT.uid_buf, 12); + return pw ? pw->pw_name : TT.uid_buf; +} + +static char *getgroupname(gid_t gid) +{ + struct group *gr = getgrgid(gid); + return gr ? gr->gr_name : utoa(gid); +} + +// Figure out size of printable entry fields for display indent/wrap + +static void entrylen(struct dirtree *dt, unsigned *len) +{ + struct stat *st = &(dt->st); + unsigned flags = toys.optflags; + + *len = strlen(dt->name); + if (endtype(st)) ++*len; + if (flags & FLAG_m) ++*len; + + if (flags & FLAG_i) *len += (len[1] = numlen(st->st_ino)); + if (flags & (FLAG_l|FLAG_o|FLAG_n|FLAG_g)) { + unsigned fn = flags & FLAG_n; + len[2] = numlen(st->st_nlink); + len[3] = strlen(fn ? utoa(st->st_uid) : getusername(st->st_uid)); + len[4] = strlen(fn ? utoa(st->st_gid) : getgroupname(st->st_gid)); + len[5] = numlen(st->st_size); + } + if (flags & FLAG_s) *len += (len[6] = numlen(st->st_blocks)); +} + +static int compare(void *a, void *b) +{ + struct dirtree *dta = *(struct dirtree **)a; + struct dirtree *dtb = *(struct dirtree **)b; + int ret = 0, reverse = (toys.optflags & FLAG_r) ? -1 : 1; + + if (toys.optflags & FLAG_S) { + if (dta->st.st_size > dtb->st.st_size) ret = -1; + else if (dta->st.st_size < dtb->st.st_size) ret = 1; + } + if (toys.optflags & FLAG_t) { + if (dta->st.st_mtime > dtb->st.st_mtime) ret = -1; + else if (dta->st.st_mtime < dtb->st.st_mtime) ret = 1; + } + if (!ret) ret = strcmp(dta->name, dtb->name); + return ret * reverse; +} + +// callback from dirtree_recurse() determining how to handle this entry. + +static int filter(struct dirtree *new) +{ + int flags = toys.optflags; + + // Special case to handle enormous dirs without running out of memory. + if (flags == (FLAG_1|FLAG_f)) { + xprintf("%s\n", new->name); + return 0; + } + + if (!(flags&FLAG_f)) { + if (flags & FLAG_a) return 0; + if (!(flags & FLAG_A) && new->name[0]=='.') return 0; + } + + 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; + return dirtree_notdotdot(new); +} + +// For column view, calculate horizontal position (for padding) and return +// index of next entry to display. + +static unsigned long next_column(unsigned long ul, unsigned long dtlen, + unsigned columns, unsigned *xpos) +{ + unsigned long transition; + unsigned height, widecols; + + // Horizontal sort is easy + if (!(toys.optflags & FLAG_C)) { + *xpos = ul % columns; + return ul; + } + + // vertical sort + + // For -x, calculate height of display, rounded up + height = (dtlen+columns-1)/columns; + + // Sanity check: does wrapping render this column count impossible + // due to the right edge wrapping eating a whole row? + if (height*columns - dtlen >= height) { + *xpos = columns; + return 0; + } + + // Uneven rounding goes along right edge + widecols = dtlen % height; + if (!widecols) widecols = height; + transition = widecols * columns; + if (ul < transition) { + *xpos = ul % columns; + return (*xpos*height) + (ul/columns); + } + + ul -= transition; + *xpos = ul % (columns-1); + + return (*xpos*height) + widecols + (ul/(columns-1)); +} + +// Display a list of dirtree entries, according to current format +// Output types -1, -l, -C, or stream + +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], + *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. + 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); + return; + } + } else { + // Read directory contents. We dup() the fd because this will close it. + indir->data = dup(dirfd); + dirtree_recurse(indir, filter, (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) { + if (sort) sort[dtlen] = dt; + dtlen++; + } + if (sort) break; + sort = xmalloc(dtlen * sizeof(void *)); + dtlen = 0; + continue; + } + + // Label directory if not top of tree, or if -R + if (indir->parent && (!indir->extra || (flags & FLAG_R))) + { + char *path = dirtree_path(indir, 0); + + if (TT.nl_title++) xputc('\n'); + xprintf("%s:\n", path); + free(path); + } + + if (!(flags & FLAG_f)) qsort(sort, dtlen, sizeof(void *), (void *)compare); + + // Find largest entry in each field for display alignment + if (flags & (FLAG_C|FLAG_x)) { + + // columns can't be more than toybuf can hold, or more than files, + // or > 1/2 screen width (one char filename, one space). + if (columns > TT.screen_width/2) columns = TT.screen_width/2; + if (columns > dtlen) columns = dtlen; + + // Try to fit as many columns as we can, dropping down by one each time + for (;columns > 1; columns--) { + unsigned c, totlen = columns; + + memset(colsizes, 0, columns*sizeof(unsigned)); + for (ul=0; ul<dtlen; ul++) { + entrylen(sort[next_column(ul, dtlen, columns, &c)], len); + if (c == columns) break; + // Does this put us over budget? + if (*len > colsizes[c]) { + totlen += *len-colsizes[c]; + colsizes[c] = *len; + if (totlen > TT.screen_width) break; + } + } + // If it 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. + memset(toybuf, ' ', 256); + width = 0; + for (ul = 0; ul<dtlen; ul++) { + unsigned curcol; + unsigned long next = next_column(ul, dtlen, columns, &curcol); + struct stat *st = &(sort[next]->st); + mode_t mode = st->st_mode; + char et = endtype(st); + + // Skip directories at the top of the tree when -d isn't set + if (S_ISDIR(mode) && !indir->parent && !(flags & FLAG_d)) continue; + TT.nl_title=1; + + // Handle padding and wrapping for display purposes + entrylen(sort[next], len); + if (ul) { + if (flags & FLAG_m) xputc(','); + if (flags & (FLAG_C|FLAG_x)) { + if (!curcol) xputc('\n'); + } else if ((flags & FLAG_1) || width+1+*len > TT.screen_width) { + xputc('\n'); + width = 0; + } else { + xputc(' '); + width++; + } + } + 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_l|FLAG_o|FLAG_n|FLAG_g)) { + struct tm *tm; + char perm[11], thyme[64], c, d, *usr, *upad, *grp, *grpad; + int i, bit; + + perm[10]=0; + for (i=0; i<9; i++) { + bit = mode & (1<<i); + c = i%3; + if (!c && (mode & (1<<((d=i/3)+9)))) { + c = "tss"[d]; + if (!bit) c &= ~0x20; + } else c = bit ? "xwr"[c] : '-'; + perm[9-i] = c; + } + + if (S_ISDIR(mode)) c = 'd'; + else if (S_ISBLK(mode)) c = 'b'; + else if (S_ISCHR(mode)) c = 'c'; + else if (S_ISLNK(mode)) c = 'l'; + else if (S_ISFIFO(mode)) c = 'p'; + else if (S_ISSOCK(mode)) c = 's'; + else c = '-'; + *perm = c; + + tm = localtime(&(st->st_mtime)); + strftime(thyme, sizeof(thyme), "%F %H:%M", tm); + + if (flags&FLAG_o) grp = grpad = toybuf+256; + else { + grp = (flags&FLAG_n) ? utoa(st->st_gid) + : getgroupname(st->st_gid); + grpad = toybuf+256-(totals[4]-len[4]); + } + + if (flags&FLAG_g) usr = upad = toybuf+256; + else { + upad = toybuf+255-(totals[3]-len[3]); + if (flags&FLAG_n) { + usr = TT.uid_buf; + utoa_to_buf(st->st_uid, TT.uid_buf, 12); + } else usr = getusername(st->st_uid); + } + + // Coerce the st types into something we know we can print. + xprintf("%s% *ld %s%s%s%s% *"PRId64" %s ", perm, totals[2]+1, + (long)st->st_nlink, usr, upad, grp, grpad, totals[5]+1, + (int64_t)st->st_size, thyme); + } + + if (flags & FLAG_q) { + char *p; + for (p=sort[next]->name; *p; p++) xputc(isprint(*p) ? *p : '?'); + } else xprintf("%s", sort[next]->name); + if ((flags & (FLAG_l|FLAG_o|FLAG_n|FLAG_g)) && S_ISLNK(mode)) + xprintf(" -> %s", sort[next]->symlink); + + if (et) xputc(et); + + // Pad columns + if (flags & (FLAG_C|FLAG_x)) { + curcol = colsizes[curcol] - *len; + if (curcol >= 0) xprintf("%s", toybuf+255-curcol); + } + } + + if (width) xputc('\n'); + + // 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; + + // Recurse into dirs if at top of the tree or given -R + if (!indir->parent || (flags & FLAG_R)) + listfiles(openat(dirfd, sort[ul]->name, 0), sort[ul]); + } + free(sort); + if (dirfd != AT_FDCWD) close(indir->data); +} + +void ls_main(void) +{ + char **s, *noargs[] = {".", 0}; + struct dirtree *dt; + + // Do we have an implied -1 + if (!isatty(1) || (toys.optflags&(FLAG_l|FLAG_o|FLAG_n|FLAG_g))) + toys.optflags |= FLAG_1; + else { + TT.screen_width = 80; + terminal_size(&TT.screen_width, NULL); + } + // The optflags parsing infrastructure should really do this for us, + // but currently it has "switch off when this is set", so "-dR" and "-Rd" + // behave differently + if (toys.optflags & FLAG_d) toys.optflags &= ~FLAG_R; + + // 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); + for (s = *toys.optargs ? toys.optargs : noargs; *s; s++) { + dt = dirtree_add_node(AT_FDCWD, *s, + (toys.optflags & (FLAG_L|FLAG_H|FLAG_l))^FLAG_l); + + if (!dt) { + toys.exitval = 1; + continue; + } + + // Typecast means double_list->prev temporarirly goes in dirtree->parent + dlist_add_nomalloc((struct double_list **)&TT.files->child, + (struct double_list *)dt); + } + + // Turn double_list into dirtree + dlist_to_dirtree(TT.files); + + // Display the files we collected + listfiles(AT_FDCWD, TT.files); + + if (CFG_TOYBOX_FREE) free(TT.files); +} diff --git a/toys/posix/mkdir.c b/toys/posix/mkdir.c new file mode 100644 index 00000000..90329019 --- /dev/null +++ b/toys/posix/mkdir.c @@ -0,0 +1,76 @@ +/* vi: set sw=4 ts=4: + * + * mkdir.c - Make directories + * + * Copyright 2012 Georgi Chorbadzhiyski <georgi@unixsol.org> + * + * See http://pubs.opengroup.org/onlinepubs/009695399/utilities/mkdir.html + * + * TODO: Add -m + +USE_MKDIR(NEWTOY(mkdir, "<1p", TOYFLAG_BIN)) + +config MKDIR + bool "mkdir" + default y + help + usage: mkdir [-p] [dirname...] + Create one or more directories. + + -p make parent directories as needed. +*/ + +#include "toys.h" + +DEFINE_GLOBALS( + long mode; +) + +#define TT this.mkdir + +static int do_mkdir(char *dir) +{ + struct stat buf; + char *s; + + // mkdir -p one/two/three is not an error if the path already exists, + // but is if "three" is a file. The others we dereference and catch + // not-a-directory along the way, but the last one we must explicitly + // test for. Might as well do it up front. + + if (!stat(dir, &buf) && !S_ISDIR(buf.st_mode)) { + errno = EEXIST; + return 1; + } + + for (s=dir; ; s++) { + char save=0; + + // Skip leading / of absolute paths. + if (s!=dir && *s == '/' && toys.optflags) { + save = *s; + *s = 0; + } else if (*s) continue; + + if (mkdir(dir, TT.mode)<0 && (!toys.optflags || errno != EEXIST)) + return 1; + + if (!(*s = save)) break; + } + + return 0; +} + +void mkdir_main(void) +{ + char **s; + + TT.mode = 0777; + + for (s=toys.optargs; *s; s++) { + if (do_mkdir(*s)) { + perror_msg("cannot create directory '%s'", *s); + toys.exitval = 1; + } + } +} diff --git a/toys/posix/mkfifo.c b/toys/posix/mkfifo.c new file mode 100644 index 00000000..ab466fc3 --- /dev/null +++ b/toys/posix/mkfifo.c @@ -0,0 +1,47 @@ +/* vi: set sw=4 ts=4: + * + * mkfifo.c - Create FIFOs (named pipes) + * + * Copyright 2012 Georgi Chorbadzhiyski <georgi@unixsol.org> + * + * See http://pubs.opengroup.org/onlinepubs/009695399/utilities/mkfifo.html + * + * TODO: Add -m + +USE_MKFIFO(NEWTOY(mkfifo, "<1m:", TOYFLAG_BIN)) + +config MKFIFO + bool "mkfifo" + default y + help + usage: mkfifo [fifo_name...] + + Create FIFOs (named pipes). +*/ + +#include "toys.h" + +DEFINE_GLOBALS( + char *m_string; + mode_t mode; +) + +#define TT this.mkfifo +#define FLAG_m (1) + +void mkfifo_main(void) +{ + char **s; + + TT.mode = 0666; + if (toys.optflags & FLAG_m) { + TT.mode = string_to_mode(TT.m_string, 0); + } + + for (s = toys.optargs; *s; s++) { + if (mknod(*s, S_IFIFO | TT.mode, 0) < 0) { + perror_msg("cannot create fifo '%s'", *s); + toys.exitval = 1; + } + } +} diff --git a/toys/posix/nice.c b/toys/posix/nice.c new file mode 100644 index 00000000..84975dfc --- /dev/null +++ b/toys/posix/nice.c @@ -0,0 +1,41 @@ +/* vi: set sw=4 ts=4: + * + * nice.c - Run a program at a different niceness level. + * + * Copyright 2010 Rob Landley <rob@landley.net> + * + * See http://www.opengroup.org/onlinepubs/9699919799/utilities/nice.html + +USE_NICE(NEWTOY(nice, "^<1n#", TOYFLAG_USR|TOYFLAG_BIN)) + +config NICE + bool "nice" + default y + help + usage: nice [-n PRIORITY] command [args...] + + Run a command line at an increased or decreased scheduling priority. + + Higher numbers make a program yield more CPU time, from -20 (highest + priority) to 19 (lowest). By default processes inherit their parent's + niceness (usually 0). By default this command adds 10 to the parent's + priority. Only root can set a negative niceness level. +*/ + +#include "toys.h" + +DEFINE_GLOBALS( + long priority; +) + +#define TT this.nice + +void nice_main(void) +{ + if (!toys.optflags) TT.priority = 10; + + errno = 0; + if (nice(TT.priority)==-1 && errno) perror_exit("Can't set priority"); + + xexec(toys.optargs); +} diff --git a/toys/posix/nohup.c b/toys/posix/nohup.c new file mode 100644 index 00000000..e11fb094 --- /dev/null +++ b/toys/posix/nohup.c @@ -0,0 +1,42 @@ +/* vi: set sw=4 ts=4: + * + * nohup.c - run commandline with SIGHUP blocked. + * + * Copyright 2011 Rob Landley <rob@landley.net> + * + * See http://opengroup.org/onlinepubs/9699919799/utilities/nohup.html + +USE_NOHUP(NEWTOY(nohup, "<1", TOYFLAG_USR|TOYFLAG_BIN)) + +config NOHUP + bool "nohup" + default y + help + usage: nohup COMMAND [ARGS...] + + Run a command that survives the end of its terminal. + If stdin is a tty, redirect from /dev/null + If stdout is a tty, redirect to file "nohup.out" +*/ + +#include "toys.h" + +void nohup_main(void) +{ + signal(SIGHUP, SIG_IGN); + if (isatty(1)) { + close(1); + if (-1 == open("nohup.out", O_CREAT|O_APPEND|O_WRONLY, + S_IRUSR|S_IWUSR )) + { + char *temp = getenv("HOME"); + temp = xmsprintf("%s/%s", temp ? temp : "", "nohup.out"); + xcreate(temp, O_CREAT|O_APPEND|O_WRONLY, S_IRUSR|S_IWUSR); + } + } + if (isatty(0)) { + close(0); + open("/dev/null", O_RDONLY); + } + xexec(toys.optargs); +} diff --git a/toys/posix/od.c b/toys/posix/od.c new file mode 100644 index 00000000..dfde4440 --- /dev/null +++ b/toys/posix/od.c @@ -0,0 +1,274 @@ +/* vi: set sw=4 ts=4: + * + * od.c - Provide octal/hex dumps of data + * + * Copyright 2012 Andre Renaud <andre@bluewatersys.com> + * Copyright 2012 Rob Landley <rob@landley.net> + * + * See http://pubs.opengroup.org/onlinepubs/9699919799/utilities/od.html + +USE_OD(NEWTOY(od, "j#vN#xsodcbA:t*", TOYFLAG_USR|TOYFLAG_BIN)) + +config OD + bool "od" + default y + help + usage: od [-bdosxv] [-j #] [-N #] [-A doxn] [-t arg] + + -A Address base (decimal, octal, hexdecimal, none) + -t output type(s) a (ascii) c (char) d (decimal) foux +*/ + +#include "toys.h" + +#define FLAG_t (1 << 0) +#define FLAG_A (1 << 1) +#define FLAG_b (1 << 2) +#define FLAG_c (1 << 3) +#define FLAG_d (1 << 4) +#define FLAG_o (1 << 5) +#define FLAG_s (1 << 6) +#define FLAG_x (1 << 7) +#define FLAG_N (1 << 8) +#define FLAG_v (1 << 9) + +DEFINE_GLOBALS( + struct arg_list *output_base; + char *address_base; + long max_count; + long jump_bytes; + + unsigned types, leftover, star, address_idx; + char *buf; + uint64_t bufs[4]; // force 64-bit alignment + off_t pos; +) + +#define TT this.od + +static char *ascii = "nulsohstxetxeotenqackbel bs ht nl vt ff cr so si" + "dledc1dc2dc3dc4naksynetbcan emsubesc fs gs rs us sp"; + +struct odtype { + int type; + int size; +}; + +static void od_outline(void) +{ + unsigned flags = toys.optflags; + char *abases[] = {"", "%07d", "%07o", "%06x"}; + struct odtype *types = (struct odtype *)toybuf, *t; + int i, len; + + if (TT.leftover<16) memset(TT.buf+TT.leftover, 0, 16-TT.leftover); + + // Handle duplciate lines as * + if (!(flags&FLAG_v) && TT.jump_bytes != TT.pos && TT.leftover + && !memcmp(TT.bufs, TT.bufs + 2, 16)) + { + if (!TT.star) { + xputs("*"); + TT.star++; + } + + // Print line position + } else { + TT.star = 0; + + xprintf(abases[TT.address_idx], TT.pos); + if (!TT.leftover) { + if (TT.address_idx) xputc('\n'); + return; + } + } + + TT.pos += len = TT.leftover; + TT.leftover = 0; + if (TT.star) return; + + // For each output type, print one line + + for (i=0; i<TT.types; i++) { + int j = 0, pad = i ? 8 : 0; + char buf[128]; + + t = types+i; + while (j<len) { + unsigned k; + int throw = 0; + + // Handle ascii + if (t->type < 2) { + char c = TT.buf[j++]; + pad += 4; + + if (!t->type) { + c &= 127; + if (c<=32) sprintf(buf, "%.3s", ascii+(3*c)); + else if (c==127) strcpy(buf, "del"); + else sprintf(buf, "%c", c); + } else { + char *bfnrtav = "\b\f\n\r\t\a\v", *s = strchr(bfnrtav, c); + if (s) sprintf(buf, "\\%c", "bfnrtav0"[s-bfnrtav]); + else if (c < 32 || c >= 127) sprintf(buf, "%03o", c); + else { + // TODO: this should be UTF8 aware. + sprintf(buf, "%c", c); + } + } + } else if (CFG_TOYBOX_FLOAT && t->type == 6) { + long double ld; + union {float f; double d; long double ld;} fdl; + + memcpy(&fdl, TT.buf+j, t->size); + j += t->size; + if (sizeof(float) == t->size) { + ld = fdl.f; + pad += (throw = 8)+7; + } else if (sizeof(double) == t->size) { + ld = fdl.d; + pad += (throw = 17)+8; + } else if (sizeof(long double) == t->size) { + ld = fdl.ld; + pad += (throw = 21)+9; + } else error_exit("bad -tf '%d'", t->size); + + sprintf(buf, "%.*Le", throw, ld); + // Integer types + } else { + unsigned long long ll = 0, or; + char *c[] = {"%*lld", "%*llu", "%0*llo", "%0*llx"}, + *class = c[t->type-2]; + + // Work out width of field + if (t->size == 8) { + or = -1LL; + if (t->type == 2) or >>= 1; + } else or = (1LL<<(8*t->size))-1; + throw = sprintf(buf, class, 0, or); + + // Accumulate integer based on size argument + for (k=0; k < t->size; k++) { + or = TT.buf[j++]; + ll |= or << (8*(IS_BIG_ENDIAN ? t->size-k-1 : k)); + } + + // Handle negative values + if (t->type == 2) { + or = sizeof(or) - t->size; + throw++; + if (or && (ll & (1l<<((8*t->size)-1)))) + ll |= ((or<<(8*or))-1) << (8*t->size); + } + + sprintf(buf, class, throw, ll); + pad += throw+1; + } + xprintf("%*s", pad, buf); + pad = 0; + } + xputc('\n'); + } + + // buffer toggle for "same as last time" check. + TT.buf = (char *)((TT.buf == (char *)TT.bufs) ? TT.bufs+2 : TT.bufs); +} + +static void do_od(int fd, char *name) +{ + // Skip input, possibly more than one entire file. + if (TT.jump_bytes < TT.pos) { + off_t off = lskip(fd, TT.jump_bytes); + if (off > 0) TT.pos += off; + if (TT.jump_bytes < TT.pos) return; + } + + for(;;) { + char *buf = TT.buf + TT.leftover; + int len = 16 - TT.leftover; + + if (toys.optflags & FLAG_N) { + if (!TT.max_count) break; + if (TT.max_count < len) len = TT.max_count; + } + + len = readall(fd, buf, len); + if (len < 0) { + perror_msg("%s", name); + break; + } + if (TT.max_count) TT.max_count -= len; + TT.leftover += len; + if (TT.leftover < 16) break; + + od_outline(); + } +} + +static void append_base(char *base) +{ + char *s = base; + struct odtype *types = (struct odtype *)toybuf; + int type; + + for (;;) { + int size = 1; + + if (!*s) return; + if (TT.types >= sizeof(toybuf)/sizeof(struct odtype)) break; + if (-1 == (type = stridx("acduox"USE_TOYBOX_FLOAT("f"), *(s++)))) break; + + if (isdigit(*s)) { + size = strtol(s, &s, 10); + if (type < 2 && size != 1) break; + if (CFG_TOYBOX_FLOAT && type == 6 && size == sizeof(long double)); + else if (size < 0 || size > 8) break; + } else if (CFG_TOYBOX_FLOAT && type == 6) { + int sizes[] = {sizeof(float), sizeof(double), sizeof(long double)}; + if (-1 == (size = stridx("FDL", *s))) size = sizeof(double); + else { + s++; + size = sizes[size]; + } + } else if (type > 1) { + if (-1 == (size = stridx("CSIL", *s))) size = 4; + else { + s++; + size = 1 << size; + } + } + + types[TT.types].type = type; + types[TT.types].size = size; + TT.types++; + } + + error_exit("bad -t %s", base); +} + +void od_main(void) +{ + struct arg_list *arg; + + TT.buf = (char *)TT.bufs; + + if (!TT.address_base) TT.address_idx = 2; + else if (0>(TT.address_idx = stridx("ndox", *TT.address_base))) + error_exit("bad -A '%c'", *TT.address_base); + + // Collect -t entries + + for (arg = TT.output_base; arg; arg = arg->next) append_base(arg->arg); + if (toys.optflags & FLAG_b) append_base("o1"); + if (toys.optflags & FLAG_d) append_base("u2"); + if (toys.optflags & FLAG_o) append_base("o2"); + if (toys.optflags & FLAG_s) append_base("d2"); + if (toys.optflags & FLAG_x) append_base("x2"); + if (!TT.output_base) append_base("o2"); + + loopfiles(toys.optargs, do_od); + + if (TT.leftover) od_outline(); + od_outline(); +} diff --git a/toys/posix/patch.c b/toys/posix/patch.c new file mode 100644 index 00000000..647bb18f --- /dev/null +++ b/toys/posix/patch.c @@ -0,0 +1,412 @@ +/* vi: set sw=4 ts=4: + * + * patch.c - Apply a "universal" diff. + * + * Copyright 2007 Rob Landley <rob@landley.net> + * + * see http://www.opengroup.org/onlinepubs/009695399/utilities/patch.html + * (But only does -u, because who still cares about "ed"?) + * + * TODO: + * -b backup + * -l treat all whitespace as a single space + * -N ignore already applied + * -d chdir first + * -D define wrap #ifdef and #ifndef around changes + * -o outfile output here instead of in place + * -r rejectfile write rejected hunks to this file + * + * -E remove empty files --remove-empty-files + * -f force (no questions asked) + * -F fuzz (number, default 2) + * [file] which file to patch + +USE_PATCH(NEWTOY(patch, USE_TOYBOX_DEBUG("x")"up#i:R", TOYFLAG_USR|TOYFLAG_BIN)) + +config PATCH + bool "patch" + default y + help + usage: patch [-i file] [-p depth] [-Ru] + + Apply a unified diff to one or more files. + + -i Input file (defaults=stdin) + -p number of '/' to strip from start of file paths (default=all) + -R Reverse patch. + -u Ignored (only handles "unified" diffs) + + This version of patch only handles unified diffs, and only modifies + a file when all all hunks to that file apply. Patch prints failed + hunks to stderr, and exits with nonzero status if any hunks fail. + + A file compared against /dev/null (or with a date <= the epoch) is + created/deleted as appropriate. +*/ + +#include "toys.h" + +DEFINE_GLOBALS( + char *infile; + long prefix; + + struct double_list *current_hunk; + long oldline, oldlen, newline, newlen; + long linenum; + int context, state, filein, fileout, filepatch, hunknum; + char *tempname; +) + +#define TT this.patch + +#define FLAG_REVERSE 1 +#define FLAG_PATHLEN 4 + +// Dispose of a line of input, either by writing it out or discarding it. + +// state < 2: just free +// state = 2: write whole line to stderr +// state = 3: write whole line to fileout +// state > 3: write line+1 to fileout when *line != state + +#define PATCH_DEBUG (CFG_TOYBOX_DEBUG && (toys.optflags & 16)) + +static void do_line(void *data) +{ + struct double_list *dlist = (struct double_list *)data; + + if (TT.state>1 && *dlist->data != TT.state) { + char *s = dlist->data+(TT.state>3 ? 1 : 0); + int i = TT.state == 2 ? 2 : TT.fileout; + + xwrite(i, s, strlen(s)); + xwrite(i, "\n", 1); + } + + if (PATCH_DEBUG) fprintf(stderr, "DO %d: %s\n", TT.state, dlist->data); + + free(dlist->data); + free(data); +} + +static void finish_oldfile(void) +{ + if (TT.tempname) replace_tempfile(TT.filein, TT.fileout, &TT.tempname); + TT.fileout = TT.filein = -1; +} + +static void fail_hunk(void) +{ + if (!TT.current_hunk) return; + TT.current_hunk->prev->next = 0; + + fprintf(stderr, "Hunk %d FAILED %ld/%ld.\n", + TT.hunknum, TT.oldline, TT.newline); + toys.exitval = 1; + + // If we got to this point, we've seeked to the end. Discard changes to + // this file and advance to next file. + + TT.state = 2; + llist_traverse(TT.current_hunk, do_line); + TT.current_hunk = NULL; + delete_tempfile(TT.filein, TT.fileout, &TT.tempname); + TT.state = 0; +} + +// Given a hunk of a unified diff, make the appropriate change to the file. +// This does not use the location information, but instead treats a hunk +// as a sort of regex. Copies data from input to output until it finds +// the change to be made, then outputs the changed data and returns. +// (Finding EOF first is an error.) This is a single pass operation, so +// multiple hunks must occur in order in the file. + +static int apply_one_hunk(void) +{ + struct double_list *plist, *buf = NULL, *check; + int matcheof = 0, reverse = toys.optflags & FLAG_REVERSE, backwarn = 0; + + // Break doubly linked list so we can use singly linked traversal function. + TT.current_hunk->prev->next = NULL; + + // Match EOF if there aren't as many ending context lines as beginning + for (plist = TT.current_hunk; plist; plist = plist->next) { + if (plist->data[0]==' ') matcheof++; + else matcheof = 0; + if (PATCH_DEBUG) fprintf(stderr, "HUNK:%s\n", plist->data); + } + matcheof = matcheof < TT.context; + + if (PATCH_DEBUG) fprintf(stderr,"MATCHEOF=%c\n", matcheof ? 'Y' : 'N'); + + // Loop through input data searching for this hunk. Match all context + // lines and all lines to be removed until we've found the end of a + // complete hunk. + plist = TT.current_hunk; + buf = NULL; + if (TT.context) for (;;) { + char *data = get_line(TT.filein); + + TT.linenum++; + + // Figure out which line of hunk to compare with next. (Skip lines + // of the hunk we'd be adding.) + while (plist && *plist->data == "+-"[reverse]) { + if (data && !strcmp(data, plist->data+1)) { + if (!backwarn) backwarn = TT.linenum; + } + plist = plist->next; + } + + // Is this EOF? + if (!data) { + if (PATCH_DEBUG) fprintf(stderr, "INEOF\n"); + + // Does this hunk need to match EOF? + if (!plist && matcheof) break; + + if (backwarn) + fprintf(stderr, "Possibly reversed hunk %d at %ld\n", + TT.hunknum, TT.linenum); + + // File ended before we found a place for this hunk. + fail_hunk(); + goto done; + } else if (PATCH_DEBUG) fprintf(stderr, "IN: %s\n", data); + check = dlist_add(&buf, data); + + // Compare this line with next expected line of hunk. + // todo: teach the strcmp() to ignore whitespace. + + // A match can fail because the next line doesn't match, or because + // we hit the end of a hunk that needed EOF, and this isn't EOF. + + // If match failed, flush first line of buffered data and + // recheck buffered data for a new match until we find one or run + // out of buffer. + + for (;;) { + if (!plist || strcmp(check->data, plist->data+1)) { + // Match failed. Write out first line of buffered data and + // recheck remaining buffered data for a new match. + + if (PATCH_DEBUG) + fprintf(stderr, "NOT: %s\n", plist->data); + + TT.state = 3; + check = llist_pop(&buf); + check->prev->next = buf; + buf->prev = check->prev; + do_line(check); + plist = TT.current_hunk; + + // If we've reached the end of the buffer without confirming a + // match, read more lines. + if (check==buf) { + buf = 0; + break; + } + check = buf; + } else { + if (PATCH_DEBUG) + fprintf(stderr, "MAYBE: %s\n", plist->data); + // This line matches. Advance plist, detect successful match. + plist = plist->next; + if (!plist && !matcheof) goto out; + check = check->next; + if (check == buf) break; + } + } + } +out: + // We have a match. Emit changed data. + TT.state = "-+"[reverse]; + llist_traverse(TT.current_hunk, do_line); + TT.current_hunk = NULL; + TT.state = 1; +done: + if (buf) { + buf->prev->next = NULL; + llist_traverse(buf, do_line); + } + + return TT.state; +} + +// Read a patch file and find hunks, opening/creating/deleting files. +// Call apply_one_hunk() on each hunk. + +// state 0: Not in a hunk, look for +++. +// state 1: Found +++ file indicator, look for @@ +// state 2: In hunk: counting initial context lines +// state 3: In hunk: getting body + +void patch_main(void) +{ + int reverse = toys.optflags&FLAG_REVERSE, state = 0, patchlinenum = 0, + strip = 0; + char *oldname = NULL, *newname = NULL; + + if (TT.infile) TT.filepatch = xopen(TT.infile, O_RDONLY); + TT.filein = TT.fileout = -1; + + // Loop through the lines in the patch + for (;;) { + char *patchline; + + patchline = get_line(TT.filepatch); + if (!patchline) break; + + // Other versions of patch accept damaged patches, + // so we need to also. + if (strip || !patchlinenum++) { + int len = strlen(patchline); + if (patchline[len-1] == '\r') { + if (!strip) fprintf(stderr, "Removing DOS newlines\n"); + strip = 1; + patchline[len-1]=0; + } + } + if (!*patchline) { + free(patchline); + patchline = xstrdup(" "); + } + + // Are we assembling a hunk? + if (state >= 2) { + if (*patchline==' ' || *patchline=='+' || *patchline=='-') { + dlist_add(&TT.current_hunk, patchline); + + if (*patchline != '+') TT.oldlen--; + if (*patchline != '-') TT.newlen--; + + // Context line? + if (*patchline==' ' && state==2) TT.context++; + else state=3; + + // If we've consumed all expected hunk lines, apply the hunk. + + if (!TT.oldlen && !TT.newlen) state = apply_one_hunk(); + continue; + } + fail_hunk(); + state = 0; + continue; + } + + // Open a new file? + if (!strncmp("--- ", patchline, 4) || !strncmp("+++ ", patchline, 4)) { + char *s, **name = &oldname; + int i; + + if (*patchline == '+') { + name = &newname; + state = 1; + } + + free(*name); + finish_oldfile(); + + // Trim date from end of filename (if any). We don't care. + for (s = patchline+4; *s && *s!='\t'; s++) + if (*s=='\\' && s[1]) s++; + i = atoi(s); + if (i>1900 && i<=1970) + *name = xstrdup("/dev/null"); + else { + *s = 0; + *name = xstrdup(patchline+4); + } + + // We defer actually opening the file because svn produces broken + // patches that don't signal they want to create a new file the + // way the patch man page says, so you have to read the first hunk + // and _guess_. + + // Start a new hunk? Usually @@ -oldline,oldlen +newline,newlen @@ + // but a missing ,value means the value is 1. + } else if (state == 1 && !strncmp("@@ -", patchline, 4)) { + int i; + char *s = patchline+4; + + // Read oldline[,oldlen] +newline[,newlen] + + TT.oldlen = TT.newlen = 1; + TT.oldline = strtol(s, &s, 10); + if (*s == ',') TT.oldlen=strtol(s+1, &s, 10); + TT.newline = strtol(s+2, &s, 10); + if (*s == ',') TT.newlen = strtol(s+1, &s, 10); + + TT.context = 0; + state = 2; + + // If this is the first hunk, open the file. + if (TT.filein == -1) { + int oldsum, newsum, del = 0; + char *name; + + oldsum = TT.oldline + TT.oldlen; + newsum = TT.newline + TT.newlen; + + name = reverse ? oldname : newname; + + // We're deleting oldname if new file is /dev/null (before -p) + // or if new hunk is empty (zero context) after patching + if (!strcmp(name, "/dev/null") || !(reverse ? oldsum : newsum)) + { + name = reverse ? newname : oldname; + del++; + } + + // handle -p path truncation. + for (i = 0, s = name; *s;) { + if ((toys.optflags & FLAG_PATHLEN) && TT.prefix == i) break; + if (*s++ != '/') continue; + while (*s == '/') s++; + name = s; + i++; + } + + if (del) { + printf("removing %s\n", name); + xunlink(name); + state = 0; + // If we've got a file to open, do so. + } else if (!(toys.optflags & FLAG_PATHLEN) || i <= TT.prefix) { + // If the old file was null, we're creating a new one. + if (!strcmp(oldname, "/dev/null") || !oldsum) { + printf("creating %s\n", name); + s = strrchr(name, '/'); + if (s) { + *s = 0; + xmkpath(name, -1); + *s = '/'; + } + TT.filein = xcreate(name, O_CREAT|O_EXCL|O_RDWR, 0666); + } else { + printf("patching %s\n", name); + TT.filein = xopen(name, O_RDWR); + } + TT.fileout = copy_tempfile(TT.filein, name, &TT.tempname); + TT.linenum = 0; + TT.hunknum = 0; + } + } + + TT.hunknum++; + + continue; + } + + // If we didn't continue above, discard this line. + free(patchline); + } + + finish_oldfile(); + + if (CFG_TOYBOX_FREE) { + close(TT.filepatch); + free(oldname); + free(newname); + } +} diff --git a/toys/posix/pwd.c b/toys/posix/pwd.c new file mode 100644 index 00000000..d84c504a --- /dev/null +++ b/toys/posix/pwd.c @@ -0,0 +1,30 @@ +/* vi: set sw=4 ts=4: + * + * pwd.c - Print working directory. + * + * Copyright 2006 Rob Landley <rob@landley.net> + * + * See http://www.opengroup.org/onlinepubs/009695399/utilities/echo.html + * + * TODO: add -L -P + +USE_PWD(NEWTOY(pwd, NULL, TOYFLAG_BIN)) + +config PWD + bool "pwd" + default y + help + usage: pwd + + The print working directory command prints the current directory. +*/ + +#include "toys.h" + +void pwd_main(void) +{ + char *pwd = xgetcwd(); + + xprintf("%s\n", pwd); + if (CFG_TOYBOX_FREE) free(pwd); +} diff --git a/toys/posix/rmdir.c b/toys/posix/rmdir.c new file mode 100644 index 00000000..be93cb69 --- /dev/null +++ b/toys/posix/rmdir.c @@ -0,0 +1,43 @@ +/* vi: set sw=4 ts=4: + * + * rmdir.c - remove directory/path + * + * Copyright 2008 Rob Landley <rob@landley.net> + * + * See http://opengroup.org/onlinepubs/9699919799/utilities/rmdir.html + +USE_RMDIR(NEWTOY(rmdir, "<1p", TOYFLAG_BIN)) + +config RMDIR + bool "rmdir" + default y + help + usage: rmdir [-p] [dirname...] + Remove one or more directories. + + -p Remove path. +*/ + +#include "toys.h" + +static void do_rmdir(char *name) +{ + for (;;) { + char *temp; + + if (rmdir(name)) { + perror_msg("%s",name); + return; + } + if (!toys.optflags) return; + if (!(temp=strrchr(name,'/'))) return; + *temp=0; + } +} + +void rmdir_main(void) +{ + char **s; + + for (s=toys.optargs; *s; s++) do_rmdir(*s); +} diff --git a/toys/posix/sed.c b/toys/posix/sed.c new file mode 100644 index 00000000..c4eb1a44 --- /dev/null +++ b/toys/posix/sed.c @@ -0,0 +1,63 @@ +/* vi: set sw=4 ts=4: + * + * sed.c - Stream editor. + * + * Copyright 2008 Rob Landley <rob@landley.net> + * + * See http://www.opengroup.org/onlinepubs/009695399/utilities/sed.c + +USE_SED(NEWTOY(sed, "irne*", TOYFLAG_BIN)) + +config SED + bool "sed" + default n + help + usage: sed [-irn] {command | [-e command]...} [FILE...] + + Stream EDitor, transforms text by appling commands to each line + of input. +*/ + +#include "toys.h" +#include "lib/xregcomp.h" + +DEFINE_GLOBALS( + struct arg_list *commands; +) + +#define TT this.sed + +struct sed_command { + // Doubly linked list of commands. + struct sed_command *next, *prev; + + // Regexes for s/match/data/ and /match_begin/,/match_end/command + regex_t *match, *match_begin, *match_end; + + // For numeric ranges ala 10,20command + int first_line, last_line; + + // Which match to replace, 0 for all. + int which; + + // s and w commands can write to a file. Slight optimization: we use 0 + // instead of -1 to mean no file here, because even when there's no stdin + // our input file would take fd 0. + int outfd; + + // Data string for (saicytb) + char *data; + + // Which command letter is this? + char command; +}; + +void sed_main(void) +{ + struct arg_list *test; + + for (test = TT.commands; test; test = test->next) + dprintf(2,"command=%s\n",test->arg); + + printf("Hello world\n"); +} diff --git a/toys/posix/sleep.c b/toys/posix/sleep.c new file mode 100644 index 00000000..ef6d827b --- /dev/null +++ b/toys/posix/sleep.c @@ -0,0 +1,51 @@ +/* vi: set sw=4 ts=4: + * + * sleep.c - Wait for a number of seconds. + * + * Copyright 2007 Rob Landley <rob@landley.net> + * Copyright 2012 Georgi Chorbadzhiyski <georgi@unixsol.org> + * + * See http://www.opengroup.org/onlinepubs/009695399/utilities/sleep.html + +USE_SLEEP(NEWTOY(sleep, "<1", TOYFLAG_BIN)) + +config SLEEP + bool "sleep" + default y + help + usage: sleep SECONDS + + Wait before exiting. + +config SLEEP_FLOAT + bool + default y + depends on SLEEP && TOYBOX_FLOAT + help + The delay can be a decimal fraction. An optional suffix can be "m" + (minutes), "h" (hours), "d" (days), or "s" (seconds, the default). +*/ + +#include "toys.h" + +void sleep_main(void) +{ + + if (!CFG_TOYBOX_FLOAT) toys.exitval = sleep(atol(*toys.optargs)); + else { + char *arg; + double d = strtod(*toys.optargs, &arg); + struct timespec tv; + + // Parse suffix + if (*arg) { + int ismhd[]={1,60,3600,86400}; + char *smhd = "smhd", *c = strchr(smhd, *arg); + if (!c) error_exit("Unknown suffix '%c'", *arg); + d *= ismhd[c-smhd]; + } + + tv.tv_nsec=1000000000*(d-(tv.tv_sec = (unsigned long)d)); + toys.exitval = !!nanosleep(&tv, NULL); + } +} diff --git a/toys/posix/sort.c b/toys/posix/sort.c new file mode 100644 index 00000000..07a57163 --- /dev/null +++ b/toys/posix/sort.c @@ -0,0 +1,421 @@ +/* vi: set sw=4 ts=4: + * + * sort.c - put input lines into order + * + * Copyright 2004, 2008 Rob Landley <rob@landley.net> + * + * See http://www.opengroup.org/onlinepubs/007904975/utilities/sort.html + +USE_SORT(NEWTOY(sort, USE_SORT_FLOAT("g")USE_SORT_BIG("S:T:m" "o:k*t:xbMcszdfi") "run", TOYFLAG_USR|TOYFLAG_BIN)) + +config SORT + bool "sort" + default y + help + usage: sort [-run] [FILE...] + + Sort all lines of text from input files (or stdin) to stdout. + + -r reverse + -u unique lines only + -n numeric order (instead of alphabetical) + +config SORT_BIG + bool "SuSv3 options (Support -ktcsbdfiozM)" + default y + depends on SORT + 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 + + 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 + the second word, -k2,4 looks from the start of the second to the end + of the fourth word. Specifying multiple keys uses the later keys as + tie breakers, in order. A type specifier appended to a sort key + (such as -2,2n) applies only to sorting that key. + +config SORT_FLOAT + bool "Floating point (-g)" + default y + depends on SORT_BIG + help + usage: sort [-g] + + This version of sort requires floating point. + + -g general numeric sort (double precision with nan and inf) + +*/ + +#include "toys.h" + +DEFINE_GLOBALS( + char *key_separator; + struct arg_list *raw_keys; + char *outfile; + char *ignore1, ignore2; // GNU compatability NOPs for -S and -T. + + void *key_list; + int linecount; + char **lines; +) + +#define TT this.sort + +// The sort types are n, g, and M. +// u, c, s, and z apply to top level only, not to keys. +// b at top level implies bb. +// The remaining options can be applied to search keys. + +#define FLAG_n (1<<0) // Sort type: numeric +#define FLAG_u (1<<1) // Unique +#define FLAG_r (1<<2) // Reverse output order + +#define FLAG_i (1<<3) // Ignore !isprint() +#define FLAG_f (1<<4) // Force uppercase +#define FLAG_d (1<<5) // Ignore !(isalnum()|isspace()) +#define FLAG_z (1<<6) // Input is null terminated, not \n +#define FLAG_s (1<<7) // Stable sort, no ascii fallback at end +#define FLAG_c (1<<8) // Check only. No output, exit(!ordered) +#define FLAG_M (1<<9) // Sort type: date +#define FLAG_b (1<<10) // Ignore leading blanks +#define FLAG_x (1<<11) // Hex sort +#define FLAG_g (1<<18) // Sort type: strtod() + +// Left off dealing with FLAG_b/FLAG_bb logic... + +#define FLAG_bb (1<<31) // Ignore trailing blanks + +struct sort_key +{ + struct sort_key *next_key; // linked list + unsigned range[4]; // start word, start char, end word, end char + int flags; +}; + +// Copy of the part of this string corresponding to a key/flags. + +static char *get_key_data(char *str, struct sort_key *key, int flags) +{ + int start=0, end, len, i, j; + + // Special case whole string, so we don't have to make a copy + + if(key->range[0]==1 && !key->range[1] && !key->range[2] && !key->range[3] + && !(flags&(FLAG_b&FLAG_d&FLAG_f&FLAG_i&FLAG_bb))) return str; + + // Find start of key on first pass, end on second pass + + len = strlen(str); + for (j=0; j<2; j++) { + if (!key->range[2*j]) end=len; + + // Loop through fields + else { + end=0; + for (i=1; i < key->range[2*j]+j; i++) { + + // Skip leading blanks + if (str[end] && !TT.key_separator) + while (isspace(str[end])) end++; + + // Skip body of key + for (; str[end]; end++) { + if (TT.key_separator) { + if (str[end]==*TT.key_separator) break; + } else if (isspace(str[end])) break; + } + } + } + if (!j) start=end; + } + + // Key with explicit separator starts after the separator + if (TT.key_separator && str[start]==*TT.key_separator) start++; + + // Strip leading and trailing whitespace if necessary + if (flags&FLAG_b) while (isspace(str[start])) start++; + if (flags&FLAG_bb) while (end>start && isspace(str[end-1])) end--; + + // Handle offsets on start and end + if (key->range[3]) { + end += key->range[3]-1; + if (end>len) end=len; + } + if (key->range[1]) { + start += key->range[1]-1; + if (start>len) start=len; + } + + // Make the copy + if (end<start) end=start; + str = xstrndup(str+start, end-start); + + // Handle -d + if (flags&FLAG_d) { + for (start = end = 0; str[end]; end++) + if (isspace(str[end]) || isalnum(str[end])) str[start++] = str[end]; + str[start] = 0; + } + + // Handle -i + if (flags&FLAG_i) { + for (start = end = 0; str[end]; end++) + if (isprint(str[end])) str[start++] = str[end]; + str[start] = 0; + } + + // Handle -f + if (flags*FLAG_f) for(i=0; str[i]; i++) str[i] = toupper(str[i]); + + return str; +} + +// append a sort_key to key_list. + +static struct sort_key *add_key(void) +{ + void **stupid_compiler = &TT.key_list; + struct sort_key **pkey = (struct sort_key **)stupid_compiler; + + while (*pkey) pkey = &((*pkey)->next_key); + return *pkey = xzalloc(sizeof(struct sort_key)); +} + +// Perform actual comparison +static int compare_values(int flags, char *x, char *y) +{ + int ff = flags & (FLAG_n|FLAG_g|FLAG_M|FLAG_x); + + // Ascii sort + if (!ff) return strcmp(x, y); + + if (CFG_SORT_FLOAT && ff == FLAG_g) { + char *xx,*yy; + double dx = strtod(x,&xx), dy = strtod(y,&yy); + int xinf, yinf; + + // not numbers < NaN < -infinity < numbers < +infinity + + if (x==xx) return y==yy ? 0 : -1; + if (y==yy) return 1; + + // Check for isnan + if (dx!=dx) return (dy!=dy) ? 0 : -1; + if (dy!=dy) return 1; + + // Check for infinity. (Could underflow, but avoids needing libm.) + xinf = (1.0/dx == 0.0); + yinf = (1.0/dy == 0.0); + if (xinf) { + if(dx<0) return (yinf && dy<0) ? 0 : -1; + return (yinf && dy>0) ? 0 : 1; + } + if (yinf) return dy<0 ? 1 : -1; + + return dx>dy ? 1 : (dx<dy ? -1 : 0); + } else if (CFG_SORT_BIG && ff == FLAG_M) { + struct tm thyme; + int dx; + char *xx,*yy; + + xx = strptime(x,"%b",&thyme); + dx = thyme.tm_mon; + yy = strptime(y,"%b",&thyme); + if (!xx) return !yy ? 0 : -1; + else if (!yy) return 1; + else return dx==thyme.tm_mon ? 0 : dx-thyme.tm_mon; + + } else if (CFG_SORT_BIG && ff == FLAG_x) { + return strtol(x, NULL, 16)-strtol(y, NULL, 16); + // This has to be ff == FLAG_n + } else { + // Full floating point version of -n + if (CFG_SORT_FLOAT) { + double dx = atof(x), dy = atof(y); + + return dx>dy ? 1 : (dx<dy ? -1 : 0); + // Integer version of -n for tiny systems + } else return atoi(x)-atoi(y); + } +} + + +// Callback from qsort(): Iterate through key_list and perform comparisons. +static int compare_keys(const void *xarg, const void *yarg) +{ + int flags = toys.optflags, retval = 0; + char *x, *y, *xx = *(char **)xarg, *yy = *(char **)yarg; + struct sort_key *key; + + if (CFG_SORT_BIG) { + for (key=(struct sort_key *)TT.key_list; !retval && key; + key = key->next_key) + { + flags = key->flags ? key->flags : toys.optflags; + + // Chop out and modify key chunks, handling -dfib + + x = get_key_data(xx, key, flags); + y = get_key_data(yy, key, flags); + + retval = compare_values(flags, x, y); + + // Free the copies get_key_data() made. + + if (x != xx) free(x); + if (y != yy) free(y); + + if (retval) break; + } + } else retval = compare_values(flags, xx, yy); + + // Perform fallback sort if necessary + if (!retval && !(CFG_SORT_BIG && (toys.optflags&FLAG_s))) { + retval = strcmp(xx, yy); + flags = toys.optflags; + } + + return retval * ((flags&FLAG_r) ? -1 : 1); +} + +// Callback from loopfiles to handle input files. +static void sort_read(int fd, char *name) +{ + // Read each line from file, appending to a big array. + + for (;;) { + char * line = (CFG_SORT_BIG && (toys.optflags&FLAG_z)) + ? get_rawline(fd, NULL, 0) : get_line(fd); + + if (!line) break; + + // handle -c here so we don't allocate more memory than necessary. + if (CFG_SORT_BIG && (toys.optflags&FLAG_c)) { + int j = (toys.optflags&FLAG_u) ? -1 : 0; + + if (TT.lines && compare_keys((void *)&TT.lines, &line)>j) + error_exit("%s: Check line %d\n", name, TT.linecount); + free(TT.lines); + TT.lines = (char **)line; + } else { + if (!(TT.linecount&63)) + TT.lines = xrealloc(TT.lines, sizeof(char *)*(TT.linecount+64)); + TT.lines[TT.linecount] = line; + } + TT.linecount++; + } +} + +void sort_main(void) +{ + int idx, fd = 1; + + // Open output file if necessary. + if (CFG_SORT_BIG && TT.outfile) + fd = xcreate(TT.outfile, O_CREAT|O_TRUNC|O_WRONLY, 0666); + + // Parse -k sort keys. + if (CFG_SORT_BIG && TT.raw_keys) { + struct arg_list *arg; + + for (arg = TT.raw_keys; arg; arg = arg->next) { + struct sort_key *key = add_key(); + char *temp; + int flag; + + idx = 0; + temp = arg->arg; + while (*temp) { + // Start of range + key->range[2*idx] = (unsigned)strtol(temp, &temp, 10); + if (*temp=='.') + key->range[(2*idx)+1] = (unsigned)strtol(temp+1, &temp, 10); + + // Handle flags appended to a key type. + for (;*temp;temp++) { + char *temp2, *optlist; + + // Note that a second comma becomes an "Unknown key" error. + + if (*temp==',' && !idx++) { + temp++; + break; + } + + // Which flag is this? + + optlist = toys.which->options; + temp2 = strchr(optlist, *temp); + flag = (1<<(optlist-temp2+strlen(optlist)-1)); + + // Was it a flag that can apply to a key? + + if (!temp2 || flag>FLAG_b + || (flag&(FLAG_u|FLAG_c|FLAG_s|FLAG_z))) + { + error_exit("Unknown key option."); + } + // b after , means strip _trailing_ space, not leading. + if (idx && flag==FLAG_b) flag = FLAG_bb; + key->flags |= flag; + } + } + } + } + + // global b flag strips both leading and trailing spaces + if (toys.optflags&FLAG_b) toys.optflags |= FLAG_bb; + + // If no keys, perform alphabetic sort over the whole line. + if (CFG_SORT_BIG && !TT.key_list) add_key()->range[0] = 1; + + // Open input files and read data, populating TT.lines[TT.linecount] + loopfiles(toys.optargs, sort_read); + + // The compare (-c) logic was handled in sort_read(), + // so if we got here, we're done. + if (CFG_SORT_BIG && (toys.optflags&FLAG_c)) goto exit_now; + + // Perform the actual sort + qsort(TT.lines, TT.linecount, sizeof(char *), compare_keys); + + // handle unique (-u) + if (toys.optflags&FLAG_u) { + int jdx; + + for (jdx=0, idx=1; idx<TT.linecount; idx++) { + if (!compare_keys(&TT.lines[jdx], &TT.lines[idx])) + free(TT.lines[idx]); + else TT.lines[++jdx] = TT.lines[idx]; + } + if (TT.linecount) TT.linecount = jdx+1; + } + + // Output result + for (idx = 0; idx<TT.linecount; idx++) { + char *s = TT.lines[idx]; + xwrite(fd, s, strlen(s)); + if (CFG_TOYBOX_FREE) free(s); + xwrite(fd, "\n", 1); + } + +exit_now: + if (CFG_TOYBOX_FREE) { + if (fd != 1) close(fd); + free(TT.lines); + } +} diff --git a/toys/posix/tail.c b/toys/posix/tail.c new file mode 100644 index 00000000..8783d6ba --- /dev/null +++ b/toys/posix/tail.c @@ -0,0 +1,232 @@ +/* vi: set sw=4 ts=4: + * + * tail.c - copy last lines from input to stdout. + * + * Copyright 2012 Timothy Elliott <tle@holymonkey.com> + * + * See http://www.opengroup.org/onlinepubs/009695399/utilities/tail.html + +USE_TAIL(NEWTOY(tail, "fc-n-", TOYFLAG_BIN)) + +config TAIL + bool "tail" + default y + help + usage: tail [-n|c number] [-f] [file...] + + Copy last lines from files to stdout. If no files listed, copy from + stdin. Filename "-" is a synonym for stdin. + + -n output the last X lines (default 10), +X counts from start. + -c output the last X bytes, +X counts from start + -f follow file, waiting for more data to be appended + +config TAIL_SEEK + bool "tail seek support" + default y + depends on TAIL + help + This version uses lseek, which is faster on large files. +*/ + +#include "toys.h" + +DEFINE_GLOBALS( + long lines; + long bytes; + + int file_no; +) + +#define TT this.tail + +#define FLAG_n 1 +#define FLAG_c 2 +#define FLAG_f 4 + +struct line_list { + struct line_list *next, *prev; + char *data; + int len; +}; + +static struct line_list *get_chunk(int fd, int len) +{ + struct line_list *line = xmalloc(sizeof(struct line_list)+len); + + line->data = ((char *)line) + sizeof(struct line_list); + line->len = readall(fd, line->data, len); + + if (line->len < 1) { + free(line); + return 0; + } + + return line; +} + +static void dump_chunk(void *ptr) +{ + struct line_list *list = ptr; + xwrite(1, list->data, list->len); + free(list); +} + +// Reading through very large files is slow. Using lseek can speed things +// up a lot, but isn't applicable to all input (cat | tail). +// Note: bytes and lines are negative here. +static int try_lseek(int fd, long bytes, long lines) +{ + struct line_list *list = 0, *temp; + int flag = 0, chunk = sizeof(toybuf); + ssize_t pos = lseek(fd, 0, SEEK_END); + + // If lseek() doesn't work on this stream, return now. + if (pos<0) return 0; + + // Seek to the right spot, output data from there. + if (bytes) { + if (lseek(fd, bytes, SEEK_END)<0) lseek(fd, 0, SEEK_SET); + xsendfile(fd, 1); + return 1; + } + + // Read from end to find enough lines, then output them. + + bytes = pos; + while (lines && pos) { + int offset; + + // Read in next chunk from end of file + if (chunk>pos) chunk = pos; + pos -= chunk; + if (pos != lseek(fd, pos, SEEK_SET)) { + perror_msg("seek failed"); + break; + } + if (!(temp = get_chunk(fd, chunk))) break; + if (list) list->next = temp; + list = temp; + + // Count newlines in this chunk. + offset = list->len; + while (offset--) { + // If the last line ends with a newline, that one doesn't count. + if (!flag) { + flag++; + + continue; + } + + // Start outputting data right after newline + if (list->data[offset] == '\n' && !++lines) { + offset++; + list->data += offset; + list->len -= offset; + + break; + } + } + } + + // Output stored data + llist_traverse(list, dump_chunk); + + // In case of -f + lseek(fd, bytes, SEEK_SET); + return 1; +} + +// Called for each file listed on command line, and/or stdin +static void do_tail(int fd, char *name) +{ + long bytes = TT.bytes, lines = TT.lines; + + if (toys.optc > 1) { + if (TT.file_no++) xputc('\n'); + xprintf("==> %s <==\n", name); + } + + // Are we measuring from the end of the file? + + if (bytes<0 || lines<0) { + struct line_list *list = 0, *new; + + // The slow codepath is always needed, and can handle all input, + // so make lseek support optional. + if (CFG_TAIL_SEEK && try_lseek(fd, bytes, lines)); + + // Read data until we run out, keep a trailing buffer + else for (;;) { + int len, count; + char *try; + + if (!(new = get_chunk(fd, sizeof(toybuf)))) break; + // append in order + dlist_add_nomalloc((struct double_list **)&list, + (struct double_list *)new); + + // Measure new chunk, discarding extra data from buffer + len = new->len; + try = new->data; + for (count=0; count<len; count++) { + if ((toys.optflags & FLAG_c) && bytes) { + bytes++; + continue; + } + + if (lines) { + if(try[count] != '\n' && count != len-1) continue; + if (lines<0) { + if (!++lines) ++lines; + continue; + } + } + + // Time to discard data; given that bytes and lines were + // nonzero coming in, we can't discard too much if we're + // measuring right. + do { + char c = *(list->data++); + if (!(--list->len)) { + struct line_list *next = list->next; + list->prev->next = next; + list->next->prev = list->prev; + free(list); + list = next; + } + if (c == '\n') break; + } while (lines); + } + } + + // Output/free the buffer. + llist_traverse(list, dump_chunk); + + // Measuring from the beginning of the file. + } else for (;;) { + int len, offset = 0; + + // Error while reading does not exit. Error writing does. + len = read(fd, toybuf, sizeof(toybuf)); + if (len<1) break; + while (bytes > 1 || lines > 1) { + bytes--; + if (toybuf[offset++] == '\n') lines--; + if (offset >= len) break; + } + if (offset<len) xwrite(1, toybuf+offset, len-offset); + } + + // -f support: cache name/descriptor +} + +void tail_main(void) +{ + // if nothing specified, default -n to -10 + if (!(toys.optflags&(FLAG_n|FLAG_c))) TT.lines = -10; + + loopfiles(toys.optargs, do_tail); + + // do -f stuff +} diff --git a/toys/posix/tee.c b/toys/posix/tee.c new file mode 100644 index 00000000..c11fb5c4 --- /dev/null +++ b/toys/posix/tee.c @@ -0,0 +1,75 @@ +/* vi: set sw=4 ts=4: + * + * tee.c - cat to multiple outputs. + * + * Copyright 2008 Rob Landley <rob@landley.net> + * + * See http://www.opengroup.org/onlinepubs/009695399/utilities/tee.html + +USE_TEE(NEWTOY(tee, "ia", TOYFLAG_BIN)) + +config TEE + bool "tee" + default y + help + usage: tee [-ai] [file...] + + Copy stdin to each listed file, and also to stdout. + Filename "-" is a synonym for stdout. + + -a append to files. + -i ignore SIGINT. +*/ + +#include "toys.h" + +DEFINE_GLOBALS( + void *outputs; +) + +#define TT this.tee + +struct fd_list { + struct fd_list *next; + int fd; +}; + +// Open each output file, saving filehandles to a linked list. + +static void do_tee_open(int fd, char *name) +{ + struct fd_list *temp; + + temp = xmalloc(sizeof(struct fd_list)); + temp->next = TT.outputs; + temp->fd = fd; + TT.outputs = temp; +} + +void tee_main(void) +{ + if (toys.optflags&2) signal(SIGINT, SIG_IGN); + + // Open output files + loopfiles_rw(toys.optargs, + O_RDWR|O_CREAT|((toys.optflags&1)?O_APPEND:O_TRUNC), 0666, 0, + do_tee_open); + + for (;;) { + struct fd_list *fdl; + int len; + + // Read data from stdin + len = xread(0, toybuf, sizeof(toybuf)); + if (len<1) break; + + // Write data to each output file, plus stdout. + fdl = TT.outputs; + for (;;) { + if(len != writeall(fdl ? fdl->fd : 1, toybuf, len)) toys.exitval=1; + if (!fdl) break; + fdl = fdl->next; + } + } + +} diff --git a/toys/posix/true.c b/toys/posix/true.c new file mode 100644 index 00000000..582b2cae --- /dev/null +++ b/toys/posix/true.c @@ -0,0 +1,23 @@ +/* vi: set sw=4 ts=4: + * + * true.c - Return zero. + * + * Copyright 2007 Rob Landley <rob@landley.net> + * + * See http://www.opengroup.org/onlinepubs/009695399/utilities/true.html + +USE_TRUE(NEWTOY(true, NULL, TOYFLAG_BIN)) + +config TRUE + bool "true" + default y + help + Return zero. +*/ + +#include "toys.h" + +void true_main(void) +{ + return; +} diff --git a/toys/posix/tty.c b/toys/posix/tty.c new file mode 100644 index 00000000..661e919a --- /dev/null +++ b/toys/posix/tty.c @@ -0,0 +1,32 @@ +/* vi: set sw=4 ts=4: + * + * tty.c - Show stdin's terminal name + * + * Copyright 2011 Rob Landley <rob@landley.net> + * + * See http://opengroup.org/onlinepubs/9699919799/utilities/tty.html + +USE_TTY(NEWTOY(tty, "s", TOYFLAG_USR|TOYFLAG_BIN)) + +config TTY + bool "tty" + default y + help + Show filename of terminal connected to stdin. + + Prints "not a tty" and exits with nonzero status if no terminal + is connected to stdin. + + -s silent mode +*/ + +#include "toys.h" + +void tty_main(void) +{ + char *tty = ttyname(0); + + if (!toys.optflags) puts(tty ? tty : "not a tty"); + + toys.exitval = !tty; +} diff --git a/toys/posix/uname.c b/toys/posix/uname.c new file mode 100644 index 00000000..185d633e --- /dev/null +++ b/toys/posix/uname.c @@ -0,0 +1,74 @@ +/* vi: set sw=4 ts=4: + * + * uname.c - return system name + * + * Copyright 2008 Rob Landley <rob@landley.net> + * + * See http://www.opengroup.org/onlinepubs/009695399/utilities/uname.html + +USE_UNAME(NEWTOY(uname, "amvrns", TOYFLAG_BIN)) + +config UNAME + bool "uname" + default y + help + usage: uname [-asnrvmpio] + + Print system information. + + -s System name + -n Network (domain) name + -r Release number + -v Version (build date) + -m Machine (hardware) name + -a All of the above +*/ + +#include "toys.h" + +// If a 32 bit x86 build environment working in a chroot under an x86-64 +// kernel returns x86_64 for -m it confuses ./configure. Special case it. + +#if defined(__i686__) +#define GROSS "i686" +#elif defined(__i586__) +#define GROSS "i586" +#elif defined(__i486__) +#define GROSS "i486" +#elif defined(__i386__) +#define GROSS "i386" +#endif + +#define FLAG_a (1<<5) + +void uname_main(void) +{ + int i, flags = toys.optflags, needspace=0; + + uname((void *)toybuf); + + if (!flags) flags=1; + for (i=0; i<5; i++) { + char *c = toybuf+(65*i); + + if (flags & ((1<<i)|FLAG_a)) { + int len = strlen(c); + + // This problem originates in autoconf, so of course the solution + // is horribly ugly. +#ifdef GROSS + if (i==4 && !strcmp(c,"x86_64")) printf(GROSS); + else +#endif + + if (needspace++) { + // We can't decrement on the first entry, because + // needspace would be 0 + *(--c)=' '; + len++; + } + xwrite(1, c, len); + } + } + putchar('\n'); +} diff --git a/toys/posix/uniq.c b/toys/posix/uniq.c new file mode 100644 index 00000000..c16c08f1 --- /dev/null +++ b/toys/posix/uniq.c @@ -0,0 +1,131 @@ +/* vi: set sw=4 ts=4: + * + * uniq.c - report or filter out repeated lines in a file + * + * Copyright 2012 Georgi Chorbadzhiyski <georgi@unixsol.org> + * + * See http://www.opengroup.org/onlinepubs/009695399/utilities/uniq.html + +USE_UNIQ(NEWTOY(uniq, "f#s#w#zicdu", TOYFLAG_BIN)) + +config UNIQ + bool "uniq" + default y + help + usage: uniq [-cduiz] [-w maxchars] [-f fields] [-s char] [input_file [output_file]] + + Report or filter out repeated lines in a file + + -c show counts before each line + -d show only lines that are repeated + -u show only lines that are unique + -i ignore case when comparing lines + -z lines end with \0 not \n + -w compare maximum X chars per line + -f ignore first X fields + -s ignore first X chars +*/ + +#include "toys.h" + +DEFINE_GLOBALS( + long maxchars; + long nchars; + long nfields; + long repeats; +) + +#define TT this.uniq + +#define FLAG_z 16 +#define FLAG_i 8 +#define FLAG_c 4 +#define FLAG_d 2 +#define FLAG_u 1 + +static char *skip(char *str) +{ + long nchars = TT.nchars, nfields; + + // Skip fields first + for (nfields = TT.nfields; nfields; str++) { + while (*str && isspace(*str)) str++; + while (*str && !isspace(*str)) str++; + nfields--; + } + // Skip chars + while (*str && nchars--) str++; + + return str; +} + +static void print_line(FILE *f, char *line) +{ + if (toys.optflags & (TT.repeats ? FLAG_u : FLAG_d)) return; + if (toys.optflags & FLAG_c) fprintf(f, "%7lu ", TT.repeats + 1); + fputs(line, f); + if (toys.optflags & FLAG_z) fputc(0, f); +} + +void uniq_main(void) +{ + FILE *infile = stdin, *outfile = stdout; + char *thisline = NULL, *prevline = NULL, *tmpline, eol = '\n'; + size_t thissize, prevsize = 0, tmpsize; + + if (toys.optc >= 1) infile = xfopen(toys.optargs[0], "r"); + if (toys.optc >= 2) outfile = xfopen(toys.optargs[1], "w"); + + if (toys.optflags & FLAG_z) eol = 0; + + // If first line can't be read + if (getdelim(&prevline, &prevsize, eol, infile) < 0) + return; + + while (getdelim(&thisline, &thissize, eol, infile) > 0) { + int diff; + char *t1, *t2; + + // If requested get the chosen fields + character offsets. + if (TT.nfields || TT.nchars) { + t1 = skip(thisline); + t2 = skip(prevline); + } else { + t1 = thisline; + t2 = prevline; + } + + if (TT.maxchars == 0) { + diff = !(toys.optflags & FLAG_i) + ? strcmp(t1, t2) + : strcasecmp(t1, t2); + } else { + diff = !(toys.optflags & FLAG_i) + ? strncmp(t1, t2, TT.maxchars) + : strncasecmp(t1, t2, TT.maxchars); + } + + if (diff == 0) { // same + TT.repeats++; + } else { + print_line(outfile, prevline); + + TT.repeats = 0; + + tmpline = prevline; + prevline = thisline; + thisline = tmpline; + + tmpsize = prevsize; + prevsize = thissize; + thissize = tmpsize; + } + } + + print_line(outfile, prevline); + + if (CFG_TOYBOX_FREE) { + free(prevline); + free(thisline); + } +} diff --git a/toys/posix/unlink.c b/toys/posix/unlink.c new file mode 100644 index 00000000..19660c5d --- /dev/null +++ b/toys/posix/unlink.c @@ -0,0 +1,26 @@ +/* vi: set sw=4 ts=4: + * + * unlink.c - delete one file + * + * Copyright 2011 Rob Landley <rob@landley.net> + * + * See http://opengroup.org/onlinepubs/9699919799/utilities/unlink.html + +USE_UNLINK(NEWTOY(unlink, "<1>1", TOYFLAG_USR|TOYFLAG_BIN)) + +config UNLINK + bool "unlink" + default y + help + usage: unlink FILE + + Deletes one file. +*/ + +#include "toys.h" + +void unlink_main(void) +{ + if (unlink(*toys.optargs)) + perror_exit("Couldn't unlink `%s'", *toys.optargs); +} diff --git a/toys/posix/wc.c b/toys/posix/wc.c new file mode 100644 index 00000000..17801188 --- /dev/null +++ b/toys/posix/wc.c @@ -0,0 +1,60 @@ +/* vi: set sw=4 ts=4: + * + * wc.c - Word count + * + * Copyright 2011 Rob Landley <rob@landley.net> + * + * See http://opengroup.org/onlinepubs/9699919799/utilities/wc.html + +USE_WC(NEWTOY(wc, "cwl", TOYFLAG_USR|TOYFLAG_BIN)) + +config WC + bool "wc" + default y + help + usage: wc -lwc [FILE...] + + Count lines, words, and characters in input. + + -l show lines + -w show words + -c show characters + + By default outputs lines, words, characters, and filename for each + argument (or from stdin if none). +*/ + +#include "toys.h" + +static void do_wc(int fd, char *name) +{ + int i, len; + unsigned long word=0, lengths[]={0,0,0}; + + for (;;) { + len = read(fd, toybuf, sizeof(toybuf)); + if (len<0) { + perror_msg("%s",name); + toys.exitval = EXIT_FAILURE; + } + if (len<1) break; + for (i=0; i<len; i++) { + if (toybuf[i]==10) lengths[0]++; + if (isspace(toybuf[i])) word=0; + else { + if (!word) lengths[1]++; + word=1; + } + lengths[2]++; + } + } + for (i=0; i<3; i++) + if (!toys.optflags || (toys.optflags&(1<<i))) + printf("%ld ", lengths[i]); + printf("%s\n", (!toys.optflags && strcmp(name,"-")) ? name : ""); +} + +void wc_main(void) +{ + loopfiles(toys.optargs, do_wc); +} diff --git a/toys/posix/who.c b/toys/posix/who.c new file mode 100644 index 00000000..d407c6b2 --- /dev/null +++ b/toys/posix/who.c @@ -0,0 +1,46 @@ +/* vi: set sw=4 ts=4: + * + * who.c - display who is on the system + * + * Copyright 2012 ProFUSION Embedded Systems + * + * by Luis Felipe Strano Moraes <lfelipe@profusion.mobi> + * + * See http://pubs.opengroup.org/onlinepubs/9699919799/utilities/who.html + +USE_WHO(NEWTOY(who, NULL, TOYFLAG_BIN)) + +config WHO + bool "who" + default n + help + usage: who + + Print logged user information on system + +*/ + +#include "toys.h" + +void who_main(void) +{ + struct utmpx *entry; + + setutxent(); + + while ((entry = getutxent())) { + if (entry->ut_type == USER_PROCESS) { + time_t time; + int time_size; + char * times; + + time = entry->ut_tv.tv_sec; + times = ctime(&time); + time_size = strlen(times) - 2; + printf("%s\t%s\t%*.*s\t(%s)\n", entry->ut_user, entry->ut_line, time_size, time_size, ctime(&time), entry->ut_host); + + } + } + + endutxent(); +} diff --git a/toys/posix/xargs.c b/toys/posix/xargs.c new file mode 100644 index 00000000..0d513253 --- /dev/null +++ b/toys/posix/xargs.c @@ -0,0 +1,188 @@ +/* vi: set sw=4 ts=4: + * + * xargs.c - Run command with arguments taken from stdin. + * + * Copyright 2011 Rob Landley <rob@landley.net> + * + * See http://opengroup.org/onlinepubs/9699919799/utilities/xargs.html + +USE_XARGS(NEWTOY(xargs, "^I:E:L#ptxrn#<1s#0", TOYFLAG_USR|TOYFLAG_BIN)) + +config XARGS + bool "xargs" + default y + help + usage: xargs [-ptxr0] [-s NUM] [-n NUM] [-L NUM] [-E STR] COMMAND... + + Run command line one or more times, appending arguments from stdin. + + If command exits with 255, don't launch another even if arguments remain. + + -s Size in bytes per command line + -n Max number of arguments per command + -0 Each argument is NULL terminated, no whitespace or quote processing + #-p Prompt for y/n from tty before running each command + #-t Trace, print command line to stderr + #-x Exit if can't fit everything in one command + #-r Don't run command with empty input + #-L Max number of lines of input per command + -E stop at line matching string +*/ + +#include "toys.h" + +DEFINE_GLOBALS( + long max_bytes; + long max_entries; + long L; + char *eofstr; + char *I; + + long entries, bytes; + char delim; +) + +#define TT this.xargs + +// If out==NULL count TT.bytes and TT.entries, stopping at max. +// Otherwise, fill out out[] + +// Returning NULL means need more data. +// Returning char * means hit data limits, start of data left over +// Returning 1 means hit data limits, but consumed all data +// Returning 2 means hit -E eofstr + +static char *handle_entries(char *data, char **entry) +{ + if (TT.delim) { + char *s = data; + + // Chop up whitespace delimited string into args + while (*s) { + char *save; + + while (isspace(*s)) { + if (entry) *s = 0; + s++; + } + + if (TT.max_entries && TT.entries >= TT.max_entries) + return *s ? s : (char *)1; + + if (!*s) break; + save = s; + + for (;;) { + if (++TT.bytes >= TT.max_bytes && TT.max_bytes) return save; + if (!*s || isspace(*s)) break; + s++; + } + if (TT.eofstr) { + int len = s-save; + if (len == strlen(TT.eofstr) && !strncmp(save, TT.eofstr, len)) + return (char *)2; + } + if (entry) entry[TT.entries] = save; + ++TT.entries; + } + + // -0 support + } else { + TT.bytes += strlen(data)+1; + if (TT.max_bytes && TT.bytes >= TT.max_bytes) return data; + if (TT.max_entries && TT.entries >= TT.max_entries) + return (char *)1; + if (entry) entry[TT.entries] = data; + TT.entries++; + } + + return NULL; +} + +void xargs_main(void) +{ + struct double_list *dlist = NULL; + int entries, bytes, done = 0, status; + char *data = NULL; + + if (!(toys.optflags&1)) TT.delim = '\n'; + + // If no optargs, call echo. + if (!toys.optc) { + free(toys.optargs); + *(toys.optargs = xzalloc(2*sizeof(char *)))="echo"; + toys.optc = 1; + } + + for (entries = 0, bytes = -1; entries < toys.optc; entries++, bytes++) + bytes += strlen(toys.optargs[entries]); + + // Loop through exec chunks. + while (data || !done) { + char **out; + + TT.entries = 0; + TT.bytes = bytes; + + // Loop reading input + for (;;) { + + // Read line + if (!data) { + ssize_t l = 0; + l = getdelim(&data, (size_t *)&l, TT.delim, stdin); + + if (l<0) { + data = 0; + done++; + break; + } + } + dlist_add(&dlist, data); + + // Count data used + data = handle_entries(data, NULL); + if (!data) continue; + if (data == (char *)2) done++; + if ((long)data <= 2) data = 0; + else data = xstrdup(data); + + break; + } + + // Accumulate cally thing + + if (data && !TT.entries) error_exit("argument too long"); + out = xzalloc((entries+TT.entries+1)*sizeof(char *)); + + if (dlist) { + struct double_list *dtemp; + + // Fill out command line to exec + memcpy(out, toys.optargs, entries*sizeof(char *)); + TT.entries = 0; + TT.bytes = bytes; + dlist->prev->next = 0; + for (dtemp = dlist; dtemp; dtemp = dtemp->next) + handle_entries(dtemp->data, out+entries); + } + pid_t pid=fork(); + if (!pid) { + xclose(0); + open("/dev/null", O_RDONLY); + xexec(out); + } + waitpid(pid, &status, 0); + status = WEXITSTATUS(status); + + // Abritrary number of execs, can't just leak memory each time... + while (dlist) { + struct double_list *dtemp = dlist->next; + + free(dlist->data); + free(dlist); + dlist = dtemp; + } + free(out); + } +} |