Author: kevans
Date: Wed Sep 25 02:37:40 2019
New Revision: 352668
URL: https://svnweb.freebsd.org/changeset/base/352668

Log:
  cron: add log suppression and mail suppression for successful runs
  
  This commit adds two new extensions to crontab, ported from OpenBSD:
  - -n: suppress mail on succesful run
  - -q: suppress logging of command execution
  
  The -q option appears decades old, but -n is relatively new. The
  original proposal by Job Snijder can be found here [1], and gives very
  convincing reasons for inclusion in base.
  
  This patch is a nearly identical port of OpenBSD cron for -q and -n
  features. It is written to follow existing conventions and style of the
  existing codebase.
  
  Example usage:
  
  # should only send email, but won't show up in log
  * * * * * -q date
  
  # should not send email
  * * * * * -n date
  
  # should not send email or log
  * * * * * -n -q date
  
  # should send email because of ping failure
  * * * * * -n -q ping -c 1 5.5.5.5
  
  [1]: https://marc.info/?l=openbsd-tech&m=152874866117948&w=2
  
  PR:           237538
  Submitted by: Naveen Nathan <freebsd_t.lastninja.net>
  Reviewed by:  bcr (manpages)
  MFC after:    1 week
  Differential Revision:        https://reviews.freebsd.org/D20046

Modified:
  head/usr.sbin/cron/cron/cron.h
  head/usr.sbin/cron/cron/do_command.c
  head/usr.sbin/cron/cron/popen.c
  head/usr.sbin/cron/crontab/crontab.5
  head/usr.sbin/cron/lib/entry.c

Modified: head/usr.sbin/cron/cron/cron.h
==============================================================================
--- head/usr.sbin/cron/cron/cron.h      Wed Sep 25 01:39:58 2019        
(r352667)
+++ head/usr.sbin/cron/cron/cron.h      Wed Sep 25 02:37:40 2019        
(r352668)
@@ -191,6 +191,8 @@ typedef     struct _entry {
 #define        NOT_UNTIL       0x10
 #define        SEC_RES         0x20
 #define        INTERVAL        0x40
+#define        DONT_LOG        0x80
+#define        MAIL_WHEN_ERR   0x100
        time_t  lastrun;
 } entry;
 
@@ -257,7 +259,7 @@ user                *load_user(int, struct passwd *, char 
*),
 entry          *load_entry(FILE *, void (*)(char *),
                                 struct passwd *, char **);
 
-FILE           *cron_popen(char *, char *, entry *);
+FILE           *cron_popen(char *, char *, entry *, PID_T *);
 
 
                                /* in the C tradition, we only create

Modified: head/usr.sbin/cron/cron/do_command.c
==============================================================================
--- head/usr.sbin/cron/cron/do_command.c        Wed Sep 25 01:39:58 2019        
(r352667)
+++ head/usr.sbin/cron/cron/do_command.c        Wed Sep 25 02:37:40 2019        
(r352668)
@@ -41,6 +41,7 @@ static const char rcsid[] =
 static void            child_process(entry *, user *),
                        do_univ(user *);
 
+static WAIT_T          wait_on_child(PID_T, const char *);
 
 void
 do_command(e, u)
@@ -94,7 +95,10 @@ child_process(e, u)
        int             stdin_pipe[2], stdout_pipe[2];
        register char   *input_data;
        char            *usernm, *mailto, *mailfrom;
-       int             children = 0;
+       PID_T           jobpid, stdinjob, mailpid;
+       register FILE   *mail;
+       register int    bytes = 1;
+       int             status = 0;
 # if defined(LOGIN_CAP)
        struct passwd   *pwd;
        login_cap_t *lc;
@@ -216,7 +220,7 @@ child_process(e, u)
 
        /* fork again, this time so we can exec the user's command.
         */
