diff options
-rw-r--r-- | etc/crontab | 4 | ||||
-rw-r--r-- | etc/mtree/4.4BSD.dist | 7 | ||||
-rw-r--r-- | usr.bin/at/Makefile | 3 | ||||
-rw-r--r-- | usr.bin/at/at.1 | 7 | ||||
-rw-r--r-- | usr.bin/at/at.c | 62 | ||||
-rw-r--r-- | usr.bin/at/pathnames.h | 46 | ||||
-rw-r--r-- | usr.sbin/cron/Makefile | 6 | ||||
-rw-r--r-- | usr.sbin/cron/atrun.c | 595 | ||||
-rw-r--r-- | usr.sbin/cron/config.h | 7 | ||||
-rw-r--r-- | usr.sbin/cron/cron.8 | 214 | ||||
-rw-r--r-- | usr.sbin/cron/cron.c | 108 | ||||
-rw-r--r-- | usr.sbin/cron/crontab.1 | 4 | ||||
-rw-r--r-- | usr.sbin/cron/crontab.c | 14 | ||||
-rw-r--r-- | usr.sbin/cron/do_command.c | 66 | ||||
-rw-r--r-- | usr.sbin/cron/entry.c | 15 | ||||
-rw-r--r-- | usr.sbin/cron/externs.h | 12 | ||||
-rw-r--r-- | usr.sbin/cron/funcs.h | 11 | ||||
-rw-r--r-- | usr.sbin/cron/globals.h | 5 | ||||
-rw-r--r-- | usr.sbin/cron/macros.h | 8 | ||||
-rw-r--r-- | usr.sbin/cron/misc.c | 15 | ||||
-rw-r--r-- | usr.sbin/cron/pathnames.h | 24 | ||||
-rw-r--r-- | usr.sbin/cron/popen.c | 31 | ||||
-rw-r--r-- | usr.sbin/cron/structs.h | 18 |
23 files changed, 1002 insertions, 280 deletions
diff --git a/etc/crontab b/etc/crontab index 66d72b80af5..eba918ef1f2 100644 --- a/etc/crontab +++ b/etc/crontab @@ -1,4 +1,4 @@ -# $OpenBSD: crontab,v 1.9 2001/09/11 19:03:55 millert Exp $ +# $OpenBSD: crontab,v 1.10 2002/07/15 19:13:28 millert Exp $ # # /var/cron/tabs/root - root's crontab # @@ -8,8 +8,6 @@ HOME=/var/log # #minute hour mday month wday command # -*/10 * * * * /usr/libexec/atrun -# # sendmail clientmqueue runner */30 * * * * /usr/sbin/sendmail -L sm-msp-queue -Ac -q # diff --git a/etc/mtree/4.4BSD.dist b/etc/mtree/4.4BSD.dist index d4aabb2d3f4..a48d84f278e 100644 --- a/etc/mtree/4.4BSD.dist +++ b/etc/mtree/4.4BSD.dist @@ -1,4 +1,4 @@ -# $OpenBSD: 4.4BSD.dist,v 1.132 2002/07/11 12:19:31 markus Exp $ +# $OpenBSD: 4.4BSD.dist,v 1.133 2002/07/15 19:13:29 millert Exp $ /set type=dir uname=root gname=wheel mode=0755 # . @@ -1853,11 +1853,6 @@ jobs gname=crontab mode=01770 # ./var/at/jobs .. -# ./var/at/spool -spool mode=0700 -# ./var/at/spool -.. - # ./var/at .. diff --git a/usr.bin/at/Makefile b/usr.bin/at/Makefile index 18fbe101830..f5d9c51e621 100644 --- a/usr.bin/at/Makefile +++ b/usr.bin/at/Makefile @@ -1,7 +1,8 @@ -# $OpenBSD: Makefile,v 1.6 2002/05/14 18:05:39 millert Exp $ +# $OpenBSD: Makefile,v 1.7 2002/07/15 19:13:29 millert Exp $ PROG= at SRCS= at.c panic.c parsetime.c perm.c +CFLAGS+=-I${.CURDIR}/../../usr.sbin/cron MAN= at.1 atrm.1 atq.1 LINKS= ${BINDIR}/at ${BINDIR}/atq \ ${BINDIR}/at ${BINDIR}/atrm \ diff --git a/usr.bin/at/at.1 b/usr.bin/at/at.1 index 9c44910d7ef..d3ef0c48997 100644 --- a/usr.bin/at/at.1 +++ b/usr.bin/at/at.1 @@ -1,4 +1,4 @@ -.\" $OpenBSD: at.1,v 1.25 2002/05/14 18:05:39 millert Exp $ +.\" $OpenBSD: at.1,v 1.26 2002/07/15 19:13:29 millert Exp $ .\" $FreeBSD: at.man,v 1.6 1997/02/22 19:54:05 peter Exp $ .Dd May 13, 2002 .Dt AT 1 @@ -50,7 +50,7 @@ Executes commands at a specified time. Executes commands when system load levels permit. In other words, when the load average drops below 1.5, or the value specified in the invocation of -.Nm atrun . +.Xr cron 8 . .El .Pp The options are as follows: @@ -280,8 +280,6 @@ This is the default configuration. .Bl -tag -width /var/at/at.allow -compact .It Pa /var/at/jobs directory containing job files -.It Pa /var/at/spool -directory containing output spool files .It Pa /var/at/at.allow allow permission control .It Pa /var/at/at.deny @@ -294,7 +292,6 @@ job sequence file .Xr sh 1 , .Xr touch 1 , .Xr umask 2 , -.Xr atrun 8 , .Xr cron 8 , .Xr sendmail 8 .Sh AUTHORS diff --git a/usr.bin/at/at.c b/usr.bin/at/at.c index 50ec0e973eb..d66a1a5edbd 100644 --- a/usr.bin/at/at.c +++ b/usr.bin/at/at.c @@ -1,4 +1,4 @@ -/* $OpenBSD: at.c,v 1.29 2002/05/14 18:05:39 millert Exp $ */ +/* $OpenBSD: at.c,v 1.30 2002/07/15 19:13:29 millert Exp $ */ /* $NetBSD: at.c,v 1.4 1995/03/25 18:13:31 glass Exp $ */ /* @@ -33,8 +33,11 @@ */ #include <sys/param.h> +#include <sys/socket.h> #include <sys/stat.h> #include <sys/time.h> +#include <sys/un.h> + #include <ctype.h> #include <dirent.h> #include <err.h> @@ -49,6 +52,7 @@ #include <string.h> #include <time.h> #include <unistd.h> +#include <utime.h> #include <utmp.h> #if (MAXLOGNAME-1) > UT_NAMESIZE @@ -69,7 +73,7 @@ #define TIMESIZE 50 /* Size of buffer passed to strftime() */ #ifndef lint -static const char rcsid[] = "$OpenBSD: at.c,v 1.29 2002/05/14 18:05:39 millert Exp $"; +static const char rcsid[] = "$OpenBSD: at.c,v 1.30 2002/07/15 19:13:29 millert Exp $"; #endif /* Variables to remove from the job's environment. */ @@ -93,6 +97,7 @@ static void sigc(int); static void alarmc(int); static void writefile(time_t, char); static void list_jobs(int, char **, int, int); +static void poke_daemon(void); static time_t ttime(const char *); static void @@ -260,8 +265,8 @@ writefile(time_t runtimer, char queue) if (fpin == NULL) perr("Cannot open input file"); } - (void)fprintf(fp, "#!/bin/sh\n# atrun uid=%u gid=%u\n# mail %*s %d\n", - real_uid, real_gid, LOGNAMESIZE, mailname, send_mail); + (void)fprintf(fp, "#!/bin/sh\n# atrun uid=%ld gid=%ld\n# mail %*s %d\n", + (long)real_uid, (long)real_gid, LOGNAMESIZE, mailname, send_mail); /* Write out the umask at the time of invocation */ (void)fprintf(fp, "umask %o\n", cmask); @@ -369,6 +374,9 @@ writefile(time_t runtimer, char queue) (void)close(fd2); + /* Poke cron so it knows to reload the at spool. */ + poke_daemon(); + runtime = *localtime(&runtimer); strftime(timestr, TIMESIZE, "%a %b %e %T %Y", &runtime); (void)fprintf(stderr, "commands will be executed using %s\n", shell); @@ -604,7 +612,7 @@ process_jobs(int argc, char **argv, int what) FILE *fp; DIR *spool; int job_matches, jobs_len, uids_len; - int error, i, ch; + int error, i, ch, changed; PRIV_START; @@ -644,6 +652,7 @@ process_jobs(int argc, char **argv, int what) } /* Loop over every file in the directory */ + changed = 0; while ((dirent = readdir(spool)) != NULL) { PRIV_START; @@ -688,7 +697,9 @@ process_jobs(int argc, char **argv, int what) if (!interactive || (interactive && rmok(runtimer))) { - if (unlink(dirent->d_name) != 0) + if (unlink(dirent->d_name) == 0) + changed = 1; + else perr(dirent->d_name); if (!force && !interactive) fprintf(stderr, @@ -733,6 +744,10 @@ process_jobs(int argc, char **argv, int what) free(jobs); free(uids); + /* If we modied the spool, poke cron so it knows to reload. */ + if (changed) + poke_daemon(); + return (error); } @@ -805,6 +820,41 @@ ttime(const char *arg) "[[CC]YY]MMDDhhmm[.SS]"); } +#define RELOAD_AT 0x4 /* XXX - from cron's macros.h */ + +/* XXX - share with crontab */ +static void +poke_daemon() { + int sock, flags; + unsigned char poke; + struct sockaddr_un sun; + + PRIV_START; + + if (utime(_PATH_ATJOBS, NULL) < 0) { + warn("can't update mtime on %s", _PATH_ATJOBS); + PRIV_END; + return; + } + + /* Failure to poke the daemon socket is not a fatal error. */ + (void) signal(SIGPIPE, SIG_IGN); + strlcpy(sun.sun_path, CRONDIR "/" SPOOL_DIR "/" CRONSOCK, + sizeof(sun.sun_path)); + sun.sun_family = AF_UNIX; + sun.sun_len = strlen(sun.sun_path); + if ((sock = socket(AF_UNIX, SOCK_STREAM, 0)) >= 0 && + connect(sock, (struct sockaddr *)&sun, sizeof(sun)) == 0) { + poke = RELOAD_AT; + write(sock, &poke, 1); + close(sock); + } else + fprintf(stderr, "Warning, cron does not appear to be running.\n"); + (void) signal(SIGPIPE, SIG_DFL); + + PRIV_END; +} + int main(int argc, char **argv) { diff --git a/usr.bin/at/pathnames.h b/usr.bin/at/pathnames.h deleted file mode 100644 index 88bc5f03ee3..00000000000 --- a/usr.bin/at/pathnames.h +++ /dev/null @@ -1,46 +0,0 @@ -/* $OpenBSD: pathnames.h,v 1.5 2002/05/11 23:16:44 millert Exp $ */ -/* $NetBSD: pathnames.h,v 1.3 1995/03/25 18:13:38 glass Exp $ */ - -/* - * Copyright (c) 1993 Christopher G. Demetriou - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * 3. All advertising materials mentioning features or use of this software - * must display the following acknowledgement: - * This product includes software developed by Christopher G. Demetriou. - * 4. The name of the author may not be used to endorse or promote products - * derived from this software without specific prior written permission - * - * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR - * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES - * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. - * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, - * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT - * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF - * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - */ - -#ifndef _PATHNAMES_H_ -#define _PATHNAMES_H_ - -#include <paths.h> - -#define _PATH_ATJOBS "/var/at/jobs/" -#define _PATH_ATSPOOL "/var/at/spool/" -#define _PATH_SEQFILE "/var/at/.SEQ" -#define _PATH_AT_ALLOW "/var/at/at.allow" -#define _PATH_AT_DENY "/var/at/at.deny" - -#endif /* _PATHNAMES_H_ */ diff --git a/usr.sbin/cron/Makefile b/usr.sbin/cron/Makefile index e4dc98b4608..00830bd05a6 100644 --- a/usr.sbin/cron/Makefile +++ b/usr.sbin/cron/Makefile @@ -1,9 +1,9 @@ -# $OpenBSD: Makefile,v 1.2 1997/09/21 11:43:33 deraadt Exp $ +# $OpenBSD: Makefile,v 1.3 2002/07/15 19:13:29 millert Exp $ PROG= cron SRCS= cron.c database.c user.c entry.c job.c do_command.c \ - misc.c env.c popen.c -CFLAGS+=-I${.CURDIR} + misc.c env.c popen.c atrun.c +CFLAGS+=-I${.CURDIR} -Wall MAN= cron.8 .include <bsd.prog.mk> diff --git a/usr.sbin/cron/atrun.c b/usr.sbin/cron/atrun.c new file mode 100644 index 00000000000..c404b2ba977 --- /dev/null +++ b/usr.sbin/cron/atrun.c @@ -0,0 +1,595 @@ +/* $OpenBSD: atrun.c,v 1.1 2002/07/15 19:13:29 millert Exp $ */ + +/* + * Copyright (c) 2002 Todd C. Miller <Todd.Miller@courtesan.com> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL + * THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#if !defined(lint) && !defined(LINT) +static const char rcsid[] = "$OpenBSD: atrun.c,v 1.1 2002/07/15 19:13:29 millert Exp $"; +#endif + +#include "cron.h" +#include <sys/resource.h> + +static void unlink_job(at_db *, atjob *); +static void run_job(atjob *, char *); + +/* + * Scan the at jobs dir and build up a list of jobs found. + */ +int +scan_atjobs(at_db *old_db, struct timeval *tv) +{ + DIR *atdir = NULL; + int cwd, queue, pending; + long l; + TIME_T run_time; + char *ep; + at_db new_db; + atjob *job, *tjob; + struct dirent *file; + struct stat statbuf; + + Debug(DLOAD, ("[%ld] scan_atjobs()\n", (long)getpid())) + + if (stat(_PATH_ATJOBS, &statbuf) != 0) { + log_it("CRON", getpid(), "CAN'T STAT", _PATH_ATJOBS); + return (0); + } + + if (old_db->mtime == statbuf.st_mtime) { + Debug(DLOAD, ("[%ld] at jobs dir mtime unch, no load needed.\n", + (long)getpid())) + return (0); + } + + /* XXX - would be nice to stash the crontab cwd */ + if ((cwd = open(".", O_RDONLY, 0)) < 0) { + log_it("CRON", getpid(), "CAN'T OPEN", "."); + return (0); + } + + if (chdir(_PATH_ATJOBS) != 0 || (atdir = opendir(".")) == NULL) { + if (atdir == NULL) + log_it("CRON", getpid(), "OPENDIR FAILED", + _PATH_ATJOBS); + else + log_it("CRON", getpid(), "CHDIR FAILED", + _PATH_ATJOBS); + fchdir(cwd); + close(cwd); + return (0); + } + + new_db.mtime = statbuf.st_mtime; /* stash at dir mtime */ + new_db.head = new_db.tail = NULL; + + pending = 0; + while ((file = readdir(atdir))) { + if (stat(file->d_name, &statbuf) != 0 || + !S_ISREG(statbuf.st_mode)) + continue; + + /* + * at jobs are named as RUNTIME.QUEUE + * RUNTIME is the time to run in seconds since the epoch + * QUEUE is a letter that designates the job's queue + */ + l = strtol(file->d_name, &ep, 10); + if (*ep != '.' || !isalpha(*(ep + 1)) || l < 0 || l >= INT_MAX) + continue; + run_time = (TIME_T)l; + queue = *(ep + 1); + if (!isalpha(queue)) + continue; + + job = (atjob *)malloc(sizeof(*job)); + if (job == NULL) { + for (job = new_db.head; job != NULL; ) { + tjob = job; + job = job->next; + free(tjob); + } + return (0); + } + job->uid = statbuf.st_uid; + job->gid = statbuf.st_gid; + job->queue = queue; + job->run_time = run_time; + job->prev = new_db.tail; + job->next = NULL; + if (new_db.head == NULL) + new_db.head = job; + if (new_db.tail != NULL) + new_db.tail->next = job; + new_db.tail = job; + if (tv != NULL && run_time <= tv->tv_sec) + pending = 1; + } + closedir(atdir); + + /* Free up old at db */ + Debug(DLOAD, ("unlinking old at database:\n")) + for (job = old_db->head; job != NULL; ) { + Debug(DLOAD, ("\t%ld.%c\n", (long)job->run_time, job->queue)) + tjob = job; + job = job->next; + free(tjob); + } + + /* Change back to the normal cron dir. */ + fchdir(cwd); + close(cwd); + + /* Install the new database */ + *old_db = new_db; + Debug(DLOAD, ("scan_atjobs is done\n")) + + return (pending); +} + +/* + * Loop through the at job database and run jobs whose time have come. + */ +void +atrun(at_db *db, double batch_maxload, TIME_T now) +{ + char atfile[PATH_MAX]; + struct stat statbuf; + double la; + atjob *job, *batch; + + Debug(DPROC, ("[%ld] atrun()\n", (long)getpid())) + + for (batch = NULL, job = db->head; job; job = job->next) { + /* Skip jobs in the future */ + if (job->run_time > now) + continue; + + snprintf(atfile, sizeof(atfile), "%s/%ld.%c", _PATH_ATJOBS, + (long)job->run_time, job->queue); + + if (stat(atfile, &statbuf) != 0) + unlink_job(db, job); /* disapeared */ + + if (!S_ISREG(statbuf.st_mode)) + continue; /* should not happen */ + + /* + * Pending jobs have the user execute bit set. + */ + if (statbuf.st_mode & S_IXUSR) { + /* new job to run */ + if (isupper(job->queue)) { + /* we run one batch job per atrun() call */ + if (batch == NULL || + job->run_time < batch->run_time) + batch = job; + } else { + /* normal at job */ + run_job(job, atfile); + unlink_job(db, job); + } + } + } + + /* Run a single batch job if there is one pending. */ + if (batch != NULL && (batch_maxload == 0.0 || + ((getloadavg(&la, 1) == 1) && la <= batch_maxload))) { + snprintf(atfile, sizeof(atfile), "%s/%ld.%c", _PATH_ATJOBS, + (long)batch->run_time, batch->queue); + run_job(batch, atfile); + unlink_job(db, batch); + } +} + +/* + * Remove the specified at job from the database. + */ +static void +unlink_job(at_db *db, atjob *job) +{ + if (job->prev == NULL) + db->head = job->next; + else + job->prev->next = job->next; + + if (job->next == NULL) + db->tail = job->prev; + else + job->next->prev = job->prev; +} + +/* + * Run the specified job contained in atfile. + */ +static void +run_job(atjob *job, char *atfile) +{ + struct stat statbuf; + struct passwd *pw; + pid_t pid; + long nuid, ngid; + FILE *fp; + WAIT_T waiter; + char *cp, *ep, mailto[MAX_UNAME], buf[BUFSIZ]; + int fd, always_mail; + int output_pipe[2]; + char *nargv[2], *nenvp[1]; + + Debug(DPROC, ("[%ld] run_job('%s')\n", (long)getpid(), atfile)) + + /* Open the file and unlink it so we don't try running it again. */ + if ((fd = open(atfile, O_RDONLY|O_NONBLOCK|O_NOFOLLOW, 0)) < OK) { + log_it("CRON", getpid(), "CAN'T OPEN", atfile); + return; + } + unlink(atfile); + + /* Fork so other pending jobs don't have to wait for us to finish. */ + switch (fork()) { + case 0: + /* child */ + break; + case -1: + /* error */ + log_it("CRON", getpid(), "error", "can't fork"); + /* FALLTHROUGH */ + default: + /* parent */ + close(fd); + return; + } + + acquire_daemonlock(1); /* close lock fd */ + + /* + * We don't want the main cron daemon to wait for our children-- + * we will do it ourselves via waitpid(). + */ + (void) signal(SIGCHLD, SIG_DFL); + + /* + * Verify the user still exists and their account has not expired. + */ + pw = getpwuid(job->uid); + if (pw == NULL) { + log_it("CRON", getpid(), "ORPHANED JOB", atfile); + _exit(ERROR_EXIT); + } + /* XXX - is this needed now that we do auth_approval? */ + if (pw->pw_expire && time(NULL) >= pw->pw_expire) { + log_it(pw->pw_name, getpid(), "ACCOUNT EXPIRED, JOB ABORTED", + atfile); + _exit(ERROR_EXIT); + } + + /* Sanity checks */ + if (fstat(fd, &statbuf) < OK) { + log_it(pw->pw_name, getpid(), "FSTAT FAILED", atfile); + _exit(ERROR_EXIT); + } + if (!S_ISREG(statbuf.st_mode)) { + log_it(pw->pw_name, getpid(), "NOT REGULAR", atfile); + _exit(ERROR_EXIT); + } + if ((statbuf.st_mode & ALLPERMS) != (S_IRUSR | S_IWUSR | S_IXUSR)) { + log_it(pw->pw_name, getpid(), "BAD FILE MODE", atfile); + _exit(ERROR_EXIT); + } + if (statbuf.st_uid != 0 && statbuf.st_uid != job->uid) { + log_it(pw->pw_name, getpid(), "WRONG FILE OWNER", atfile); + _exit(ERROR_EXIT); + } + if (statbuf.st_nlink > 1) { + log_it(pw->pw_name, getpid(), "BAD LINK COUNT", atfile); + _exit(ERROR_EXIT); + } + + if ((fp = fdopen(dup(fd), "r")) == NULL) { + log_it("CRON", getpid(), "error", "dup(2) failed"); + _exit(ERROR_EXIT); + } + + /* + * Check the at job header for sanity and extract the + * uid, gid, mailto user and always_mail flag. + * + * The header should look like this: + * #!/bin/sh + * # atrun uid=123 gid=123 + * # mail joeuser 0 + */ + if (fgets(buf, sizeof(buf), fp) == NULL || + strcmp(buf, "#!/bin/sh\n") != 0 || + fgets(buf, sizeof(buf), fp) == NULL || + strncmp(buf, "# atrun uid=", 12) != 0) + goto bad_file; + + /* Pull out uid */ + cp = buf + 12; + errno = 0; + nuid = strtol(cp, &ep, 10); + if (errno == ERANGE || (uid_t)nuid > UID_MAX || cp == ep || + strncmp(ep, " gid=", 5) != 0) + goto bad_file; + + /* Pull out gid */ + cp = ep + 5; + errno = 0; + ngid = strtol(cp, &ep, 10); + if (errno == ERANGE || (uid_t)ngid > GID_MAX || cp == ep || *ep != '\n') + goto bad_file; + + /* Pull out mailto user (and always_mail flag) */ + if (fgets(buf, sizeof(buf), fp) == NULL || + strncmp(buf, "# mail ", 7) != 0) + goto bad_file; + cp = buf + 7; + while (isspace(*cp)) + cp++; + ep = cp; + while (!isspace(*ep) && *ep != '\0') + ep++; + if (*ep == '\0' || *ep != ' ' || ep - cp >= sizeof(mailto)) + goto bad_file; + memcpy(mailto, cp, ep - cp); + mailto[ep - cp] = '\0'; + always_mail = *(ep + 1) == '1'; + + (void)fclose(fp); + if (!safe_p(pw->pw_name, mailto)) + _exit(ERROR_EXIT); + if ((uid_t)nuid != job->uid) { + log_it(pw->pw_name, getpid(), "UID MISMATCH", atfile); + _exit(ERROR_EXIT); + } + if ((gid_t)ngid != job->gid) { + log_it(pw->pw_name, getpid(), "GID MISMATCH", atfile); + _exit(ERROR_EXIT); + } + +#ifdef CAPITALIZE_FOR_PS + /* mark ourselves as different to PS command watchers by upshifting + * our program name. This has no effect on some kernels. + * XXX - really want to set proc title to at job name instead + */ + /*local*/{ + char *pch; + + for (pch = ProgramName; *pch; pch++) + *pch = MkUpper(*pch); + } +#endif /* CAPITALIZE_FOR_PS */ + + pipe(output_pipe); /* child's stdout/stderr */ + + /* Fork again, child will run the job, parent will catch output. */ + switch ((pid = fork())) { + case -1: + log_it("CRON", getpid(), "error", "can't fork"); + _exit(ERROR_EXIT); + /*NOTREACHED*/ + case 0: + Debug(DPROC, ("[%ld] grandchild process fork()'ed\n", + (long)getpid())) + + /* Write log message now that we have our real pid. */ + log_it(pw->pw_name, getpid(), "ATJOB", atfile); + +#ifdef SYSLOG + closelog(); +#endif + + /* Connect grandchild's stdin to the at job file. */ + if (lseek(fd, (off_t) 0, SEEK_SET) < 0) { + perror("lseek"); + _exit(ERROR_EXIT); + } + if (fd != STDIN) { + dup2(fd, STDIN); + close(fd); + } + + /* Connect stdout/stderr to the pipe from our parent. */ + if (output_pipe[WRITE_PIPE] != STDOUT) { + dup2(output_pipe[WRITE_PIPE], STDOUT); + close(output_pipe[WRITE_PIPE]); + } + dup2(STDOUT, STDERR); + close(output_pipe[READ_PIPE]); + + (void) setsid(); + +#ifdef LOGIN_CAP + { + login_cap_t *lc; +# ifdef BSD_AUTH + auth_session_t *as; +# endif + if ((lc = login_getclass(pw->pw_class)) == NULL) { + fprintf(stderr, + "Cannot get login class for %s\n", + pw->pw_name); + _exit(ERROR_EXIT); + + } + + if (setusercontext(lc, pw, pw->pw_uid, LOGIN_SETALL)) { + fprintf(stderr, + "setusercontext failed for %s\n", + pw->pw_name); + _exit(ERROR_EXIT); + } +# ifdef BSD_AUTH + as = auth_open(); + if (as == NULL || auth_setpwd(as, pw) != 0) { + fprintf(stderr, "can't malloc\n"); + _exit(ERROR_EXIT); + } + if (auth_approval(as, lc, pw->pw_name, "cron") <= 0) { + fprintf(stderr, "approval failed for %s\n", + pw->pw_name); + _exit(ERROR_EXIT); + } + auth_close(as); + login_close(lc); +# endif /* BSD_AUTH */ + } +#else + setgid(pw->pw_gid); + initgroups(pw->pw_name, pw->pw_gid); + setlogin(pw->pw_name); + setuid(pw->pw_uid); + +#endif /* LOGIN_CAP */ + + chdir("/"); /* at job will chdir to correct place */ + + /* If this is a low priority job, nice ourself. */ + if (job->queue > 'b') + (void)setpriority(PRIO_PROCESS, 0, job->queue - 'b'); + +#if DEBUGGING + if (DebugFlags & DTEST) { + fprintf(stderr, + "debug DTEST is on, not exec'ing at job %s\n", + atfile); + _exit(OK_EXIT); + } +#endif /*DEBUGGING*/ + + /* + * Exec /bin/sh with stdin connected to the at job file + * and stdout/stderr hooked up to our parent. + * The at file will set the environment up for us. + */ + nargv[0] = "sh"; + nargv[1] = NULL; + nenvp[0] = NULL; + if (execve(_PATH_BSHELL, nargv, nenvp) != 0) { + perror("execve: " _PATH_BSHELL); + _exit(ERROR_EXIT); + } + break; + default: + /* parent */ + break; + } + + Debug(DPROC, ("[%ld] child continues, closing output pipe\n", + (long)getpid())) + + /* Close the atfile's fd and the end of the pipe we don't use. */ + close(fd); + close(output_pipe[WRITE_PIPE]); + + /* Read piped output (if any) from the at job. */ + Debug(DPROC, ("[%ld] child reading output from grandchild\n", + (long)getpid())) + + fp = fdopen(output_pipe[READ_PIPE], "r"); + if (always_mail || !feof(fp)) { + FILE *mail; + int bytes = 0; + int status = 0; + char mailcmd[MAX_COMMAND]; + char hostname[MAXHOSTNAMELEN]; + size_t nread; + + Debug(DPROC|DEXT, ("[%ld] got data from grandchild\n", + (long)getpid())) + + if (gethostname(hostname, sizeof(hostname)) != 0) + strcpy(hostname, "unknown"); + if (snprintf(mailcmd, sizeof mailcmd, MAILFMT, + MAILARG) >= sizeof mailcmd) { + fprintf(stderr, "mailcmd too long\n"); + (void) _exit(ERROR_EXIT); + } + if (!(mail = cron_popen(mailcmd, "w", pw))) { + perror(mailcmd); + (void) _exit(ERROR_EXIT); + } + fprintf(mail, "From: %s (Atrun Service)\n", pw->pw_name); + fprintf(mail, "To: %s\n", mailto); + fprintf(mail, "Subject: Output from \"at\" job\n"); +#ifdef MAIL_DATE + fprintf(mail, "Date: %s\n", arpadate(&StartTime)); +#endif /*MAIL_DATE*/ + fprintf(mail, "\nYour \"at\" job on %s\n\"%s\"\n", + hostname, atfile); + fprintf(mail, "\nproduced the following output:\n\n"); + + /* Pipe the job's output to sendmail. */ + while ((nread = fread(buf, 1, sizeof(buf), fp)) > 0) { + bytes += nread; + fwrite(buf, nread, 1, mail); + } + + /* + * If the mailer exits with non-zero exit status, log + * this fact so the problem can (hopefully) be debugged. + */ + Debug(DPROC, ("[%ld] closing pipe to mail\n", + (long)getpid())) + if ((status = cron_pclose(mail)) != 0) { + snprintf(buf, sizeof(buf), "mailed %d byte%s of output" + " but got status 0x%04x\n", + bytes, (bytes == 1) ? "" : "s", status); + log_it(pw->pw_name, getpid(), "MAIL", buf); + } + } + Debug(DPROC, ("[%ld] got EOF from grandchild\n", (long)getpid())) + + fclose(fp); /* also closes output_pipe[READ_PIPE] */ + + /* Wait for grandchild to die. */ + Debug(DPROC, ("[%ld] waiting for grandchild (%ld) to finish\n", + (long)getpid(), (long)pid)) + for (;;) { + if (waitpid(pid, &waiter, 0) == -1) { + if (errno == EINTR) + continue; + Debug(DPROC, + ("[%ld] no grandchild process--mail written?\n", + (long)getpid())) + break; + } else { + Debug(DPROC, ("[%ld] grandchild (%ld) finished, status=%04x", + (long)getpid(), (long)pid, WEXITSTATUS(waiter))) + if (WIFSIGNALED(waiter) && WCOREDUMP(waiter)) + Debug(DPROC, (", dumped core")) + Debug(DPROC, ("\n")) + break; + } + } + _exit(OK_EXIT); + +bad_file: + log_it(pw->pw_name, getpid(), "BAD FILE FORMAT", atfile); + _exit(ERROR_EXIT); +} diff --git a/usr.sbin/cron/config.h b/usr.sbin/cron/config.h index 61c573bc3b0..1a1639c553e 100644 --- a/usr.sbin/cron/config.h +++ b/usr.sbin/cron/config.h @@ -1,4 +1,4 @@ -/* $OpenBSD: config.h,v 1.12 2002/07/08 23:42:17 millert Exp $ */ +/* $OpenBSD: config.h,v 1.13 2002/07/15 19:13:29 millert Exp $ */ /* Copyright 1988,1990,1993,1994 by Paul Vixie * All rights reserved @@ -21,7 +21,7 @@ * SOFTWARE. */ -/* config.h - configurables for Vixie Cron +/* config.h - configurables for ISC cron */ /* @@ -95,3 +95,6 @@ /* if your OS supports BSD authentication */ #define BSD_AUTH /*-*/ + + /* maximum load at which batch jobs will still run */ +#define BATCH_MAXLOAD 1.5 diff --git a/usr.sbin/cron/cron.8 b/usr.sbin/cron/cron.8 index 70b1c7a2dec..09cb6baf4f2 100644 --- a/usr.sbin/cron/cron.8 +++ b/usr.sbin/cron/cron.8 @@ -1,101 +1,166 @@ -.\"/* Copyright 1988,1990,1993,1996 by Paul Vixie -.\" * All rights reserved -.\" */ .\" -.\" Copyright (c) 1997,2000 by Internet Software Consortium, Inc. +.\" Copyright (c) 2002 Todd C. Miller <Todd.Miller@courtesan.com> +.\" All rights reserved. .\" -.\" Permission to use, copy, modify, and distribute this software for any -.\" purpose with or without fee is hereby granted, provided that the above -.\" copyright notice and this permission notice appear in all copies. +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" 3. The name of the author may not be used to endorse or promote products +.\" derived from this software without specific prior written permission. .\" -.\" THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SOFTWARE CONSORTIUM DISCLAIMS -.\" ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES -.\" OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL INTERNET SOFTWARE -.\" CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL -.\" DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR -.\" PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS -.\" ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS -.\" SOFTWARE. +.\" THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, +.\" INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY +.\" AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +.\" THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +.\" EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +.\" PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; +.\" OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +.\" WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR +.\" OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF +.\" ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. .\" -.\" $OpenBSD: cron.8,v 1.15 2002/07/08 18:11:02 millert Exp $ +.\" $OpenBSD: cron.8,v 1.16 2002/07/15 19:13:29 millert Exp $ .\" -.Dd June 6, 1999 +.Dd July 6, 2002 .Dt CRON 8 .Os .Sh NAME .Nm cron -.Nd daemon to execute scheduled commands (Vixie Cron) +.Nd clock daemon .Sh SYNOPSIS .Nm cron +.Op Fl l Ar load_avg +.Op Fl x Ar [ext,sch,proc,pars,load,misc,test,bit] .Sh DESCRIPTION +The .Nm -should be started from +daemon schedules commands to be run at specified dates and times. +Commands that are to be run periodically are specified within +.Xr crontab 5 +files. +Commands that are only to be run once are scheduled via the +.Xr at 1 +and +.Xr batch 1 +commands. +Normally, the +.Nm +daemon is started from the .Pa /etc/rc -or -.Pa /etc/rc.local . -It will return immediately, so you don't need to start it with -.Ql \&& . +command script. Because it can execute commands on a user's behalf, .Nm should be run late in the startup sequence, as close to the time when logins are accepted as possible. .Pp .Nm -searches its spool directory -.Pf ( Pa /var/cron/tabs Ns ) -for +loads .Xr crontab 5 -files which are named after accounts in -.Pa /etc/passwd ; -crontabs found are loaded into memory. -.Nm -also searches for -.Pa /etc/crontab -which is in a different format (see -.Xr crontab 5 ) . +and +.Xr at 1 +files when it starts up and also when changes are made via the +.Xr crontab 1 +and +.Xr at 1 +commands. +Additionally, .Nm -then wakes up every minute, examining all loaded crontabs, checking each -command to see if it should be run in the current minute. -When executing commands, any output is mailed to the user named in the +checks the modification time on the system crontab file +.Pq Pa /etc/crontab , +the crontab spool +.Pq Pa /var/cron/tabs , +and the at spool +.Pq Pa /var/at/jobs +once a minute. +If the modification time has changed, the affected files are reloaded. +.Pp +Any output produced by a command is sent to the used specified in the .Ev MAILTO -environment variable in the crontab, or to the owner of the crontab if +environment variable as set in the +.Xr crontab 5 +file or, if no .Ev MAILTO -is not present. +variable is set (or if this is an +.Xr at 1 +or +.Xr batch 1 +job), to the job's owner. +If a command produces no output, no mail will be sent. +The exception to this are +.Xr at 1 +or +.Xr batch 1 +jobs submitted with the +.Fl m +flag. +In this case, mail will be sent even if the job produces no output. +.Ss Daylight Saving Time and other time changes +Local time changes of less than three hours, such as those caused +by the start or end of Daylight Saving Time, are handled specially. +This only applies to jobs that run at a specific time and jobs that +are run with a granularity greater than one hour. Jobs that run +more frequently are scheduled normally. .Pp -Additionally, -.Nm -checks each minute to see if its spool directory's modtime (or the modtime on -.Pa /etc/crontab ) -has changed, and if it has, -.Nm -examines the modtime on all crontabs and reloads those which have -changed. -Thus -.Nm -need not be restarted whenever a crontab file is modified. -Note that the -.Xr crontab 1 -command updates the modtime of the spool directory whenever it changes a -crontab. +If time has moved forward, those jobs that would have run in the +interval that has been skipped will be run immediately. +Conversely, if time has moved backward, care is taken to avoid running +jobs twice. .Pp -Special considerations exist when the clock is changed by less than 3 -hours; for example, at the beginning and end of Daylight Saving -Time. -If the time has moved forward, those jobs which would have -run in the time that was skipped will be run soon after the change. -Conversely, if the time has moved backward by less than 3 hours, -those jobs that fall into the repeated time will not be run. +Time changes of more than 3 hours are considered to be corrections to +the clock or timezone, and the new time is used immediately. .Pp -Only jobs that run at a particular time (not specified as @hourly, nor with -.Ql * -in the hour or minute specifier) -are -affected. -Jobs which are specified with wildcards are run based on the -new time immediately. +The options are as follows: +.Bl -tag -width Ds +.It Fl l Ar load_avg +If the current load average is greater than +.Ar load_avg , +.Xr batch 1 +jobs will not be run. +The default value is 1.5. +To allow +.Xr batch 1 +jobs to run regardless of the load, a value of 0.0 may be used. +.It Fl x Ar debug_flags +If +.Nm +was compiled with debugging support, a number of debugging flags +are available to show what +.Nm +is doing. +The following flags may be specified: +.Bl -tag -width Ds +.It ext +show extended information; used in conjunction with other debug flags +to provide even more information +.It sch +print information related to scheduling jobs +.It proc +print information related to running processes +.It pars +print information related to parsing +.Xr crontab 5 +files +.It load +print when loading the databases +.It misc +show misc other debugging information +.It test +test mode; don't actually execute commands +.El .Pp -Clock changes of more than 3 hours are considered to be corrections to -the clock, and the new time is used immediately. +Multiple flags may be specified, sepatated by a comma +.Pq So , Sc +Regardless of which flags were specified, the +.Fl x +flag will cause +.Nm +to stay in the foreground and not become a daemon. +.El .Sh SIGNALS .Bl -tag -width Ds .It Dv SIGHUP @@ -112,10 +177,16 @@ logs via .El .Sh FILES .Bl -tag -width "/var/cron/tabs/.sock" -compact +.It Pa /etc/crontab +system crontab file +.It Pa /var/at/jobs +directory containing +.Xr at 1 +jobs .It Pa /var/cron/log cron's log file .It Pa /var/cron/tabs -directory of individual crontabs +directory containing individual crontab files .It Pa /var/cron/tabs/.sock used by .Xr crontab 1 @@ -124,6 +195,7 @@ to tell to check for crontab changes immediately .El .Sh SEE ALSO +.Xr at 1 , .Xr crontab 1 , .Xr syslog 3 , .Xr crontab 5 diff --git a/usr.sbin/cron/cron.c b/usr.sbin/cron/cron.c index 7afcdcdab6d..c3605ed50f3 100644 --- a/usr.sbin/cron/cron.c +++ b/usr.sbin/cron/cron.c @@ -1,4 +1,4 @@ -/* $OpenBSD: cron.c,v 1.26 2002/07/09 18:59:12 millert Exp $ */ +/* $OpenBSD: cron.c,v 1.27 2002/07/15 19:13:29 millert Exp $ */ /* Copyright 1988,1990,1993,1994 by Paul Vixie * All rights reserved */ @@ -21,7 +21,7 @@ */ #if !defined(lint) && !defined(LINT) -static const char rcsid[] = "$OpenBSD: cron.c,v 1.26 2002/07/09 18:59:12 millert Exp $"; +static const char rcsid[] = "$OpenBSD: cron.c,v 1.27 2002/07/15 19:13:29 millert Exp $"; #endif #define MAIN_PROGRAM @@ -38,7 +38,6 @@ static void usage(void), sigchld_handler(int), sighup_handler(int), sigchld_reaper(void), - check_sigs(int), quit(int), parse_args(int c, char *v[]); @@ -46,12 +45,14 @@ static volatile sig_atomic_t got_sighup, got_sigchld; static int timeRunning, virtualTime, clockTime, cronSock; static long GMToff; static cron_db database; +static at_db at_database; +static double batch_maxload = BATCH_MAXLOAD; static void usage(void) { const char **dflags; - fprintf(stderr, "usage: %s [-x [", ProgramName); + fprintf(stderr, "usage: %s [-l load_avg] [-x [", ProgramName); for (dflags = DebugFlagNames; *dflags; dflags++) fprintf(stderr, "%s%s", *dflags, dflags[1] ? "," : "]"); fprintf(stderr, "]\n"); @@ -136,6 +137,10 @@ main(int argc, char *argv[]) { database.tail = NULL; database.mtime = (time_t) 0; load_database(&database); + at_database.head = NULL; + at_database.tail = NULL; + at_database.mtime = (time_t) 0; + scan_atjobs(&at_database, NULL); set_time(1); run_reboot_jobs(&database); timeRunning = virtualTime = clockTime; @@ -154,7 +159,6 @@ main(int argc, char *argv[]) { int timeDiff; int wakeupKind; - check_sigs(TRUE); /* ... wait for the time (in minutes) to change ... */ do { cron_sleep(timeRunning + 1); @@ -257,6 +261,22 @@ main(int argc, char *argv[]) { /* Jobs to be run (if any) are loaded; clear the queue. */ job_runqueue(); + + /* Run any jobs in the at queue. */ + atrun(&at_database, batch_maxload, + timeRunning * SECONDS_PER_MINUTE - GMToff); + + /* Check to see if we received a signal while running jobs. */ + if (got_sighup) { + got_sighup = 0; + log_close(); + } + if (got_sigchld) { + got_sigchld = 0; + sigchld_reaper(); + } + load_database(&database); + scan_atjobs(&at_database, NULL); } } @@ -303,8 +323,8 @@ find_jobs(int vtime, cron_db *db, int doWild, int doNonWild) { for (u = db->head; u != NULL; u = u->next) { for (e = u->crontab; e != NULL; e = e->next) { Debug(DSCH|DEXT, ("user [%s:%ld:%ld:...] cmd=\"%s\"\n", - env_get("LOGNAME", e->envp), - (long)e->uid, (long)e->gid, e->cmd)) + e->pwd->pw_name, (long)e->pwd->pw_uid, + (long)e->pwd->pw_gid, e->cmd)) if (bit_test(e->minute, minute) && bit_test(e->hour, hour) && bit_test(e->month, month) && @@ -348,8 +368,8 @@ set_time(int initialize) { */ static void cron_sleep(int target) { - char c; int fd, nfds; + unsigned char poke; struct timeval t1, t2, tv; struct sockaddr_un sun; socklen_t sunlen; @@ -359,8 +379,6 @@ cron_sleep(int target) { t1.tv_sec += GMToff; tv.tv_sec = (target * SECONDS_PER_MINUTE - t1.tv_sec) + 1; tv.tv_usec = 0; - Debug(DSCH, ("[%ld] Target time=%ld, sec-to-wait=%ld\n", - (long)getpid(), (long)target*SECONDS_PER_MINUTE, tv.tv_sec)) if (fdsr == NULL) { fdsr = (fd_set *)calloc(howmany(cronSock + 1, NFDBITS), @@ -368,9 +386,13 @@ cron_sleep(int target) { } while (timerisset(&tv) && tv.tv_sec < 65) { + Debug(DSCH, ("[%ld] Target time=%ld, sec-to-wait=%ld\n", + (long)getpid(), (long)target*SECONDS_PER_MINUTE, tv.tv_sec)) + + poke = 0; if (fdsr) FD_SET(cronSock, fdsr); - /* Sleep until we time out, get a crontab poke, or signal. */ + /* Sleep until we time out, get a poke, or get a signal. */ nfds = select(cronSock + 1, fdsr, NULL, NULL, &tv); if (nfds == 0) break; /* timer expired */ @@ -381,18 +403,35 @@ cron_sleep(int target) { (long)getpid())) fd = accept(cronSock, (struct sockaddr *)&sun, &sunlen); if (fd >= 0) { - while (read(fd, &c, sizeof c) > 0) - ; /* suck up anything in the socket */ + (void) read(fd, &poke, 1); close(fd); + if (poke & RELOAD_CRON) + load_database(&database); + if (poke & RELOAD_AT) { + /* + * We run any pending at jobs right + * away so that "at now" really runs + * jobs immediately. + */ + gettimeofday(&t2, NULL); + if (scan_atjobs(&at_database, &t2)) + atrun(&at_database, + batch_maxload, t2.tv_sec); + } + } + } else { + /* Interrupted by a signal. */ + if (got_sighup) { + got_sighup = 0; + log_close(); + } + if (got_sigchld) { + got_sigchld = 0; + sigchld_reaper(); } } - /* - * Check to see if we were interrupted by a signal or a poke - * on the socket. If so, service the signal/poke, adjust tv - * and continue the select() where we left off. - */ - check_sigs(nfds > 0); + /* Adjust tv and continue where we left off. */ gettimeofday(&t2, NULL); t2.tv_sec += GMToff; timersub(&t2, &t1, &t1); @@ -451,31 +490,28 @@ sigchld_reaper() { } static void -check_sigs(int force_dbload) { - if (got_sighup) { - got_sighup = 0; - log_close(); - } - if (got_sigchld) { - got_sigchld = 0; - sigchld_reaper(); - } - if (force_dbload) - load_database(&database); -} - -static void parse_args(int argc, char *argv[]) { int argch; + char *ep; - while (-1 != (argch = getopt(argc, argv, "x:"))) { + while (-1 != (argch = getopt(argc, argv, "l:x:"))) { switch (argch) { - default: - usage(); + case 'l': + errno = 0; + batch_maxload = strtod(optarg, &ep); + if (*ep != '\0' || ep == optarg || errno == ERANGE || + batch_maxload < 0) { + fprintf(stderr, "Illegal load average: %s\n", + optarg); + usage(); + } + break; case 'x': if (!set_debug_flags(optarg)) usage(); break; + default: + usage(); } } } diff --git a/usr.sbin/cron/crontab.1 b/usr.sbin/cron/crontab.1 index d0d7de14d4c..31150510cdf 100644 --- a/usr.sbin/cron/crontab.1 +++ b/usr.sbin/cron/crontab.1 @@ -15,7 +15,7 @@ .\" ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS .\" SOFTWARE. .\" -.\" $OpenBSD: crontab.1,v 1.14 2002/07/08 18:11:02 millert Exp $ +.\" $OpenBSD: crontab.1,v 1.15 2002/07/15 19:13:29 millert Exp $ .\" .Dd June 8, 1999 .Dt CRONTAB 1 @@ -39,7 +39,7 @@ is the program used to install, deinstall, or list the tables used to drive the .Xr cron 8 -daemon in Vixie Cron. +daemon. Each user can have their own .Xr crontab 5 , and though these are files in diff --git a/usr.sbin/cron/crontab.c b/usr.sbin/cron/crontab.c index 346f249f0e5..48bcf3c4afc 100644 --- a/usr.sbin/cron/crontab.c +++ b/usr.sbin/cron/crontab.c @@ -1,4 +1,4 @@ -/* $OpenBSD: crontab.c,v 1.33 2002/07/11 20:17:04 millert Exp $ */ +/* $OpenBSD: crontab.c,v 1.34 2002/07/15 19:13:29 millert Exp $ */ /* Copyright 1988,1990,1993,1994 by Paul Vixie * All rights reserved */ @@ -21,7 +21,7 @@ */ #if !defined(lint) && !defined(LINT) -static char const rcsid[] = "$OpenBSD: crontab.c,v 1.33 2002/07/11 20:17:04 millert Exp $"; +static char const rcsid[] = "$OpenBSD: crontab.c,v 1.34 2002/07/15 19:13:29 millert Exp $"; #endif /* crontab - install and manage per-user crontab files @@ -639,6 +639,7 @@ done: static void poke_daemon() { int sock, flags; + unsigned char poke; struct sockaddr_un sun; if (utime(SPOOL_DIR, NULL) < OK) { @@ -654,12 +655,13 @@ poke_daemon() { sun.sun_family = AF_UNIX; sun.sun_len = strlen(sun.sun_path); if ((sock = socket(AF_UNIX, SOCK_STREAM, 0)) >= 0 && - (flags = fcntl(sock, F_GETFL)) >= 0 && - fcntl(sock, F_SETFL, flags | O_NONBLOCK) >= 0 && connect(sock, (struct sockaddr *)&sun, sizeof(sun)) == 0) { - write(sock, "!", 1); + poke = RELOAD_CRON; + write(sock, &poke, 1); close(sock); - } + } else + fprintf(stderr, "Warning, cron does not appear to be running.\n"); + } (void) signal(SIGPIPE, SIG_DFL); } diff --git a/usr.sbin/cron/do_command.c b/usr.sbin/cron/do_command.c index e1d278e85fc..17e3943df05 100644 --- a/usr.sbin/cron/do_command.c +++ b/usr.sbin/cron/do_command.c @@ -1,4 +1,4 @@ -/* $OpenBSD: do_command.c,v 1.18 2002/07/12 18:35:24 millert Exp $ */ +/* $OpenBSD: do_command.c,v 1.19 2002/07/15 19:13:29 millert Exp $ */ /* Copyright 1988,1990,1993,1994 by Paul Vixie * All rights reserved */ @@ -21,19 +21,18 @@ */ #if !defined(lint) && !defined(LINT) -static char const rcsid[] = "$OpenBSD: do_command.c,v 1.18 2002/07/12 18:35:24 millert Exp $"; +static char const rcsid[] = "$OpenBSD: do_command.c,v 1.19 2002/07/15 19:13:29 millert Exp $"; #endif #include "cron.h" static void child_process(entry *, user *); -static int safe_p(const char *, const char *); void do_command(entry *e, user *u) { Debug(DPROC, ("[%ld] do_command(%s, (%s,%lu,%lu))\n", (long)getpid(), e->cmd, u->name, - (u_long)e->uid, (u_long)e->gid)) + (u_long)e->pwd->pw_uid, (u_long)e->pwd->pw_gid)) /* fork to become asynchronous -- parent process is done immediately, * and continues to run the normal cron code, which means return to @@ -84,7 +83,7 @@ child_process(entry *e, user *u) { /* discover some useful and important environment settings */ - usernm = env_get("LOGNAME", e->envp); + usernm = e->pwd->pw_name; mailto = env_get("MAILTO", e->envp); /* our parent is watching for our death by catching SIGCHLD. we @@ -104,10 +103,9 @@ child_process(entry *e, user *u) { * * if a % is present in the command, previous characters are the * command, and subsequent characters are the additional input to - * the command. Subsequent %'s will be transformed into newlines, + * the command. An escaped % will have the escape character stripped + * from it. Subsequent %'s will be transformed into newlines, * but that happens later. - * - * If there are escaped %'s, remove the escape character. */ /*local*/{ int escaped = FALSE; @@ -141,11 +139,11 @@ child_process(entry *e, user *u) { */ switch (fork()) { case -1: - log_it("CRON", getpid(), "error", "can't vfork"); + log_it("CRON", getpid(), "error", "can't fork"); exit(ERROR_EXIT); /*NOTREACHED*/ case 0: - Debug(DPROC, ("[%ld] grandchild process Vfork()'ed\n", + Debug(DPROC, ("[%ld] grandchild process fork()'ed\n", (long)getpid())) /* write a log message. we've waited this long to do it @@ -197,45 +195,49 @@ child_process(entry *e, user *u) { */ #ifdef LOGIN_CAP { - struct passwd *pwd; - char *ep, *np; + login_cap_t *lc; + char **p; + extern char **environ; /* XXX - should just pass in a login_cap_t * */ - pwd = getpwuid(e->uid); - if (pwd == NULL) { - fprintf(stderr, "getpwuid: couldn't get entry for %u\n", e->uid); + if ((lc = login_getclass(e->pwd->pw_class)) == NULL) { + fprintf(stderr, + "unable to get login class for %s\n", + e->pwd->pw_name); _exit(ERROR_EXIT); } - if (setusercontext(0, pwd, e->uid, LOGIN_SETALL) < 0) { - fprintf(stderr, "setusercontext failed for %u\n", e->uid); + if (setusercontext(lc, e->pwd, e->pwd->pw_uid, LOGIN_SETALL) < 0) { + fprintf(stderr, "setusercontext failed for %s\n", e->pwd->pw_name); _exit(ERROR_EXIT); } #ifdef BSD_AUTH - if (auth_approval(0, 0, pwd->pw_name, "cron") <= 0) { - fprintf(stderr, "approval failed for %u\n", e->uid); + /* XXX - stash pwd with auth_setpwd to avoid lookup */ + if (auth_approval(0, lc, usernm, "cron") <= 0) { + fprintf(stderr, "approval failed for %s\n", + e->pwd->pw_name); _exit(ERROR_EXIT); } #endif /* BSD_AUTH */ + login_close(lc); + /* If no PATH specified in crontab file but * we just added one via login.conf, add it to * the crontab environment. */ - if (env_get("PATH", e->envp) == NULL && - (ep = getenv("PATH"))) { - np = malloc(strlen(ep) + 6); - if (np) { - strcpy(np, "PATH="); - strcat(np, ep); - e->envp = env_set(e->envp, np); + if (env_get("PATH", e->envp) == NULL && environ != NULL) { + for (p = environ; *p; p++) { + if (strncmp(*p, "PATH=", 5) == 0) { + e->envp = env_set(e->envp, *p); + break; + } } } - } #else - setgid(e->gid); - initgroups(env_get("LOGNAME", e->envp), e->gid); + setgid(e->pwd->pw_gid); + initgroups(usernm, e->pwd->pw_gid); setlogin(usernm); - setuid(e->uid); /* we aren't root after this... */ + setuid(e->pw->pw_uid); /* we aren't root after this... */ #endif /* LOGIN_CAP */ chdir(env_get("HOME", e->envp)); @@ -402,7 +404,7 @@ child_process(entry *e, user *u) { fprintf(stderr, "mailcmd too long\n"); (void) _exit(ERROR_EXIT); } - if (!(mail = cron_popen(mailcmd, "w", e))) { + if (!(mail = cron_popen(mailcmd, "w", e->pwd))) { perror(mailcmd); (void) _exit(ERROR_EXIT); } @@ -499,7 +501,7 @@ child_process(entry *e, user *u) { } } -static int +int safe_p(const char *usernm, const char *s) { static const char safe_delim[] = "@!:%-.,"; /* conservative! */ const char *t; diff --git a/usr.sbin/cron/entry.c b/usr.sbin/cron/entry.c index b672babcc9d..603f94538af 100644 --- a/usr.sbin/cron/entry.c +++ b/usr.sbin/cron/entry.c @@ -1,4 +1,4 @@ -/* $OpenBSD: entry.c,v 1.12 2002/07/11 20:15:40 millert Exp $ */ +/* $OpenBSD: entry.c,v 1.13 2002/07/15 19:13:29 millert Exp $ */ /* * Copyright 1988,1990,1993,1994 by Paul Vixie * All rights reserved @@ -22,7 +22,7 @@ */ #if !defined(lint) && !defined(LINT) -static char const rcsid[] = "$OpenBSD: entry.c,v 1.12 2002/07/11 20:15:40 millert Exp $"; +static char const rcsid[] = "$OpenBSD: entry.c,v 1.13 2002/07/15 19:13:29 millert Exp $"; #endif /* vix 26jan87 [RCS'd; rest of log is in RCS file] @@ -61,6 +61,7 @@ static int set_element(bitstr_t *, int, int, int); void free_entry(entry *e) { free(e->cmd); + free(e->pwd); env_free(e->envp); free(e); } @@ -253,14 +254,16 @@ load_entry(FILE *file, void (*error_func)(), struct passwd *pw, char **envp) { goto eof; } Debug(DPARS, ("load_entry()...uid %ld, gid %ld\n", - (long)e->uid, (long)e->gid)) + (long)e->pwd->pw_uid, (long)e->pwd->pw_gid)) } else if (ch == '*') { ecode = e_cmd; goto eof; } - e->uid = pw->pw_uid; - e->gid = pw->pw_gid; + if ((e->pwd = pw_dup(pw)) == NULL) { + ecode = e_memory; + goto eof; + } /* copy and fix up environment. some variables are just defaults and * others are overrides. @@ -372,6 +375,8 @@ load_entry(FILE *file, void (*error_func)(), struct passwd *pw, char **envp) { eof: if (e->envp) env_free(e->envp); + if (e->pwd) + free(e->pwd); if (e->cmd) free(e->cmd); free(e); diff --git a/usr.sbin/cron/externs.h b/usr.sbin/cron/externs.h index 2b4555dfa86..a39066461f1 100644 --- a/usr.sbin/cron/externs.h +++ b/usr.sbin/cron/externs.h @@ -1,4 +1,4 @@ -/* $OpenBSD: externs.h,v 1.6 2002/07/08 23:42:17 millert Exp $ */ +/* $OpenBSD: externs.h,v 1.7 2002/07/15 19:13:29 millert Exp $ */ /* Copyright 1993,1994 by Paul Vixie * All rights reserved @@ -57,14 +57,6 @@ # include <syslog.h> #endif -#if (defined(BSD)) && (BSD >= 199103) || defined(__linux) || defined(AIX) -# include <paths.h> -#endif /*BSD*/ - -#if !defined(_PATH_SENDMAIL) -# define _PATH_SENDMAIL "/usr/lib/sendmail" -#endif /*SENDMAIL*/ - #if defined(LOGIN_CAP) #include <login_cap.h> #endif /*LOGIN_CAP*/ @@ -115,7 +107,7 @@ extern int optind, opterr, optopt; */ extern int flock(int, int); -/* not all systems who provice flock() provide these definitions. +/* not all systems who provide flock() provide these definitions. */ #ifndef LOCK_SH # define LOCK_SH 1 diff --git a/usr.sbin/cron/funcs.h b/usr.sbin/cron/funcs.h index 7d11afd97ac..84a2bf67260 100644 --- a/usr.sbin/cron/funcs.h +++ b/usr.sbin/cron/funcs.h @@ -1,4 +1,4 @@ -/* $OpenBSD: funcs.h,v 1.7 2002/07/08 18:11:02 millert Exp $ */ +/* $OpenBSD: funcs.h,v 1.8 2002/07/15 19:13:29 millert Exp $ */ /* * Copyright (c) 1997,2000 by Internet Software Consortium, Inc. @@ -38,7 +38,8 @@ void set_cron_uid(void), acquire_daemonlock(int), skip_comments(FILE *), log_it(const char *, int, const char *, const char *), - log_close(void); + log_close(void), + atrun(at_db *, double, time_t); int job_runqueue(void), set_debug_flags(const char *), @@ -52,7 +53,9 @@ int job_runqueue(void), strcmp_until(const char *, const char *, char), allowed(const char *), strdtb(char *), - open_socket(void); + open_socket(void), + safe_p(const char *, const char *), + scan_atjobs(at_db *, struct timeval *); char *env_get(char *, char **), *arpadate(time_t *), @@ -67,7 +70,7 @@ user *load_user(int, struct passwd *, const char *), entry *load_entry(FILE *, void (*)(), struct passwd *, char **); -FILE *cron_popen(char *, char *, entry *); +FILE *cron_popen(char *, char *, struct passwd *); #ifndef HAVE_TM_GMTOFF long get_gmtoff(time_t *, struct tm *); diff --git a/usr.sbin/cron/globals.h b/usr.sbin/cron/globals.h index dfd1874cc3c..53ae2d79d78 100644 --- a/usr.sbin/cron/globals.h +++ b/usr.sbin/cron/globals.h @@ -1,4 +1,4 @@ -/* $OpenBSD: globals.h,v 1.3 2001/02/19 14:33:33 millert Exp $ */ +/* $OpenBSD: globals.h,v 1.4 2002/07/15 19:13:29 millert Exp $ */ /* * Copyright (c) 1997,2000 by Internet Software Consortium, Inc. @@ -64,8 +64,7 @@ XTRN int DebugFlags INIT(0); XTRN const char *DebugFlagNames[] #ifdef MAIN_PROGRAM = { - "ext", "sch", "proc", "pars", "load", "misc", "test", "bit",\ - NULL + "ext", "sch", "proc", "pars", "load", "misc", "test", NULL } #endif ; diff --git a/usr.sbin/cron/macros.h b/usr.sbin/cron/macros.h index a325d2eed6b..452334c0ec2 100644 --- a/usr.sbin/cron/macros.h +++ b/usr.sbin/cron/macros.h @@ -1,4 +1,4 @@ -/* $OpenBSD: macros.h,v 1.2 2001/02/20 02:03:19 millert Exp $ */ +/* $OpenBSD: macros.h,v 1.3 2002/07/15 19:13:29 millert Exp $ */ /* * Copyright (c) 1997,2000 by Internet Software Consortium, Inc. @@ -60,9 +60,7 @@ #define DLOAD 0x0010 /* database loading debug mask */ #define DMISC 0x0020 /* misc debug mask */ #define DTEST 0x0040 /* test mode: don't execute any commands */ -#define DBIT 0x0080 /* bit twiddling shown (long) */ -#define CRON_TAB(u) "%s/%s", SPOOL_DIR, u #define PPC_NULL ((const char **)NULL) #ifndef MAXHOSTNAMELEN @@ -95,6 +93,10 @@ LineNumber = ln; \ } +/* Data values used on cron socket */ +#define RELOAD_CRON 0x2 +#define RELOAD_AT 0x4 + #ifdef HAVE_TM_GMTOFF #define get_gmtoff(c, t) (t->tm_gmtoff) #endif diff --git a/usr.sbin/cron/misc.c b/usr.sbin/cron/misc.c index 6f2f3fefbf2..4c2a38eada0 100644 --- a/usr.sbin/cron/misc.c +++ b/usr.sbin/cron/misc.c @@ -1,4 +1,4 @@ -/* $OpenBSD: misc.c,v 1.21 2002/07/09 18:59:12 millert Exp $ */ +/* $OpenBSD: misc.c,v 1.22 2002/07/15 19:13:29 millert Exp $ */ /* Copyright 1988,1990,1993,1994 by Paul Vixie * All rights reserved */ @@ -21,7 +21,7 @@ */ #if !defined(lint) && !defined(LINT) -static char const rcsid[] = "$OpenBSD: misc.c,v 1.21 2002/07/09 18:59:12 millert Exp $"; +static char const rcsid[] = "$OpenBSD: misc.c,v 1.22 2002/07/15 19:13:29 millert Exp $"; #endif /* vix 26jan87 [RCS has the rest of the log] @@ -725,7 +725,7 @@ long get_gmtoff(time_t *clock, struct tm *local) int open_socket() { - int sock, flags; + int sock; mode_t omask; struct sockaddr_un sun; @@ -736,15 +736,6 @@ open_socket() log_it("CRON", getpid(), "DEATH", "can't create socket"); exit(ERROR_EXIT); } - if ((flags = fcntl(sock, F_GETFL)) == -1 || - fcntl(sock, F_SETFL, flags | O_NONBLOCK) == -1) { - fprintf(stderr, "%s: can't set non-block: %s\n", - ProgramName, strerror(errno)); - log_it("CRON", getpid(), "DEATH", "can't set non-block"); - exit(ERROR_EXIT); - } - (void) fcntl(sock, F_SETFD, 1); - if (!glue_strings(sun.sun_path, sizeof sun.sun_path, SPOOL_DIR, CRONSOCK, '/')) { fprintf(stderr, "%s/%s: path too long\n", SPOOL_DIR, CRONSOCK); diff --git a/usr.sbin/cron/pathnames.h b/usr.sbin/cron/pathnames.h index dad71a081dd..52a4f6255cd 100644 --- a/usr.sbin/cron/pathnames.h +++ b/usr.sbin/cron/pathnames.h @@ -1,4 +1,4 @@ -/* $OpenBSD: pathnames.h,v 1.7 2002/07/09 18:59:12 millert Exp $ */ +/* $OpenBSD: pathnames.h,v 1.8 2002/07/15 19:13:29 millert Exp $ */ /* Copyright 1993,1994 by Paul Vixie * All rights reserved @@ -21,8 +21,15 @@ * SOFTWARE. */ +#ifndef _PATHNAMES_H_ +#define _PATHNAMES_H_ + +#if (defined(BSD)) && (BSD >= 199103) || defined(__linux) || defined(AIX) +# include <paths.h> +#endif /*BSD*/ + #ifndef CRONDIR - /* CRONDIR is where crond(8) and crontab(1) both chdir + /* CRONDIR is where cron(8) and crontab(1) both chdir * to; SPOOL_DIR, ALLOW_FILE, DENY_FILE, and LOG_FILE * are all relative to this directory. */ @@ -32,7 +39,7 @@ /* SPOOLDIR is where the crontabs live. * This directory will have its modtime updated * whenever crontab(1) changes a crontab; this is - * the signal for crond(8) to look at each individual + * the signal for cron(8) to look at each individual * crontab file and reload those whose modtimes are * newer than they were last time around (or which * didn't exist last time around...) @@ -95,3 +102,14 @@ #ifndef _PATH_DEVNULL # define _PATH_DEVNULL "/dev/null" #endif + +#if !defined(_PATH_SENDMAIL) +# define _PATH_SENDMAIL "/usr/lib/sendmail" +#endif /*SENDMAIL*/ + +/* XXX */ +#define _PATH_ATJOBS "/var/at/jobs" +#define _PATH_AT_ALLOW "/var/at/at.allow" +#define _PATH_AT_DENY "/var/at/at.deny" + +#endif /* _PATHNAMES_H_ */ diff --git a/usr.sbin/cron/popen.c b/usr.sbin/cron/popen.c index e2963ac747e..e8f5cfdd3c1 100644 --- a/usr.sbin/cron/popen.c +++ b/usr.sbin/cron/popen.c @@ -1,4 +1,4 @@ -/* $OpenBSD: popen.c,v 1.16 2002/07/09 00:24:50 millert Exp $ */ +/* $OpenBSD: popen.c,v 1.17 2002/07/15 19:13:29 millert Exp $ */ /* * Copyright (c) 1988, 1993, 1994 @@ -42,7 +42,7 @@ #if 0 static const sccsid[] = "@(#)popen.c 8.3 (Berkeley) 4/6/94"; #else -static const char rcsid[] = "$OpenBSD: popen.c,v 1.16 2002/07/09 00:24:50 millert Exp $"; +static const char rcsid[] = "$OpenBSD: popen.c,v 1.17 2002/07/15 19:13:29 millert Exp $"; #endif #endif /* not lint */ @@ -60,7 +60,7 @@ static PID_T *pids; static int fds; FILE * -cron_popen(char *program, char *type, entry *e) { +cron_popen(char *program, char *type, struct passwd *pw) { char *cp; FILE *iop; int argc, pdes[2]; @@ -93,28 +93,23 @@ cron_popen(char *program, char *type, entry *e) { return (NULL); /* NOTREACHED */ case 0: /* child */ - if (e) { + if (pw) { #if defined(LOGIN_CAP) - struct passwd *pwd; - - pwd = getpwuid(e->uid); - if (pwd == NULL) { - fprintf(stderr, "getpwuid: couldn't get entry for %u\n", e->uid); - _exit(ERROR_EXIT); - } - if (setusercontext(0, pwd, e->uid, LOGIN_SETALL) < 0) { - fprintf(stderr, "setusercontext failed for %u\n", e->uid); + if (setusercontext(0, pw, pw->pw_uid, LOGIN_SETALL) < 0) { + fprintf(stderr, + "setusercontext failed for %s\n", + pw->pw_name); _exit(ERROR_EXIT); } #else - if (setgid(e->gid) || + if (setgid(pw->pw_gid) || setgroups(0, NULL) || - initgroups(env_get("LOGNAME", e->envp), e->gid)) + initgroups(pw->pw_name, pw->pw_gid)) _exit(1); - setlogin(env_get("LOGNAME", e->envp)); - if (setuid(e->uid)) + setlogin(pw->pw_name); + if (setuid(pw->pw_uid)) _exit(1); - chdir(env_get("HOME", e->envp)); + chdir(pw->pw_dir); #endif /* LOGIN_CAP */ } if (*type == 'r') { diff --git a/usr.sbin/cron/structs.h b/usr.sbin/cron/structs.h index a0920f7249e..e3602ab4830 100644 --- a/usr.sbin/cron/structs.h +++ b/usr.sbin/cron/structs.h @@ -1,4 +1,4 @@ -/* $OpenBSD: structs.h,v 1.3 2002/07/08 18:11:02 millert Exp $ */ +/* $OpenBSD: structs.h,v 1.4 2002/07/15 19:13:29 millert Exp $ */ /* * Copyright (c) 1997,2000 by Internet Software Consortium, Inc. @@ -19,8 +19,7 @@ typedef struct _entry { struct _entry *next; - uid_t uid; - gid_t gid; + struct passwd *pwd; char **envp; char *cmd; bitstr_t bit_decl(minute, MINUTE_COUNT); @@ -55,6 +54,19 @@ typedef struct _cron_db { user *head, *tail; /* links */ time_t mtime; /* last modtime on spooldir */ } cron_db; + +typedef struct _atjob { + struct _atjob *next, *prev; /* links */ + uid_t uid; /* uid of the job */ + gid_t gid; /* gid of the job */ + int queue; /* name of the at queue */ + time_t run_time; /* time to run at job */ +} atjob; + +typedef struct _at_db { + atjob *head, *tail; /* links */ + time_t mtime; /* last modtime on spooldir */ +} at_db; /* in the C tradition, we only create * variables for the main program, just * extern them elsewhere. |