2 * FCRON - periodic command scheduler
4 * Copyright 2000-2014 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"
35 #include "fcrondyn_svr.h"
40 void check_signal(void);
41 void reset_sig_cont(void);
44 void print_schedule(void);
45 RETSIGTYPE sighup_handler(int x);
46 RETSIGTYPE sigterm_handler(int x);
47 RETSIGTYPE sigchild_handler(int x);
48 RETSIGTYPE sigusr1_handler(int x);
49 RETSIGTYPE sigusr2_handler(int x);
50 RETSIGTYPE sigcont_handler(int x);
51 int parseopt(int argc, char *argv[]);
53 int is_system_reboot(void);
54 void create_spooldir(char *dir);
56 /* command line options */
59 char foreground = 1; /* set to 1 when we are on foreground, else 0 */
61 char foreground = 0; /* set to 1 when we are on foreground, else 0 */
64 time_t first_sleep = FIRST_SLEEP;
65 const time_t min_sleep_usec = 1000; /* 1ms -- we won't sleep for less than this time */
66 time_t save_time = SAVE;
67 char once = 0; /* set to 1 if fcron shall return immediately after running
68 * all jobs that are due at the time when fcron is started */
70 /* Get the default locale character set for the mail
71 * "Content-Type: ...; charset=" header */
72 char default_mail_charset[TERM_LEN] = "";
74 /* used in temp_file() : create it in current dir (normally spool dir) */
77 /* process identity */
79 mode_t saved_umask; /* default root umask */
80 char *prog_name = NULL;
81 char *orig_tz_envvar = NULL;
83 /* uid/gid of user/group root
84 * (we don't use the static UID or GID as we ask for user and group names
85 * in the configure script) */
89 /* have we got a signal ? */
90 char sig_conf = 0; /* is 1 when we got a SIGHUP, 2 for a SIGUSR1 */
91 char sig_chld = 0; /* is 1 when we got a SIGCHLD */
92 char sig_debug = 0; /* is 1 when we got a SIGUSR2 */
93 char sig_cont = 0; /* is 1 when we got a SIGCONT */
96 struct cf_t *file_base; /* point to the first file of the list */
97 struct job_t *queue_base; /* ordered list of normal jobs to be run */
98 unsigned long int next_id; /* id for next line to enter database */
100 struct cl_t **serial_array; /* ordered list of job to be run one by one */
101 short int serial_array_size; /* size of serial_array */
102 short int serial_array_index; /* the index of the first job */
103 short int serial_num; /* number of job being queued */
104 short int serial_running; /* number of running serial jobs */
106 /* do not run more than this number of serial job simultaneously */
107 short int serial_max_running = SERIAL_MAX_RUNNING;
108 short int serial_queue_max = SERIAL_QUEUE_MAX;
110 lavg_list_t *lavg_list; /* jobs waiting for a given system load value */
111 short int lavg_queue_max = LAVG_QUEUE_MAX;
112 short int lavg_serial_running; /* number of serialized lavg job being running */
114 exe_list_t *exe_list; /* jobs which are executed */
116 time_t now; /* the current time */
119 pam_handle_t *pamh = NULL;
120 const struct pam_conv apamconv = { NULL };
125 /* print some informations about this program :
126 * version, license */
129 "fcron " VERSION_QUOTED " - periodic command scheduler\n"
130 "Copyright " COPYRIGHT_QUOTED " Thibault Godouet <fcron@free.fr>\n"
131 "This program is free software distributed WITHOUT ANY WARRANTY.\n"
132 "See the GNU General Public License for more details.\n");
141 /* print a help message about command line options and exit */
143 fprintf(stderr, "\nfcron " VERSION_QUOTED "\n\n"
144 "fcron [-d] [-f] [-b]\n"
146 " -s t --savetime t Save fcrontabs on disk every t sec.\n"
147 " -l t --firstsleep t Sets the initial delay before any job is executed"
148 ",\n default to %d seconds.\n"
149 " -m n --maxserial n Set to n the max number of running serial jobs.\n"
150 " -c f --configfile f Make fcron use config file f.\n"
151 " -n d --newspooldir d Create d as a new spool directory.\n"
152 " -f --foreground Stay in foreground.\n"
153 " -b --background Go to background.\n"
154 " -y --nosyslog Don't log to syslog at all.\n"
155 " -p --logfilepath If set, log to the file given as argument.\n"
156 " -o --once Execute all jobs that need to be run, wait for "
157 "them,\n then return. Sets firstsleep to 0.\n"
158 " Especially useful with -f and -y.\n"
159 " -d --debug Set Debug mode.\n"
160 " -h --help Show this help message.\n"
161 " -V --version Display version & infos about fcron.\n",
170 /* print the current schedule on syslog */
176 explain("Printing schedule ...");
177 for (cf = file_base; cf; cf = cf->cf_next) {
178 explain(" File %s", cf->cf_user);
179 for (cl = cf->cf_line_base; cl; cl = cl->cl_next) {
180 ftime = localtime(&(cl->cl_nextexe));
181 explain(" cmd '%s' next exec %04d-%02d-%02d wday:%d %02d:%02d"
183 cl->cl_shell, (ftime->tm_year + 1900), (ftime->tm_mon + 1),
184 ftime->tm_mday, ftime->tm_wday, ftime->tm_hour,
189 explain("... end of printing schedule.");
194 xexit(int exit_value)
195 /* exit after having freed memory and removed lock file */
201 /* we save all files now and after having waiting for all
202 * job being executed because we might get a SIGKILL
203 * if we don't exit quickly */
207 fcrondyn_socket_close(NULL);
212 if (f->cf_running > 0) {
214 debug("waiting jobs for %s ...", f->cf_user);
216 wait_all(&f->cf_running);
219 delete_file(f->cf_user);
221 /* delete_file remove the f file from the list :
222 * next file to remove is now pointed by file_base. */
228 exe_list_destroy(exe_list);
229 lavg_list_destroy(lavg_list);
232 Free_safe(orig_tz_envvar);
234 explain("Exiting with code %d", exit_value);
241 /* check if another fcron daemon is running with the same config (-c option) :
242 * in this case, die. if not, write our pid to /var/run/fcron.pid in order to lock,
243 * and to permit fcrontab to read our pid and signal us */
246 FILE *daemon_lockfp = NULL;
249 if (((fd = open(pidfile, O_RDWR | O_CREAT, 0644)) == -1)
250 || ((daemon_lockfp = fdopen(fd, "r+"))) == NULL)
251 die_e("can't open or create %s", pidfile);
254 if (flock(fd, LOCK_EX | LOCK_NB) != 0)
255 #else /* HAVE_FLOCK */
256 if (lockf(fileno(daemon_lockfp), F_TLOCK, 0) != 0)
257 #endif /* ! HAVE_FLOCK */
259 if (fscanf(daemon_lockfp, "%d", &otherpid) >= 1)
260 die_e("can't lock %s, running daemon's pid may be %d", pidfile,
263 die_e("can't lock %s, and unable to read running"
264 " daemon's pid", pidfile);
267 fcntl(fd, F_SETFD, 1);
269 rewind(daemon_lockfp);
270 fprintf(daemon_lockfp, "%d\n", (int)daemon_pid);
271 fflush(daemon_lockfp);
272 if (ftruncate(fileno(daemon_lockfp), ftell(daemon_lockfp)) < 0)
274 ("Unable to ftruncate(fileno(daemon_lockfp), ftell(daemon_lockfp))");
276 /* abandon fd and daemon_lockfp even though the file is open. we need to
277 * keep it open and locked, but we don't need the handles elsewhere.
283 is_system_startup(void)
287 /* lock exist - skip reboot jobs */
288 if (access(REBOOT_LOCK, F_OK) == 0) {
289 explain("@reboot jobs will only be run at computer's startup.");
290 /* don't run @reboot jobs */
293 /* lock doesn't exist - create lock, run reboot jobs */
294 if ((reboot = creat(REBOOT_LOCK, S_IRUSR & S_IWUSR)) < 0)
295 error_e("Can't create lock for reboot jobs.");
297 xclose_check(&reboot, REBOOT_LOCK);
299 /* run @reboot jobs */
305 parseopt(int argc, char *argv[])
312 #ifdef HAVE_GETOPT_LONG
313 static struct option opt[] = {
314 {"debug", 0, NULL, 'd'},
315 {"foreground", 0, NULL, 'f'},
316 {"background", 0, NULL, 'b'},
317 {"nosyslog", 0, NULL, 'y'},
318 {"logfilepath", 1, NULL, 'p'},
319 {"help", 0, NULL, 'h'},
320 {"version", 0, NULL, 'V'},
321 {"once", 0, NULL, 'o'},
322 {"savetime", 1, NULL, 's'},
323 {"firstsleep", 1, NULL, 'l'},
324 {"maxserial", 1, NULL, 'm'},
325 {"configfile", 1, NULL, 'c'},
326 {"newspooldir", 1, NULL, 'n'},
327 {"queuelen", 1, NULL, 'q'},
330 #endif /* HAVE_GETOPT_LONG */
333 extern int optind, opterr, optopt;
335 /* constants and variables defined by command line */
338 #ifdef HAVE_GETOPT_LONG
339 c = getopt_long(argc, argv, "dfbyp:hVos:l:m:c:n:q:", opt, NULL);
341 c = getopt(argc, argv, "dfbyp:hVos:l:m:c:n:q:");
342 #endif /* HAVE_GETOPT_LONG */
372 logfile_path = strdup2(optarg);
381 if ((save_time = strtol(optarg, NULL, 10)) < 60
382 || save_time >= TIME_T_MAX)
383 die("Save time can only be set between 60 and %d.", TIME_T_MAX);
387 if ((first_sleep = strtol(optarg, NULL, 10)) < 0
388 || first_sleep >= TIME_T_MAX)
389 die("First sleep can only be set between 0 and %d.",
394 if ((serial_max_running = strtol(optarg, NULL, 10)) <= 0
395 || serial_max_running >= SHRT_MAX)
396 die("Max running can only be set between 1 and %d.", SHRT_MAX);
400 Set(fcronconf, optarg);
404 create_spooldir(optarg);
408 if ((lavg_queue_max = serial_queue_max =
409 strtol(optarg, NULL, 10)) < 5 || serial_queue_max >= SHRT_MAX)
410 die("Queue length can only be set between 5 and %d.", SHRT_MAX);
414 error("(parseopt) Missing parameter");
421 warn("(parseopt) Warning: getopt returned %c", c);
426 for (i = optind; i <= argc; i++)
427 error("Unknown argument \"%s\"", argv[i]);
436 create_spooldir(char *dir)
437 /* create a new spool dir for fcron : set correctly its mode and owner */
441 uid_t useruid = get_user_uid_safe(USERNAME);
442 gid_t usergid = get_group_gid_safe(GROUPNAME);
444 if (mkdir(dir, 0) != 0 && errno != EEXIST)
445 die_e("Cannot create dir %s", dir);
447 if ((dir_fd = open(dir, 0)) < 0)
448 die_e("Cannot open dir %s", dir);
450 if (fstat(dir_fd, &st) != 0) {
451 xclose_check(&dir_fd, "spooldir");
452 die_e("Cannot fstat %s", dir);
455 if (!S_ISDIR(st.st_mode)) {
456 xclose_check(&dir_fd, "spooldir");
457 die("%s exists and is not a directory", dir);
460 if (fchown(dir_fd, useruid, usergid) != 0) {
461 xclose_check(&dir_fd, "spooldir");
462 die_e("Cannot fchown dir %s to %s:%s", dir, USERNAME, GROUPNAME);
467 S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IWGRP | S_IXGRP) != 0) {
468 xclose_check(&dir_fd, "spooldir");
469 die_e("Cannot change dir %s's mode to 770", dir);
472 xclose_check(&dir_fd, "spooldir");
480 sigterm_handler(int x)
484 explain("SIGTERM signal received");
489 sighup_handler(int x)
490 /* update configuration */
492 /* we don't call the synchronize_dir() function directly,
493 * because it may cause some problems if this signal
494 * is not received during the sleep
500 sigchild_handler(int x)
501 /* call wait_chld() to take care of finished jobs */
510 sigusr1_handler(int x)
511 /* reload all configurations */
513 /* we don't call the synchronize_dir() function directly,
514 * because it may cause some problems if this signal
515 * is not received during the sleep
522 sigusr2_handler(int x)
523 /* print schedule and switch on/off debug mode */
529 sigcont_handler(int x)
530 /* used to notify fcron of a system resume after suspend.
531 * However this signal could also be received in other cases. */
537 main(int argc, char **argv)
539 char *codeset = NULL;
541 rootuid = get_user_uid_safe(ROOTNAME);
542 rootgid = get_group_gid_safe(ROOTGROUP);
544 /* we set it to 022 in order to get a pidfile readable by fcrontab
545 * (will be set to 066 later) */
546 saved_umask = umask(022);
550 if (strrchr(argv[0], '/') == NULL)
553 prog_name = strrchr(argv[0], '/') + 1;
557 if ((daemon_uid = getuid()) != rootuid)
558 die("Fcron must be executed as root");
561 /* we have to set daemon_pid before the fork because it's
562 * used in die() and die_e() functions */
563 daemon_pid = getpid();
565 /* save the value of the TZ env variable (used for option timezone) */
566 orig_tz_envvar = strdup2(getenv("TZ"));
568 parseopt(argc, argv);
570 /* read fcron.conf and update global parameters */
573 /* initialize the logs before we become a daemon */
576 /* change directory */
578 if (chdir(fcrontabs) != 0)
579 die_e("Could not change dir to %s", fcrontabs);
581 /* Get the default locale character set for the mail
582 * "Content-Type: ...; charset=" header */
583 setlocale(LC_ALL, ""); /* set locale to system defaults or to
584 * that specified by any LC_* env vars */
585 /* Except that "US-ASCII" is preferred to "ANSI_x3.4-1968" in MIME,
586 * even though "ANSI_x3.4-1968" is the official charset name. */
587 if ((codeset = nl_langinfo(CODESET)) != 0L &&
588 strcmp(codeset, "ANSI_x3.4-1968") != 0)
589 strncpy(default_mail_charset, codeset, sizeof(default_mail_charset));
591 strcpy(default_mail_charset, "US-ASCII");
593 if (freopen("/dev/null", "r", stdin) == NULL)
594 error_e("Could not open /dev/null as stdin");
596 if (foreground == 0) {
598 /* close stdout and stderr.
599 * close unused descriptors
600 * optional detach from controlling terminal */
605 switch (pid = fork()) {
614 /* printf("%s[%d] " VERSION_QUOTED " : started.\n", */
615 /* prog_name, pid); */
619 daemon_pid = getpid();
621 if ((fd = open("/dev/tty", O_RDWR)) >= 0) {
623 ioctl(fd, TIOCNOTTY, 0);
625 xclose_check(&fd, "/dev/tty");
628 if (freopen("/dev/null", "w", stdout) == NULL)
629 error_e("Could not open /dev/null as stdout");
630 if (freopen("/dev/null", "w", stderr) == NULL)
631 error_e("Could not open /dev/null as stderr");
633 /* close most other open fds */
635 for (fd = 3; fd < 250; fd++)
636 /* don't use xclose_check() as we do expect most of them to fail */
639 /* finally, create a new session */
641 error("Could not setsid()");
645 /* check if another fcron daemon is running, create pid file and lock it */
648 /* this program belongs to root : we set default permission mode
649 * to 600 for security reasons, but we reset them to the saved
650 * umask just before we run a job */
653 explain("%s[%d] " VERSION_QUOTED " started", prog_name, daemon_pid);
656 /* FIXME: check for errors */
657 signal(SIGTERM, sigterm_handler);
658 signal(SIGHUP, sighup_handler);
659 siginterrupt(SIGHUP, 0);
660 signal(SIGCHLD, sigchild_handler);
661 siginterrupt(SIGCHLD, 0);
662 signal(SIGUSR1, sigusr1_handler);
663 siginterrupt(SIGUSR1, 0);
664 signal(SIGUSR2, sigusr2_handler);
665 siginterrupt(SIGUSR2, 0);
666 signal(SIGCONT, sigcont_handler);
667 siginterrupt(SIGCONT, 0);
668 /* we don't want SIGPIPE to kill fcron, and don't need to handle it as when ignored
669 * write() on a pipe closed at the other end will return EPIPE */
670 signal(SIGPIPE, SIG_IGN);
672 /* FIXME: check for errors */
673 sigset(SIGTERM, sigterm_handler);
674 sigset(SIGHUP, sighup_handler);
675 sigset(SIGCHLD, sigchild_handler);
676 sigset(SIGUSR1, sigusr1_handler);
677 sigset(SIGUSR2, sigusr2_handler);
678 sigset(SIGCONT, sigcont_handler);
679 sigset(SIGPIPE, SIG_IGN);
682 /* initialize job database */
685 /* initialize exe_array */
686 exe_list = exe_list_init();
688 /* initialize serial_array */
690 serial_array_index = 0;
692 serial_array_size = SERIAL_INITIAL_SIZE;
694 alloc_safe(serial_array_size * sizeof(cl_t *), "serial_array");
696 /* initialize lavg_array */
697 lavg_list = lavg_list_init();
698 lavg_list->max_entries = lavg_queue_max;
699 lavg_serial_running = 0;
701 /* initialize random number generator :
702 * WARNING : easy to guess !!! */
703 /* we use the hostname and tv_usec in order to get different seeds
704 * on two different machines starting fcron at the same moment */
709 #ifdef HAVE_GETTIMEOFDAY
710 struct timeval tv; /* we use usec field to get more precision */
711 gettimeofday(&tv, NULL);
712 seed = ((unsigned int)tv.tv_usec) ^ ((unsigned int)tv.tv_sec);
714 seed = (unsigned int)time(NULL);
716 gethostname(hostname, sizeof(hostname));
718 for (i = 0; i < sizeof(hostname) - sizeof(seed); i += sizeof(seed))
719 seed ^= (unsigned int)*(hostname + i);
733 /* check if a signal has been received and handle it */
736 /* we reinstall the signal handler functions here and not directly in the handlers,
737 * as it is not supported on some systems (HP-UX) and makes fcron crash */
743 (void)signal(SIGCHLD, sigchild_handler);
744 siginterrupt(SIGCHLD, 0);
751 /* update configuration */
752 synchronize_dir(".", 0);
755 signal(SIGHUP, sighup_handler);
756 siginterrupt(SIGHUP, 0);
760 /* reload all configuration */
764 signal(SIGUSR1, sigusr1_handler);
765 siginterrupt(SIGUSR1, 0);
773 debug_opt = (debug_opt > 0) ? 0 : 1;
774 explain("debug_opt = %d", debug_opt);
777 signal(SIGUSR2, sigusr2_handler);
778 siginterrupt(SIGUSR2, 0);
786 /* reinitialize the SIGCONT handler if it was raised */
792 signal(SIGCONT, sigcont_handler);
793 siginterrupt(SIGCONT, 0);
798 #ifdef HAVE_GETTIMEOFDAY
799 #define debug_print_tstamp(where_str) \
802 gettimeofday(&now_tv, NULL); \
803 debug(where_str ": now=%ld now_tv sec=%ld usec=%ld", now, now_tv.tv_sec, \
808 #define debug_print_tstamp(where_str) { ; }
813 /* main loop - get the time to sleep until next job execution,
814 * sleep, and then test all jobs and execute if needed. */
816 time_t save; /* time remaining until next save */
817 time_t slept_from; /* time it was when we went into sleep */
818 time_t nwt; /* next wake time */
819 #ifdef HAVE_GETTIMEOFDAY
820 struct select_instance main_select;
821 struct timeval now_tv; /* we use usec field to get more precision */
822 struct timeval sleep_tv; /* we use usec field to get more precision */
825 debug("Entering main loop");
827 #ifdef HAVE_GETTIMEOFDAY
828 select_init(&main_select);
830 fcrondyn_socket_init(&main_select);
836 synchronize_dir(".", is_system_startup());
838 /* synchronize save with jobs execution */
839 save = now + save_time;
841 if (serial_num > 0 || once) {
842 nwt = now + first_sleep;
845 /* force first execution after first_sleep sec : execution of jobs
846 * during system boot time is not what we want */
847 nwt = tmax(next_wake_time(save), now + first_sleep);
849 debug("initial wake time : %s", ctime(&nwt));
853 /* remember when we started to sleep -- this is to detect suspend/hibernate */
854 slept_from = time(NULL);
856 #ifdef HAVE_GETTIMEOFDAY
857 gettimeofday(&now_tv, NULL);
858 debug("now gettimeofday tv_sec=%ld, tv_usec=%ld %s", now_tv.tv_sec,
859 now_tv.tv_usec, ctime(&nwt));
861 if (nwt <= now_tv.tv_sec) {
863 sleep_tv.tv_usec = 0;
866 /* compensate for any time spent in the loop,
867 * so as we wake up exactly at the beginning of the second */
868 sleep_tv.tv_sec = nwt - now_tv.tv_sec - 1;
869 /* we set tv_usec to slightly more than necessary so as we don't wake
870 * up too early (e.g. due to rounding to the system clock granularity),
871 * in which case we would have to go back to sleep again */
872 sleep_tv.tv_usec = 1000000 + min_sleep_usec - now_tv.tv_usec;
875 if (sleep_tv.tv_usec > 999999) {
876 /* On some systems (BSD, etc), tv_usec cannot be greater than 999999 */
877 sleep_tv.tv_usec = 999999;
879 else if (sleep_tv.tv_sec == 0 && sleep_tv.tv_usec < min_sleep_usec) {
880 /* to prevent any infinite loop, sleep at least 1ms */
882 ("We'll sleep for a tiny bit to avoid any risk of infinite loop");
883 sleep_tv.tv_usec = min_sleep_usec;
885 debug("nwt=%s, sleep sec=%ld, usec=%ld", ctime(&nwt), sleep_tv.tv_sec,
887 select_call(&main_select, &sleep_tv);
888 #else /* HAVE_GETTIMEOFDAY */
892 #endif /* HAVE_GETTIMEOFDAY */
896 debug_print_tstamp("just woke up")
899 check_suspend(slept_from, nwt, &sig_cont);
901 debug_print_tstamp("after check_signal and suspend")
904 debug_print_tstamp("after test_jobs")
906 while (serial_num > 0 && serial_running < serial_max_running) {
911 explain("Running with option once : exiting ... ");
916 save = now + save_time;
920 debug_print_tstamp("after save")
921 #if defined(FCRONDYN) && defined(HAVE_GETTIMEOFDAY)
922 /* check if there's a new connection, a new command to answer, etc ... */
923 /* we do that *after* other checks, to avoid Denial Of Service attacks */
924 fcrondyn_socket_check(&main_select);
927 nwt = check_lavg(save);
928 debug_print_tstamp("after check_lavg")
929 debug("next wake time : %s", ctime(&nwt));