-       switch (vfork()) {
+       switch (jobpid = vfork()) {
        case -1:
                log_it("CRON",getpid(),"error","can't vfork");
                exit(ERROR_EXIT);
@@ -237,7 +241,7 @@ child_process(e, u)
                 * the actual user command shell was going to get and the
                 * PID is part of the log message.
                 */
-               /*local*/{
+               if ((e->flags & DONT_LOG) == 0) {
                        char *x = mkprints((u_char *)e->cmd, strlen(e->cmd));
 
                        log_it(usernm, getpid(), "CMD", x);
@@ -359,8 +363,6 @@ child_process(e, u)
                break;
        }
 
-       children++;
-
        /* middle process, child of original cron, parent of process running
         * the user's command.
         */
@@ -384,7 +386,7 @@ child_process(e, u)
         * we would block here.  thus we must fork again.
         */
 
-       if (*input_data && fork() == 0) {
+       if (*input_data && (stdinjob = fork()) == 0) {
                register FILE   *out = fdopen(stdin_pipe[WRITE_PIPE], "w");
                register int    need_newline = FALSE;
                register int    escaped = FALSE;
@@ -440,8 +442,6 @@ child_process(e, u)
         */
        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
@@ -462,10 +462,6 @@ child_process(e, u)
 
                ch = getc(in);
                if (ch != EOF) {
-                       register FILE   *mail;
-                       register int    bytes = 1;
-                       int             status = 0;
-
                        Debug(DPROC|DEXT,
                                ("[%d] got data (%x:%c) from grandchild\n",
                                        getpid(), ch, ch))
@@ -500,7 +496,7 @@ child_process(e, u)
                                hostname[sizeof(hostname) - 1] = '\0';
                                (void) snprintf(mailcmd, sizeof(mailcmd),
                                               MAILARGS, MAILCMD);
-                               if (!(mail = cron_popen(mailcmd, "w", e))) {
+                               if (!(mail = cron_popen(mailcmd, "w", e, 
&mailpid))) {
                                        warn("%s", MAILCMD);
                                        (void) _exit(ERROR_EXIT);
                                }
@@ -538,28 +534,56 @@ child_process(e, u)
                                if (mailto)
                                        putc(ch, mail);
                        }
+               }
+               /*if data from grandchild*/
 
-                       /* only close pipe if we opened it -- i.e., we're
-                        * mailing...
-                        */
+               Debug(DPROC, ("[%d] got EOF from grandchild\n", getpid()))
 
-                       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 = cron_pclose(mail);
-                       }
+               /* also closes stdout_pipe[READ_PIPE] */
+               fclose(in);
+       }
 
+       /* wait for children to die.
+        */
+       if (jobpid > 0) {
+               WAIT_T  waiter;
+
+               waiter = wait_on_child(jobpid, "grandchild command job");
+
+               /* If everything went well, and -n was set, _and_ we have mail,
+                * we won't be mailing... so shoot the messenger!
+                */
+               if (WIFEXITED(waiter) && WEXITSTATUS(waiter) == 0
+                   && (e->flags & MAIL_WHEN_ERR) == MAIL_WHEN_ERR
+                   && mailto) {
+                       Debug(DPROC, ("[%d] %s executed successfully, mail 
suppressed\n",
+                               getpid(), "grandchild command job"))
+                       kill(mailpid, SIGKILL);
+                       (void)fclose(mail);
+                       mailto = NULL;
+               }
+
+
+               /* 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 = cron_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) {
+                       if (status) {
                                char buf[MAX_TEMPSTR];
 
                                snprintf(buf, sizeof(buf),
@@ -568,35 +592,38 @@ child_process(e, u)
                                        status);
                                log_it(usernm, getpid(), "MAIL", buf);
                        }
+               }
+       }
 
-               } /*if data from grandchild*/
+       if (*input_data && stdinjob > 0)
+               wait_on_child(stdinjob, "grandchild stdinjob");
+}
 
-               Debug(DPROC, ("[%d] got EOF from grandchild\n", getpid()))
+static WAIT_T
+wait_on_child(PID_T childpid, const char *name) {
+       WAIT_T  waiter;
+       PID_T   pid;
 
-               fclose(in);     /* also closes stdout_pipe[READ_PIPE] */
-       }
+       Debug(DPROC, ("[%d] waiting for %s (%d) to finish\n",
+               getpid(), name, childpid))
 
-       /* wait for children to die.
-        */
-       for (;  children > 0;  children--)
-       {
-               WAIT_T          waiter;
-               PID_T           pid;
+#ifdef POSIX
+       while ((pid = waitpid(childpid, &waiter, 0)) < 0 && errno == EINTR)
+#else
+       while ((pid = wait4(childpid, &waiter, 0, NULL)) < 0 && errno == EINTR)
+#endif
+               ;
 
-               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, WEXITSTATUS(waiter)))
-               if (WIFSIGNALED(waiter) && WCOREDUMP(waiter))
-                       Debug(DPROC, (", dumped core"))
-               Debug(DPROC, ("\n"))
-       }
+       if (pid < OK)
+               return waiter;
+
+       Debug(DPROC, ("[%d] %s (%d) finished, status=%04x",
+               getpid(), name, pid, WEXITSTATUS(waiter)))
+       if (WIFSIGNALED(waiter) && WCOREDUMP(waiter))
+               Debug(DPROC, (", dumped core"))
+       Debug(DPROC, ("\n"))
+
+       return waiter;
 }
 
 

