| /* crontab.c - files used to schedule the execution of programs. |
| * |
| * Copyright 2014 Ranjan Kumar <ranjankumar.bth@gmail.com> |
| * |
| * 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); |
| } |