2 * FCRON - periodic command scheduler
4 * Copyright 2000-2016 Thibault Godouet <fcron@free.fr>
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
20 * The GNU General Public License can also be found in the file
21 * `LICENSE' that comes with the fcron source distribution.
30 #include "temp_file.h"
31 #include "fcronconf.h"
38 void check_signal(void);
41 void print_schedule(void);
42 RETSIGTYPE sighup_handler(int x);
43 RETSIGTYPE sigterm_handler(int x);
44 RETSIGTYPE sigchild_handler(int x);
45 RETSIGTYPE sigusr1_handler(int x);
46 RETSIGTYPE sigusr2_handler(int x);
47 int parseopt(int argc, char *argv[]);
49 int is_system_reboot(void);
50 void create_spooldir(char *dir);
52 /* command line options */
55 char foreground = 1; /* set to 1 when we are on foreground, else 0 */
57 char foreground = 0; /* set to 1 when we are on foreground, else 0 */
60 time_t first_sleep = FIRST_SLEEP;
61 const time_t min_sleep_usec = 1000; /* 1ms -- we won't sleep for less than this time */
62 time_t save_time = SAVE;
63 char once = 0; /* set to 1 if fcron shall return immediately after running
64 * all jobs that are due at the time when fcron is started */
66 /* Get the default locale character set for the mail
67 * "Content-Type: ...; charset=" header */
68 char default_mail_charset[TERM_LEN] = "";
70 /* used in temp_file() : create it in current dir (normally spool dir) */
73 /* process identity */
75 mode_t saved_umask; /* default root umask */
76 char *prog_name = NULL;
77 char *orig_tz_envvar = NULL;
79 /* uid/gid of user/group root
80 * (we don't use the static UID or GID as we ask for user and group names
81 * in the configure script) */
85 /* have we got a signal ? */
86 char sig_conf = 0; /* is 1 when we got a SIGHUP, 2 for a SIGUSR1 */
87 char sig_chld = 0; /* is 1 when we got a SIGCHLD */
88 char sig_debug = 0; /* is 1 when we got a SIGUSR2 */
91 struct cf_t *file_base; /* point to the first file of the list */
92 struct job_t *queue_base; /* ordered list of normal jobs to be run */
93 unsigned long int next_id; /* id for next line to enter database */
95 struct cl_t **serial_array; /* ordered list of job to be run one by one */
96 short int serial_array_size; /* size of serial_array */
97 short int serial_array_index; /* the index of the first job */
98 short int serial_num; /* number of job being queued */
99 short int serial_running; /* number of running serial jobs */
101 /* do not run more than this number of serial job simultaneously */
102 short int serial_max_running = SERIAL_MAX_RUNNING;
103 short int serial_queue_max = SERIAL_QUEUE_MAX;
105 lavg_list_t *lavg_list; /* jobs waiting for a given system load value */
106 short int lavg_queue_max = LAVG_QUEUE_MAX;
107 short int lavg_serial_running; /* number of serialized lavg job being running */
109 exe_list_t *exe_list; /* jobs which are executed */
111 time_t begin_sleep; /* the time at which sleep began */
112 time_t now; /* the current time */
115 pam_handle_t *pamh = NULL;
116 const struct pam_conv apamconv = { NULL };
121 /* print some informations about this program :
122 * version, license */
125 "fcron " VERSION_QUOTED " - periodic command scheduler\n"
126 "Copyright " COPYRIGHT_QUOTED " Thibault Godouet <fcron@free.fr>\n"
127 "This program is free software distributed WITHOUT ANY WARRANTY.\n"
128 "See the GNU General Public License for more details.\n");
137 /* print a help message about command line options and exit */
139 fprintf(stderr, "\nfcron " VERSION_QUOTED "\n\n"
140 "fcron [-d] [-f] [-b]\n"
142 " -s t --savetime t Save fcrontabs on disk every t sec.\n"
143 " -l t --firstsleep t Sets the initial delay before any job is executed"
144 ",\n default to %d seconds.\n"
145 " -m n --maxserial n Set to n the max number of running serial jobs.\n"
146 " -c f --configfile f Make fcron use config file f.\n"
147 " -n d --newspooldir d Create d as a new spool directory.\n"
148 " -f --foreground Stay in foreground.\n"
149 " -b --background Go to background.\n"
150 " -y --nosyslog Don't log to syslog at all.\n"
151 " -p --logfilepath If set, log to the file given as argument.\n"
152 " -o --once Execute all jobs that need to be run, wait for "
153 "them,\n then return. Sets firstsleep to 0.\n"
154 " Especially useful with -f and -y.\n"
155 " -d --debug Set Debug mode.\n"
156 " -h --help Show this help message.\n"
157 " -V --version Display version & infos about fcron.\n",
166 /* print the current schedule on syslog */
172 explain("Printing schedule ...");
173 for (cf = file_base; cf; cf = cf->cf_next) {
174 explain(" File %s", cf->cf_user);
175 for (cl = cf->cf_line_base; cl; cl = cl->cl_next) {
176 ftime = localtime(&(cl->cl_nextexe));
177 explain(" cmd '%s' next exec %04d-%02d-%02d wday:%d %02d:%02d"
179 cl->cl_shell, (ftime->tm_year + 1900), (ftime->tm_mon + 1),
180 ftime->tm_mday, ftime->tm_wday, ftime->tm_hour,
185 explain("... end of printing schedule.");
190 xexit(int exit_value)
191 /* exit after having freed memory and removed lock file */
197 /* we save all files now and after having waiting for all
198 * job being executed because we might get a SIGKILL
199 * if we don't exit quickly */
208 if (f->cf_running > 0) {
210 debug("waiting jobs for %s ...", f->cf_user);
212 wait_all(&f->cf_running);
215 delete_file(f->cf_user);
217 /* delete_file remove the f file from the list :
218 * next file to remove is now pointed by file_base. */
224 exe_list_destroy(exe_list);
225 lavg_list_destroy(lavg_list);
228 Free_safe(orig_tz_envvar);
230 explain("Exiting with code %d", exit_value);
237 /* check if another fcron daemon is running with the same config (-c option) :
238 * in this case, die. if not, write our pid to /var/run/fcron.pid in order to lock,
239 * and to permit fcrontab to read our pid and signal us */
242 FILE *daemon_lockfp = NULL;
245 if (((fd = open(pidfile, O_RDWR | O_CREAT, 0644)) == -1)
246 || ((daemon_lockfp = fdopen(fd, "r+"))) == NULL)
247 die_e("can't open or create %s", pidfile);
250 if (flock(fd, LOCK_EX | LOCK_NB) != 0)
251 #else /* HAVE_FLOCK */
252 if (lockf(fileno(daemon_lockfp), F_TLOCK, 0) != 0)
253 #endif /* ! HAVE_FLOCK */
255 if (fscanf(daemon_lockfp, "%d", &otherpid) >= 1)
256 die_e("can't lock %s, running daemon's pid may be %d", pidfile,
259 die_e("can't lock %s, and unable to read running"
260 " daemon's pid", pidfile);
263 fcntl(fd, F_SETFD, 1);
265 rewind(daemon_lockfp);
266 fprintf(daemon_lockfp, "%d\n", (int)daemon_pid);
267 fflush(daemon_lockfp);
268 if (ftruncate(fileno(daemon_lockfp), ftell(daemon_lockfp)) < 0)
270 ("Unable to ftruncate(fileno(daemon_lockfp), ftell(daemon_lockfp))");
272 /* abandon fd and daemon_lockfp even though the file is open. we need to
273 * keep it open and locked, but we don't need the handles elsewhere.
279 is_system_startup(void)
283 /* lock exist - skip reboot jobs */
284 if (access(REBOOT_LOCK, F_OK) == 0) {
285 explain("@reboot jobs will only be run at computer's startup.");
286 /* don't run @reboot jobs */
289 /* lock doesn't exist - create lock, run reboot jobs */
290 if ((reboot = creat(REBOOT_LOCK, S_IRUSR & S_IWUSR)) < 0)
291 error_e("Can't create lock for reboot jobs.");
293 xclose_check(&reboot, REBOOT_LOCK);
295 /* run @reboot jobs */
301 parseopt(int argc, char *argv[])
308 #ifdef HAVE_GETOPT_LONG
309 static struct option opt[] = {
310 {"debug", 0, NULL, 'd'},
311 {"foreground", 0, NULL, 'f'},
312 {"background", 0, NULL, 'b'},
313 {"nosyslog", 0, NULL, 'y'},
314 {"logfilepath", 1, NULL, 'p'},
315 {"help", 0, NULL, 'h'},
316 {"version", 0, NULL, 'V'},
317 {"once", 0, NULL, 'o'},
318 {"savetime", 1, NULL, 's'},
319 {"firstsleep", 1, NULL, 'l'},
320 {"maxserial", 1, NULL, 'm'},
321 {"configfile", 1, NULL, 'c'},
322 {"newspooldir", 1, NULL, 'n'},
323 {"queuelen", 1, NULL, 'q'},
326 #endif /* HAVE_GETOPT_LONG */
329 extern int optind, opterr, optopt;
331 /* constants and variables defined by command line */
334 #ifdef HAVE_GETOPT_LONG
335 c = getopt_long(argc, argv, "dfbyp:hVos:l:m:c:n:q:", opt, NULL);
337 c = getopt(argc, argv, "dfbyp:hVos:l:m:c:n:q:");
338 #endif /* HAVE_GETOPT_LONG */
368 logfile_path = strdup2(optarg);
377 if ((save_time = strtol(optarg, NULL, 10)) < 60
378 || save_time >= TIME_T_MAX)
379 die("Save time can only be set between 60 and %d.", TIME_T_MAX);
383 if ((first_sleep = strtol(optarg, NULL, 10)) < 0
384 || first_sleep >= TIME_T_MAX)
385 die("First sleep can only be set between 0 and %d.",
390 if ((serial_max_running = strtol(optarg, NULL, 10)) <= 0
391 || serial_max_running >= SHRT_MAX)
392 die("Max running can only be set between 1 and %d.", SHRT_MAX);
396 Set(fcronconf, optarg);
400 create_spooldir(optarg);
404 if ((lavg_queue_max = serial_queue_max =
405 strtol(optarg, NULL, 10)) < 5 || serial_queue_max >= SHRT_MAX)
406 die("Queue length can only be set between 5 and %d.", SHRT_MAX);
410 error("(parseopt) Missing parameter");
417 warn("(parseopt) Warning: getopt returned %c", c);
422 for (i = optind; i <= argc; i++)
423 error("Unknown argument \"%s\"", argv[i]);
432 create_spooldir(char *dir)
433 /* create a new spool dir for fcron : set correctly its mode and owner */
437 uid_t useruid = get_user_uid_safe(USERNAME);
438 gid_t usergid = get_group_gid_safe(GROUPNAME);
440 if (mkdir(dir, 0) != 0 && errno != EEXIST)
441 die_e("Cannot create dir %s", dir);
443 if ((dir_fd = open(dir, 0)) < 0)
444 die_e("Cannot open dir %s", dir);
446 if (fstat(dir_fd, &st) != 0) {
447 xclose_check(&dir_fd, "spooldir");
448 die_e("Cannot fstat %s", dir);
451 if (!S_ISDIR(st.st_mode)) {
452 xclose_check(&dir_fd, "spooldir");
453 die("%s exists and is not a directory", dir);
456 if (fchown(dir_fd, useruid, usergid) != 0) {
457 xclose_check(&dir_fd, "spooldir");
458 die_e("Cannot fchown dir %s to %s:%s", dir, USERNAME, GROUPNAME);
463 S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IWGRP | S_IXGRP) != 0) {
464 xclose_check(&dir_fd, "spooldir");
465 die_e("Cannot change dir %s's mode to 770", dir);
468 xclose_check(&dir_fd, "spooldir");
476 sigterm_handler(int x)
480 explain("SIGTERM signal received");
485 sighup_handler(int x)
486 /* update configuration */
488 /* we don't call the synchronize_dir() function directly,
489 * because it may cause some problems if this signal
490 * is not received during the sleep
496 sigchild_handler(int x)
497 /* call wait_chld() to take care of finished jobs */
506 sigusr1_handler(int x)
507 /* reload all configurations */
509 /* we don't call the synchronize_dir() function directly,
510 * because it may cause some problems if this signal
511 * is not received during the sleep
518 sigusr2_handler(int x)
519 /* print schedule and switch on/off debug mode */
526 main(int argc, char **argv)
528 char *codeset = NULL;
530 rootuid = get_user_uid_safe(ROOTNAME);
531 rootgid = get_group_gid_safe(ROOTGROUP);
533 /* we set it to 022 in order to get a pidfile readable by fcrontab
534 * (will be set to 066 later) */
535 saved_umask = umask(022);
539 if (strrchr(argv[0], '/') == NULL)
542 prog_name = strrchr(argv[0], '/') + 1;
546 if ((daemon_uid = getuid()) != rootuid)
547 die("Fcron must be executed as root");
550 /* we have to set daemon_pid before the fork because it's
551 * used in die() and die_e() functions */
552 daemon_pid = getpid();
554 /* save the value of the TZ env variable (used for option timezone) */
555 orig_tz_envvar = strdup2(getenv("TZ"));
557 parseopt(argc, argv);
559 /* read fcron.conf and update global parameters */
562 /* initialize the logs before we become a daemon */
565 /* change directory */
567 if (chdir(fcrontabs) != 0)
568 die_e("Could not change dir to %s", fcrontabs);
570 /* Get the default locale character set for the mail
571 * "Content-Type: ...; charset=" header */
572 setlocale(LC_ALL, ""); /* set locale to system defaults or to
573 * that specified by any LC_* env vars */
574 /* Except that "US-ASCII" is preferred to "ANSI_x3.4-1968" in MIME,
575 * even though "ANSI_x3.4-1968" is the official charset name. */
576 if ((codeset = nl_langinfo(CODESET)) != 0L &&
577 strcmp(codeset, "ANSI_x3.4-1968") != 0)
578 strncpy(default_mail_charset, codeset, sizeof(default_mail_charset));
580 strcpy(default_mail_charset, "US-ASCII");
582 if (freopen("/dev/null", "r", stdin) == NULL)
583 error_e("Could not open /dev/null as stdin");
585 if (foreground == 0) {
587 /* close stdout and stderr.
588 * close unused descriptors
589 * optional detach from controlling terminal */
594 switch (pid = fork()) {
603 /* printf("%s[%d] " VERSION_QUOTED " : started.\n", */
604 /* prog_name, pid); */
608 daemon_pid = getpid();
610 if ((fd = open("/dev/tty", O_RDWR)) >= 0) {
612 ioctl(fd, TIOCNOTTY, 0);
614 xclose_check(&fd, "/dev/tty");
617 if (freopen("/dev/null", "w", stdout) == NULL)
618 error_e("Could not open /dev/null as stdout");
619 if (freopen("/dev/null", "w", stderr) == NULL)
620 error_e("Could not open /dev/null as stderr");
622 /* close most other open fds */
624 for (fd = 3; fd < 250; fd++)
625 /* don't use xclose_check() as we do expect most of them to fail */
628 /* finally, create a new session */
630 error("Could not setsid()");
634 /* check if another fcron daemon is running, create pid file and lock it */
637 /* this program belongs to root : we set default permission mode
638 * to 600 for security reasons, but we reset them to the saved
639 * umask just before we run a job */
642 explain("%s[%d] " VERSION_QUOTED " started", prog_name, daemon_pid);
645 signal(SIGTERM, sigterm_handler);
646 signal(SIGHUP, sighup_handler);
647 siginterrupt(SIGHUP, 0);
648 signal(SIGCHLD, sigchild_handler);
649 siginterrupt(SIGCHLD, 0);
650 signal(SIGUSR1, sigusr1_handler);
651 siginterrupt(SIGUSR1, 0);
652 signal(SIGUSR2, sigusr2_handler);
653 siginterrupt(SIGUSR2, 0);
654 /* we don't want SIGPIPE to kill fcron, and don't need to handle it as when ignored
655 * write() on a pipe closed at the other end will return EPIPE */
656 signal(SIGPIPE, SIG_IGN);
658 sigset(SIGTERM, sigterm_handler);
659 sigset(SIGHUP, sighup_handler);
660 sigset(SIGCHLD, sigchild_handler);
661 sigset(SIGUSR1, sigusr1_handler);
662 sigset(SIGUSR2, sigusr2_handler);
663 sigset(SIGPIPE, SIG_IGN);
666 /* initialize job database */
669 /* initialize exe_array */
670 exe_list = exe_list_init();
672 /* initialize serial_array */
674 serial_array_index = 0;
676 serial_array_size = SERIAL_INITIAL_SIZE;
678 alloc_safe(serial_array_size * sizeof(cl_t *), "serial_array");
680 /* initialize lavg_array */
681 lavg_list = lavg_list_init();
682 lavg_list->max_entries = lavg_queue_max;
683 lavg_serial_running = 0;
686 /* initialize socket */
690 /* initialize random number generator :
691 * WARNING : easy to guess !!! */
692 /* we use the hostname and tv_usec in order to get different seeds
693 * on two different machines starting fcron at the same moment */
698 #ifdef HAVE_GETTIMEOFDAY
699 struct timeval tv; /* we use usec field to get more precision */
700 gettimeofday(&tv, NULL);
701 seed = ((unsigned int)tv.tv_usec) ^ ((unsigned int)tv.tv_sec);
703 seed = (unsigned int)time(NULL);
705 gethostname(hostname, sizeof(hostname));
707 for (i = 0; i < sizeof(hostname) - sizeof(seed); i += sizeof(seed))
708 seed ^= (unsigned int)*(hostname + i);
722 /* check if a signal has been received and handle it */
725 /* we reinstall the signal handler functions here and not directly in the handlers,
726 * as it is not supported on some systems (HP-UX) and makes fcron crash */
732 (void)signal(SIGCHLD, sigchild_handler);
733 siginterrupt(SIGCHLD, 0);
739 /* update configuration */
740 synchronize_dir(".", 0);
743 signal(SIGHUP, sighup_handler);
744 siginterrupt(SIGHUP, 0);
748 /* reload all configuration */
752 signal(SIGUSR1, sigusr1_handler);
753 siginterrupt(SIGUSR1, 0);
760 debug_opt = (debug_opt > 0) ? 0 : 1;
761 explain("debug_opt = %d", debug_opt);
764 signal(SIGUSR2, sigusr2_handler);
765 siginterrupt(SIGUSR2, 0);
773 /* main loop - get the time to sleep until next job execution,
774 * sleep, and then test all jobs and execute if needed. */
776 time_t save; /* time remaining until next save */
777 time_t nwt; /* next wake time */
778 #ifdef HAVE_GETTIMEOFDAY
779 struct timeval now_tv; /* we use usec field to get more precision */
781 #if defined(FCRONDYN) && defined(HAVE_GETTIMEOFDAY)
783 struct timeval sleep_tv; /* we use usec field to get more precision */
786 debug("Entering main loop");
790 synchronize_dir(".", is_system_startup());
792 /* synchronize save with jobs execution */
793 save = now + save_time;
795 if (serial_num > 0 || once) {
796 nwt = now + first_sleep;
799 /* force first execution after first_sleep sec : execution of jobs
800 * during system boot time is not what we want */
801 nwt = tmax(next_wake_time(save), now + first_sleep);
803 debug("initial wake time : %s", ctime(&nwt));
807 #ifdef HAVE_GETTIMEOFDAY
809 gettimeofday(&now_tv, NULL);
810 debug("now gettimeofday tv_sec=%ld, tv_usec=%ld %s", now_tv.tv_sec,
811 now_tv.tv_usec, ctime(&nwt));
813 if (nwt <= now_tv.tv_sec) {
815 sleep_tv.tv_usec = 0;
818 /* compensate for any time spent in the loop,
819 * so as we wake up exactly at the beginning of the second */
820 sleep_tv.tv_sec = nwt - now_tv.tv_sec - 1;
821 /* we set tv_usec to slightly more than necessary so as we don't wake
822 * up too early (e.g. due to rounding to the system clock granularity),
823 * in which case we would have to go back to sleep again */
824 sleep_tv.tv_usec = 1000000 + min_sleep_usec - now_tv.tv_usec;
827 if (sleep_tv.tv_usec > 999999) {
828 /* On some systems (BSD, etc), tv_usec cannot be greater than 999999 */
829 sleep_tv.tv_usec = 999999;
831 else if (sleep_tv.tv_sec == 0 && sleep_tv.tv_usec < min_sleep_usec) {
832 /* to prevent any infinite loop, sleep at least 1ms */
834 ("We'll sleep for a tiny bit to avoid any risk of infinite loop");
835 sleep_tv.tv_usec = min_sleep_usec;
837 /* note: read_set is set in socket.c */
838 debug("nwt=%s, sleep sec=%ld, usec=%ld", ctime(&nwt), sleep_tv.tv_sec,
841 select(set_max_fd + 1, &read_set, NULL, NULL, &sleep_tv)) < 0
843 die_e("select returned %d", errno);
846 sleep(nwt - now - 1);
848 gettimeofday(&now_tv, NULL);
849 /* we set tv_usec to slightly more than necessary to avoid
851 usleep(1000000 + min_sleep_usec - now_tv.tv_usec);
852 #endif /* FCRONDYN */
853 #else /* HAVE_GETTIMEOFDAY */
857 #endif /* HAVE_GETTIMEOFDAY */
861 #ifdef HAVE_GETTIMEOFDAY
863 gettimeofday(&now_tv, NULL);
864 debug("now=%ld now_tv sec=%ld usec=%ld", now, now_tv.tv_sec,
870 #ifdef HAVE_GETTIMEOFDAY
872 gettimeofday(&now_tv, NULL);
873 debug("after check_signal: now_tv sec=%ld usec=%ld", now_tv.tv_sec,
879 #ifdef HAVE_GETTIMEOFDAY
881 gettimeofday(&now_tv, NULL);
882 debug("after test_jobs: now_tv sec=%ld usec=%ld", now_tv.tv_sec,
887 while (serial_num > 0 && serial_running < serial_max_running) {
892 explain("Running with option once : exiting ... ");
897 save = now + save_time;
901 #ifdef HAVE_GETTIMEOFDAY
903 gettimeofday(&now_tv, NULL);
904 debug("after save: now_tv sec=%ld usec=%ld", now_tv.tv_sec,
909 #if defined(FCRONDYN) && defined(HAVE_GETTIMEOFDAY)
910 /* check if there's a new connection, a new command to answer, etc ... */
911 /* we do that *after* other checks, to avoid Denial Of Service attacks */
912 check_socket(retcode);
915 nwt = check_lavg(save);
916 #ifdef HAVE_GETTIMEOFDAY
918 gettimeofday(&now_tv, NULL);
919 debug("after check_lavg: now_tv sec=%ld usec=%ld", now_tv.tv_sec,
923 debug("next wake time : %s", ctime(&nwt));