]> granicus.if.org Git - fcron/blob - fcron.c
updated copyright years to 2000-2016
[fcron] / fcron.c
1 /*
2  * FCRON - periodic command scheduler 
3  *
4  *  Copyright 2000-2016 Thibault Godouet <fcron@free.fr>
5  *
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.
10  *
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.
15  * 
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
19  * 
20  *  The GNU General Public License can also be found in the file
21  *  `LICENSE' that comes with the fcron source distribution.
22  */
23
24
25 #include "fcron.h"
26
27 #include "database.h"
28 #include "conf.h"
29 #include "job.h"
30 #include "temp_file.h"
31 #include "fcronconf.h"
32 #ifdef FCRONDYN
33 #include "socket.h"
34 #endif
35
36
37 void main_loop(void);
38 void check_signal(void);
39 void info(void);
40 void usage(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[]);
48 void get_lock(void);
49 int is_system_reboot(void);
50 void create_spooldir(char *dir);
51
52 /* command line options */
53
54 #ifdef FOREGROUND
55 char foreground = 1;            /* set to 1 when we are on foreground, else 0 */
56 #else
57 char foreground = 0;            /* set to 1 when we are on foreground, else 0 */
58 #endif
59
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 */
65
66 /* Get the default locale character set for the mail
67  * "Content-Type: ...; charset=" header */
68 char default_mail_charset[TERM_LEN] = "";
69
70 /* used in temp_file() : create it in current dir (normally spool dir) */
71 char *tmp_path = "";
72
73 /* process identity */
74 pid_t daemon_pid;
75 mode_t saved_umask;             /* default root umask */
76 char *prog_name = NULL;
77 char *orig_tz_envvar = NULL;
78
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) */
82 uid_t rootuid = 0;
83 gid_t rootgid = 0;
84
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 */
89
90 /* jobs database */
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 */
94
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 */
100
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;
104
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 */
108
109 exe_list_t *exe_list;           /* jobs which are executed */
110
111 time_t begin_sleep;             /* the time at which sleep began */
112 time_t now;                     /* the current time */
113
114 #ifdef HAVE_LIBPAM
115 pam_handle_t *pamh = NULL;
116 const struct pam_conv apamconv = { NULL };
117 #endif
118
119 void
120 info(void)
121     /* print some informations about this program :
122      * version, license */
123 {
124     fprintf(stderr,
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");
129
130     exit(EXIT_OK);
131
132 }
133
134
135 void
136 usage(void)
137   /*  print a help message about command line options and exit */
138 {
139     fprintf(stderr, "\nfcron " VERSION_QUOTED "\n\n"
140             "fcron [-d] [-f] [-b]\n"
141             "fcron -h\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",
158             FIRST_SLEEP);
159
160     exit(EXIT_ERR);
161 }
162
163
164 void
165 print_schedule(void)
166     /* print the current schedule on syslog */
167 {
168     cf_t *cf;
169     cl_t *cl;
170     struct tm *ftime;
171
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"
178                     " (system time)",
179                     cl->cl_shell, (ftime->tm_year + 1900), (ftime->tm_mon + 1),
180                     ftime->tm_mday, ftime->tm_wday, ftime->tm_hour,
181                     ftime->tm_min);
182
183         }
184     }
185     explain("... end of printing schedule.");
186 }
187
188
189 void
190 xexit(int exit_value)
191     /* exit after having freed memory and removed lock file */
192 {
193     cf_t *f = NULL;
194
195     now = my_time();
196
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 */
200     save_file(NULL);
201
202 #ifdef FCRONDYN
203     close_socket();
204 #endif
205
206     f = file_base;
207     while (f != NULL) {
208         if (f->cf_running > 0) {
209             /* */
210             debug("waiting jobs for %s ...", f->cf_user);
211             /* */
212             wait_all(&f->cf_running);
213             save_file(f);
214         }
215         delete_file(f->cf_user);
216
217         /* delete_file remove the f file from the list :
218          * next file to remove is now pointed by file_base. */
219         f = file_base;
220     }
221
222     remove(pidfile);
223
224     exe_list_destroy(exe_list);
225     lavg_list_destroy(lavg_list);
226     free_conf();
227
228     Free_safe(orig_tz_envvar);
229
230     explain("Exiting with code %d", exit_value);
231     exit(exit_value);
232
233 }
234
235 void
236 get_lock()
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 */
240 {
241     int otherpid = 0;
242     FILE *daemon_lockfp = NULL;
243     int fd;
244
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);
248
249 #ifdef HAVE_FLOCK
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 */
254     {
255         if (fscanf(daemon_lockfp, "%d", &otherpid) >= 1)
256             die_e("can't lock %s, running daemon's pid may be %d", pidfile,
257                   otherpid);
258         else
259             die_e("can't lock %s, and unable to read running"
260                   " daemon's pid", pidfile);
261     }
262
263     fcntl(fd, F_SETFD, 1);
264
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)
269         error_e
270             ("Unable to ftruncate(fileno(daemon_lockfp), ftell(daemon_lockfp))");
271
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.
274      */
275
276 }
277
278 int
279 is_system_startup(void)
280 {
281     int reboot = 0;
282
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 */
287         return 0;
288     }
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.");
292     else
293         xclose_check(&reboot, REBOOT_LOCK);
294
295     /* run @reboot jobs */
296     return 1;
297 }
298
299
300 int
301 parseopt(int argc, char *argv[])
302   /* set options */
303 {
304
305     int c;
306     int i;
307
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'},
324         {0, 0, 0, 0}
325     };
326 #endif                          /* HAVE_GETOPT_LONG */
327
328     extern char *optarg;
329     extern int optind, opterr, optopt;
330
331     /* constants and variables defined by command line */
332
333     while (1) {
334 #ifdef HAVE_GETOPT_LONG
335         c = getopt_long(argc, argv, "dfbyp:hVos:l:m:c:n:q:", opt, NULL);
336 #else
337         c = getopt(argc, argv, "dfbyp:hVos:l:m:c:n:q:");
338 #endif                          /* HAVE_GETOPT_LONG */
339         if (c == EOF)
340             break;
341         switch ((char)c) {
342
343         case 'V':
344             info();
345             break;
346
347         case 'h':
348             usage();
349             break;
350
351         case 'd':
352             debug_opt = 1;
353             break;
354
355         case 'f':
356             foreground = 1;
357             break;
358
359         case 'b':
360             foreground = 0;
361             break;
362
363         case 'y':
364             dosyslog = 0;
365             break;
366
367         case 'p':
368             logfile_path = strdup2(optarg);
369             break;
370
371         case 'o':
372             once = 1;
373             first_sleep = 0;
374             break;
375
376         case 's':
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);
380             break;
381
382         case 'l':
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.",
386                     TIME_T_MAX);
387             break;
388
389         case 'm':
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);
393             break;
394
395         case 'c':
396             Set(fcronconf, optarg);
397             break;
398
399         case 'n':
400             create_spooldir(optarg);
401             break;
402
403         case 'q':
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);
407             break;
408
409         case ':':
410             error("(parseopt) Missing parameter");
411             usage();
412
413         case '?':
414             usage();
415
416         default:
417             warn("(parseopt) Warning: getopt returned %c", c);
418         }
419     }
420
421     if (optind < argc) {
422         for (i = optind; i <= argc; i++)
423             error("Unknown argument \"%s\"", argv[i]);
424         usage();
425     }
426
427     return OK;
428
429 }
430
431 void
432 create_spooldir(char *dir)
433     /* create a new spool dir for fcron : set correctly its mode and owner */
434 {
435     int dir_fd = -1;
436     struct stat st;
437     uid_t useruid = get_user_uid_safe(USERNAME);
438     gid_t usergid = get_group_gid_safe(GROUPNAME);
439
440     if (mkdir(dir, 0) != 0 && errno != EEXIST)
441         die_e("Cannot create dir %s", dir);
442
443     if ((dir_fd = open(dir, 0)) < 0)
444         die_e("Cannot open dir %s", dir);
445
446     if (fstat(dir_fd, &st) != 0) {
447         xclose_check(&dir_fd, "spooldir");
448         die_e("Cannot fstat %s", dir);
449     }
450
451     if (!S_ISDIR(st.st_mode)) {
452         xclose_check(&dir_fd, "spooldir");
453         die("%s exists and is not a directory", dir);
454     }
455
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);
459     }
460
461     if (fchmod
462         (dir_fd,
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);
466     }
467
468     xclose_check(&dir_fd, "spooldir");
469
470     exit(EXIT_OK);
471
472 }
473
474
475 RETSIGTYPE
476 sigterm_handler(int x)
477   /* exit safely */
478 {
479     debug("");
480     explain("SIGTERM signal received");
481     xexit(EXIT_OK);
482 }
483
484 RETSIGTYPE
485 sighup_handler(int x)
486   /* update configuration */
487 {
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
491      */
492     sig_conf = 1;
493 }
494
495 RETSIGTYPE
496 sigchild_handler(int x)
497   /* call wait_chld() to take care of finished jobs */
498 {
499
500     sig_chld = 1;
501
502 }
503
504
505 RETSIGTYPE
506 sigusr1_handler(int x)
507   /* reload all configurations */
508 {
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
512      */
513     sig_conf = 2;
514 }
515
516
517 RETSIGTYPE
518 sigusr2_handler(int x)
519   /* print schedule and switch on/off debug mode */
520 {
521     sig_debug = 1;
522 }
523
524
525 int
526 main(int argc, char **argv)
527 {
528     char *codeset = NULL;
529
530     rootuid = get_user_uid_safe(ROOTNAME);
531     rootgid = get_group_gid_safe(ROOTGROUP);
532
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);
536
537     /* parse options */
538
539     if (strrchr(argv[0], '/') == NULL)
540         prog_name = argv[0];
541     else
542         prog_name = strrchr(argv[0], '/') + 1;
543
544     {
545         uid_t daemon_uid;
546         if ((daemon_uid = getuid()) != rootuid)
547             die("Fcron must be executed as root");
548     }
549
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();
553
554     /* save the value of the TZ env variable (used for option timezone) */
555     orig_tz_envvar = strdup2(getenv("TZ"));
556
557     parseopt(argc, argv);
558
559     /* read fcron.conf and update global parameters */
560     read_conf();
561
562     /* initialize the logs before we become a daemon */
563     xopenlog();
564
565     /* change directory */
566
567     if (chdir(fcrontabs) != 0)
568         die_e("Could not change dir to %s", fcrontabs);
569
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));
579     else
580         strcpy(default_mail_charset, "US-ASCII");
581
582     if (freopen("/dev/null", "r", stdin) == NULL)
583         error_e("Could not open /dev/null as stdin");
584
585     if (foreground == 0) {
586
587         /* close stdout and stderr.
588          * close unused descriptors
589          * optional detach from controlling terminal */
590
591         int fd;
592         pid_t pid;
593
594         switch (pid = fork()) {
595         case -1:
596             die_e("fork");
597             break;
598         case 0:
599             /* child */
600             break;
601         default:
602             /* parent */
603 /*          printf("%s[%d] " VERSION_QUOTED " : started.\n", */
604 /*                 prog_name, pid); */
605             exit(0);
606         }
607
608         daemon_pid = getpid();
609
610         if ((fd = open("/dev/tty", O_RDWR)) >= 0) {
611 #ifndef _HPUX_SOURCE
612             ioctl(fd, TIOCNOTTY, 0);
613 #endif
614             xclose_check(&fd, "/dev/tty");
615         }
616
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");
621
622         /* close most other open fds */
623         xcloselog();
624         for (fd = 3; fd < 250; fd++)
625             /* don't use xclose_check() as we do expect most of them to fail */
626             (void)close(fd);
627
628         /* finally, create a new session */
629         if (setsid() == -1)
630             error("Could not setsid()");
631
632     }
633
634     /* check if another fcron daemon is running, create pid file and lock it */
635     get_lock();
636
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 */
640     umask(066);
641
642     explain("%s[%d] " VERSION_QUOTED " started", prog_name, daemon_pid);
643
644 #ifdef HAVE_SIGNAL
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);
657 #elif HAVE_SIGSET
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);
664 #endif
665
666     /* initialize job database */
667     next_id = 0;
668
669     /* initialize exe_array */
670     exe_list = exe_list_init();
671
672     /* initialize serial_array */
673     serial_running = 0;
674     serial_array_index = 0;
675     serial_num = 0;
676     serial_array_size = SERIAL_INITIAL_SIZE;
677     serial_array =
678         alloc_safe(serial_array_size * sizeof(cl_t *), "serial_array");
679
680     /* initialize lavg_array */
681     lavg_list = lavg_list_init();
682     lavg_list->max_entries = lavg_queue_max;
683     lavg_serial_running = 0;
684
685 #ifdef FCRONDYN
686     /* initialize socket */
687     init_socket();
688 #endif
689
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 */
694     {
695         char hostname[50];
696         int i;
697         unsigned int seed;
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);
702 #else
703         seed = (unsigned int)time(NULL);
704 #endif
705         gethostname(hostname, sizeof(hostname));
706
707         for (i = 0; i < sizeof(hostname) - sizeof(seed); i += sizeof(seed))
708             seed ^= (unsigned int)*(hostname + i);
709
710         srand(seed);
711     }
712
713     main_loop();
714
715     /* never reached */
716     return EXIT_OK;
717 }
718
719
720 void
721 check_signal()
722     /* check if a signal has been received and handle it */
723 {
724
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 */
727
728     if (sig_chld > 0) {
729         wait_chld();
730         sig_chld = 0;
731 #ifdef HAVE_SIGNAL
732         (void)signal(SIGCHLD, sigchild_handler);
733         siginterrupt(SIGCHLD, 0);
734 #endif
735     }
736     if (sig_conf > 0) {
737
738         if (sig_conf == 1) {
739             /* update configuration */
740             synchronize_dir(".", 0);
741             sig_conf = 0;
742 #ifdef HAVE_SIGNAL
743             signal(SIGHUP, sighup_handler);
744             siginterrupt(SIGHUP, 0);
745 #endif
746         }
747         else {
748             /* reload all configuration */
749             reload_all(".");
750             sig_conf = 0;
751 #ifdef HAVE_SIGNAL
752             signal(SIGUSR1, sigusr1_handler);
753             siginterrupt(SIGUSR1, 0);
754 #endif
755         }
756
757     }
758     if (sig_debug > 0) {
759         print_schedule();
760         debug_opt = (debug_opt > 0) ? 0 : 1;
761         explain("debug_opt = %d", debug_opt);
762         sig_debug = 0;
763 #ifdef HAVE_SIGNAL
764         signal(SIGUSR2, sigusr2_handler);
765         siginterrupt(SIGUSR2, 0);
766 #endif
767     }
768
769 }
770
771 void
772 main_loop()
773   /* main loop - get the time to sleep until next job execution,
774    *             sleep, and then test all jobs and execute if needed. */
775 {
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 */
780 #endif
781 #if defined(FCRONDYN) && defined(HAVE_GETTIMEOFDAY)
782     int retcode = 0;
783     struct timeval sleep_tv;    /* we use usec field to get more precision */
784 #endif
785
786     debug("Entering main loop");
787
788     now = my_time();
789
790     synchronize_dir(".", is_system_startup());
791
792     /* synchronize save with jobs execution */
793     save = now + save_time;
794
795     if (serial_num > 0 || once) {
796         nwt = now + first_sleep;
797     }
798     else {
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);
802     }
803     debug("initial wake time : %s", ctime(&nwt));
804
805     for (;;) {
806
807 #ifdef HAVE_GETTIMEOFDAY
808 #ifdef FCRONDYN
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));
812
813         if (nwt <= now_tv.tv_sec) {
814             sleep_tv.tv_sec = 0;
815             sleep_tv.tv_usec = 0;
816         }
817         else {
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;
825         }
826
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;
830         }
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 */
833             debug
834                 ("We'll sleep for a tiny bit to avoid any risk of infinite loop");
835             sleep_tv.tv_usec = min_sleep_usec;
836         }
837         /* note: read_set is set in socket.c */
838         debug("nwt=%s, sleep sec=%ld, usec=%ld", ctime(&nwt), sleep_tv.tv_sec,
839               sleep_tv.tv_usec);
840         if ((retcode =
841              select(set_max_fd + 1, &read_set, NULL, NULL, &sleep_tv)) < 0
842             && errno != EINTR)
843             die_e("select returned %d", errno);
844 #else                           /* FCRONDYN */
845         if (nwt - now > 0) {
846             sleep(nwt - now - 1);
847         }
848         gettimeofday(&now_tv, NULL);
849         /* we set tv_usec to slightly more than necessary to avoid
850          * infinite loop */
851         usleep(1000000 + min_sleep_usec - now_tv.tv_usec);
852 #endif                          /* FCRONDYN */
853 #else                           /* HAVE_GETTIMEOFDAY */
854         if (nwt - now > 0) {
855             sleep(nwt - now);
856         }
857 #endif                          /* HAVE_GETTIMEOFDAY */
858
859         debug("\n");
860         now = my_time();
861 #ifdef HAVE_GETTIMEOFDAY
862         if (debug_opt) {
863             gettimeofday(&now_tv, NULL);
864             debug("now=%ld now_tv sec=%ld usec=%ld", now, now_tv.tv_sec,
865                   now_tv.tv_usec);
866         }
867 #endif
868
869         check_signal();
870 #ifdef HAVE_GETTIMEOFDAY
871         if (debug_opt) {
872             gettimeofday(&now_tv, NULL);
873             debug("after check_signal: now_tv sec=%ld usec=%ld", now_tv.tv_sec,
874                   now_tv.tv_usec);
875         }
876 #endif
877
878         test_jobs();
879 #ifdef HAVE_GETTIMEOFDAY
880         if (debug_opt) {
881             gettimeofday(&now_tv, NULL);
882             debug("after test_jobs: now_tv sec=%ld usec=%ld", now_tv.tv_sec,
883                   now_tv.tv_usec);
884         }
885 #endif
886
887         while (serial_num > 0 && serial_running < serial_max_running) {
888             run_serial_job();
889         }
890
891         if (once) {
892             explain("Running with option once : exiting ... ");
893             xexit(EXIT_OK);
894         }
895
896         if (save <= now) {
897             save = now + save_time;
898             /* save all files */
899             save_file(NULL);
900         }
901 #ifdef HAVE_GETTIMEOFDAY
902         if (debug_opt) {
903             gettimeofday(&now_tv, NULL);
904             debug("after save: now_tv sec=%ld usec=%ld", now_tv.tv_sec,
905                   now_tv.tv_usec);
906         }
907 #endif
908
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);
913 #endif
914
915         nwt = check_lavg(save);
916 #ifdef HAVE_GETTIMEOFDAY
917         if (debug_opt) {
918             gettimeofday(&now_tv, NULL);
919             debug("after check_lavg: now_tv sec=%ld usec=%ld", now_tv.tv_sec,
920                   now_tv.tv_usec);
921         }
922 #endif
923         debug("next wake time : %s", ctime(&nwt));
924
925         check_signal();
926
927     }
928
929 }