#include #include #include #include #define NAMELEN 1024 char CRONLOG[] = "cron"; typedef struct Job Job; typedef struct Time Time; typedef struct User User; struct Time{ /* bit masks for each valid time */ ulong min; /* actually 1 bit for every 2 min */ ulong hour; ulong mday; ulong wday; ulong mon; }; struct Job{ char host[NAMELEN]; /* where ... */ Time time; /* when ... */ char *cmd; /* and what to execute */ Job *next; }; struct User{ Qid lastqid; /* of last read /cron/user/cron */ char name[NAMELEN]; /* who ... */ Job *jobs; /* wants to execute these jobs */ }; User me; char *savec; char *savetok; int tok; int debug; ulong lexval; void rexec(User*, Job*); void readalljobs(void); Job *readjobs(char*, User*); int getname(char*); ulong gettime(int, int); int gettok(int, int); void pushtok(void); void usage(void); void freejobs(Job*); void *emalloc(ulong); void *erealloc(void*, ulong); int myauth(int, char*); void createuser(void); int mkcmd(char*, char*, int); void printjobs(void); int qidcmp(Qid, Qid); void main(int argc, char *argv[]) { Job *j; Tm tm; Time t; ulong now, last, x; debug = 0; ARGBEGIN{ case 'd': debug = 1; break; default: usage(); }ARGEND USED(argc, argv); if(debug){ readalljobs(); printjobs(); exits(0); } switch(fork()){ case -1: sysfatal("can't fork"); case 0: break; default: exits(0); } argv0 = "cron"; srand(getpid()*time(0)); last = time(0) / 60; for(;;){ readalljobs(); now = time(0) / 60; for(; last <= now; last += 2){ tm = *localtime(last*60); t.min = 1 << tm.min/2; t.hour = 1 << tm.hour; t.wday = 1 << tm.wday; t.mday = 1 << tm.mday; t.mon = 1 << (tm.mon + 1); for(j=me.jobs; j; j=j->next) if(j->time.min & t.min && j->time.hour & t.hour && j->time.wday & t.wday && j->time.mday & t.mday && j->time.mon & t.mon) rexec(&me, j); } x = time(0) / 60; if(x - now < 2) sleep((2 - (x - now))*60*1000); } exits(0); } void readalljobs(void) { User *u; Dir *db; char file[3*NAMELEN]; u = &me; if(u->name[0] == '\0') { strcpy(u->name, getuser()); u->lastqid = (Qid){-1, -1, -1}; u->jobs = 0; } sprint(file, "/cron/%s/cron", u->name); if((db = dirstat(file)) == nil) return; if(qidcmp(u->lastqid, db->qid) != 0){ freejobs(u->jobs); u->jobs = readjobs(file, u); } free(db); } /* * parse user's cron file * other lines: minute hour monthday month weekday host command */ Job * readjobs(char *file, User *user) { Biobuf *b; Job *j, *jobs; Dir *d; int line; b = Bopen(file, OREAD); if(!b) return 0; if ((d = dirstat(file)) == nil){ Bterm(b); return(0); } user->lastqid = d->qid; jobs = 0; for(line = 1; savec = Brdline(b, '\n'); line++){ savec[Blinelen(b) - 1] = '\0'; while(*savec == ' ' || *savec == '\t') savec++; if(*savec == '#' || *savec == '\0') continue; if(strlen(savec) > 1024){ syslog(0, CRONLOG, "%s: line %d: line too long", user->name, line); continue; } j = emalloc(sizeof *j); if((j->time.min = gettime(0, 59)) && (j->time.hour = gettime(0, 23)) && (j->time.mday = gettime(1, 31)) && (j->time.mon = gettime(1, 12)) && (j->time.wday = gettime(0, 6)) && getname(j->host)){ j->cmd = emalloc(strlen(savec) + 1); strcpy(j->cmd, savec); j->next = jobs; jobs = j; }else{ syslog(0, CRONLOG, "%s: line %d: syntax error", user->name, line); free(j); } } Bterm(b); return jobs; } void printjobs(void) { char buf[8*1024]; Job *j; print("user %s\n", me.name); for(j = me.jobs; j; j = j->next){ if(!mkcmd(j->cmd, buf, sizeof buf)) print("\tbad job %s on host %s\n", j->cmd, j->host); else print("\tjob %s on host %s\n", buf, j->host); } } void freejobs(Job *j) { Job *fj; for(fj = j; fj; fj = j){ j = j->next; free(fj); } } int getname(char *name) { int c; int i; if(!savec) return 0; while(*savec == ' ' || *savec == '\t') savec++; for(i = 0; (c = *savec) && c != ' ' && c != '\t'; i++){ if(i == NAMELEN - 1) return 0; name[i] = *savec++; } name[i] = '\0'; while(*savec == ' ' || *savec == '\t') savec++; return i; } /* * return the next time range in the file: * times: '*' * | range * range: number * | number '-' number * | range ',' range * a return of zero means a syntax error was discovered */ ulong gettime(int min, int max) { ulong n, m, e; if(gettok(min, max) == '*') return ~0; n = 0; while(tok == '1'){ m = 1 << lexval; n |= m; if(gettok(0, 0) == '-'){ if(gettok(lexval, max) != '1') return 0; e = 1 << lexval; for( ; m <= e; m <<= 1) n |= m; gettok(min, max); } if(tok != ',') break; if(gettok(min, max) != '1') return 0; } pushtok(); return n; } void pushtok(void) { savec = savetok; } int gettok(int min, int max) { char c; savetok = savec; if(!savec) return tok = 0; while((c = *savec) == ' ' || c == '\t') savec++; switch(c){ case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': lexval = strtoul(savec, &savec, 10); if(lexval < min || lexval > max) return tok = 0; if(max > 32) lexval /= 2; /* yuk: correct min by / 2 */ return tok = '1'; case '*': case '-': case ',': savec++; return tok = c; default: return tok = 0; } } int call(char *host) { char *na, *p; na = netmkaddr(host, 0, "rexexec"); p = utfrune(na, L'!'); if(!p) return -1; p = utfrune(p+1, L'!'); if(!p) return -1; if(strcmp(p, "!rexexec") != 0) return -2; return dial(na, 0, 0, 0); } /* * convert command to run properly on the remote machine * need to escape the quotes wo they don't get stripped */ int mkcmd(char *cmd, char *buf, int len) { char *p; int n, m; n = sizeof "exec rc -c '" -1; if(n >= len) return 0; strcpy(buf, "exec rc -c '"); while(p = utfrune(cmd, L'\'')){ p++; m = p - cmd; if(n + m + 1 >= len) return 0; strncpy(&buf[n], cmd, m); n += m; buf[n++] = '\''; cmd = p; } m = strlen(cmd); if(n + m + sizeof "'/dev/null>[2=1]" >= len) return 0; strcpy(&buf[n], cmd); strcpy(&buf[n+m], "'/dev/null>[2=1]"); return 1; } void rexec(User *user, Job *j) { char buf[8*1024]; int fd; if(strcmp(j->host, "local") != 0) return; switch(rfork(RFPROC|RFNOWAIT|RFNAMEG|RFENVG|RFFDG)){ case 0: break; case -1: syslog(0, CRONLOG, "can't fork a job for %s: %r\n", user->name); default: return; } if(!mkcmd(j->cmd, buf, sizeof buf)){ syslog(0, CRONLOG, "internal error: cmd buffer overflow"); _exits(0); } /* * remote call, auth, cmd with no i/o */ fd = open("/dev/null", ORDWR); dup(fd, 0); dup(fd, 1); dup(fd, 2); execl("/bin/rc", "rc", "-c", buf, 0); _exits(0); } void * emalloc(ulong n) { void *p; if(p = malloc(n)) return p; sysfatal("out of memory"); return 0; } void * erealloc(void *p, ulong n) { if(p = realloc(p, n)) return p; sysfatal("out of memory"); return 0; } void usage(void) { fprint(2, "usage: cron [-c]\n"); exits("usage"); } int qidcmp(Qid a, Qid b) { /* might be useful to know if a > b, but not for cron */ return(a.path != b.path || a.vers != b.vers); }