Modified: head/usr.sbin/cron/cron/popen.c
==============================================================================
--- head/usr.sbin/cron/cron/popen.c     Wed Sep 25 01:39:58 2019        
(r352667)
+++ head/usr.sbin/cron/cron/popen.c     Wed Sep 25 02:37:40 2019        
(r352668)
@@ -55,9 +55,10 @@ static PID_T *pids;
 static int fds;
 
 FILE *
-cron_popen(program, type, e)
+cron_popen(program, type, e, pidptr)
        char *program, *type;
        entry *e;
+       PID_T *pidptr;
 {
        register char *cp;
        FILE *iop;
@@ -218,6 +219,9 @@ pfree:
                free((char *)argv[argc]);
        }
 #endif
+
+       *pidptr = pid;
+
        return(iop);
 }
 

Modified: head/usr.sbin/cron/crontab/crontab.5
==============================================================================
--- head/usr.sbin/cron/crontab/crontab.5        Wed Sep 25 01:39:58 2019        
(r352667)
+++ head/usr.sbin/cron/crontab/crontab.5        Wed Sep 25 02:37:40 2019        
(r352668)
@@ -17,7 +17,7 @@
 .\"
 .\" $FreeBSD$
 .\"
-.Dd April 19, 2019
+.Dd September 24, 2019
 .Dt CRONTAB 5
 .Os
 .Sh NAME
@@ -199,6 +199,8 @@ lists of names are not allowed.
 .Pp
 The ``sixth'' field (the rest of the line) specifies the command to be
 run.
+One or more command options may precede the command to modify processing
+behavior.
 The entire command portion of the line, up to a newline or %
 character, will be executed by
 .Pa /bin/sh
@@ -211,6 +213,22 @@ Percent-signs (%) in the command, unless escaped with 
 after the first % will be sent to the command as standard
 input.
 .Pp
