diff --git a/.gitignore b/.gitignore index ca9c061..014b055 100644 --- a/.gitignore +++ b/.gitignore @@ -23,6 +23,7 @@ runsvc syslog usyslogd klogd +gcrond services/sigkill services/sigterm diff --git a/Makefile.am b/Makefile.am index 063a8e8..806b777 100644 --- a/Makefile.am +++ b/Makefile.am @@ -20,6 +20,7 @@ dist_man8_MANS = include lib/Makemodule.am include cmd/Makemodule.am include initd/Makemodule.am +include crond/Makemodule.am include scripts/Makemodule.am include services/Makemodule.am include syslogd/Makemodule.am @@ -51,6 +52,10 @@ install-data-local: $(LN_S) $(TEMPLATEDIR)/ifcfg $(DESTDIR)$(SVCDIR)/ifcfg $(LN_S) $(TEMPLATEDIR)/modules $(DESTDIR)$(SVCDIR)/modules $(LN_S) $(TEMPLATEDIR)/network $(DESTDIR)$(SVCDIR)/network +if GCROND + $(MKDIR_P) $(DESTDIR)$(GCRONDIR) + $(LN_S) $(TEMPLATEDIR)/gcrond $(DESTDIR)$(SVCDIR)/gcrond +endif if USYSLOGD $(LN_S) $(TEMPLATEDIR)/usyslogd $(DESTDIR)$(SVCDIR)/usyslogd endif diff --git a/cmd/runsvc/runsvc.c b/cmd/runsvc/runsvc.c index 9e646e7..e36010b 100644 --- a/cmd/runsvc/runsvc.c +++ b/cmd/runsvc/runsvc.c @@ -17,51 +17,6 @@ */ #include "runsvc.h" -static int setup_tty(service_t *svc) -{ - int fd; - - if (svc->ctty != NULL) { - fd = open(svc->ctty, O_RDWR); - if (fd < 0) { - perror(svc->ctty); - return -1; - } - - if (svc->flags & SVC_FLAG_TRUNCATE_OUT) - ftruncate(fd, 0); - - close(STDIN_FILENO); - close(STDOUT_FILENO); - close(STDERR_FILENO); - - setsid(); - - dup2(fd, STDIN_FILENO); - dup2(fd, STDOUT_FILENO); - dup2(fd, STDERR_FILENO); - close(fd); - } - - return 0; -} - -/*****************************************************************************/ - -static NORETURN void argv_exec(exec_t *e) -{ - char **argv = alloca(e->argc + 1), *ptr; - int i; - - for (ptr = e->args, i = 0; i < e->argc; ++i, ptr += strlen(ptr) + 1) - argv[i] = ptr; - - argv[i] = NULL; - execvp(argv[0], argv); - perror(argv[0]); - exit(EXIT_FAILURE); -} - static int runlst_wait(exec_t *list) { pid_t ret, pid; @@ -128,7 +83,7 @@ int main(int argc, char **argv) if (initenv()) goto out; - if (setup_tty(svc)) + if (setup_tty(svc->ctty, (svc->flags & SVC_FLAG_TRUNCATE_OUT) != 0)) goto out; if (svc->exec->next == NULL) diff --git a/configure.ac b/configure.ac index e0374c5..84ab673 100644 --- a/configure.ac +++ b/configure.ac @@ -55,9 +55,19 @@ AC_ARG_WITH([klogd], esac], [AM_CONDITIONAL([KLOGD], [true])]) +AC_ARG_WITH([gcrond], + [AS_HELP_STRING([--without-gcrond], [Build without gcron daemon])], + [case "${withval}" in + yes) AM_CONDITIONAL([GCROND], [true]) ;; + no) AM_CONDITIONAL([GCROND], [false]) ;; + *) AC_MSG_ERROR([bad value ${withval} for --without-gcron]) ;; + esac], + [AM_CONDITIONAL([GCROND], [true])]) + AC_CONFIG_HEADERS([lib/include/config.h]) AC_DEFINE_DIR(SVCDIR, sysconfdir/init.d, [Startup service directory]) +AC_DEFINE_DIR(GCRONDIR, sysconfdir/gcron.d, [Cron service directory]) AC_DEFINE_DIR(TEMPLATEDIR, datadir/init, [Service template directory]) AC_DEFINE_DIR(SCRIPTDIR, libexecdir/init, [Helper script directory]) AC_DEFINE_DIR(SOCKDIR, localstatedir/run, [Directory for initd socket]) diff --git a/crond/Makemodule.am b/crond/Makemodule.am new file mode 100644 index 0000000..51960e9 --- /dev/null +++ b/crond/Makemodule.am @@ -0,0 +1,9 @@ +if GCROND +gcrond_SOURCES = crond/main.c crond/gcrond.h crond/runjob.c +gcrond_CPPFLAGS = $(AM_CPPFLAGS) +gcrond_CFLAGS = $(AM_CFLAGS) +gcrond_LDFLAGS = $(AM_LDFLAGS) +gcrond_LDADD = libcron.a libinit.a libcfg.a + +sbin_PROGRAMS += gcrond +endif diff --git a/crond/gcrond.h b/crond/gcrond.h new file mode 100644 index 0000000..4cc2417 --- /dev/null +++ b/crond/gcrond.h @@ -0,0 +1,37 @@ +/* SPDX-License-Identifier: GPL-3.0-or-later */ +/* + * Copyright (C) 2018 - David Oberhollenzer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#ifndef GCROND_H +#define GCROND_H + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "crontab.h" +#include "config.h" +#include "util.h" + +int runjob(crontab_t *tab); + +#endif /* GCROND_H */ + diff --git a/crond/main.c b/crond/main.c new file mode 100644 index 0000000..d7cc33e --- /dev/null +++ b/crond/main.c @@ -0,0 +1,139 @@ +/* SPDX-License-Identifier: GPL-3.0-or-later */ +/* + * Copyright (C) 2018 - David Oberhollenzer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include "gcrond.h" + +static crontab_t *jobs; +static sig_atomic_t run = 1; +static sig_atomic_t rescan = 1; + +static void read_config(void) +{ + if (cronscan(GCRONDIR, &jobs)) { + fputs("Error reading configuration. Continuing anyway.\n", + stderr); + } +} + +static void cleanup_config(void) +{ + crontab_t *t; + + while (jobs != NULL) { + t = jobs; + jobs = jobs->next; + delcron(t); + } +} + +static int calc_timeout(void) +{ + time_t now = time(NULL), future; + struct tm tmstruct; + crontab_t mask, *t; + int minutes; + + for (minutes = 0; minutes < 120; ++minutes) { + future = now + minutes * 60; + + localtime_r(&future, &tmstruct); + cron_tm_to_mask(&mask, &tmstruct); + + for (t = jobs; t != NULL; t = t->next) { + if (cron_should_run(t, &mask)) + goto out; + } + } +out: + return minutes ? minutes * 60 : 60; +} + +static void runjobs(void) +{ + time_t now = time(NULL); + struct tm tmstruct; + crontab_t mask, *t; + + localtime_r(&now, &tmstruct); + cron_tm_to_mask(&mask, &tmstruct); + + for (t = jobs; t != NULL; t = t->next) { + if (cron_should_run(t, &mask)) + runjob(t); + } +} + +static void sighandler(int signo) +{ + switch (signo) { + case SIGINT: + case SIGTERM: + run = 0; + break; + case SIGHUP: + rescan = 1; + break; + } +} + +int main(void) +{ + struct timespec stime; + struct sigaction act; + crontab_t *t; + int timeout; + pid_t pid; + + memset(&act, 0, sizeof(act)); + act.sa_handler = sighandler; + sigaction(SIGINT, &act, NULL); + sigaction(SIGTERM, &act, NULL); + sigaction(SIGHUP, &act, NULL); + + while (run) { + if (rescan == 1) { + cleanup_config(); + read_config(); + timeout = 60; + rescan = 0; + } else { + runjobs(); + timeout = calc_timeout(); + } + + stime.tv_sec = timeout; + stime.tv_nsec = 0; + + while (nanosleep(&stime, &stime) != 0 && run && !rescan) { + if (errno != EINTR) { + perror("nanosleep"); + break; + } + } + + while ((pid = waitpid(-1, NULL, WNOHANG)) != -1) { + for (t = jobs; t != NULL; t = t->next) { + if (t->pid == pid) { + t->pid = -1; + break; + } + } + } + } + + return EXIT_SUCCESS; +} diff --git a/crond/runjob.c b/crond/runjob.c new file mode 100644 index 0000000..7650748 --- /dev/null +++ b/crond/runjob.c @@ -0,0 +1,87 @@ +/* SPDX-License-Identifier: GPL-3.0-or-later */ +/* + * Copyright (C) 2018 - David Oberhollenzer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include "gcrond.h" + +int runjob(crontab_t *tab) +{ + struct sigaction act; + pid_t pid; + exec_t *e; + int ret; + + if (tab->exec == NULL) + return 0; + + pid = fork(); + if (pid == -1) { + perror("fork"); + return -1; + } + + if (pid != 0) { + tab->pid = pid; + return 0; + } + + /* XXX: inside the child process */ + memset(&act, 0, sizeof(act)); + act.sa_handler = SIG_DFL; + sigaction(SIGINT, &act, NULL); + sigaction(SIGTERM, &act, NULL); + sigaction(SIGHUP, &act, NULL); + + if (setup_tty(tab->ctty, tab->tty_truncate)) + exit(EXIT_FAILURE); + + if (tab->gid != 0) { + if (setresgid(tab->gid, tab->gid, tab->gid)) { + perror("setgid"); + exit(EXIT_FAILURE); + } + } + + if (tab->uid != 0) { + if (setresuid(tab->uid, tab->uid, tab->uid)) { + perror("setuid"); + exit(EXIT_FAILURE); + } + } + + if (tab->exec->next == NULL) + argv_exec(tab->exec); + + for (e = tab->exec; e != NULL; e = e->next) { + pid = fork(); + if (pid == -1) { + perror("fork"); + exit(EXIT_FAILURE); + } + + if (pid == 0) + argv_exec(e); + + while (waitpid(pid, &ret, 0) != pid) + ; + + ret = WIFEXITED(ret) ? WEXITSTATUS(ret) : EXIT_FAILURE; + if (ret != EXIT_SUCCESS) + break; + } + + exit(ret); +} diff --git a/lib/Makemodule.am b/lib/Makemodule.am index 7620e63..fb6ca92 100644 --- a/lib/Makemodule.am +++ b/lib/Makemodule.am @@ -4,7 +4,7 @@ libinit_a_SOURCES = lib/util/delsvc.c lib/util/svcmap.c lib/util/enum_by_name.c libinit_a_SOURCES += lib/util/rdsvc.c lib/util/svcscan.c lib/util/mksock.c libinit_a_SOURCES += lib/util/del_svc_list.c lib/util/svc_tsort.c libinit_a_SOURCES += lib/util/opensock.c lib/util/enum_to_name.c -libinit_a_SOURCES += lib/util/print_version.c $(HEADRS) +libinit_a_SOURCES += lib/util/print_version.c lib/util/argv_exec.c $(HEADRS) libinit_a_CPPFLAGS = $(AM_CPPFLAGS) libinit_a_CFLAGS = $(AM_CFLAGS) diff --git a/lib/cron/rdcron.c b/lib/cron/rdcron.c index 42936ce..c581b7e 100644 --- a/lib/cron/rdcron.c +++ b/lib/cron/rdcron.c @@ -490,6 +490,11 @@ crontab_t *rdcron(int dirfd, const char *filename) } cron->pid = -1; + cron->minute = 0xFFFFFFFFFFFFFFFFUL; + cron->hour = 0xFFFFFFFF; + cron->dayofmonth = 0xFFFFFFFF; + cron->month = 0xFFFF; + cron->dayofweek = 0xFF; rdline_init(&rd, fd, filename, 0, NULL); ret = rdcfg(cron, &rd, cron_params, ARRAY_SIZE(cron_params), 0); diff --git a/lib/include/service.h b/lib/include/service.h index ceaf782..abe4968 100644 --- a/lib/include/service.h +++ b/lib/include/service.h @@ -20,6 +20,8 @@ #include +#include "util.h" + enum { /* Start the service in the background and continue with @@ -123,5 +125,9 @@ const char *svc_target_to_string(int target); int svc_target_from_string(const char *target); +int setup_tty(const char *tty, bool truncate); + +NORETURN void argv_exec(exec_t *e); + #endif /* SERVICE_H */ diff --git a/lib/util/argv_exec.c b/lib/util/argv_exec.c new file mode 100644 index 0000000..e39153c --- /dev/null +++ b/lib/util/argv_exec.c @@ -0,0 +1,67 @@ +/* SPDX-License-Identifier: GPL-3.0-or-later */ +/* + * Copyright (C) 2018 - David Oberhollenzer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include "service.h" + +#include +#include +#include +#include +#include + +int setup_tty(const char *tty, bool truncate) +{ + int fd; + + if (tty == NULL) + return 0; + + fd = open(tty, O_RDWR); + if (fd < 0) { + perror(tty); + return -1; + } + + if (truncate) + ftruncate(fd, 0); + + close(STDIN_FILENO); + close(STDOUT_FILENO); + close(STDERR_FILENO); + + setsid(); + + dup2(fd, STDIN_FILENO); + dup2(fd, STDOUT_FILENO); + dup2(fd, STDERR_FILENO); + close(fd); + return 0; +} + +void argv_exec(exec_t *e) +{ + char **argv = alloca(e->argc + 1), *ptr; + int i; + + for (ptr = e->args, i = 0; i < e->argc; ++i, ptr += strlen(ptr) + 1) + argv[i] = ptr; + + argv[i] = NULL; + execvp(argv[0], argv); + perror(argv[0]); + exit(EXIT_FAILURE); +} diff --git a/services/Makemodule.am b/services/Makemodule.am index 0cea6ff..ac297a6 100644 --- a/services/Makemodule.am +++ b/services/Makemodule.am @@ -18,9 +18,13 @@ if USYSLOGD init_DATA += services/klogd endif +if GCROND +init_DATA += services/gcrond +endif + EXTRA_DIST += services/sysinit services/vfs services/agetty services/hostname EXTRA_DIST += services/hwclock services/loopback services/klogd EXTRA_DIST += services/sync services/sysctl services/tmpfs EXTRA_DIST += services/dhcpcd services/dhcpcdmaster services/unbound EXTRA_DIST += services/usyslogd services/dnsmasq services/network -EXTRA_DIST += services/consolefont +EXTRA_DIST += services/consolefont services/gcrond diff --git a/services/gcrond b/services/gcrond new file mode 100644 index 0000000..0ee1ee5 --- /dev/null +++ b/services/gcrond @@ -0,0 +1,5 @@ +description start gcron daemon +exec gcrond +type respawn +target boot +after network