386BSD 0.1 development
authorWilliam F. Jolitz <wjolitz@soda.berkeley.edu>
Wed, 18 Jul 1990 07:24:00 +0000 (23:24 -0800)
committerWilliam F. Jolitz <wjolitz@soda.berkeley.edu>
Wed, 18 Jul 1990 07:24:00 +0000 (23:24 -0800)
Work on file usr/src/libexec/crond/do_command.c
Work on file usr/src/libexec/crond/crond.8
Work on file usr/src/libexec/crond/entry.c
Work on file usr/src/libexec/crond/user.c
Work on file usr/src/libexec/crond/cron.h
Work on file usr/src/libexec/crond/env.c
Work on file usr/src/libexec/crond/database.c
Work on file usr/src/libexec/crond/job.c

Co-Authored-By: Lynne Greer Jolitz <ljolitz@cardio.ucsf.edu>
Synthesized-from: 386BSD-0.1

usr/src/libexec/crond/cron.h [new file with mode: 0644]
usr/src/libexec/crond/crond.8 [new file with mode: 0644]
usr/src/libexec/crond/database.c [new file with mode: 0644]
usr/src/libexec/crond/do_command.c [new file with mode: 0644]
usr/src/libexec/crond/entry.c [new file with mode: 0644]
usr/src/libexec/crond/env.c [new file with mode: 0644]
usr/src/libexec/crond/job.c [new file with mode: 0644]
usr/src/libexec/crond/user.c [new file with mode: 0644]

