Index: usr.sbin/cron/cron/cron.h =================================================================== --- usr.sbin/cron/cron/cron.h +++ usr.sbin/cron/cron/cron.h @@ -191,6 +191,8 @@ #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 @@ 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 Index: usr.sbin/cron/cron/do_command.c =================================================================== --- usr.sbin/cron/cron/do_command.c +++ usr.sbin/cron/cron/do_command.c @@ -41,6 +41,7 @@ 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,12 @@ 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 +222,7 @@ /* 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 +243,7 @@ * 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 +365,6 @@ break; } - children++; - /* middle process, child of original cron, parent of process running * the user's command. */ @@ -384,7 +388,7 @@ * 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 +444,6 @@ */ 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 +464,6 @@ 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 +498,7 @@ 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 +536,54 @@ 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); - } + fclose(in); /* also closes stdout_pipe[READ_PIPE] */ + } + + /* 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 successful, 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 @@ 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; - - 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")) - } +#ifdef POSIX + while ((pid = waitpid(childpid, &waiter, 0)) < 0 && errno == EINTR) +#else + while ((pid = wait4(childpid, &waiter, 0, NULL)) < 0 && errno == EINTR) +#endif + ; + + 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; } Index: usr.sbin/cron/cron/popen.c =================================================================== --- usr.sbin/cron/cron/popen.c +++ usr.sbin/cron/cron/popen.c @@ -55,9 +55,10 @@ 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 @@ free((char *)argv[argc]); } #endif + + *pidptr = pid; + return(iop); } Index: usr.sbin/cron/crontab/crontab.5 =================================================================== --- usr.sbin/cron/crontab/crontab.5 +++ usr.sbin/cron/crontab/crontab.5 @@ -17,7 +17,7 @@ .\" .\" $FreeBSD$ .\" -.Dd April 19, 2019 +.Dd April 26, 2019 .Dt CRONTAB 5 .Os .Sh NAME @@ -199,6 +199,8 @@ .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 @@ 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 @@ 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 @@ .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 paul@vix.com .Sh BUGS Index: usr.sbin/cron/lib/entry.c =================================================================== --- usr.sbin/cron/lib/entry.c +++ usr.sbin/cron/lib/entry.c @@ -35,7 +35,8 @@ 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 @@ "bad time specifier", "bad username", "bad group name", + "bad option", "out of memory", #ifdef LOGIN_CAP "bad class name", @@ -429,6 +431,53 @@ } #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")) /* Everything up to the next \n or EOF is part of the command...