2 * FCRON - periodic command scheduler
4 * Copyright 2000-2012 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 time_t save_time = SAVE;
62 char once = 0; /* set to 1 if fcron shall return immediately after running
63 * all jobs that are due at the time when fcron is started */
65 /* Get the default locale character set for the mail
66 * "Content-Type: ...; charset=" header */
67 char default_mail_charset[TERM_LEN] = "";
69 /* used in temp_file() : create it in current dir (normally spool dir) */
72 /* process identity */
74 mode_t saved_umask; /* default root umask */
75 char *prog_name = NULL;
76 char *orig_tz_envvar = NULL;
78 /* uid/gid of user/group root
79 * (we don't use the static UID or GID as we ask for user and group names
80 * in the configure script) */
84 /* have we got a signal ? */
85 char sig_conf = 0; /* is 1 when we got a SIGHUP, 2 for a SIGUSR1 */
86 char sig_chld = 0; /* is 1 when we got a SIGCHLD */
87 char sig_debug = 0; /* is 1 when we got a SIGUSR2 */
90 struct cf_t *file_base; /* point to the first file of the list */
91 struct job_t *queue_base; /* ordered list of normal jobs to be run */
92 unsigned long int next_id; /* id for next line to enter database */
94 struct cl_t **serial_array; /* ordered list of job to be run one by one */
95 short int serial_array_size; /* size of serial_array */
96 short int serial_array_index; /* the index of the first job */
97 short int serial_num; /* number of job being queued */
98 short int serial_running; /* number of running serial jobs */
100 /* do not run more than this number of serial job simultaneously */
101 short int serial_max_running = SERIAL_MAX_RUNNING;
102 short int serial_queue_max = SERIAL_QUEUE_MAX;
104 lavg_list_t *lavg_list; /* jobs waiting for a given system load value */
105 short int lavg_queue_max = LAVG_QUEUE_MAX;
106 short int lavg_serial_running; /* number of serialized lavg job being running */
108 exe_list_t *exe_list; /* jobs which are executed */
110 time_t begin_sleep; /* the time at which sleep began */
111 time_t now; /* the current time */
114 pam_handle_t *pamh = NULL;
115 const struct pam_conv apamconv = { NULL };
120 /* print some informations about this program :
121 * version, license */
124 "fcron " VERSION_QUOTED " - periodic command scheduler\n"
125 "Copyright " COPYRIGHT_QUOTED " Thibault Godouet <fcron@free.fr>\n"
126 "This program is free software distributed WITHOUT ANY WARRANTY.\n"
127 "See the GNU General Public License for more details.\n");
136 /* print a help message about command line options and exit */
138 fprintf(stderr, "\nfcron " VERSION_QUOTED "\n\n"
139 "fcron [-d] [-f] [-b]\n"
141 " -s t --savetime t Save fcrontabs on disk every t sec.\n"
142 " -l t --firstsleep t Sets the initial delay before any job is executed"
143 ",\n default to %d seconds.\n"
144 " -m n --maxserial n Set to n the max number of running serial jobs.\n"
145 " -c f --configfile f Make fcron use config file f.\n"
146 " -n d --newspooldir d Create d as a new spool directory.\n"
147 " -f --foreground Stay in foreground.\n"
148 " -b --background Go to background.\n"
149 " -y --nosyslog Don't log to syslog at all.\n"
150 " -p --logfilepath If set, log to the file given as argument.\n"
151 " -o --once Execute all jobs that need to be run, wait for "
152 "them,\n then return. Sets firstsleep to 0.\n"
153 " Especially useful with -f and -y.\n"
154 " -d --debug Set Debug mode.\n"
155 " -h --help Show this help message.\n"
156 " -V --version Display version & infos about fcron.\n",
165 /* print the current schedule on syslog */
171 explain("Printing schedule ...");
172 for (cf = file_base; cf; cf = cf->cf_next) {
173 explain(" File %s", cf->cf_user);
174 for (cl = cf->cf_line_base; cl; cl = cl->cl_next) {
175 ftime = localtime(&(cl->cl_nextexe));
176 explain(" cmd %s next exec %d/%d/%d wday:%d %02d:%02d"
178 cl->cl_shell, (ftime->tm_mon + 1), ftime->tm_mday,
179 (ftime->tm_year + 1900), ftime->tm_wday,
180 ftime->tm_hour, ftime->tm_min);
184 explain("... end of printing schedule.");
189 xexit(int exit_value)
190 /* exit after having freed memory and removed lock file */
196 /* we save all files now and after having waiting for all
197 * job being executed because we might get a SIGKILL
198 * if we don't exit quickly */
207 if (f->cf_running > 0) {
209 debug("waiting jobs for %s ...", f->cf_user);
211 wait_all(&f->cf_running);
214 delete_file(f->cf_user);
216 /* delete_file remove the f file from the list :
217 * next file to remove is now pointed by file_base. */
223 exe_list_destroy(exe_list);
224 lavg_list_destroy(lavg_list);
227 Free_safe(orig_tz_envvar);
229 explain("Exiting with code %d", exit_value);
236 /* check if another fcron daemon is running with the same config (-c option) :
237 * in this case, die. if not, write our pid to /var/run/fcron.pid in order to lock,
238 * and to permit fcrontab to read our pid and signal us */
241 FILE *daemon_lockfp = NULL;
244 if (((fd = open(pidfile, O_RDWR | O_CREAT, 0644)) == -1)
245 || ((daemon_lockfp = fdopen(fd, "r+"))) == NULL)
246 die_e("can't open or create %s", pidfile);
249 if (flock(fd, LOCK_EX | LOCK_NB) != 0)
250 #else /* HAVE_FLOCK */
251 if (lockf(fileno(daemon_lockfp), F_TLOCK, 0) != 0)
252 #endif /* ! HAVE_FLOCK */
254 if (fscanf(daemon_lockfp, "%d", &otherpid) >= 1)
255 die_e("can't lock %s, running daemon's pid may be %d", pidfile,
258 die_e("can't lock %s, and unable to read running"
259 " daemon's pid", pidfile);
262 fcntl(fd, F_SETFD, 1);
264 rewind(daemon_lockfp);
265 fprintf(daemon_lockfp, "%d\n", (int)daemon_pid);
266 fflush(daemon_lockfp);
267 if (ftruncate(fileno(daemon_lockfp), ftell(daemon_lockfp)) < 0)
269 ("Unable to ftruncate(fileno(daemon_lockfp), ftell(daemon_lockfp))");
271 /* abandon fd and daemon_lockfp even though the file is open. we need to
272 * keep it open and locked, but we don't need the handles elsewhere.
278 is_system_startup(void)
282 /* lock exist - skip reboot jobs */
283 if (access(REBOOT_LOCK, F_OK) == 0) {
284 explain("@reboot jobs will only be run at computer's startup.");
285 /* don't run @reboot jobs */
288 /* lock doesn't exist - create lock, run reboot jobs */
289 if ((reboot = creat(REBOOT_LOCK, S_IRUSR & S_IWUSR)) < 0)
290 error_e("Can't create lock for reboot jobs.");
292 xclose_check(&reboot, REBOOT_LOCK);
294 /* run @reboot jobs */
300 parseopt(int argc, char *argv[])
307 #ifdef HAVE_GETOPT_LONG
308 static struct option opt[] = {
309 {"debug", 0, NULL, 'd'},
310 {"foreground", 0, NULL, 'f'},
311 {"background", 0, NULL, 'b'},
312 {"nosyslog", 0, NULL, 'y'},
313 {"logfilepath", 1, NULL, 'p'},
314 {"help", 0, NULL, 'h'},
315 {"version", 0, NULL, 'V'},
316 {"once", 0, NULL, 'o'},
317 {"savetime", 1, NULL, 's'},
318 {"firstsleep", 1, NULL, 'l'},
319 {"maxserial", 1, NULL, 'm'},
320 {"configfile", 1, NULL, 'c'},
321 {"newspooldir", 1, NULL, 'n'},
322 {"queuelen", 1, NULL, 'q'},
325 #endif /* HAVE_GETOPT_LONG */
328 extern int optind, opterr, optopt;
330 /* constants and variables defined by command line */
333 #ifdef HAVE_GETOPT_LONG
334 c = getopt_long(argc, argv, "dfbyp:hVos:l:m:c:n:q:", opt, NULL);
336 c = getopt(argc, argv, "dfbyp:hVos:l:m:c:n:q:");
337 #endif /* HAVE_GETOPT_LONG */
367 logfile_path = strdup2(optarg);
376 if ((save_time = strtol(optarg, NULL, 10)) < 60
377 || save_time >= LONG_MAX)
378 die("Save time can only be set between 60 and %d.", LONG_MAX);
382 if ((first_sleep = strtol(optarg, NULL, 10)) < 0
383 || first_sleep >= LONG_MAX)
384 die("First sleep can only be set between 0 and %d.", LONG_MAX);
388 if ((serial_max_running = strtol(optarg, NULL, 10)) <= 0
389 || serial_max_running >= SHRT_MAX)
390 die("Max running can only be set between 1 and %d.", SHRT_MAX);
394 Set(fcronconf, optarg);
398 create_spooldir(optarg);
402 if ((lavg_queue_max = serial_queue_max =
403 strtol(optarg, NULL, 10)) < 5 || serial_queue_max >= SHRT_MAX)
404 die("Queue length can only be set between 5 and %d.", SHRT_MAX);
408 error("(parseopt) Missing parameter");
415 warn("(parseopt) Warning: getopt returned %c", c);
420 for (i = optind; i <= argc; i++)
421 error("Unknown argument \"%s\"", argv[i]);
430 create_spooldir(char *dir)
431 /* create a new spool dir for fcron : set correctly its mode and owner */
435 uid_t useruid = get_user_uid_safe(USERNAME);
436 gid_t usergid = get_group_gid_safe(GROUPNAME);
438 if (mkdir(dir, 0) != 0 && errno != EEXIST)
439 die_e("Cannot create dir %s", dir);
441 if ((dir_fd = open(dir, 0)) < 0)
442 die_e("Cannot open dir %s", dir);
444 if (fstat(dir_fd, &st) != 0) {
445 xclose_check(&dir_fd, "spooldir");
446 die_e("Cannot fstat %s", dir);
449 if (!S_ISDIR(st.st_mode)) {
450 xclose_check(&dir_fd, "spooldir");
451 die("%s exists and is not a directory", dir);
454 if (fchown(dir_fd, useruid, usergid) != 0) {
455 xclose_check(&dir_fd, "spooldir");
456 die_e("Cannot fchown dir %s to %s:%s", dir, USERNAME, GROUPNAME);
461 S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IWGRP | S_IXGRP) != 0) {
462 xclose_check(&dir_fd, "spooldir");
463 die_e("Cannot change dir %s's mode to 770", dir);
466 xclose_check(&dir_fd, "spooldir");
474 sigterm_handler(int x)
478 explain("SIGTERM signal received");
483 sighup_handler(int x)
484 /* update configuration */
486 /* we don't call the synchronize_dir() function directly,
487 * because it may cause some problems if this signal
488 * is not received during the sleep
494 sigchild_handler(int x)
495 /* call wait_chld() to take care of finished jobs */
504 sigusr1_handler(int x)
505 /* reload all configurations */
507 /* we don't call the synchronize_dir() function directly,
508 * because it may cause some problems if this signal
509 * is not received during the sleep
516 sigusr2_handler(int x)
517 /* print schedule and switch on/off debug mode */
524 main(int argc, char **argv)
526 char *codeset = NULL;
528 rootuid = get_user_uid_safe(ROOTNAME);
529 rootgid = get_group_gid_safe(ROOTGROUP);
531 /* we set it to 022 in order to get a pidfile readable by fcrontab
532 * (will be set to 066 later) */
533 saved_umask = umask(022);
537 if (strrchr(argv[0], '/') == NULL)
540 prog_name = strrchr(argv[0], '/') + 1;
544 if ((daemon_uid = getuid()) != rootuid)
545 die("Fcron must be executed as root");
548 /* we have to set daemon_pid before the fork because it's
549 * used in die() and die_e() functions */
550 daemon_pid = getpid();
552 /* save the value of the TZ env variable (used for option timezone) */
553 orig_tz_envvar = strdup2(getenv("TZ"));
555 parseopt(argc, argv);
557 /* read fcron.conf and update global parameters */
560 /* initialize the logs before we become a daemon */
563 /* change directory */
565 if (chdir(fcrontabs) != 0)
566 die_e("Could not change dir to %s", fcrontabs);
568 /* Get the default locale character set for the mail
569 * "Content-Type: ...; charset=" header */
570 setlocale(LC_ALL, ""); /* set locale to system defaults or to
571 * that specified by any LC_* env vars */
572 /* Except that "US-ASCII" is preferred to "ANSI_x3.4-1968" in MIME,
573 * even though "ANSI_x3.4-1968" is the official charset name. */
574 if ((codeset = nl_langinfo(CODESET)) != 0L &&
575 strcmp(codeset, "ANSI_x3.4-1968") != 0)
576 strncpy(default_mail_charset, codeset, sizeof(default_mail_charset));
578 strcpy(default_mail_charset, "US-ASCII");
580 if (freopen("/dev/null", "r", stdin) == NULL)
581 error_e("Could not open /dev/null as stdin");
583 if (foreground == 0) {
585 /* close stdout and stderr.
586 * close unused descriptors
587 * optional detach from controlling terminal */
592 switch (pid = fork()) {
601 /* printf("%s[%d] " VERSION_QUOTED " : started.\n", */
602 /* prog_name, pid); */
606 daemon_pid = getpid();
608 if ((fd = open("/dev/tty", O_RDWR)) >= 0) {
610 ioctl(fd, TIOCNOTTY, 0);
612 xclose_check(&fd, "/dev/tty");
615 if (freopen("/dev/null", "w", stdout) == NULL)
616 error_e("Could not open /dev/null as stdout");
617 if (freopen("/dev/null", "w", stderr) == NULL)
618 error_e("Could not open /dev/null as stderr");
620 /* close most other open fds */
622 for (fd = 3; fd < 250; fd++)
623 /* don't use xclose_check() as we do expect most of them to fail */
626 /* finally, create a new session */
628 error("Could not setsid()");
632 /* check if another fcron daemon is running, create pid file and lock it */
635 /* this program belongs to root : we set default permission mode
636 * to 600 for security reasons, but we reset them to the saved
637 * umask just before we run a job */
640 explain("%s[%d] " VERSION_QUOTED " started", prog_name, daemon_pid);
643 signal(SIGTERM, sigterm_handler);
644 signal(SIGHUP, sighup_handler);
645 siginterrupt(SIGHUP, 0);
646 signal(SIGCHLD, sigchild_handler);
647 siginterrupt(SIGCHLD, 0);
648 signal(SIGUSR1, sigusr1_handler);
649 siginterrupt(SIGUSR1, 0);
650 signal(SIGUSR2, sigusr2_handler);
651 siginterrupt(SIGUSR2, 0);
652 /* we don't want SIGPIPE to kill fcron, and don't need to handle it */
653 signal(SIGPIPE, SIG_IGN);
655 sigset(SIGTERM, sigterm_handler);
656 sigset(SIGHUP, sighup_handler);
657 sigset(SIGCHLD, sigchild_handler);
658 sigset(SIGUSR1, sigusr1_handler);
659 sigset(SIGUSR2, sigusr2_handler);
660 sigset(SIGPIPE, SIG_IGN);
663 /* initialize job database */
666 /* initialize exe_array */
667 exe_list = exe_list_init();
669 /* initialize serial_array */
671 serial_array_index = 0;
673 serial_array_size = SERIAL_INITIAL_SIZE;
675 alloc_safe(serial_array_size * sizeof(cl_t *), "serial_array");
677 /* initialize lavg_array */
678 lavg_list = lavg_list_init();
679 lavg_list->max_entries = lavg_queue_max;
680 lavg_serial_running = 0;
683 /* initialize socket */
687 /* initialize random number generator :
688 * WARNING : easy to guess !!! */
689 /* we use the hostname and tv_usec in order to get different seeds
690 * on two different machines starting fcron at the same moment */
695 #ifdef HAVE_GETTIMEOFDAY
696 struct timeval tv; /* we use usec field to get more precision */
697 gettimeofday(&tv, NULL);
698 seed = ((unsigned int)tv.tv_usec) ^ ((unsigned int)tv.tv_sec);
700 seed = (unsigned int)time(NULL);
702 gethostname(hostname, sizeof(hostname));
704 for (i = 0; i < sizeof(hostname) - sizeof(seed); i += sizeof(seed))
705 seed ^= (unsigned int)*(hostname + i);
719 /* check if a signal has been received and handle it */
722 /* we reinstall the signal handler functions here and not directly in the handlers,
723 * as it is not supported on some systems (HP-UX) and makes fcron crash */
729 (void)signal(SIGCHLD, sigchild_handler);
730 siginterrupt(SIGCHLD, 0);
736 /* update configuration */
737 synchronize_dir(".", 0);
740 signal(SIGHUP, sighup_handler);
741 siginterrupt(SIGHUP, 0);
745 /* reload all configuration */
749 signal(SIGUSR1, sigusr1_handler);
750 siginterrupt(SIGUSR1, 0);
757 debug_opt = (debug_opt > 0) ? 0 : 1;
758 explain("debug_opt = %d", debug_opt);
761 signal(SIGUSR2, sigusr2_handler);
762 siginterrupt(SIGUSR2, 0);
770 /* main loop - get the time to sleep until next job execution,
771 * sleep, and then test all jobs and execute if needed. */
773 time_t save; /* time remaining until next save */
774 time_t stime; /* time to sleep until next job
776 #ifdef HAVE_GETTIMEOFDAY
777 struct timeval tv; /* we use usec field to get more precision */
783 debug("Entering main loop");
787 synchronize_dir(".", is_system_startup());
789 /* synchronize save with jobs execution */
790 save = now + save_time;
792 if (serial_num > 0 || once)
794 else if ((stime = time_to_sleep(save)) < first_sleep)
795 /* force first execution after first_sleep sec : execution of jobs
796 * during system boot time is not what we want */
798 debug("initial sleep time : %ld", stime);
802 #ifdef HAVE_GETTIMEOFDAY
804 gettimeofday(&tv, NULL);
805 tv.tv_sec = (stime > 1) ? stime - 1 : 0;
806 /* we set tv_usec to slightly more than necessary so as
807 * we don't wake up too early, in which case we would
808 * have to sleep again for some time */
809 tv.tv_usec = 1001000 - tv.tv_usec;
810 /* On some systems (BSD, etc), tv_usec cannot be greater than 999999 */
811 if (tv.tv_usec > 999999)
813 /* note: read_set is set in socket.c */
814 if ((retcode = select(set_max_fd + 1, &read_set, NULL, NULL, &tv)) < 0
816 die_e("select returned %d", errno);
820 gettimeofday(&tv, NULL);
821 /* we set tv_usec to slightly more than necessary to avoid
823 usleep(1001000 - tv.tv_usec);
824 #endif /* FCRONDYN */
827 #endif /* HAVE_GETTIMEOFDAY */
836 while (serial_num > 0 && serial_running < serial_max_running)
840 explain("Running with option once : exiting ... ");
845 save = now + save_time;
851 /* check if there's a new connection, a new command to answer, etc ... */
852 /* we do that *after* other checks, to avoid Denial Of Service attacks */
853 check_socket(retcode);
856 stime = check_lavg(save);
857 debug("next sleep time : %ld", stime);