diff --git a/usr/src/libexec/crond/cron.h b/usr/src/libexec/crond/cron.h
new file mode 100644 (file)
index 0000000..449dd44
--- /dev/null
@@ -0,0 +1,260 @@
+/* cron.h - header for vixie's cron
+ *
+ * $Header: cron.h,v 2.1 90/07/18 00:23:47 vixie Exp $
+ * $Source: /jove_u3/vixie/src/cron/RCS/cron.h,v $
+ * $Revision: 2.1 $
+ * $Log:       cron.h,v $
+ * Revision 2.1  90/07/18  00:23:47  vixie
+ * Baseline for 4.4BSD release
+ * 
+ * Revision 2.0  88/12/10  04:57:39  vixie
+ * V2 Beta
+ * 
+ * Revision 1.2  88/11/29  13:05:46  vixie
+ * seems to work on Ultrix 3.0 FT1
+ * 
+ * Revision 1.1  88/11/14  12:27:49  vixie
+ * Initial revision
+ * 
+ * Revision 1.4  87/05/02  17:33:08  paul
+ * baseline for mod.sources release
+ *
+ * vix 14jan87 [0 or 7 can be sunday; thanks, mwm@berkeley]
+ * vix 30dec86 [written]
+ */
+
+/* Copyright 1988,1990 by Paul Vixie
+ * All rights reserved
+ *
+ * Distribute freely, except: don't remove my name from the source or
+ * documentation (don't take credit for my work), mark your changes (don't
+ * get me blamed for your possible bugs), don't alter or remove this
+ * notice.  May be sold if buildable source is provided to buyer.  No
+ * warrantee of any kind, express or implied, is included with this
+ * software; use at your own risk, responsibility for damages (if any) to
+ * anyone resulting from the use of this software rests entirely with the
+ * user.
+ *
+ * Send bug reports, bug fixes, enhancements, requests, flames, etc., and
+ * I'll try to keep a version up to date.  I can be reached as follows:
+ * Paul Vixie, 329 Noe Street, San Francisco, CA, 94114, (415) 864-7013,
+ * paul@vixie.sf.ca.us || {hoptoad,pacbell,decwrl,crash}!vixie!paul
+ */
+
+#ifndef        _CRON_FLAG
+#define        _CRON_FLAG
+
+#include <stdio.h>
+#include <ctype.h>
+#include <bitstring.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include "config.h"
+
+       /* these are really immutable, and are
+        *   defined for symbolic convenience only
+        * TRUE, FALSE, and ERR must be distinct
+        */
+#define TRUE           1
+#define FALSE          0
+       /* system calls return this on success */
+#define OK             0
+       /*   or this on error */
+#define ERR            (-1)
+
+       /* meaningless cookie for smart compilers that will pick their
+        * own register variables; this makes the code neater.
+        */
+#define        local           /**/
+
+       /* turn this on to get '-x' code */
+#ifndef DEBUGGING
+#define DEBUGGING      FALSE
+#endif
+
+#define READ_PIPE      0       /* which end of a pipe pair do you read? */
+#define WRITE_PIPE     1       /*   or write to? */
+#define STDIN          0       /* what is stdin's file descriptor? */
+#define STDOUT         1       /*   stdout's? */
+#define STDERR         2       /*   stderr's? */
+#define ERROR_EXIT     1       /* exit() with this will scare the shell */
+#define        OK_EXIT         0       /* exit() with this is considered 'normal' */
+#define        MAX_FNAME       100     /* max length of internally generated fn */
+#define        MAX_COMMAND     1000    /* max length of internally generated cmd */
+#define        MAX_ENVSTR      1000    /* max length of envvar=value\0 strings */
+#define        MAX_TEMPSTR     100     /* obvious */
+#define        MAX_UNAME       20      /* max length of username, should be overkill */
+#define        ROOT_UID        0       /* don't change this, it really must be root */
+#define        ROOT_USER       "root"  /* ditto */
+
+                               /* NOTE: these correspond to DebugFlagNames,
+                                *      defined below.
+                                */
+#define        DEXT            0x0001  /* extend flag for other debug masks */
+#define        DSCH            0x0002  /* scheduling debug mask */
+#define        DPROC           0x0004  /* process control debug mask */
+#define        DPARS           0x0008  /* parsing debug mask */
+#define        DLOAD           0x0010  /* database loading debug mask */
+#define        DMISC           0x0020  /* misc debug mask */
+#define        DTEST           0x0040  /* test mode: don't execute any commands */
+
+                               /* the code does not depend on any of vfork's
+                                * side-effects; it just uses it as a quick
+                                * fork-and-exec.
+                                */
+#if defined(BSD)
+# define VFORK         vfork
+#endif
+#if defined(ATT)
+# define VFORK         fork
+#endif
+
+#define        CRON_TAB(u)     "%s/%s", SPOOL_DIR, u
+#define        REG             register
+#define        PPC_NULL        ((char **)NULL)
+
+#ifndef MAXHOSTNAMELEN
+#define MAXHOSTNAMELEN 64
+#endif
+
+#define        Skip_Blanks(c, f) \
+                       while (c == '\t' || c == ' ') \
+                               c = get_char(f);
+
+#define        Skip_Nonblanks(c, f) \
+                       while (c!='\t' && c!=' ' && c!='\n' && c != EOF) \
+                               c = get_char(f);
+
+#define        Skip_Line(c, f) \
+                       do {c = get_char(f);} while (c != '\n' && c != EOF);
+
+#if DEBUGGING
+# define Debug(mask, message) \
+                       if ( (DebugFlags & (mask) ) == (mask) ) \
+                               printf message;
+#else /* !DEBUGGING */
+# define Debug(mask, message) \
+                       ;
+#endif /* DEBUGGING */
+
+#define        MkLower(ch)     (isupper(ch) ? tolower(ch) : ch)
+#define        MkUpper(ch)     (islower(ch) ? toupper(ch) : ch)
+#define        Set_LineNum(ln) {Debug(DPARS|DEXT,("linenum=%d\n",ln)); \
+                        LineNumber = ln; \
+                       }
+
+#define        FIRST_MINUTE    0
+#define        LAST_MINUTE     59
+#define        MINUTE_COUNT    (LAST_MINUTE - FIRST_MINUTE + 1)
+
+#define        FIRST_HOUR      0
+#define        LAST_HOUR       23
+#define        HOUR_COUNT      (LAST_HOUR - FIRST_HOUR + 1)
+
+#define        FIRST_DOM       1
+#define        LAST_DOM        31
+#define        DOM_COUNT       (LAST_DOM - FIRST_DOM + 1)
+
+#define        FIRST_MONTH     1
+#define        LAST_MONTH      12
+#define        MONTH_COUNT     (LAST_MONTH - FIRST_MONTH + 1)
+
+/* note on DOW: 0 and 7 are both Sunday, for compatibility reasons. */
+#define        FIRST_DOW       0
+#define        LAST_DOW        7
+#define        DOW_COUNT       (LAST_DOW - FIRST_DOW + 1)
+
+                       /* each user's crontab will be held as a list of
+                        * the following structure.
+                        *
+                        * These are the cron commands.
+                        */
+
+typedef        struct  _entry
+       {
+               struct _entry   *next;
+               char            *cmd;
+               bitstr_t        bit_decl(minute, MINUTE_COUNT);
+               bitstr_t        bit_decl(hour,   HOUR_COUNT);
+               bitstr_t        bit_decl(dom,    DOM_COUNT);
+               bitstr_t        bit_decl(month,  MONTH_COUNT);
+               bitstr_t        bit_decl(dow,    DOW_COUNT);
+               int             flags;
+# define       DOM_STAR        0x1
+# define       DOW_STAR        0x2
+# define       WHEN_REBOOT     0x4
+       }
+       entry;
+
+                       /* the crontab database will be a list of the
+                        * following structure, one element per user.
+                        *
+                        * These are the crontabs.
+                        */
+
+typedef        struct  _user
+       {
+               struct _user    *next, *prev;   /* links */
+               int             uid;            /* uid from passwd file */
+               int             gid;            /* gid from passwd file */
+               char            **envp;         /* environ for commands */
+               time_t          mtime;          /* last modtime of crontab */
+               entry           *crontab;       /* this person's crontab */
+       }
+       user;
+
+typedef        struct  _cron_db
+       {
+               user            *head, *tail;   /* links */
+               time_t          mtime;          /* last modtime on spooldir */
+       }
+       cron_db;
+
+                               /* in the C tradition, we only create
+                                * variables for the main program, just
+                                * extern them elsewhere.
+                                */
+
+#ifdef MAIN_PROGRAM
+
+# if !defined(LINT) && !defined(lint)
+               static char *copyright[] = {
+                       "@(#) Copyright (C) 1988, 1989, 1990 by Paul Vixie",
+                       "@(#) All rights reserved"
+               };
+# endif
+
+               char *MonthNames[] = {
+                       "Jan", "Feb", "Mar", "Apr", "May", "Jun",
+                       "Jul", "Aug", "Sep", "Oct", "Nov", "Dec",
+                       NULL};
+               char *DowNames[] = {
+                       "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun",
+                       NULL};
+               char *ProgramName;
+               int LineNumber;
+               time_t TargetTime;
+
+# if DEBUGGING
+               int DebugFlags;
+               char *DebugFlagNames[] = {      /* sync with #defines */
+                       "ext", "sch", "proc", "pars", "load", "misc", "test",
+                       NULL};  /* NULL must be last element */
+# endif /* DEBUGGING */
+
+#else /* !MAIN_PROGRAM */
+
+               extern char     *MonthNames[];
+               extern char     *DowNames[];
+               extern char     *ProgramName;
+               extern int      LineNumber;
+               extern time_t   TargetTime;
+# if DEBUGGING
+               extern int      DebugFlags;
+               extern char     *DebugFlagNames[];
+# endif /* DEBUGGING */
+#endif /* MAIN_PROGRAM */
+
+
+#endif /* _CRON_FLAG */
diff --git a/usr/src/libexec/crond/crond.8 b/usr/src/libexec/crond/crond.8
new file mode 100644 (file)
index 0000000..0e85515
--- /dev/null
@@ -0,0 +1,56 @@
+.\" $Header: crond.8,v 2.1 90/07/18 00:23:40 vixie Exp $
+.\" 
+.\"/* Copyright 1988,1990 by Paul Vixie
+.\" * All rights reserved
+.\" *
+.\" * Distribute freely, except: don't remove my name from the source or
+.\" * documentation (don't take credit for my work), mark your changes (don't
+.\" * get me blamed for your possible bugs), don't alter or remove this
+.\" * notice.  May be sold if buildable source is provided to buyer.  No
+.\" * warrantee of any kind, express or implied, is included with this
+.\" * software; use at your own risk, responsibility for damages (if any) to
+.\" * anyone resulting from the use of this software rests entirely with the
+.\" * user.
+.\" *
+.\" * Send bug reports, bug fixes, enhancements, requests, flames, etc., and
+.\" * I'll try to keep a version up to date.  I can be reached as follows:
+.\" * Paul Vixie, 329 Noe Street, San Francisco, CA, 94114, (415) 864-7013,
+.\" * paul@vixie.sf.ca.us || {hoptoad,pacbell,decwrl,crash}!vixie!paul
+.\" */
+.TH CROND 8 "15 Nov 1988"
+.UC 4
+.SH NAME
+crond \- daemon to execute scheduled commands (Vixie Cron)
+.SH SYNOPSIS
+crond
+.SH DESCRIPTION
+.I Crond
+should be started from /etc/rc or /etc/rc.local.  It will return immediately,
+so you don't need to start it with '&'.
+.PP
+.I Crond
+searches /var/cron/tabs for crontab files which are named after accounts in
+/etc/passwd; crontabs found are loaded into memory.
+.I Crond
+then wakes up every minute, examining all stored crontabs, checking each
+command to see if it should be run in the current minute.  When executing
+commands, any output is mailed to the owner of the crontab (or to the user
+named in the MAILTO environment variable in the crontab, if such exists).
+.PP
+Additionally,
+.I crond
+checks each minute to see if its spool directory's modtime has changed, and
+if it has,
+.I crond
+will then examine the modtime on all crontabs and reload those which have
+changed.  Thus
+.I crond
+need not be restarted whenever a crontab file is modified.  Note that the
+.IR Crontab (1)
+command updates the modtime of the spool directory whenever it changes a
+crontab.
+.SH "SEE ALSO"
+crontab(1), crontab(5)
+.SH AUTHOR
+.nf
+Paul Vixie, paul@vixie.sf.ca.us
diff --git a/usr/src/libexec/crond/database.c b/usr/src/libexec/crond/database.c
new file mode 100644 (file)
index 0000000..57520e1
--- /dev/null
@@ -0,0 +1,273 @@
+#if !defined(lint) && !defined(LINT)
+static char rcsid[] = "$Header: database.c,v 2.1 90/07/18 00:23:51 vixie Exp $";
+#endif
+
+/* vix 26jan87 [RCS has the log]
+ */
+
+/* Copyright 1988,1990 by Paul Vixie
+ * All rights reserved
+ *
+ * Distribute freely, except: don't remove my name from the source or
+ * documentation (don't take credit for my work), mark your changes (don't
+ * get me blamed for your possible bugs), don't alter or remove this
+ * notice.  May be sold if buildable source is provided to buyer.  No
+ * warrantee of any kind, express or implied, is included with this
+ * software; use at your own risk, responsibility for damages (if any) to
+ * anyone resulting from the use of this software rests entirely with the
+ * user.
+ *
+ * Send bug reports, bug fixes, enhancements, requests, flames, etc., and
+ * I'll try to keep a version up to date.  I can be reached as follows:
+ * Paul Vixie, 329 Noe Street, San Francisco, CA, 94114, (415) 864-7013,
+ * paul@vixie.sf.ca.us || {hoptoad,pacbell,decwrl,crash}!vixie!paul
+ */
+
+
+#include "cron.h"
+#include <pwd.h>
+#if defined(BSD)
+# include <sys/file.h>
+# include <sys/dir.h>
+#endif
+#if defined(ATT)
+# include <sys/file.h>
+# include <ndir.h>
+# include <fcntl.h>
+#endif
+
+
+extern void    perror(), exit();
+
+
+void
+load_database(old_db)
+       cron_db         *old_db;
+{
+       extern void     link_user(), unlink_user(), free_user();
+       extern user     *load_user(), *find_user();
+       extern char     *env_get();
+
+       static DIR      *dir = NULL;
+
+       struct stat     statbuf;
+       struct direct   *dp;
+       cron_db         new_db;
+       user            *u;
+
+       Debug(DLOAD, ("[%d] load_database()\n", getpid()))
+
+       /* before we start loading any data, do a stat on SPOOL_DIR
+        * so that if anything changes as of this moment (i.e., before we've
+        * cached any of the database), we'll see the changes next time.
+        */
+       if (stat(SPOOL_DIR, &statbuf) < OK)
+       {
+               log_it("CROND", getpid(), "STAT FAILED", SPOOL_DIR);
+               (void) exit(ERROR_EXIT);
+       }
+
+       /* if spooldir's mtime has not changed, we don't need to fiddle with
+        * the database.  Note that if /etc/passwd changes (like, someone's
+        * UID/GID/HOME/SHELL, we won't see it.  Maybe we should
+        * keep an mtime for the passwd file?  HINT
+        *
+        * Note that old_db->mtime is initialized to 0 in main(), and
+        * so is guaranteed to be different than the stat() mtime the first
+        * time this function is called.
+        */
+       if (old_db->mtime == statbuf.st_mtime)
+       {
+               Debug(DLOAD, ("[%d] spool dir mtime unch, no load needed.\n",
+                       getpid()))
+               return;
+       }
+
+       /* make sure the dir is open.  only happens the first time, since
+        * the DIR is static and we don't close it.  Rewind the dir.
+        */
+       if (dir == NULL)
+       {
+               if (!(dir = opendir(SPOOL_DIR)))
+               {
+                       log_it("CROND", getpid(), "OPENDIR FAILED", SPOOL_DIR);
+                       (void) exit(ERROR_EXIT);
+               }
+       }
+       (void) rewinddir(dir);
+
+       /* something's different.  make a new database, moving unchanged
+        * elements from the old database, reloading elements that have
+        * actually changed.  Whatever is left in the old database when
+        * we're done is chaff -- crontabs that disappeared.
+        */
+       new_db.mtime = statbuf.st_mtime;
+       new_db.head = new_db.tail = NULL;
+
+       while (NULL != (dp = readdir(dir)))
+       {
+               extern struct passwd    *getpwnam();
+               struct passwd           *pw;
+               int                     crontab_fd;
+               char                    fname[MAXNAMLEN+1],
+                                       tabname[MAXNAMLEN+1];
+
+               (void) strncpy(fname, dp->d_name, (int) dp->d_namlen);
+               fname[dp->d_namlen] = '\0';
+
+               /* avoid file names beginning with ".".  this is good
+                * because we would otherwise waste two guaranteed calls
+                * to getpwnam() for . and .., and also because user names
+                * starting with a period are just too nasty to consider.
+                */
+               if (fname[0] == '.')
+                       goto next_crontab;
+
+               if (NULL == (pw = getpwnam(fname)))
+               {
+                       /* file doesn't have a user in passwd file.
+                        */
+                       log_it(fname, getpid(), "ORPHAN", "no passwd entry");
+                       goto next_crontab;
+               }
+
+               sprintf(tabname, CRON_TAB(fname));
+               if ((crontab_fd = open(tabname, O_RDONLY, 0)) < OK)
+               {
+                       /* crontab not accessible?
+                        */
+                       log_it(fname, getpid(), "CAN'T OPEN", tabname);
+                       goto next_crontab;
+               }
+
+               if (fstat(crontab_fd, &statbuf) < OK)
+               {
+                       log_it(fname, getpid(), "FSTAT FAILED", tabname);
+                       goto next_crontab;
+               }
+
+               Debug(DLOAD, ("\t%s:", fname))
+               u = find_user(old_db, fname);
+               if (u != NULL)
+               {
+                       /* if crontab has not changed since we last read it
+                        * in, then we can just use our existing entry.
+                        * note that we do not check for changes in the
+                        * passwd entry (uid, home dir, etc).  HINT
+                        */
+                       if (u->mtime == statbuf.st_mtime)
+                       {
+                               Debug(DLOAD, (" [no change, using old data]"))
+                               unlink_user(old_db, u);
+                               link_user(&new_db, u);
+                               goto next_crontab;
+                       }
+
+                       /* before we fall through to the code that will reload
+                        * the user, let's deallocate and unlink the user in
+                        * the old database.  This is more a point of memory
+                        * efficiency than anything else, since all leftover
+                        * users will be deleted from the old database when
+                        * we finish with the crontab...
+                        */
+                       Debug(DLOAD, (" [delete old data]"))
+                       unlink_user(old_db, u);
+                       free_user(u);
+               }
+               u = load_user(
+                       crontab_fd,
+                       pw->pw_name,
+                       pw->pw_uid,
+                       pw->pw_gid,
+                       pw->pw_dir,
+                       pw->pw_shell
+               );
+               if (u != NULL)
+               {
+                       u->mtime = statbuf.st_mtime;
+                       link_user(&new_db, u);
+               }
+next_crontab:
+               if (crontab_fd >= OK) {
+                       Debug(DLOAD, (" [done]\n"))
+                       close(crontab_fd);
+               }
+       }
+       /* if we don't do this, then when our children eventually call
+        * getpwnam() in do_command.c's child_process to verify MAILTO=,
+        * they will screw us up (and v-v).
+        *
+        * (this was lots of fun to find...)
+        */
+       endpwent();
+
+       /* whatever's left in the old database is now junk.
+        */
+       Debug(DLOAD, ("unlinking old database:\n"))
+       for (u = old_db->head;  u != NULL;  u = u->next)
+       {
+               Debug(DLOAD, ("\t%s\n", env_get(USERENV, u->envp)))
+               unlink_user(old_db, u);
+               free_user(u);
+       }
+
+       /* overwrite the database control block with the new one.
+        */
+       Debug(DLOAD, ("installing new database\n"))
+#if defined(BSD)
+       /* BSD has structure assignments */
+       *old_db = new_db;
+#endif
+#if defined(ATT)
+       /* ATT, well, I don't know.  Use memcpy(). */
+       memcpy(old_db, &new_db, sizeof(cron_db));
+#endif
+       Debug(DLOAD, ("load_database is done\n"))
+}
+
+
+void
+link_user(db, u)
+       cron_db *db;
+       user    *u;
+{
+       if (db->head == NULL)
+               db->head = u;
+       if (db->tail)
+               db->tail->next = u;
+       u->prev = db->tail;
+       u->next = NULL;
+       db->tail = u;
+}
+
+
+void
+unlink_user(db, u)
+       cron_db *db;
+       user    *u;
+{
+       if (u->prev == NULL)
+               db->head = u->next;
+       else
+               u->prev->next = u->next;
+
+       if (u->next == NULL)
+               db->tail = u->prev;
+       else
+               u->next->prev = u->prev;
+}
+
+
+user *
+find_user(db, name)
+       cron_db *db;
+       char    *name;
+{
+       char    *env_get();
+       user    *u;
+
+       for (u = db->head;  u != NULL;  u = u->next)
+               if (!strcmp(env_get(USERENV, u->envp), name))
+                       break;
+       return u;
+}
diff --git a/usr/src/libexec/crond/do_command.c b/usr/src/libexec/crond/do_command.c
new file mode 100644 (file)
index 0000000..62b3eeb
--- /dev/null
@@ -0,0 +1,572 @@
+#if !defined(lint) && !defined(LINT)
+static char rcsid[] = "$Header: do_command.c,v 2.1 90/07/18 00:23:38 vixie Exp $";
+#endif
+
+/* $Source: /jove_u3/vixie/src/cron/RCS/do_command.c,v $
+ * $Revision: 2.1 $
+ * $Log:       do_command.c,v $
+ * Revision 2.1  90/07/18  00:23:38  vixie
+ * Baseline for 4.4BSD release
+ * 
+ * Revision 2.0  88/12/10  04:57:44  vixie
+ * V2 Beta
+ * 
+ * Revision 1.5  88/11/29  13:06:06  vixie
+ * seems to work on Ultrix 3.0 FT1
+ * 
+ * Revision 1.4  87/05/02  17:33:35  paul
+ * baseline for mod.sources release
+ * 
+ * Revision 1.3  87/04/09  00:03:58  paul
+ * improved data hiding, locality of declaration/references
+ * fixed a rs@mirror bug by redesigning the mailto stuff completely
+ * 
+ * Revision 1.2  87/03/19  12:46:24  paul
+ * implemented suggestions from rs@mirror (Rich $alz):
+ *    MAILTO="" means no mail should be sent
+ *    various fixes of bugs or lint complaints
+ *    put a To: line in the mail message
+ * 
+ * Revision 1.1  87/01/26  23:47:00  paul
+ * Initial revision
+ */
+
+/* Copyright 1988,1990 by Paul Vixie
+ * All rights reserved
+ *
+ * Distribute freely, except: don't remove my name from the source or
+ * documentation (don't take credit for my work), mark your changes (don't
+ * get me blamed for your possible bugs), don't alter or remove this
+ * notice.  May be sold if buildable source is provided to buyer.  No
+ * warrantee of any kind, express or implied, is included with this
+ * software; use at your own risk, responsibility for damages (if any) to
+ * anyone resulting from the use of this software rests entirely with the
+ * user.
+ *
+ * Send bug reports, bug fixes, enhancements, requests, flames, etc., and
+ * I'll try to keep a version up to date.  I can be reached as follows:
+ * Paul Vixie, 329 Noe Street, San Francisco, CA, 94114, (415) 864-7013,
+ * paul@vixie.sf.ca.us || {hoptoad,pacbell,decwrl,crash}!vixie!paul
+ */
+
+
+#include "cron.h"
+#include <signal.h>
+#include <pwd.h>
+#if defined(BSD)
+# include <sys/wait.h>
+#endif /*BSD*/
+#if defined(sequent)
+# include <strings.h>
+# include <sys/universe.h>
+#endif
+
+
+void
+do_command(cmd, u)
+       char    *cmd;
+       user    *u;
+{
+       extern int      fork(), _exit();
+       extern void     child_process(), log_it();
+       extern char     *env_get();
+
+       Debug(DPROC, ("[%d] do_command(%s, (%s,%d,%d))\n",
+               getpid(), cmd, env_get(USERENV, u->envp), u->uid, u->gid))
+
+       /* fork to become asynchronous -- parent process is done immediately,
+        * and continues to run the normal cron code, which means return to
+        * tick().  the child and grandchild don't leave this function, alive.
+        *
+        * vfork() is unsuitable, since we have much to do, and the parent
+        * needs to be able to run off and fork other processes.
+        */
+       switch (fork())
+       {
+       case -1:
+               log_it("CROND",getpid(),"error","can't fork");
+               break;
+       case 0:
+               /* child process */
+               child_process(cmd, u);
+               Debug(DPROC, ("[%d] child process done, exiting\n", getpid()))
+               _exit(OK_EXIT);
+               break;
+       }
+       Debug(DPROC, ("[%d] main process returning to work\n", getpid()))
+}
+
+
+static void
+child_process(cmd, u)
+       char    *cmd;
+       user    *u;
+{
+       extern struct passwd    *getpwnam();
+       extern void     sigpipe_func(), be_different(), log_it();
+       extern int      VFORK();
+       extern char     *index(), *env_get();
+
+       auto int        stdin_pipe[2], stdout_pipe[2];
+       register char   *input_data, *usernm, *mailto;
+       auto int        children = 0;
+#if defined(sequent)
+       extern void     do_univ();
+#endif
+
+       Debug(DPROC, ("[%d] child_process('%s')\n", getpid(), cmd))
+
+       /* mark ourselves as different to PS command watchers by upshifting
+        * our program name.  This has no effect on some kernels.
+        */
+       {
+               register char   *pch;
+
+               for (pch = ProgramName;  *pch;  pch++)
+                       *pch = MkUpper(*pch);
+       }
+
+       /* discover some useful and important environment settings
+        */
+       usernm = env_get(USERENV, u->envp);
+       mailto = env_get("MAILTO", u->envp);
+
+#if defined(BSD)
+       /* our parent is watching for our death by catching SIGCHLD.  we
+        * do not care to watch for our children's deaths this way -- we
+        * use wait() explictly.  so we have to disable the signal (which
+        * was inherited from the parent).
+        *
+        * this isn't needed for system V, since our parent is already
+        * SIG_IGN on SIGCLD -- which, hopefully, will cause children to
+        * simply vanish when they die.
+        */
+       (void) signal(SIGCHLD, SIG_IGN);
+#endif /*BSD*/
+
+       /* create some pipes to talk to our future child
+        */
+       pipe(stdin_pipe);       /* child's stdin */
+       pipe(stdout_pipe);      /* child's stdout */
+       
+       /* since we are a forked process, we can diddle the command string
+        * we were passed -- nobody else is going to use it again, right?
+        *
+        * 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,
+        * but that happens later.
+        */
+       if (NULL == (input_data = index(cmd, '%')))
+       {
+               /* no %.  point input_data at a null string.
+                */
+               input_data = "";
+       }
+       else
+       {
+               /* % found.  replace with a null (remember, we're a forked
+                * process and the string won't be reused), and increment
+                * input_data to point at the following character.
+                */
+               *input_data++ = '\0';
+       }
+
+       /* fork again, this time so we can exec the user's command.  Vfork()
+        * is okay this time, since we are going to exec() pretty quickly.
+        * I'm assuming that closing pipe ends &whatnot will not affect our
+        * suspended pseudo-parent/alter-ego.
+        */
+       if (VFORK() == 0)
+       {
+               Debug(DPROC, ("[%d] grandchild process VFORK()'ed\n", getpid()))
+
+               /* write a log message.  we've waited this long to do it
+                * because it was not until now that we knew the PID that
+                * the actual user command shell was going to get and the
+                * PID is part of the log message.
+                */
+#ifdef LOG_FILE
+               {
+                       extern char *mkprints();
+                       char *x = mkprints(cmd, strlen(cmd));
+
+                       log_it(usernm, getpid(), "CMD", x);
+                       free(x);
+               }
+#endif
+
+               /* get new pgrp, void tty, etc.
+                */
+               be_different();
+
+               /* close the pipe ends that we won't use.  this doesn't affect
+                * the parent, who has to read and write them; it keeps the
+                * kernel from recording us as a potential client TWICE --
+                * which would keep it from sending SIGPIPE in otherwise
+                * appropriate circumstances.
+                */
+               close(stdin_pipe[WRITE_PIPE]);
+               close(stdout_pipe[READ_PIPE]);
+
+               /* grandchild process.  make std{in,out} be the ends of
+                * pipes opened by our daddy; make stderr go to stdout.
+                */
+               close(STDIN);   dup2(stdin_pipe[READ_PIPE], STDIN);
+               close(STDOUT);  dup2(stdout_pipe[WRITE_PIPE], STDOUT);
+               close(STDERR);  dup2(STDOUT, STDERR);
+
+               /* close the pipes we just dup'ed.  The resources will remain,
+                * since they've been dup'ed... :-)...
+                */
+               close(stdin_pipe[READ_PIPE]);
+               close(stdout_pipe[WRITE_PIPE]);
+
+# if defined(sequent)
+               /* set our login universe.  Do this in the grandchild
+                * so that the child can invoke /usr/lib/sendmail
+                * without surprises.
+                */
+               do_univ(u);
+# endif
+
+               /* set our directory, uid and gid.  Set gid first, since once
+                * we set uid, we've lost root privledges.  (oops!)
+                */
+               setgid(u->gid);
+# if defined(BSD)
+               initgroups(env_get(USERENV, u->envp), u->gid);
+# endif
+               setuid(u->uid);         /* you aren't root after this... */
+               chdir(env_get("HOME", u->envp));
+
+               /* exec the command.
+                */
+               {
+                       char    *shell = env_get("SHELL", u->envp);
+
+# if DEBUGGING
+                       if (DebugFlags & DTEST) {
+                               fprintf(stderr,
+                               "debug DTEST is on, not exec'ing command.\n");
+                               fprintf(stderr,
+                               "\tcmd='%s' shell='%s'\n", cmd, shell);
+                               _exit(OK_EXIT);
+                       }
+# endif /*DEBUGGING*/
+                       /* normally you can't put debugging stuff here because
+                        * it gets mailed with the command output.
+                        */
+                       /*
+                       Debug(DPROC, ("[%d] execle('%s', '%s', -c, '%s')\n",
+                                       getpid(), shell, shell, cmd))
+                        */
+
+# ifdef bad_idea
+                       /* files writable by non-owner are a no-no
+                        */
+                       {
+                               struct stat sb;
+
+                               if (0 != stat(cmd, &sb)) {
+                                       fputs("crond: stat(2): ", stderr);
+                                       perror(cmd);
+                                       _exit(ERROR_EXIT);
+                               } else if (sb.st_mode & 022) {
+                                       fprintf(stderr,
+                                       "crond: %s writable by nonowner\n",
+                                               cmd);
+                                       _exit(ERROR_EXIT);
+                               } else if (sb.st_uid & 022) {
+                                       fprintf(stderr,
+                                       "crond: %s owned by uid %d\n",
+                                               cmd, sb.st_uid);
+                                       _exit(ERROR_EXIT);
+                               }
+                       }
+# endif /*bad_idea*/
+
+                       execle(shell, shell, "-c", cmd, (char *)0, u->envp);
+                       fprintf(stderr, "execl: couldn't exec `%s'\n", shell);
+                       perror("execl");
+                       _exit(ERROR_EXIT);
+               }
+       }
+
+       children++;
+
+       /* middle process, child of original cron, parent of process running
+        * the user's command.
+        */
+
+       Debug(DPROC, ("[%d] child continues, closing pipes\n", getpid()))
+
+       /* close the ends of the pipe that will only be referenced in the
+        * grandchild process...
+        */
+       close(stdin_pipe[READ_PIPE]);
+       close(stdout_pipe[WRITE_PIPE]);
+
+       /*
+        * write, to the pipe connected to child's stdin, any input specified
+        * after a % in the crontab entry.  while we copy, convert any
+        * additional %'s to newlines.  when done, if some characters were
+        * written and the last one wasn't a newline, write a newline.
+        *
+        * Note that if the input data won't fit into one pipe buffer (2K
+        * or 4K on most BSD systems), and the child doesn't read its stdin,
+        * we would block here.  the solution, of course, is to fork again.
+        */
+
+       if (*input_data && fork() == 0) {
+               register FILE   *out = fdopen(stdin_pipe[WRITE_PIPE], "w");
+               register int    need_newline = FALSE;
+               register int    escaped = FALSE;
+               register int    ch;
+
+               Debug(DPROC, ("[%d] child2 sending data to grandchild\n", getpid()))
+
+               /* close the pipe we don't use, since we inherited it and
+                * are part of its reference count now.
+                */
+               close(stdout_pipe[READ_PIPE]);
+
+               /* translation:
+                *      \% -> %
+                *      %  -> \n
+                *      \x -> \x        for all x != %
+                */
+               while (ch = *input_data++)
+               {
+                       if (escaped) {
+                               if (ch != '%')
+                                       putc('\\', out);
+                       } else {
+                               if (ch == '%')
+                                       ch = '\n';
+                       }
+
+                       if (!(escaped = (ch == '\\'))) {
+                               putc(ch, out);
+                               need_newline = (ch != '\n');
+                       }
+               }
+               if (escaped)
+                       putc('\\', out);
+               if (need_newline)
+                       putc('\n', out);
+
+               /* close the pipe, causing an EOF condition.  fclose causes
+                * stdin_pipe[WRITE_PIPE] to be closed, too.
+                */
+               fclose(out);
+
+               Debug(DPROC, ("[%d] child2 done sending to grandchild\n", getpid()))
+               exit(0);
+       }
+
+       /* close the pipe to the grandkiddie's stdin, since its wicked uncle
+        * ernie back there has it open and will close it when he's done.
+        */
+       close(stdin_pipe[WRITE_PIPE]);
+
+       children++;
+
+       /*
+        * read output from the grandchild.  it's stderr has been redirected to
+        * it's stdout, which has been redirected to our pipe.  if there is any
+        * output, we'll be mailing it to the user whose crontab this is...
+        * when the grandchild exits, we'll get EOF.
+        */
+
+       Debug(DPROC, ("[%d] child reading output from grandchild\n", getpid()))
+
+       {
+               register FILE   *in = fdopen(stdout_pipe[READ_PIPE], "r");
+               register int    ch = getc(in);
+
+               if (ch != EOF)
+               {
+                       register FILE   *mail;
+                       register int    bytes = 1;
+                       union wait      status;
+
+                       Debug(DPROC|DEXT,
+                               ("[%d] got data (%x:%c) from grandchild\n",
+                                       getpid(), ch, ch))
+
+                       /* get name of recipient.  this is MAILTO if set to a
+                        * valid local username; USER otherwise.
+                        */
+                       if (mailto)
+                       {
+                               /* MAILTO was present in the environment
+                                */
+                               if (!*mailto)
+                               {
+                                       /* ... but it's empty. set to NULL
+                                        */
+                                       mailto = NULL;
+                               }
+                       }
+                       else
+                       {
+                               /* MAILTO not present, set to USER.
+                                */
+                               mailto = usernm;
+                       }
+               
+                       /* if we are supposed to be mailing, MAILTO will
+                        * be non-NULL.  only in this case should we set
+                        * up the mail command and subjects and stuff...
+                        */
+
+                       if (mailto)
+                       {
+                               extern FILE     *popen();
+                               extern char     *sprintf(), *print_cmd();
+                               register char   **env;
+                               auto char       mailcmd[MAX_COMMAND];
+                               auto char       hostname[MAXHOSTNAMELEN];
+
+                               (void) gethostname(hostname, MAXHOSTNAMELEN);
+                               (void) sprintf(mailcmd, MAILCMD, mailto);
+                               if (!(mail = popen(mailcmd, "w")))
+                               {
+                                       perror(MAILCMD);
+                                       (void) _exit(ERROR_EXIT);
+                               }
+                               fprintf(mail, "From: root (Cron Daemon)\n");
+                               fprintf(mail, "To: %s\n", mailto);
+                               fprintf(mail,
+                               "Subject: cron for %s@%s said this\n",
+                                       usernm, first_word(hostname, ".")
+                               );
+                               fprintf(mail, "Date: %s", ctime(&TargetTime));
+                               fprintf(mail, "X-Cron-Cmd: <%s>\n", cmd);
+                               for (env = u->envp;  *env;  env++)
+                                       fprintf(mail, "X-Cron-Env: <%s>\n",
+                                               *env);
+                               fprintf(mail, "\n");
+
+                               /* this was the first char from the pipe
+                                */
+                               putc(ch, mail);
+                       }
+
+                       /* we have to read the input pipe no matter whether
+                        * we mail or not, but obviously we only write to
+                        * mail pipe if we ARE mailing.
+                        */
+
+                       while (EOF != (ch = getc(in)))
+                       {
+                               bytes++;
+                               if (mailto)
+                                       putc(ch, mail);
+                       }
+
+                       /* only close pipe if we opened it -- i.e., we're
+                        * mailing...
+                        */
+
+                       if (mailto) {
+                               Debug(DPROC, ("[%d] closing pipe to mail\n",
+                                       getpid()))
+                               /* Note: the pclose will probably see
+                                * the termination of the grandchild
+                                * in addition to the mail process, since
+                                * it (the grandchild) is likely to exit
+                                * after closing its stdout.
+                                */
+                               status.w_status = pclose(mail);
+                       }
+
+                       /* if there was output and we could not mail it,
+                        * log the facts so the poor user can figure out
+                        * what's going on.
+                        */
+                       if (mailto && status.w_status) {
+                               char buf[MAX_TEMPSTR];
+
+                               sprintf(buf,
+                       "mailed %d byte%s of output but got status 0x%04x\n",
+                                       bytes, (bytes==1)?"":"s",
+                                       status.w_status);
+                               log_it(usernm, getpid(), "MAIL", buf);
+                       }
+
+               } /*if data from grandchild*/
+
+               Debug(DPROC, ("[%d] got EOF from grandchild\n", getpid()))
+
+               fclose(in);     /* also closes stdout_pipe[READ_PIPE] */
+       }
+
+#if defined(BSD)
+       /* wait for children to die.
+        */
+       for (;  children > 0;  children--)
+       {
+               int             pid;
+               union wait      waiter;
+
+               Debug(DPROC, ("[%d] waiting for grandchild #%d to finish\n",
+                       getpid(), children))
+               pid = wait(&waiter);
+               if (pid < OK) {
+                       Debug(DPROC, ("[%d] no more grandchildren--mail written?\n",
+                               getpid()))
+                       break;
+               }
+               Debug(DPROC, ("[%d] grandchild #%d finished, status=%04x",
+                       getpid(), pid, waiter.w_status))
+               if (waiter.w_coredump)
+                       Debug(DPROC, (", dumped core"))
+               Debug(DPROC, ("\n"))
+       }
+#endif /*BSD*/
+}
+
+
+#if defined(sequent)
+/* Dynix (Sequent) hack to put the user associated with
+ * the passed user structure into the ATT universe if
+ * necessary.  We have to dig the gecos info out of
+ * the user's password entry to see if the magic
+ * "universe(att)" string is present.  If we do change
+ * the universe, also set "LOGNAME".
+ */
+
+void
+do_univ(u)
+       user    *u;
+{
+       struct  passwd  *p;
+       char    *s;
+       int     i;
+       char    envstr[MAX_ENVSTR], **env_set();
+
+       p = getpwuid(u->uid);
+       (void) endpwent();
+
+       if (p == NULL)
+               return;
+
+       s = p->pw_gecos;
+
+       for (i = 0; i < 4; i++)
+       {
+               if ((s = index(s, ',')) == NULL)
+                       return;
+               s++;
+       }
+       if (strcmp(s, "universe(att)"))
+               return;
+
+       (void) sprintf(envstr, "LOGNAME=%s", p->pw_name);
+       u->envp = env_set(u->envp, envstr);
+
+       (void) universe(U_ATT);
+}
+#endif
diff --git a/usr/src/libexec/crond/entry.c b/usr/src/libexec/crond/entry.c
new file mode 100644 (file)
index 0000000..72e4d8b
--- /dev/null
@@ -0,0 +1,486 @@
+#if !defined(lint) && !defined(LINT)
+static char rcsid[] = "$Header: entry.c,v 2.1 90/07/18 00:23:41 vixie Exp $";
+#endif
+
+/* vix 26jan87 [RCS'd; rest of log is in RCS file]
+ * vix 01jan87 [added line-level error recovery]
+ * vix 31dec86 [added /step to the from-to range, per bob@acornrc]
+ * vix 30dec86 [written]
+ */
+
+
+/* Copyright 1988,1990 by Paul Vixie
+ * All rights reserved
+ *
+ * Distribute freely, except: don't remove my name from the source or
+ * documentation (don't take credit for my work), mark your changes (don't
+ * get me blamed for your possible bugs), don't alter or remove this
+ * notice.  May be sold if buildable source is provided to buyer.  No
+ * warrantee of any kind, express or implied, is included with this
+ * software; use at your own risk, responsibility for damages (if any) to
+ * anyone resulting from the use of this software rests entirely with the
+ * user.
+ *
+ * Send bug reports, bug fixes, enhancements, requests, flames, etc., and
+ * I'll try to keep a version up to date.  I can be reached as follows:
+ * Paul Vixie, 329 Noe Street, San Francisco, CA, 94114, (415) 864-7013,
+ * paul@vixie.sf.ca.us || {hoptoad,pacbell,decwrl,crash}!vixie!paul
+ */
+
+
+#include "cron.h"
+
+typedef        enum
+       {e_none, e_minute, e_hour, e_dom, e_month, e_dow, e_cmd, e_timespec}
+       ecode_e;
+static char *ecodes[] =
+       {
+               "no error",
+               "bad minute",
+               "bad hour",
+               "bad day-of-month",
+               "bad month",
+               "bad day-of-week",
+               "bad command",
+               "bad time specifier"
+       };
+
+void
+free_entry(e)
+       entry   *e;
+{
+       int     free();
+
+       (void) free(e->cmd);
+       (void) free(e);
+}
+
+
+entry *
+load_entry(file, error_func)
+       FILE    *file;
+       void    (*error_func)();
+{
+       /* this function reads one crontab entry -- the next -- from a file.
+        * it skips any leading blank lines, ignores comments, and returns
+        * EOF if for any reason the entry can't be read and parsed.
+        *
+        * the entry IS parsed here, btw.
+        *
+        * syntax:
+        *      minutes hours doms months dows cmd\n
+        */
+
+       extern int      free();
+       extern char     *malloc(), *savestr();
+       extern void     unget_char();
+       static char     get_list();
+
+       ecode_e ecode = e_none;
+       entry   *e;
+       int     ch;
+       void    skip_comments();
+       char    cmd[MAX_COMMAND];
+
+       e = (entry *) calloc(sizeof(entry), sizeof(char));
+
+       Debug(DPARS, ("load_entry()...about to eat comments\n"))
+
+       skip_comments(file);
+
+       ch = get_char(file);
+
+       /* ch is now the first useful character of a useful line.
+        * it may be an @special or it may be the first character
+        * of a list of minutes.
+        */
+
+       if (ch == '@')
+       {
+               /* all of these should be flagged and load-limited; i.e.,
+                * instead of @hourly meaning "0 * * * *" it should mean
+                * "close to the front of every hour but not 'til the
+                * system load is low".  Problems are: how do you know
+                * what "low" means? (save me from /etc/crond.conf!) and:
+                * how to guarantee low variance (how low is low?), which
+                * means how to we run roughly every hour -- seems like
+                * we need to keep a history or let the first hour set
+                * the schedule, which means we aren't load-limited
+                * anymore.  too much for my overloaded brain. (vix, jan90)
+                * HINT
+                */
+               ch = get_string(cmd, MAX_COMMAND, file, " \t\n");
+               if (!strcmp("reboot", cmd)) {
+                       e->flags |= WHEN_REBOOT;
+               } else if (!strcmp("yearly", cmd) || !strcmp("annually", cmd)){
+                       bit_set(e->minute, 0);
+                       bit_set(e->hour, 0);
+                       bit_set(e->dom, 0);
+                       bit_set(e->month, 0);
+                       bit_nset(e->dow, 0, (LAST_DOW-FIRST_DOW+1));
+               } else if (!strcmp("monthly", cmd)) {
+                       bit_set(e->minute, 0);
+                       bit_set(e->hour, 0);
+                       bit_set(e->dom, 0);
+                       bit_nset(e->month, 0, (LAST_MONTH-FIRST_MONTH+1));
+                       bit_nset(e->dow, 0, (LAST_DOW-FIRST_DOW+1));
+               } else if (!strcmp("weekly", cmd)) {
+                       bit_set(e->minute, 0);
+                       bit_set(e->hour, 0);
+                       bit_nset(e->dom, 0, (LAST_DOM-FIRST_DOM+1));
+                       bit_nset(e->month, 0, (LAST_MONTH-FIRST_MONTH+1));
+                       bit_set(e->dow, 0);
+               } else if (!strcmp("daily", cmd) || !strcmp("midnight", cmd)) {
+                       bit_set(e->minute, 0);
+                       bit_set(e->hour, 0);
+                       bit_nset(e->dom, 0, (LAST_DOM-FIRST_DOM+1));
+                       bit_nset(e->month, 0, (LAST_MONTH-FIRST_MONTH+1));
+                       bit_nset(e->dow, 0, (LAST_DOW-FIRST_DOW+1));
+               } else if (!strcmp("hourly", cmd)) {
+                       bit_set(e->minute, 0);
+                       bit_set(e->hour, (LAST_HOUR-FIRST_HOUR+1));
+                       bit_nset(e->dom, 0, (LAST_DOM-FIRST_DOM+1));
+                       bit_nset(e->month, 0, (LAST_MONTH-FIRST_MONTH+1));
+                       bit_nset(e->dow, 0, (LAST_DOW-FIRST_DOW+1));
+               } else {
+                       ecode = e_timespec;
+                       goto eof;
+               }
+       }
+       else
+       {
+               Debug(DPARS, ("load_entry()...about to parse numerics\n"))
+
+               ch = get_list(e->minute, FIRST_MINUTE, LAST_MINUTE,
+                               PPC_NULL, ch, file);
+               if (ch == EOF)
+               {
+                       ecode = e_minute;
+                       goto eof;
+               }
+
+               /* hours
+                */
+
+               ch = get_list(e->hour, FIRST_HOUR, LAST_HOUR,
+                               PPC_NULL, ch, file);
+               if (ch == EOF)
+               {
+                       ecode = e_hour;
+                       goto eof;
+               }
+
+               /* DOM (days of month)
+                */
+
+               if (ch == '*') e->flags |= DOM_STAR;
+               ch = get_list(e->dom, FIRST_DOM, LAST_DOM, PPC_NULL, ch, file);
+               if (ch == EOF)
+               {
+                       ecode = e_dom;
+                       goto eof;
+               }
+
+               /* month
+                */
+
+               ch = get_list(e->month, FIRST_MONTH, LAST_MONTH,
+                               MonthNames, ch, file);
+               if (ch == EOF)
+               {
+                       ecode = e_month;
+                       goto eof;
+               }
+
+               /* DOW (days of week)
+                */
+
+               if (ch == '*') e->flags |= DOW_STAR;
+               ch = get_list(e->dow, FIRST_DOW, LAST_DOW,
+                               DowNames, ch, file);
+               if (ch == EOF)
+               {
+                       ecode = e_dow;
+                       goto eof;
+               }
+       }
+
+       /* make sundays equivilent */
+       if (bit_test(e->dow, 0) || bit_test(e->dow, 7))
+       {
+               bit_set(e->dow, 0);
+               bit_set(e->dow, 7);
+       }
+
+       Debug(DPARS, ("load_entry()...about to parse command\n"))
+
+       /* ch is first character of a command.  everything up to the next
+        * \n or EOF is part of the command... too bad we don't know in
+        * advance how long it will be, since we need to malloc a string
+        * for it... so, we limit it to MAX_COMMAND
+        */ 
+       unget_char(ch, file);
+       ch = get_string(cmd, MAX_COMMAND, file, "\n");
+
+       /* a file without a \n before the EOF is rude, so we'll complain...
+        */
+       if (ch == EOF)
+       {
+               ecode = e_cmd;
+               goto eof;
+       }
+
+       /* got the command in the 'cmd' string; save it in *e.
+        */
+       e->cmd = savestr(cmd);
+
+       Debug(DPARS, ("load_entry()...returning successfully\n"))
+
+       /* success, fini, return pointer to the entry we just created...
+        */
+       return e;
+
+eof:   /* if we want to return EOF, we have to jump down here and
+        * free the entry we've been building.
+        *
+        * now, in some cases, a parse routine will have returned EOF to
+        * indicate an error, but the file is not actually done.  since, in
+        * that case, we only want to skip the line with the error on it,
+        * we'll do that here.
+        *
+        * many, including the author, see what's below as evil programming
+        * practice: since I didn't want to change the structure of this
+        * whole function to support this error recovery, I recurse.  Cursed!
+        * (At least it's tail-recursion, as if it matters in C - vix/8feb88)
+        * I'm seriously considering using (another) GOTO...   argh!
+        * (this does not get less disgusting over time.  vix/15nov88)
+        */
+
+       (void) free(e);
+
+       if (feof(file))
+               return NULL;
+
+       if (error_func)
+               (*error_func)(ecodes[(int)ecode]);
+       do  {ch = get_char(file);}
+       while (ch != EOF && ch != '\n');
+       if (ch == EOF)
+               return NULL;
+       return load_entry(file, error_func);
+}
+
+
+static char
+get_list(bits, low, high, names, ch, file)
+       bitstr_t        *bits;          /* one bit per flag, default=FALSE */
+       int             low, high;      /* bounds, impl. offset for bitstr */
+       char            *names[];       /* NULL or *[] of names for these elements */
+       int             ch;             /* current character being processed */
+       FILE            *file;          /* file being read */
+{
+       static char     get_range();
+       register int    done;
+
+       /* we know that we point to a non-blank character here;
+        * must do a Skip_Blanks before we exit, so that the
+        * next call (or the code that picks up the cmd) can
+        * assume the same thing.
+        */
+
+       Debug(DPARS|DEXT, ("get_list()...entered\n"))
+
+       /* list = "*" | range {"," range}
+        */
+       
+       if (ch == '*')
+       {
+               /* '*' means 'all elements'.
+                */
+               bit_nset(bits, 0, (high-low+1));
+               goto exit;
+       }
+
+       /* clear the bit string, since the default is 'off'.
+        */
+       bit_nclear(bits, 0, (high-low+1));
+
+       /* process all ranges
+        */
+       done = FALSE;
+       while (!done)
+       {
+               ch = get_range(bits, low, high, names, ch, file);
+               if (ch == ',')
+                       ch = get_char(file);
+               else
+                       done = TRUE;
+       }
+
+exit:  /* exiting.  skip to some blanks, then skip over the blanks.
+        */
+       Skip_Nonblanks(ch, file)
+       Skip_Blanks(ch, file)
+
+       Debug(DPARS|DEXT, ("get_list()...exiting w/ %02x\n", ch))
+
+       return ch;
+}
+
+
+static char
+get_range(bits, low, high, names, ch, file)
+       bitstr_t        *bits;          /* one bit per flag, default=FALSE */
+       int             low, high;      /* bounds, impl. offset for bitstr */
+       char            *names[];       /* NULL or names of elements */
+       int             ch;             /* current character being processed */
+       FILE            *file;          /* file being read */
+{
+       /* range = number | number "-" number [ "/" number ]
+        */
+
+       static int      set_element();
+       static char     get_number();
+       register int    i;
+       auto int        num1, num2, num3;
+
+       Debug(DPARS|DEXT, ("get_range()...entering, exit won't show\n"))
+
+       if (EOF == (ch = get_number(&num1, low, names, ch, file)))
+               return EOF;
+
+       if (ch != '-')
+       {
+               /* not a range, it's a single number.
+                */
+               if (EOF == set_element(bits, low, high, num1))
+                       return EOF;
+       }
+       else
+       {
+               /* eat the dash
+                */
+               ch = get_char(file);
+               if (ch == EOF)
+                       return EOF;
+
+               /* get the number following the dash
+                */
+               ch = get_number(&num2, low, names, ch, file);
+               if (ch == EOF)
+                       return EOF;
+
+               /* check for step size
+                */
+               if (ch == '/')
+               {
+                       /* eat the slash
+                        */
+                       ch = get_char(file);
+                       if (ch == EOF)
+                               return EOF;
+
+                       /* get the step size -- note: we don't pass the
+                        * names here, because the number is not an
+                        * element id, it's a step size.  'low' is
+                        * sent as a 0 since there is no offset either.
+                        */
+                       ch = get_number(&num3, 0, PPC_NULL, ch, file);
+                       if (ch == EOF)
+                               return EOF;
+               }
+               else
+               {
+                       /* no step.  default==1.
+                        */
+                       num3 = 1;
+               }
+
+               /* range. set all elements from num1 to num2, stepping
+                * by num3.  (the step is a downward-compatible extension
+                * proposed conceptually by bob@acornrc, syntactically
+                * designed then implmented by paul vixie).
+                */
+               for (i = num1;  i <= num2;  i += num3)
+                       if (EOF == set_element(bits, low, high, i))
+                               return EOF;
+       }
+       return ch;
+}
+
+
+static char
+get_number(numptr, low, names, ch, file)
+       int     *numptr;
+       int     low;
+       char    *names[];
+       char    ch;
+       FILE    *file;
+{
+       char    temp[MAX_TEMPSTR], *pc;
+       int     len, i, all_digits;
+
+       /* collect alphanumerics into our fixed-size temp array
+        */
+       pc = temp;
+       len = 0;
+       all_digits = TRUE;
+       while (isalnum(ch))
+       {
+               if (++len >= MAX_TEMPSTR)
+                       return EOF;
+
+               *pc++ = ch;
+
+               if (!isdigit(ch))
+                       all_digits = FALSE;
+
+               ch = get_char(file);
+       }
+       *pc = '\0';
+
+       /* try to find the name in the name list
+        */
+       if (names)
+               for (i = 0;  names[i] != NULL;  i++)
+               {
+                       Debug(DPARS|DEXT,
+                               ("get_num, compare(%s,%s)\n", names[i], temp))
+                       if (!nocase_strcmp(names[i], temp))
+                       {
+                               *numptr = i+low;
+                               return ch;
+                       }
+               }
+
+       /* no name list specified, or there is one and our string isn't
+        * in it.  either way: if it's all digits, use its magnitude.
+        * otherwise, it's an error.
+        */
+       if (all_digits)
+       {
+               *numptr = atoi(temp);
+               return ch;
+       }
+
+       return EOF;
+}
+
+
+static int
+set_element(bits, low, high, number)
+       bitstr_t        *bits;          /* one bit per flag, default=FALSE */
+       int             low;
+       int             high;
+       int             number;
+{
+       Debug(DPARS|DEXT, ("set_element(?,%d,%d,%d)\n", low, high, number))
+
+       if (number < low || number > high)
+               return EOF;
+
+       Debug(DPARS|DEXT, ("bit_set(%x,%d)\n",bits,(number-low)))
+       bit_set(bits, (number-low));
+       Debug(DPARS|DEXT, ("bit_set succeeded\n"))
+       return OK;
+}
diff --git a/usr/src/libexec/crond/env.c b/usr/src/libexec/crond/env.c
new file mode 100644 (file)
index 0000000..01199db
--- /dev/null
@@ -0,0 +1,155 @@
+#if !defined(lint) && !defined(LINT)
+static char rcsid[] = "$Header: env.c,v 2.2 90/07/18 00:23:48 vixie Exp $";
+#endif
+
+/* Copyright 1988,1990 by Paul Vixie
+ * All rights reserved
+ *
+ * Distribute freely, except: don't remove my name from the source or
+ * documentation (don't take credit for my work), mark your changes (don't
+ * get me blamed for your possible bugs), don't alter or remove this
+ * notice.  May be sold if buildable source is provided to buyer.  No
+ * warrantee of any kind, express or implied, is included with this
+ * software; use at your own risk, responsibility for damages (if any) to
+ * anyone resulting from the use of this software rests entirely with the
+ * user.
+ *
+ * Send bug reports, bug fixes, enhancements, requests, flames, etc., and
+ * I'll try to keep a version up to date.  I can be reached as follows:
+ * Paul Vixie, 329 Noe Street, San Francisco, CA, 94114, (415) 864-7013,
+ * paul@vixie.sf.ca.us || {hoptoad,pacbell,decwrl,crash}!vixie!paul
+ */
+
+
+#include "cron.h"
+
+
+char **
+env_init()
+{
+       extern char     *malloc();
+       register char   **p = (char **) malloc(sizeof(char **));
+
+       p[0] = NULL;
+       return p;
+}
+
+
+char **
+env_set(envp, envstr)
+       char    **envp;
+       char    *envstr;
+{
+       extern char     *realloc(), *savestr();
+       register int    count, found;
+       register char   **p;
+
+       /*
+        * count the number of elements, including the null pointer;
+        * also set 'found' to -1 or index of entry if already in here.
+        */
+       found = -1;
+       for (count = 0;  envp[count] != NULL;  count++)
+       {
+               if (!strcmp_until(envp[count], envstr, '='))
+                       found = count;
+       }
+       count++;                /* for the null pointer
+                                */
+
+       if (found != -1)
+       {
+               /*
+                * it exists already, so just free the existing setting,
+                * save our new one there, and return the existing array.
+                */
+               free(envp[found]);
+               envp[found] = savestr(envstr);
+               return envp;
+       }
+
+       /*
+        * it doesn't exist yet, so resize the array, move null pointer over
+        * one, save our string over the old null pointer, and return resized
+        * array.
+        */
+       p = (char **) realloc(
+                       (char *)   envp,
+                       (unsigned) ((count+1) * sizeof(char **))
+       );
+       p[count] = p[count-1];
+       p[count-1] = savestr(envstr);
+       return p;
+}
+
+
+int
+load_env(envstr, f)
+       char    *envstr;
+       FILE    *f;
+{
+       /* return       ERR = end of file
+        *              FALSE = not an env setting (file was repositioned)
+        *              TRUE = was an env setting
+        */
+       char    *strcpy(), *sprintf();
+       long    filepos;
+       int     fileline;
+       char    name[MAX_TEMPSTR], val[MAX_ENVSTR];
+       int     fields, strdtb();
+       void    skip_comments();
+
+       filepos = ftell(f);
+       fileline = LineNumber;
+       skip_comments(f);
+       if (EOF == get_string(envstr, MAX_ENVSTR, f, "\n"))
+               return ERR;
+
+       Debug(DPARS, ("load_env, read <%s>\n", envstr))
+
+       name[0] = val[0] = '\0';
+       fields = sscanf(envstr, "%[^ =] = %[^\n#]", name, val);
+       if (fields != 2)
+       {
+               Debug(DPARS, ("load_env, not 2 fields (%d)\n", fields))
+               fseek(f, filepos, 0);
+               Set_LineNum(fileline);
+               return FALSE;
+       }
+
+       /* 2 fields from scanf; looks like an env setting
+        */
+
+       /*
+        * process value string
+        */
+       {
+               int     len = strdtb(val);
+
+               if (len >= 2)
+                       if (val[0] == '\'' || val[0] == '"')
+                               if (val[len-1] == val[0])
+                               {
+                                       val[len-1] = '\0';
+                                       (void) strcpy(val, val+1);
+                               }
+       }
+
+       (void) sprintf(envstr, "%s=%s", name, val);
+       Debug(DPARS, ("load_env, <%s> <%s> -> <%s>\n", name, val, envstr))
+       return TRUE;
+}
+
+
+char *
+env_get(name, envp)
+       char    *name;
+       char    **envp;
+{
+       char    *index();
+
+       for (;  *envp;  envp++)
+               if (!strcmp_until(*envp, name, '='))
+                       return index(*envp, '=') + 1;
+       return NULL;
+}
diff --git a/usr/src/libexec/crond/job.c b/usr/src/libexec/crond/job.c
new file mode 100644 (file)
index 0000000..0de2c54
--- /dev/null
@@ -0,0 +1,76 @@
+#if !defined(lint) && !defined(LINT)
+static char rcsid[] = "$Header: job.c,v 1.2 90/07/18 00:23:59 vixie Exp $";
+#endif
+
+/* Copyright 1988,1990 by Paul Vixie
+ * All rights reserved
+ *
+ * Distribute freely, except: don't remove my name from the source or
+ * documentation (don't take credit for my work), mark your changes (don't
+ * get me blamed for your possible bugs), don't alter or remove this
+ * notice.  May be sold if buildable source is provided to buyer.  No
+ * warrantee of any kind, express or implied, is included with this
+ * software; use at your own risk, responsibility for damages (if any) to
+ * anyone resulting from the use of this software rests entirely with the
+ * user.
+ *
+ * Send bug reports, bug fixes, enhancements, requests, flames, etc., and
+ * I'll try to keep a version up to date.  I can be reached as follows:
+ * Paul Vixie, 329 Noe Street, San Francisco, CA, 94114, (415) 864-7013,
+ * paul@vixie.sf.ca.us || {hoptoad,pacbell,decwrl,crash}!vixie!paul
+ */
+
+
+#include "cron.h"
+
+
+typedef        struct  _job
+       {
+               struct _job     *next;
+               char            *cmd;
+               user            *u;
+       }
+       job;
+
+
+static job     *jhead = NULL, *jtail = NULL;
+
+
+void
+job_add(cmd, u)
+       register char *cmd;
+       register user *u;
+{
+       register job *j;
+
+       /* if already on queue, keep going */
+       for (j=jhead; j; j=j->next)
+               if (j->cmd == cmd && j->u == u) { return; }
+
+       /* build a job queue element */
+       j = (job*)malloc(sizeof(job));
+       j->next = (job*) NULL;
+       j->cmd = cmd;
+       j->u = u;
+
+       /* add it to the tail */
+       if (!jhead) { jhead=j; }
+       else { jtail->next=j; }
+       jtail = j;
+}
+
+
+int
+job_runqueue()
+{
+       register job    *j;
+       register int    run = 0;
+
+       for (j=jhead; j; j=j->next) {
+               do_command(j->cmd, j->u);
+               free(j);
+               run++;
+       }
+       jhead = jtail = NULL;
+       return run;
+}
diff --git a/usr/src/libexec/crond/user.c b/usr/src/libexec/crond/user.c
new file mode 100644 (file)
index 0000000..5e288bd
--- /dev/null
@@ -0,0 +1,125 @@
+#if !defined(lint) && !defined(LINT)
+static char rcsid[] = "$Header: user.c,v 2.1 90/07/18 00:23:45 vixie Exp $";
+#endif
+
+/* vix 26jan87 [log is in RCS file]
+ */
+
+/* Copyright 1988,1990 by Paul Vixie
+ * All rights reserved
+ *
+ * Distribute freely, except: don't remove my name from the source or
+ * documentation (don't take credit for my work), mark your changes (don't
+ * get me blamed for your possible bugs), don't alter or remove this
+ * notice.  May be sold if buildable source is provided to buyer.  No
+ * warrantee of any kind, express or implied, is included with this
+ * software; use at your own risk, responsibility for damages (if any) to
+ * anyone resulting from the use of this software rests entirely with the
+ * user.
+ *
+ * Send bug reports, bug fixes, enhancements, requests, flames, etc., and
+ * I'll try to keep a version up to date.  I can be reached as follows:
+ * Paul Vixie, 329 Noe Street, San Francisco, CA, 94114, (415) 864-7013,
+ * paul@vixie.sf.ca.us || {hoptoad,pacbell,decwrl,crash}!vixie!paul
+ */
+
+
+#include "cron.h"
+
+
+void
+free_user(u)
+       user    *u;
+{
+       void    free_entry();
+       int     free();
+       entry   *e;
+       char    **env;
+
+       for (e = u->crontab;  e != NULL;  e = e->next)
+               free_entry(e);
+       for (env = u->envp;  *env;  env++)
+               (void) free(*env);
+       (void) free(u->envp);
+       (void) free(u);
+}
+
+
+user *
+load_user(crontab_fd, name, uid, gid, dir, shell)
+       int     crontab_fd;
+       char    *name;
+       int     uid;
+       int     gid;
+       char    *dir;
+       char    *shell;
+{
+       char    *malloc(), *sprintf(), **env_init(), **env_set();
+       int     load_env();
+       entry   *load_entry();
+
+       char    envstr[MAX_ENVSTR];
+       FILE    *file;
+       user    *u;
+       entry   *e;
+       int     status;
+
+       if (!(file = fdopen(crontab_fd, "r")))
+       {
+               perror("fdopen on crontab_fd in load_user");
+               return NULL;
+       }
+
+       Debug(DPARS, ("load_user()\n"))
+
+       /* file is open.  build user entry, then read the crontab file.
+        */
+       u = (user *) malloc(sizeof(user));
+       u->uid     = uid;
+       u->gid     = gid;
+       u->envp    = env_init();
+       u->crontab = NULL;
+
+       /*
+        * do auto env settings that the user could reset in the cron tab
+        */
+       sprintf(envstr, "SHELL=%s", (*shell) ?shell :"/bin/sh");
+       u->envp = env_set(u->envp, envstr);
+
+       sprintf(envstr, "HOME=%s", dir);
+       u->envp = env_set(u->envp, envstr);
+
+       /* load the crontab
+        */
+       while ((status = load_env(envstr, file)) >= OK)
+       {
+               if (status == TRUE)
+               {
+                       u->envp = env_set(u->envp, envstr);
+               }
+               else
+               {
+                       if (NULL != (e = load_entry(file, NULL)))
+                       {
+                               e->next = u->crontab;
+                               u->crontab = e;
+                       }
+               }
+       }
+
+       /*
+        * do automatic env settings that should have precedence over any
+        * set in the cron tab.
+        */
+       (void) sprintf(envstr, "%s=%s", USERENV, name);
+       u->envp = env_set(u->envp, envstr);
+
+       /*
+        * done. close file, return pointer to 'user' structure
+        */
+       fclose(file);
+
+       Debug(DPARS, ("...load_user() done\n"))
+
+       return u;
+}