From edd602e23d1796e086450521ee98d49f4019da1e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Poho=C5=99elsk=C3=BD?= Date: Fri, 6 Oct 2023 14:56:39 +0200 Subject: [PATCH] Add -n option for crontab entries In order to test this new feature before cronie upstream tags a new version, I'm adding this patch directly. Now you can suppress mails from crontabs when the job is successful. This is done by using '-n' option in your crontab: `* * * * * -n foo.sh` --- add_-n_option_for_crontab_entries.patch | 205 ++++++++++++++++++++++++ cronie.spec | 3 + 2 files changed, 208 insertions(+) create mode 100644 add_-n_option_for_crontab_entries.patch diff --git a/add_-n_option_for_crontab_entries.patch b/add_-n_option_for_crontab_entries.patch new file mode 100644 index 0000000..441c214 --- /dev/null +++ b/add_-n_option_for_crontab_entries.patch @@ -0,0 +1,205 @@ +From ce7d5bf0a43d147f8502e6424cd523b56adf5599 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Ond=C5=99ej=20Poho=C5=99elsk=C3=BD?= +Date: Mon, 26 Sep 2022 16:51:41 +0200 +Subject: [PATCH] Add -n option for crontab entries + +The -n option causes no mail to be sent +when the command finishes successfully. + +These kind of options are already supported in *BSD; +in fact, this is a port of a patch from NetBSD [1]. + +This was requested in [2]. + +[1]: NetBSD/src@666eac5 +[2]: https://bugzilla.redhat.com/show_bug.cgi?id=1591763 +--- + src/do_command.c | 32 +++++++++++++++++++++++++++++--- + src/entry.c | 31 +++++++++++++++++++++++++++++++ + src/funcs.h | 1 + + src/popen.c | 33 ++++++++++++++++++++++++++++++--- + src/structs.h | 1 + + 5 files changed, 92 insertions(+), 6 deletions(-) + +diff --git a/src/do_command.c b/src/do_command.c +index 87f996f..6a3886a 100644 +--- a/src/do_command.c ++++ b/src/do_command.c +@@ -94,6 +94,7 @@ static int child_process(entry * e, char **jobenv) { + char mailfrom_expanded[MAX_EMAILSTR]; + int children = 0; + pid_t pid = getpid(); ++ pid_t jobpid = -1; + struct sigaction sa; + + /* Ignore SIGPIPE as we will be writing to pipes and do not want to terminate +@@ -199,7 +200,7 @@ static int child_process(entry * e, char **jobenv) { + + /* fork again, this time so we can exec the user's command. + */ +- switch (fork()) { ++ switch (jobpid = fork()) { + case -1: + log_it("CRON", pid, "CAN'T FORK", "child_process", errno); + return ERROR_EXIT; +@@ -552,10 +553,35 @@ static int child_process(entry * e, char **jobenv) { + } + #endif + } +- /* only close pipe if we opened it -- i.e., we're +- * mailing... ++ /* if -n option was specified, abort the sending ++ * now when we read all of the command output ++ * and thus can wait for it's exit status + */ ++ if (mail && e->flags & MAIL_WHEN_ERR) { ++ int jobstatus = -1; ++ if (jobpid > 0) { ++ while (waitpid(jobpid, &jobstatus, WNOHANG) == -1) { ++ if (errno == EINTR) continue; ++ log_it("CRON", getpid(), "error", "invalid job pid", errno); ++ break; ++ } ++ } else { ++ log_it("CRON", getpid(), "error", "invalid job pid", 0); ++ } + ++ /* if everything went well, -n is set, and we have mail, ++ * we won't be mailing – so shoot the messenger! ++ */ ++ if (WIFEXITED(jobstatus) && WEXITSTATUS(jobstatus) == EXIT_SUCCESS) { ++ Debug(DPROC, ("[%ld] aborting pipe to mail\n", (long)getpid())); ++ status = cron_pabort(mail); ++ mail = NULL; ++ } ++ } ++ ++ /* only close pipe if we opened it -- i.e., we're (still) ++ * mailing... ++ */ + if (mail) { + Debug(DPROC, ("[%ld] closing pipe to mail\n", (long) getpid())); + /* Note: the pclose will probably see +diff --git a/src/entry.c b/src/entry.c +index bb7cb62..9e199fe 100644 +--- a/src/entry.c ++++ b/src/entry.c +@@ -417,6 +417,37 @@ entry *load_entry(FILE * file, void (*error_func) (), struct passwd *pw, + + Debug(DPARS, ("load_entry()...about to parse command\n")); + ++ /* If the first character of the command is '-', it is a cron option. */ ++ ch = get_char(file); ++ while (ch == '-') { ++ switch (ch = get_char(file)) { ++ case 'n': ++ /* only allow user to set the option once */ ++ if ((e->flags & MAIL_WHEN_ERR) == MAIL_WHEN_ERR) { ++ ecode = e_option; ++ goto eof; ++ } ++ e->flags |= MAIL_WHEN_ERR; ++ break; ++ ++ default: ++ 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); ++ + /* 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. +diff --git a/src/funcs.h b/src/funcs.h +index dea737e..427e027 100644 +--- a/src/funcs.h ++++ b/src/funcs.h +@@ -67,6 +67,7 @@ int load_database(cron_db *), + swap_uids_back(void), + load_env(char *, FILE *), + env_set_from_environ(char ***envpp), ++ cron_pabort(FILE *), + cron_pclose(FILE *), + glue_strings(char *, size_t, const char *, const char *, char), + strcmp_until(const char *, const char *, char), +diff --git a/src/popen.c b/src/popen.c +index 4397264..3043eb6 100644 +--- a/src/popen.c ++++ b/src/popen.c +@@ -167,7 +167,7 @@ FILE *cron_popen(char *program, const char *type, struct passwd *pw, char **jobe + return (iop); + } + +-int cron_pclose(FILE * iop) { ++static int cron_finalize(FILE * iop, int sig) { + int fdes; + sigset_t oset, nset; + WAIT_T stat_loc; +@@ -180,7 +180,12 @@ int cron_pclose(FILE * iop) { + fdes = fileno(iop); + if (pids == NULL || fdes >= fds || pids[fdes] == 0L) + return (-1); +- (void) fclose(iop); ++ ++ if (!sig) { ++ (void) fclose(iop); ++ } else if (kill(pids[fdes], sig) == -1) { ++ return -1; ++ } + + sigemptyset(&nset); + sigaddset(&nset, SIGINT); +@@ -189,6 +194,28 @@ int cron_pclose(FILE * iop) { + (void) sigprocmask(SIG_BLOCK, &nset, &oset); + while ((pid = wait(&stat_loc)) != pids[fdes] && pid != -1) ; + (void) sigprocmask(SIG_SETMASK, &oset, NULL); ++ ++ if (sig) { ++ (void) fclose(iop); ++ } + pids[fdes] = 0; +- return (pid == -1 ? -1 : WEXITSTATUS(stat_loc)); ++ ++ if (pid < 0) { ++ return pid; ++ } ++ ++ if (WIFEXITED(stat_loc)) { ++ return WEXITSTATUS(stat_loc); ++ } else { ++ return WTERMSIG(stat_loc); ++ } ++} ++ ++int cron_pclose(FILE * iop) { ++ return cron_finalize(iop, 0); ++} ++ ++int cron_pabort(FILE * iop) { ++ int esig = cron_finalize(iop, SIGKILL); ++ return esig == SIGKILL ? 0 : esig; + } +diff --git a/src/structs.h b/src/structs.h +index 6d3c15b..d930da5 100644 +--- a/src/structs.h ++++ b/src/structs.h +@@ -48,6 +48,7 @@ typedef struct _entry { + #define DOW_STAR 0x08 + #define WHEN_REBOOT 0x10 + #define DONT_LOG 0x20 ++#define MAIL_WHEN_ERR 0x40 + } entry; + + /* the crontab database will be a list of the diff --git a/cronie.spec b/cronie.spec index 8fb23e2..49a6672 100644 --- a/cronie.spec +++ b/cronie.spec @@ -11,6 +11,9 @@ License: GPL-2.0-or-later AND BSD-3-Clause AND BSD-2-Clause AND ISC AND LGPL-2 URL: https://github.com/cronie-crond/cronie Source0: https://github.com/cronie-crond/cronie/releases/download/cronie-%{version}/cronie-%{version}.tar.gz +# https://github.com/cronie-crond/cronie/commit/ce7d5bf0a43d147f8502e6424cd523b56adf5599 +Patch: add_-n_option_for_crontab_entries.patch + Requires: dailyjobs %if %{with selinux}