# HG changeset patch # User Ashwini Sharma # Date 1414712169 18000 # Node ID fd587c7872a97c18fd9f9d3c3ec6cc2a95411337 # Parent 28b0987d49527490727995f725dc4dd06bf06257 Attached are new toys TR and CRONTAB. *tr.c*: It translate, squezze and delete characters. Supported classes are alpha, alnum, digit, lower, upper space, blank, puct, cntrl and xdigit. *crontab.c*: Companion of crond. It maintains crontab files. diff -r 28b0987d4952 -r fd587c7872a9 toys/pending/crontab.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/toys/pending/crontab.c Thu Oct 30 18:36:09 2014 -0500 @@ -0,0 +1,366 @@ +/* crontab.c - files used to schedule the execution of programs. + * + * Copyright 2014 Ranjan Kumar + * + * http://pubs.opengroup.org/onlinepubs/9699919799/utilities/crontab.html + +USE_CRONTAB(NEWTOY(crontab, "c:u:elr[!elr]", TOYFLAG_USR|TOYFLAG_BIN|TOYFLAG_STAYROOT)) + +config CRONTAB + bool "crontab" + default n + help + usage: crontab [-u user] FILE + [-u user] [-e | -l | -r] + [-c dir] + + Files used to schedule the execution of programs. + + -c crontab dir + -e edit user's crontab + -l list user's crontab + -r delete user's crontab + -u user + FILE Replace crontab by FILE ('-': stdin) +*/ +#define FOR_crontab +#include "toys.h" + +GLOBALS( + char *user; + char *cdir; +) + +static char *omitspace(char *line) +{ + while (*line == ' ' || *line == '\t') line++; + return line; +} + +/* + * Names can also be used for the 'month' and 'day of week' fields + * (First three letters of the particular day or month). + */ +static int getindex(char *src, int size) +{ + int i; + char days[]={"sun""mon""tue""wed""thu""fri""sat"}; + char months[]={"jan""feb""mar""apr""may""jun""jul" + "aug""sep""oct""nov""dec"}; + char *field = (size == 12) ? months : days; + + // strings are not allowed for min, hour and dom fields. + if (!(size == 7 || size == 12)) return -1; + + for (i = 0; field[i]; i += 3) { + if (!strncasecmp(src, &field[i], 3)) + return (i/3); + } + return -1; +} + +static long getval(char *num, long low, long high) +{ + long val = strtol(num, &num, 10); + + if (*num || (val < low) || (val > high)) return -1; + return val; +} + +// Validate minute, hour, day of month, month and day of week fields. +static int validate_component(int min, int max, char *src) +{ + int skip = 0; + char *ptr; + + if (!src) return 1; + if ((ptr = strchr(src, '/'))) { + *ptr++ = 0; + if ((skip = getval(ptr, min, (min ? max: max-1))) < 0) return 1; + } + + if (*src == '-' || *src == ',') return 1; + if (*src == '*') { + if (*(src+1)) return 1; + } + else { + for (;;) { + char *ctoken = strsep(&src, ","), *dtoken; + + if (!ctoken) break; + if (!*ctoken) return 1; + + // validate start position. + dtoken = strsep(&ctoken, "-"); + if (isdigit(*dtoken)) { + if (getval(dtoken, min, (min ? max : max-1)) < 0) return 1; + } else if (getindex(dtoken, max) < 0) return 1; + + // validate end position. + if (!ctoken) { + if (skip) return 1; // case 10/20 or 1,2,4/3 + } + else if (*ctoken) {// e.g. N-M + if (isdigit(*ctoken)) { + if (getval(ctoken, min, (min ? max : max-1)) < 0) return 1; + } else if (getindex(ctoken, max) < 0) return 1; + } else return 1; // error condition 'N-' + } + } + return 0; +} + +static int parse_crontab(char *fname) +{ + char *line; + int lno, fd = xopen(fname, O_RDONLY); + long plen = 0; + + for (lno = 1; (line = get_rawline(fd, &plen, '\n')); lno++,free(line)) { + char *name, *val, *tokens[5] = {0,}, *ptr = line; + int count = 0; + + if (line[plen - 1] == '\n') line[--plen] = '\0'; + else { + snprintf(toybuf, sizeof(toybuf), "'%d': premature EOF\n", lno); + goto OUT; + } + + ptr = omitspace(ptr); + if (!*ptr || *ptr == '#' || *ptr == '@') continue; + while (count<5) { + int len = strcspn(ptr, " \t"); + + if (ptr[len]) ptr[len++] = '\0'; + tokens[count++] = ptr; + ptr += len; + ptr = omitspace(ptr); + if (!*ptr) break; + } + switch (count) { + case 1: // form SHELL=/bin/sh + name = tokens[0]; + if ((val = strchr(name, '='))) *val++ = 0; + if (!val || !*val) { + snprintf(toybuf, sizeof(toybuf), "'%d': %s\n", lno, line); + goto OUT; + } + break; + case 2: // form SHELL =/bin/sh or SHELL= /bin/sh + name = tokens[0]; + if ((val = strchr(name, '='))) { + *val = 0; + val = tokens[1]; + } else { + if (*(tokens[1]) != '=') { + snprintf(toybuf, sizeof(toybuf), "'%d': %s\n", lno, line); + goto OUT; + } + val = tokens[1] + 1; + } + if (!*val) { + snprintf(toybuf, sizeof(toybuf), "'%d': %s\n", lno, line); + goto OUT; + } + break; + case 3: // NAME = VAL + name = tokens[0]; + val = tokens[2]; + if (*(tokens[1]) != '=') { + snprintf(toybuf, sizeof(toybuf), "'%d': %s\n", lno, line); + goto OUT; + } + break; + default: + if (validate_component(0, 60, tokens[0])) { + snprintf(toybuf, sizeof(toybuf), "'%d': bad minute\n", lno); + goto OUT; + } + if (validate_component(0, 24, tokens[1])) { + snprintf(toybuf, sizeof(toybuf), "'%d': bad hour\n", lno); + goto OUT; + } + if (validate_component(1, 31, tokens[2])) { + snprintf(toybuf, sizeof(toybuf), "'%d': bad day-of-month\n", lno); + goto OUT; + } + if (validate_component(1, 12, tokens[3])) { + snprintf(toybuf, sizeof(toybuf), "'%d': bad month\n", lno); + goto OUT; + } + if (validate_component(0, 7, tokens[4])) { + snprintf(toybuf, sizeof(toybuf), "'%d': bad day-of-week\n", lno); + goto OUT; + } + if (!*ptr) { // don't have any cmd to execute. + snprintf(toybuf, sizeof(toybuf), "'%d': bad command\n", lno); + goto OUT; + } + break; + } + } + xclose(fd); + return 0; +OUT: + free(line); + printf("Error at line no %s", toybuf); + xclose(fd); + return 1; +} + +static void do_list(char *name) +{ + int fdin; + + snprintf(toybuf, sizeof(toybuf), "%s%s", TT.cdir, name); + if ((fdin = open(toybuf, O_RDONLY)) == -1) + error_exit("No crontab for '%s'", name); + xsendfile(fdin, 1); + xclose(fdin); +} + +static void do_remove(char *name) +{ + snprintf(toybuf, sizeof(toybuf), "%s%s", TT.cdir, name); + if (unlink(toybuf)) + error_exit("No crontab for '%s'", name); +} + +static void update_crontab(char *src, char *dest) +{ + int fdin, fdout; + + snprintf(toybuf, sizeof(toybuf), "%s%s", TT.cdir, dest); + fdout = xcreate(toybuf, O_WRONLY|O_CREAT|O_TRUNC, 0600); + fdin = xopen(src, O_RDONLY); + xsendfile(fdin, fdout); + xclose(fdin); + + fchown(fdout, getuid(), geteuid()); + xclose(fdout); +} + +static void do_replace(char *name) +{ + char *fname = *toys.optargs ? *toys.optargs : "-"; + char tname[] = "/tmp/crontab.XXXXXX"; + + if ((*fname == '-') && !*(fname+1)) { + int tfd = mkstemp(tname); + + if (tfd < 0) perror_exit("mkstemp"); + xsendfile(0, tfd); + xclose(tfd); + fname = tname; + } + + if (parse_crontab(fname)) + error_exit("errors in crontab file '%s', can't install.", fname); + update_crontab(fname, name); + unlink(tname); +} + +static void do_edit(struct passwd *pwd) +{ + struct stat sb; + time_t mtime = 0; + int srcfd, destfd, status; + pid_t pid, cpid; + char tname[] = "/tmp/crontab.XXXXXX"; + + if ((destfd = mkstemp(tname)) < 0) + perror_exit("Can't open tmp file"); + + fchmod(destfd, 0666); + snprintf(toybuf, sizeof(toybuf), "%s%s", TT.cdir, pwd->pw_name); + + if (!stat(toybuf, &sb)) { // file exists and have some content. + if (sb.st_size) { + srcfd = xopen(toybuf, O_RDONLY); + xsendfile(srcfd, destfd); + xclose(srcfd); + } + } else printf("No crontab for '%s'- using an empty one\n", pwd->pw_name); + xclose(destfd); + + if (!stat(tname, &sb)) mtime = sb.st_mtime; + +RETRY: + if (!(pid = xfork())) { + char *prog = pwd->pw_shell; + + xsetuser(pwd); + if (pwd->pw_uid) { + if (setenv("USER", pwd->pw_name, 1)) _exit(1); + if (setenv("LOGNAME", pwd->pw_name, 1)) _exit(1); + } + if (setenv("HOME", pwd->pw_dir, 1)) _exit(1); + if (setenv("SHELL",((!prog || !*prog) ? "/bin/sh" : prog), 1)) _exit(1); + + if (!(prog = getenv("VISUAL"))) { + if (!(prog = getenv("EDITOR"))) + prog = "vi"; + } + execlp(prog, prog, tname, (char *) NULL); + perror_exit("can't execute '%s'", prog); + } + + // Parent Process. + do { + cpid = waitpid(pid, &status, 0); + } while ((cpid == -1) && (errno == EINTR)); + + if (!stat(tname, &sb) && (mtime == sb.st_mtime)) { + printf("%s: no changes made to crontab\n", toys.which->name); + unlink(tname); + return; + } + printf("%s: installing new crontab\n", toys.which->name); + if (parse_crontab(tname)) { + snprintf(toybuf, sizeof(toybuf), "errors in crontab file, can't install.\n" + "Do you want to retry the same edit? "); + if (!yesno(toybuf, 0)) { + error_msg("edits left in '%s'", tname); + return; + } + goto RETRY; + } + // parsing of crontab success; update the crontab. + update_crontab(tname, pwd->pw_name); + unlink(tname); +} + +void crontab_main(void) +{ + struct passwd *pwd = NULL; + long FLAG_elr = toys.optflags & (FLAG_e|FLAG_l|FLAG_r); + + if (TT.cdir && (TT.cdir[strlen(TT.cdir)-1] != '/')) + TT.cdir = xmprintf("%s/", TT.cdir); + if (!TT.cdir) TT.cdir = xstrdup("/var/spool/cron/crontabs/"); + + if (toys.optflags & FLAG_u) { + if (getuid()) error_exit("must be privileged to use -u"); + pwd = xgetpwnam(TT.user); + } else pwd = xgetpwuid(getuid()); + + if (!toys.optc) { + if (!FLAG_elr) { + if (toys.optflags & FLAG_u) { + toys.exithelp++; + error_exit("file name must be specified for replace"); + } + do_replace(pwd->pw_name); + } + else if (toys.optflags & FLAG_e) do_edit(pwd); + else if (toys.optflags & FLAG_l) do_list(pwd->pw_name); + else if (toys.optflags & FLAG_r) do_remove(pwd->pw_name); + } else { + if (FLAG_elr) { + toys.exithelp++; + error_exit("no arguments permitted after this option"); + } + do_replace(pwd->pw_name); + } + if (!(toys.optflags & FLAG_c)) free(TT.cdir); +} diff -r 28b0987d4952 -r fd587c7872a9 toys/pending/tr.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/toys/pending/tr.c Thu Oct 30 18:36:09 2014 -0500 @@ -0,0 +1,268 @@ +/* tr.c - translate or delete characters + * + * Copyright 2014 Sandeep Sharma + * + * See http://pubs.opengroup.org/onlinepubs/9699919799/utilities/tr.html + +USE_TR(NEWTOY(tr, "^>2<1Ccsd[+cC]", TOYFLAG_USR|TOYFLAG_BIN)) + +config TR + bool "tr" + default n + help + usage: tr [-cds] SET1 [SET2] + + Translate, squeeze, or delete characters from stdin, writing to stdout + + -c/-C Take complement of SET1 + -d Delete input characters coded SET1 + -s Squeeze multiple output characters of SET2 into one character +*/ + +#define FOR_tr +#include "toys.h" + +GLOBALS( + short map[256]; //map of chars + int len1, len2; +) + +enum { + class_alpha, class_alnum, class_digit, + class_lower,class_upper,class_space,class_blank, + class_punct,class_cntrl,class_xdigit,class_invalid +}; + +static void map_translation(char *set1 , char *set2) +{ + int i = TT.len1, k = 0; + + if (toys.optflags & FLAG_d) + for (; i; i--, k++) TT.map[set1[k]] = set1[k]|0x100; //set delete bit + + if (toys.optflags & FLAG_s) { + for (i = TT.len1, k = 0; i; i--, k++) + TT.map[set1[k]] = TT.map[set1[k]]|0x200; + for (i = TT.len2, k = 0; i; i--, k++) + TT.map[set2[k]] = TT.map[set2[k]]|0x200; + } + i = k = 0; + while (!(toys.optflags & FLAG_d) && set2 && TT.len1--) { //ignore set2 if -d present + TT.map[set1[i]] = ((TT.map[set1[i]] & 0xFF00) | set2[k]); + if (set2[k + 1]) k++; + i++; + } +} + +static int handle_escape_char(char **esc_val) //taken from printf +{ + char *ptr = *esc_val; + int esc_length = 0; + unsigned base = 0, num = 0, result = 0, count = 0; + + if (*ptr == 'x') { + ptr++; + esc_length++; + base = 16; + } else if (isdigit(*ptr)) base = 8; + + while (esc_length < 3 && base) { + num = tolower(*ptr) - '0'; + if (num > 10) num += ('0' - 'a' + 10); + if (num >= base) { + if (base == 16) { + esc_length--; + if (!esc_length) {// Invalid hex value eg. /xvd, print as it is /xvd + result = '\\'; + ptr--; + } + } + break; + } + esc_length++; + count = result = (count * base) + num; + ptr++; + } + if (base) { + ptr--; + *esc_val = ptr; + return (char)result; + } else { + switch (*ptr) { + case 'n': result = '\n'; break; + case 't': result = '\t'; break; + case 'e': result = (char)27; break; + case 'b': result = '\b'; break; + case 'a': result = '\a'; break; + case 'f': result = '\f'; break; + case 'v': result = '\v'; break; + case 'r': result = '\r'; break; + case '\\': result = '\\'; break; + default : + result = '\\'; + ptr--; // Let pointer pointing to / we will increment after returning. + break; + } + } + *esc_val = ptr; + return (char)result; +} + +static int find_class(char *class_name) +{ + int i; + static char *class[] = { + "[:alpha:]","[:alnum:]","[:digit:]", + "[:lower:]","[:upper:]","[:space:]", + "[:blank:]","[:punct:]","[:cntrl:]", + "[:xdigit:]","NULL" + }; + + for (i = 0; i != class_invalid; i++) { + if (!memcmp(class_name, class[i], (class_name[0] == 'x')?10:9)) break; + } + return i; +} + +static char *expand_set(char *arg, int *len) +{ + int i = 0, j, k, size = 256; + char *set = xzalloc(size*sizeof(char)); + + while (*arg) { + + if (i >= size) { + size += 256; + set = xrealloc(set, size); + } + if (*arg == '\\') { + arg++; + set[i++] = (int)handle_escape_char(&arg); + arg++; + continue; + } + if (arg[1] == '-') { + if (arg[2] == '\0') goto save; + j = arg[0]; + k = arg[2]; + if (j > k) perror_exit("reverse colating order"); + while (j <= k) set[i++] = j++; + arg += 3; + continue; + } + if (arg[0] == '[' && arg[1] == ':') { + + if ((j = find_class(arg)) == class_invalid) goto save; + + if ((j == class_alpha) || (j == class_upper) || (j == class_alnum)) { + for (k = 'A'; k <= 'Z'; k++) set[i++] = k; + } + if ((j == class_alpha) || (j == class_lower) || (j == class_alnum)) { + for (k = 'a'; k <= 'z'; k++) set[i++] = k; + } + if ((j == class_alnum) || (j == class_digit) || (j == class_xdigit)) { + for (k = '0'; k <= '9'; k++) set[i++] = k; + } + if (j == class_space || j == class_blank) { + set[i++] = '\t'; + if (j == class_space) { + set[i++] = '\n'; + set[i++] = '\f'; + set[i++] = '\r'; + set[i++] = '\v'; + } + set[i++] = ' '; + } + if (j == class_punct) { + for (k = 0; k <= 255; k++) + if (ispunct(k)) set[i++] = k; + } + if (j == class_cntrl) { + for (k = 0; k <= 255; k++) + if (iscntrl(k)) set[i++] = k; + } + if (j == class_xdigit) { + for (k = 'A'; k <= 'F'; k++) { + set[i + 6] = k | 0x20; + set[i++] = k; + } + i += 6; + arg += 10; + continue; + } + + arg += 9; //never here for class_xdigit. + continue; + } + if (arg[0] == '[' && arg[1] == '=') { //[=char=] only + arg += 2; + if (*arg) set[i++] = *arg; + if (!arg[1] || arg[1] != '=' || arg[2] != ']') + error_exit("bad equiv class"); + continue; + } +save: + set[i++] = *arg++; + } + *len = i; + return set; +} + +static void print_map(char *set1, char *set2) +{ + int r = 0, i, prev_char = -1; + + while (1) + { + i = 0; + r = read(STDIN_FILENO, (toybuf), sizeof(toybuf)); + if (!r) break; + for (;r > i;i++) { + + if ((toys.optflags & FLAG_d) && (TT.map[(int)toybuf[i]] & 0x100)) continue; + if (toys.optflags & FLAG_s) { + if ((TT.map[(int)toybuf[i]] & 0x200) && + (prev_char == TT.map[(int)toybuf[i]])) { + continue; + } + } + xputc(TT.map[(int)toybuf[i]] & 0xFF); + prev_char = TT.map[(int)toybuf[i]]; + fflush(stdout); + } + } +} + +static void do_complement(char **set) +{ + int i, j; + char *comp = xmalloc(256); + + for (i = 0, j = 0;i < 256; i++) { + if (memchr(*set, i, TT.len1)) continue; + else comp[j++] = (char)i; + } + free(*set); + TT.len1 = j; + *set = comp; +} + +void tr_main(void) +{ + char *set1, *set2 = NULL; + int i; + + for (i = 0; i < 256; i++) TT.map[i] = i; //init map + + set1 = expand_set(toys.optargs[0], &TT.len1); + if (toys.optflags & FLAG_c) do_complement(&set1); + if (toys.optargs[1]) { + if (toys.optargs[1][0] == '\0') error_exit("set2 can't be empty string"); + set2 = expand_set(toys.optargs[1], &TT.len2); + } + map_translation(set1, set2); + + print_map(set1, set2); + free(set1); + free(set2); +}