+The following command options can be supplied:
+.Bl -tag -width Ds
+.It Fl n
+No mail is sent after a successful run.
+The execution output will only be mailed if the command exits with a non-zero
+exit code.
+The
+.Fl n
+option is an attempt to cure potentially copious volumes of mail coming from
+.Xr cron 8 .
+.It Fl q
+Execution will not be logged.
+.El
+.sp
+Duplicate options are not allowed.
+.Pp
 Note: The day of a command's execution can be specified by two
 fields \(em day of month, and day of week.
 If both fields are
@@ -271,6 +289,10 @@ MAILTO=paul
 5 4 * * sun     echo "run at 5 after 4 every sunday"
 # run at 5 minutes intervals, no matter how long it takes
 @300           svnlite up /usr/src
+# run every minute, suppress logging
+* * * * *       -q date
+# run every minute, only send mail if ping fails
+* * * * *       -n ping -c 1 freebsd.org
 .Ed
 .Sh SEE ALSO
 .Xr crontab 1 ,
@@ -314,6 +336,14 @@ All of the
 .Sq @
 directives that can appear in place of the first five fields
 are extensions.
+.Pp
+Command processing can be modified using command options.
+The
+.Sq -q
+option suppresses logging.
+The
+.Sq -n
+option does not mail on successful run.
 .Sh AUTHORS
 .An Paul Vixie Aq Mt p...@vix.com
 .Sh BUGS

Modified: head/usr.sbin/cron/lib/entry.c
==============================================================================
--- head/usr.sbin/cron/lib/entry.c      Wed Sep 25 01:39:58 2019        
(r352667)
+++ head/usr.sbin/cron/lib/entry.c      Wed Sep 25 02:37:40 2019        
(r352668)
@@ -35,7 +35,8 @@ static const char rcsid[] =
 
 typedef        enum ecode {
        e_none, e_minute, e_hour, e_dom, e_month, e_dow,
-       e_cmd, e_timespec, e_username, e_group, e_mem
+       e_cmd, e_timespec, e_username, e_group, e_option,
+       e_mem
 #ifdef LOGIN_CAP
        , e_class
 #endif
@@ -58,6 +59,7 @@ static char *ecodes[] =
                "bad time specifier",
                "bad username",
                "bad group name",
+               "bad option",
                "out of memory",
 #ifdef LOGIN_CAP
                "bad class name",
@@ -428,6 +430,53 @@ load_entry(file, error_func, pw, envp)
                goto eof;
        }
 #endif
+
+       Debug(DPARS, ("load_entry()...checking for command options\n"))
+
+       ch = get_char(file);
+
+       while (ch == '-') {
+               Debug(DPARS|DEXT, ("load_entry()...expecting option\n"))
+               switch (ch = get_char(file)) {
+               case 'n':
+                       Debug(DPARS|DEXT, ("load_entry()...got MAIL_WHEN_ERR 
('n') option\n"))
+                       /* only allow the user to set the option once */
+                       if ((e->flags & MAIL_WHEN_ERR) == MAIL_WHEN_ERR) {
+                               Debug(DPARS|DEXT, ("load_entry()...duplicate 
MAIL_WHEN_ERR ('n') option\n"))
+                               ecode = e_option;
+                               goto eof;
+                       }
+                       e->flags |= MAIL_WHEN_ERR;
+                       break;
+               case 'q':
+                       Debug(DPARS|DEXT, ("load_entry()...got DONT_LOG ('q') 
option\n"))
+                       /* only allow the user to set the option once */
+                       if ((e->flags & DONT_LOG) == DONT_LOG) {
+                               Debug(DPARS|DEXT, ("load_entry()...duplicate 
DONT_LOG ('q') option\n"))
+                               ecode = e_option;
+                               goto eof;
+                       }
+                       e->flags |= DONT_LOG;
+                       break;
+               default:
+                       Debug(DPARS|DEXT, ("load_entry()...invalid option 
'%c'\n", ch))
+                       ecode = e_option;
+                       goto eof;
+               }
+               ch = get_char(file);
+               if (ch!='\t' && ch!=' ') {
+                       ecode = e_option;
+                       goto eof;
+               }
+
+               Skip_Blanks(ch, file)
+               if (ch == EOF || ch == '\n') {
+                       ecode = e_cmd;
+                       goto eof;
+               }
+       }
+
+       unget_char(ch, file);
 
        Debug(DPARS, ("load_entry()...about to parse command\n"))
 
_______________________________________________
svn-src-all@freebsd.org mailing list
https://lists.freebsd.org/mailman/listinfo/svn-src-all
To unsubscribe, send any mail to "svn-src-all-unsubscr...@freebsd.org"

Reply via email to