3 * FCRON - periodic command scheduler
5 * Copyright 2000-2012 Thibault Godouet <fcron@free.fr>
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
21 * The GNU General Public License can also be found in the file
22 * `LICENSE' that comes with the fcron source distribution.
31 int read_file(const char *file_name, cf_t * cf, int is_system_startup);
32 int add_line_to_file(cl_t * cl, cf_t * cf, uid_t runas, char *runas_str,
33 time_t t_save, int is_system_startup);
34 int read_strn(int fd, char **str, short int size);
35 int read_type(int fd, short int *type, short int *size);
36 void synchronize_file(char *file_name, int is_system_startup);
39 /* this is used to create a list of files to remove, to add */
40 typedef struct list_t {
47 reload_all(const char *dir_name)
48 /* save all current configuration, remove it from the memory,
49 * and reload from dir_name */
53 explain("Removing current configuration from memory");
57 if (f->cf_running > 0)
58 wait_all(&f->cf_running);
60 delete_file(f->cf_user);
62 /* delete_file remove the f file from the list :
63 * next file to remove is now pointed by file_base. */
67 synchronize_dir(dir_name, 0);
73 synchronize_dir(const char *dir_name, int is_system_startup)
74 /* read dir_name and create three list of files to remove,
75 * new files and normal files. Then remove each file
76 * listed in the first list, then read normal files,
77 * finally add new files. */
80 list_t *rm_list = NULL;
81 list_t *new_list = NULL;
82 list_t *file_list = NULL;
83 list_t *list_cur = NULL;
87 if (strcmp(dir_name, ".") == 0)
88 explain("updating configuration from %s", fcrontabs);
90 explain("updating configuration from %s", dir_name);
92 if ((dir = opendir("."))) {
93 while ((den = readdir(dir))) {
95 if (strncmp(den->d_name, "rm.", 3) == 0) {
96 /* this is a file to remove from database */
97 Alloc(list_cur, list_t);
98 list_cur->str = strdup2(den->d_name);
99 list_cur->next = rm_list;
102 else if (strncmp(den->d_name, "new.", 4) == 0) {
103 /* this is a file to append to database */
104 Alloc(list_cur, list_t);
105 list_cur->str = strdup2(den->d_name);
106 list_cur->next = new_list;
109 else if (strchr(den->d_name, '.') != NULL)
112 /* this is a normal file : if file_base is not null,
113 * so if a database has already been created, we
115 if (file_base == NULL) {
116 Alloc(list_cur, list_t);
117 list_cur->str = strdup2(den->d_name);
118 list_cur->next = file_list;
119 file_list = list_cur;
126 die("Unable to open current dir!");
129 /* proceed to adds or removes */
131 /* begin by removing files which are no longer wanted */
132 for (list_cur = rm_list; list_cur; list_cur = list_cur->next) {
133 explain("removing file %s", list_cur->str + 3);
134 delete_file(list_cur->str + 3); /* len("rm.") = 3 */
135 if (remove(list_cur->str + 3) != 0 && errno != ENOENT)
136 error_e("Could not remove %s", list_cur->str + 3);
137 if (remove(list_cur->str) != 0 && errno != ENOENT)
138 error_e("Could not remove %s", list_cur->str);
141 /* then add normal files, if any, to database */
142 for (list_cur = file_list; list_cur; list_cur = list_cur->next) {
144 if (getpwnam(list_cur->str)
146 || strcmp(list_cur->str, SYSFCRONTAB) == 0
149 explain("adding file %s", list_cur->str);
150 synchronize_file(list_cur->str, is_system_startup);
153 error_e("ignoring file \"%s\" : not in passwd file.",
157 /* finally add new files */
158 for (list_cur = new_list; list_cur; list_cur = list_cur->next) {
159 /* len("new.") = 4 : */
161 if (getpwnam(list_cur->str + 4)
163 || strcmp(list_cur->str + 4, SYSFCRONTAB) == 0
166 explain("adding new file %s", list_cur->str + 4);
167 synchronize_file(list_cur->str, is_system_startup);
170 error_e("ignoring file %s : not in passwd file.",
171 (list_cur->str + 4));
180 while ((l = next) != NULL) {
187 while ((l = next) != NULL) {
194 while ((l = next) != NULL) {
206 synchronize_file(char *file_name, int is_system_startup)
212 if (strchr(file_name, '.') != NULL) {
213 /* this is a new file : we have to check if there is an old
214 * version in database in order to keep a maximum of fields
215 * (cl_nextexe) to their current value */
220 /* we add 4 to file_name pointer because of the "new."
221 * string at the beginning of a new file */
222 user = (file_name + 4);
224 for (cur_f = file_base; cur_f; cur_f = cur_f->cf_next) {
225 if (strcmp(user, cur_f->cf_user) == 0)
231 /* an old version of this file exist in database */
236 /* size used when comparing two line :
237 * it's the size of all time table (mins, days ...) */
238 const size_t size = (bitstr_size(60) + bitstr_size(24) +
239 bitstr_size(32) + bitstr_size(12) +
246 if (read_file(file_name, cur_f, is_system_startup) != 0) {
247 /* an error as occured */
251 /* assign old pointer to the old file, and move it to the first
252 * place of the list : delete_file() only remove the first
253 * occurrence of the file which has the name given in argument */
255 prev->cf_next = old->cf_next;
256 old->cf_next = file_base;
260 /* this is the first file in the list : no need to move it */
263 /* compare each lines between the new and the old
264 * version of the file */
265 for (new_l = cur_f->cf_line_base; new_l; new_l = new_l->cl_next)
266 for (old_l = old->cf_line_base; old_l; old_l = old_l->cl_next) {
268 /* compare the shell command and the fields
269 * from cl_mins down to cl_runfreq or the timefreq */
270 if (strcmp(new_l->cl_shell, old_l->cl_shell) == 0
271 && ((is_freq(new_l->cl_option)
272 && new_l->cl_timefreq == old_l->cl_timefreq)
273 || (is_td(new_l->cl_option)
274 && memcmp(&(new_l->cl_mins), &(old_l->cl_mins),
276 && is_dayor(new_l->cl_option) ==
277 is_dayor(old_l->cl_option))
280 if (new_l->cl_runfreq == old_l->cl_runfreq)
281 new_l->cl_remain = old_l->cl_remain;
282 /* check if there is a change about the tz diff */
283 if ((new_l->cl_file->cf_tzdiff !=
284 old_l->cl_file->cf_tzdiff) &&
285 (old_l->cl_nextexe - old_l->cl_file->cf_tzdiff
286 + new_l->cl_file->cf_tzdiff > now))
287 new_l->cl_nextexe = old_l->cl_nextexe
288 - old_l->cl_file->cf_tzdiff +
289 new_l->cl_file->cf_tzdiff;
291 new_l->cl_nextexe = old_l->cl_nextexe;
293 if (is_runonce(new_l->cl_option)
294 && is_runonce(old_l->cl_option)
295 && is_hasrun(old_l->cl_option)) {
297 (" from last conf: job '%s' with runonce set has "
298 "already run since last system startup: not "
299 "re-scheduling.", new_l->cl_shell);
300 set_hasrun(new_l->cl_option);
301 /* job has already run: remove from the queue */
302 job_queue_remove(new_l);
305 /* update the position in the queue */
306 insert_nextexe(new_l);
308 if (debug_opt && !is_hasrun(new_l->cl_option)) {
310 ftime = localtime(&new_l->cl_nextexe);
311 debug(" from last conf: %s next exec %d/%d/%d"
312 " wday:%d %02d:%02d:%02d (system time)",
314 (ftime->tm_mon + 1), ftime->tm_mday,
315 (ftime->tm_year + 1900), ftime->tm_wday,
316 ftime->tm_hour, ftime->tm_min, ftime->tm_sec);
324 /* remove old file from the list */
327 /* insert new file in the list */
328 cur_f->cf_next = file_base;
331 /* save final file */
334 /* delete new.user file */
335 if (remove(file_name) != 0)
336 error_e("could not remove %s", file_name);
341 /* no old version exist in database : load this file
342 * as a normal file, but change its name */
346 if (read_file(file_name, cur_f, is_system_startup) != 0) {
347 /* an error as occured */
351 /* insert the file in the list */
352 cur_f->cf_next = file_base;
355 /* save as a normal file */
358 /* delete new.user file */
359 if (remove(file_name) != 0)
360 error_e("could not remove %s", file_name);
366 /* this is a normal file */
370 if (read_file(file_name, cur_f, is_system_startup) != 0) {
371 /* an error as occured */
375 /* insert the file in the list */
376 cur_f->cf_next = file_base;
385 read_strn(int fd, char **str, short int size)
386 /* read a "size"-length string in a binary fcrontab file */
388 if ((*str = calloc(size + 1, sizeof(char))) == NULL)
391 if (read(fd, *str, size) < size)
403 read_type(int fd, short int *type, short int *size)
404 /* read the type and size of the next field in a binary fcrontab file */
406 if (read(fd, type, sizeof(short int)) < sizeof(short int))
408 if (read(fd, size, sizeof(short int)) < sizeof(short int))
419 /* macros for read_file() */
420 /* read "size" bytes from file "ff", put them in "to", and check for errors */
421 #define Read(TO, SIZE, ERR_STR) \
423 if ( read(fileno(ff), &(TO), SIZE) < SIZE ) { \
429 #define Read_strn(TO, SIZE, ERR_STR) \
431 if ( read_strn(fileno(ff), &(TO), SIZE) != OK ) { \
438 read_file(const char *file_name, cf_t * cf, int is_system_startup)
439 /* read a formated fcrontab.
440 * return ERR on error, OK otherwise */
447 char *runas_str = NULL;
448 struct stat file_stat;
449 struct passwd *pass = NULL;
450 short int type = 0, size = 0;
452 int has_read_cl_first = 0; /* have we read S_FIRST_T? */
454 int flask_enabled = is_selinux_enabled();
456 struct av_decision avd;
457 const char *user_name;
461 if ((ff = fopen(file_name, "r")) == NULL) {
462 warn_e("Could not read %s (may have just been removed)", file_name);
466 /* check if this file is owned by root : otherwise, all runas fields
467 * of this field should be set to the owner */
468 rc = fstat(fileno(ff), &file_stat);
470 error_e("Could not stat %s", file_name);
474 if (flask_enabled && fgetfilecon(fileno(ff), &cf->cf_file_context) < 0) {
475 error_e("Could not get context of %s", file_name);
480 if (strncmp(file_name, "new.", 4) == 0) {
481 if (file_stat.st_uid == rootuid) {
482 /* file is owned by root : no test needed : set runas to rootuid */
486 /* this is a standard user's new fcrontab : set the runas field to
487 * the owner of the file */
488 runas = file_stat.st_uid;
489 if ((pass = getpwuid(file_stat.st_uid)) == NULL) {
490 error_e("Could not getpwuid(%d)", file_stat.st_uid);
493 runas_str = strdup2(pass->pw_name);
495 cf->cf_user = strdup2(file_name + 4);
499 cf->cf_user = strdup2(file_name);
500 if (file_stat.st_uid == rootuid) {
501 /* file is owned by root : either this file has already been parsed
502 * at least once by fcron, or it is root's fcrontab */
506 error("Non-new file %s owned by someone else than root", file_name);
513 * Since fcrontab files are not directly executed,
514 * fcrond must ensure that the fcrontab file has
515 * a context that is appropriate for the context of
516 * the user fcron job. It performs an entrypoint
517 * permission check for this purpose.
520 if (!strcmp(cf->cf_user, SYSFCRONTAB))
521 user_name = "system_u";
523 #endif /* def SYSFCRONTAB */
524 user_name = cf->cf_user;
526 if (get_default_context(user_name, NULL, &cf->cf_user_context))
527 error_e("NO CONTEXT for user \"%s\"", cf->cf_user_context);
529 security_compute_av(cf->cf_user_context, cf->cf_file_context,
530 SECCLASS_FILE, FILE__ENTRYPOINT, &avd);
532 if (retval || ((FILE__ENTRYPOINT & avd.allowed) != FILE__ENTRYPOINT)) {
533 syslog(LOG_ERR, "ENTRYPOINT FAILED for user \"%s\" "
534 "(CONTEXT %s) for file CONTEXT %s", cf->cf_user,
535 cf->cf_user_context, cf->cf_file_context);
541 debug("User %s Entry", file_name);
543 /* get version of fcrontab file: it permits to daemon not to load
544 * a file which he won't understand the syntax, for example
545 * a file using a depreciated format generated by an old fcrontab,
546 * if the syntax has changed */
547 if (read_type(fileno(ff), &type, &size) != OK || type != S_HEADER_T ||
548 read(fileno(ff), &bufi, size) < size || bufi != S_FILEVERSION) {
549 error("File %s is not valid: ignored.", file_name);
550 error("This file may have been generated by an old version of fcron.");
551 error("In that case, you should try to use the converter given in the "
552 "source package, or install it again using fcrontab.");
556 if (read_type(fileno(ff), &type, &size) != OK || type != S_USER_T) {
557 error("Invalid binary fcrontab (no USER field)");
560 /* get the owner's name */
561 /* we set cf->cf_user before for SE Linux, so we need to free it here */
562 Free_safe(cf->cf_user);
563 if (read_strn(fileno(ff), &cf->cf_user, size) != OK) {
564 error("Cannot read user's name : file ignored");
567 if (runas != rootuid) {
568 /* we use file owner's name for more security (see above) */
569 /* free the value obtained by read_strn() (we need to read it anyway
570 * to set the file ptr to the next thing to read) */
571 Free_safe(cf->cf_user);
572 cf->cf_user = runas_str;
575 /* get the time & date of the saving */
576 /* a new file generated by fcrontab has t_save set to 0 */
577 if (read_type(fileno(ff), &type, &size) != OK || type != S_TIMEDATE_T
578 || read(fileno(ff), &t_save, size) < size) {
579 error("could not get time and date of saving");
583 if (cf->cf_env_list == NULL)
584 cf->cf_env_list = env_list_init();
587 /* main loop : read env variables, and lines */
588 while (read_type(fileno(ff), &type, &size) == OK) {
589 /* action is determined by the type of the field */
593 /* read a env variable and add it to the env var list */
597 /* Read_strn go to "err" on error */
598 Read_strn(envvar, size, "Error while reading env var");
599 debug(" Env: \"%s\"", envvar);
600 /* we do not allow USER or LOGNAME assignment.
601 * this was already checked by fcrontab, but we check again
603 if (strcmp_until(envvar, "USER", '=') == 0
604 || strcmp_until(envvar, "LOGNAME", '=') == 0) {
606 ("USER or LOGNAME assignement is not allowed: ignored.");
609 env_list_putenv(cf->cf_env_list, envvar, 1);
616 /* time diff between local (real) and system hour */
617 Read(bufi, size, "Error while reading tzdiff field");
618 cf->cf_tzdiff = (signed char)bufi;
622 /* read the timezone (string) in which the line should run */
623 Read_strn(cl->cl_tz, size, "Error while reading timezone field");
627 Read_strn(cl->cl_shell, size, "Error while reading shell field");
631 Read_strn(cl->cl_runas, size, "Error while reading runas field");
635 Read_strn(cl->cl_mailto, size, "Error while reading mailto field");
639 Read(bufi, size, "Error while reading nextexe field");
640 cl->cl_nextexe = (time_t) bufi;
644 Read(bufi, size, "Error while reading first field");
645 cl->cl_first = (time_t) bufi;
646 has_read_cl_first = 1;
650 if (size < OPTION_SIZE)
651 /* set the options not defined in the savefile to default */
652 set_default_opt(cl->cl_option);
653 Read(cl->cl_option, size, "Error while reading option field");
657 Read(cl->cl_numexe, size, "Error while reading numexe field");
661 Read(cl->cl_lavg, size, "Error while reading lavg field");
665 Read(bufi, size, "Error while reading until field");
666 cl->cl_until = (time_t) bufi;
670 Read(cl->cl_nice, size, "Error while reading nice field");
674 Read(bufi, size, "Error while reading runfreq field");
675 cl->cl_runfreq = (unsigned short)bufi;
679 Read(bufi, size, "Error while reading remain field");
680 cl->cl_remain = (unsigned short)bufi;
684 Read(bufi, size, "Error while reading timefreq field");
685 cl->cl_timefreq = (time_t) bufi;
689 /* read the jitter (uchar) to use to set next execution time */
690 Read(bufi, size, "Error while reading jitter field");
691 cl->cl_jitter = (unsigned char)bufi;
695 Read(cl->cl_mins, size, "Error while reading mins field");
699 Read(cl->cl_hrs, size, "Error while reading hrs field");
703 Read(cl->cl_days, size, "Error while reading days field");
707 Read(cl->cl_mons, size, "Error while reading mons field");
711 Read(cl->cl_dow, size, "Error while reading dow field");
715 if (is_freq(cl->cl_option) && !has_read_cl_first) {
716 /* Up to fcron 3.0.X, cl_first/S_FIRST_T was not saved for all @-lines */
717 cl->cl_first = cl->cl_nextexe;
720 (cl, cf, runas, runas_str, t_save, is_system_startup) != 0)
725 /* default case in "switch(type)" */
727 error("Error while loading %s : unknown field type %d (ignored)",
731 /* skip the data corresponding to the unknown field */
733 /* we avoid using fseek(), as it seems not to work correctly
734 * on some systems when we use read() on the FILE stream */
736 for (i = 0; i < size; i++)
743 /* free last cl Alloc : unused */
746 /* check for an error */
748 error("file %s is truncated : you should reinstall it with fcrontab",
751 xfclose_check(&ff, file_name);
757 xfclose_check(&ff, file_name);
759 if (cl != NULL && cl->cl_next == NULL) {
760 /* line is not yet in the line list of the file : free it */
761 Free_safe(cl->cl_shell);
762 Free_safe(cl->cl_runas);
763 Free_safe(cl->cl_mailto);
767 /* check if we have started to read the lines and env var */
769 /* insert the line in the file list in order to be able to free
770 * the memory using delete_file() */
772 cf->cf_next = file_base;
775 delete_file(cf->cf_user);
779 Free_safe(cf->cf_user);
788 add_line_to_file(cl_t * cl, cf_t * cf, uid_t runas, char *runas_str,
789 time_t t_save, int is_system_startup)
790 /* check if the line is valid, and if yes, add it to the file cf */
792 time_t slept = now - t_save;
794 if (cl->cl_shell == NULL || cl->cl_runas == NULL || cl->cl_mailto == NULL) {
795 error("Line is not valid (empty shell, runas or mailto field)"
800 /* set runas field if necessary (to improve security) */
801 if (runas != rootuid) {
802 if (strcmp(cl->cl_runas, runas_str) != 0)
803 warn("warning: runas(%s) is not owner (%s): overridden.",
804 cl->cl_runas, runas_str);
805 Set(cl->cl_runas, runas_str);
808 /* we need that here because the user's name contained in the
809 * struct cf_t may be required */
812 /* check if the mailto field is valid */
813 if (cl->cl_mailto && (*(cl->cl_mailto) == '-' ||
814 strcspn(cl->cl_mailto,
815 " \t\n") != strlen(cl->cl_mailto))) {
816 error("mailto field \'%s\' is not valid : set to owner %s.",
817 cl->cl_mailto, cl->cl_file->cf_user);
818 Set(cl->cl_mailto, cl->cl_file->cf_user);
821 /* job has been stopped during execution: insert it in lavg or serial queue
822 * if it was in one at fcron's stops. */
823 /* NOTE: runatreboot is prioritary over jobs that were still running
824 * when fcron stops, because the former will get run quicker as they are not
825 * put into the serial queue. runatreboot jobs will be handled later on. */
826 if (cl->cl_numexe > 0 && !is_runatreboot(cl->cl_option)) {
829 if (is_lavg(cl->cl_option)) {
830 if (!is_strict(cl->cl_option))
831 add_lavg_job(cl, -1);
833 else if (is_serial(cl->cl_option)
834 || is_serial_once(cl->cl_option))
835 add_serial_job(cl, -1);
837 /* job has been stopped during execution :
839 warn("job %s did not finish : running it again.", cl->cl_shell);
840 set_serial_once(cl->cl_option);
841 add_serial_job(cl, -1);
845 if (is_system_startup || is_volatile(cl->cl_option)) {
846 clear_hasrun(cl->cl_option);
849 if (is_runonce(cl->cl_option) && is_hasrun(cl->cl_option)) {
850 /* if we get here, then is_system_startup is_volatile are both false */
851 /* do nothing: don't re-schedule or add to the job queue */
852 explain("job '%s' with runonce set has already run since last "
853 "system startup: not re-scheduling.", cl->cl_shell);
855 else if (is_td(cl->cl_option)) {
857 /* set the time and date of the next execution */
858 if (is_system_startup && is_runatreboot(cl->cl_option)) {
860 if (is_notice_notrun(cl->cl_option)) {
862 if (cl->cl_runfreq == 1) {
864 set_next_exe_notrun(cl, SYSDOWN_RUNATREBOOT);
867 /* set next exe and mail user */
868 time_t since = cl->cl_nextexe;
870 cl->cl_nextexe = now;
871 mail_notrun_time_t(cl, SYSDOWN, since);
876 cl->cl_nextexe = now;
882 else if (cl->cl_nextexe <= now) {
883 if (cl->cl_nextexe == 0)
884 /* the is a line from a new file */
885 set_next_exe(cl, NO_GOTO, -1);
886 else if (cl->cl_runfreq == 1 && is_notice_notrun(cl->cl_option))
887 set_next_exe_notrun(cl, SYSDOWN);
888 else if (is_bootrun(cl->cl_option) && t_save != 0
889 && cl->cl_runfreq != 1) {
890 if (cl->cl_remain > 0 && --cl->cl_remain > 0) {
891 debug(" cl_remain: %d", cl->cl_remain);
894 /* run bootrun jobs */
895 cl->cl_remain = cl->cl_runfreq;
896 debug(" boot-run %s", cl->cl_shell);
897 if (!is_lavg(cl->cl_option)) {
898 set_serial_once(cl->cl_option);
899 add_serial_job(cl, -1);
902 add_lavg_job(cl, -1);
904 set_next_exe(cl, STD, -1);
907 if (is_notice_notrun(cl->cl_option)) {
908 /* set next exe and mail user */
909 time_t since = cl->cl_nextexe;
911 set_next_exe(cl, NO_GOTO, -1);
912 mail_notrun_time_t(cl, SYSDOWN, since);
916 set_next_exe(cl, NO_GOTO, -1);
920 /* value of nextexe is valid : just insert line in queue */
924 else { /* is_td(cl->cl_option) */
925 /* standard @-lines */
926 if (is_system_startup && is_runatreboot(cl->cl_option)) {
927 cl->cl_nextexe = now;
929 /* t_save == 0 means this is a new file, hence a new line */
930 else if (t_save == 0 || is_volatile(cl->cl_option)
931 || (is_system_startup && (is_rebootreset(cl->cl_option)
932 || is_runonce(cl->cl_option)))) {
933 /* cl_first is always saved to disk for a volatile line */
934 if (cl->cl_first == LONG_MAX) {
935 cl->cl_nextexe = LONG_MAX;
938 cl->cl_nextexe = now + cl->cl_first;
939 if (cl->cl_nextexe < now) {
940 /* there was an integer overflow! */
942 ("Error while setting next exe time for job %s: cl_nextexe"
943 " overflowed. now=%lu, cl_timefreq=%lu, cl_nextexe=%lu.",
944 cl->cl_shell, now, cl->cl_timefreq, cl->cl_nextexe);
946 ("Setting cl_nextexe to LONG_MAX to prevent an infinite loop.");
947 cl->cl_nextexe = LONG_MAX;
952 if (cl->cl_nextexe != LONG_MAX) {
953 cl->cl_nextexe += slept;
954 if (cl->cl_nextexe < now) {
955 /* there was an integer overflow! */
957 ("Error while setting next exe time for job %s: cl_nextexe"
958 " overflowed. now=%lu, cl_timefreq=%lu, cl_nextexe=%lu.",
959 cl->cl_shell, now, cl->cl_timefreq, cl->cl_nextexe);
961 ("Setting cl_nextexe to LONG_MAX to prevent an infinite loop.");
962 cl->cl_nextexe = LONG_MAX;
967 if (cl->cl_timefreq < 10) {
968 error("Invalid timefreq for %s: set to 1 day", cl->cl_shell);
969 cl->cl_timefreq = 3600 * 24;
975 if (debug_opt && !(is_runonce(cl->cl_option) && is_hasrun(cl->cl_option))) {
977 ftime = localtime(&(cl->cl_nextexe));
978 debug(" cmd %s next exec %d/%d/%d wday:%d %02d:%02d:%02d"
980 cl->cl_shell, (ftime->tm_mon + 1), ftime->tm_mday,
981 (ftime->tm_year + 1900), ftime->tm_wday,
982 ftime->tm_hour, ftime->tm_min, ftime->tm_sec);
985 /* add the current line to the list, and allocate a new line */
986 if ((cl->cl_id = next_id++) >= ULONG_MAX - 1) {
987 warn("Line id reached %ld: cycling back to zero.", ULONG_MAX);
990 cl->cl_next = cf->cf_line_base;
991 cf->cf_line_base = cl;
996 delete_file(const char *user_name)
997 /* free a file if user_name is not null
998 * otherwise free all files */
1001 cf_t *prev_file = NULL;
1004 struct job_t *j = NULL;
1005 struct job_t *prev_j;
1007 struct cl_t **s_a = NULL;
1012 while (file != NULL) {
1013 if (strcmp(user_name, file->cf_user) != 0) {
1015 file = file->cf_next;
1019 for (e = exe_list_first(exe_list); e != NULL;
1020 e = exe_list_next(exe_list))
1021 if (e->e_line != NULL && e->e_line->cl_file == file) {
1022 /* we set the e_line to NULL, as so we know in wait_chld()
1023 * and wait_all() the corresponding file has been removed.
1024 * Plus, we decrement serial_running and lavg_serial_running
1025 * as we won't be able to do it at the end of the job */
1026 if ((is_serial(e->e_line->cl_option) ||
1027 is_serial_once(e->e_line->cl_option)) &&
1028 !is_lavg(e->e_line->cl_option))
1030 else if (is_serial(e->e_line->cl_option) &&
1031 is_lavg(e->e_line->cl_option))
1032 lavg_serial_running--;
1036 /* free lavg queue entries */
1037 for (l = lavg_list_first(lavg_list); l != NULL;
1038 l = lavg_list_next(lavg_list))
1039 if (l->l_line->cl_file == file) {
1040 debug("removing %s from lavg queue", l->l_line->cl_shell);
1041 lavg_list_remove_cur(lavg_list);
1044 /* free serial queue entries */
1045 for (i = 0; i < serial_array_size; i++)
1046 if (serial_array[i] != NULL && serial_array[i]->cl_file == file) {
1049 alloc_safe(serial_array_size * sizeof(cl_t *),
1052 debug("removing %s from serial queue",
1053 serial_array[i]->cl_shell);
1055 serial_array[i]->cl_numexe--;
1056 serial_array[i] = NULL;
1058 /* remove from queue and move the rest of the jobs to get
1059 * a queue in order without empty entries */
1061 goto end_of_serial_recomputing;
1063 if ((k = serial_array_index + serial_num) >= serial_array_size)
1064 k -= serial_array_size;
1065 for (i = k = 0; i < serial_array_size; i++) {
1066 if (serial_array_index + i < serial_array_size) {
1067 if ((s_a[k] = serial_array[serial_array_index + i]) != NULL)
1071 serial_array[serial_array_index + i - serial_array_size])
1075 Free_safe(serial_array);
1077 serial_array_index = 0;
1079 end_of_serial_recomputing:
1082 cur_line = file->cf_line_base;
1083 while ((line = cur_line) != NULL) {
1084 cur_line = line->cl_next;
1086 /* remove from the main queue */
1088 for (j = queue_base; j != NULL; j = j->j_next)
1089 if (j->j_line == line) {
1091 prev_j->j_next = j->j_next;
1093 queue_base = j->j_next;
1100 /* free line itself */
1103 /* delete_file() MUST remove only the first occurrence :
1104 * this is needed by synchronize_file() */
1109 /* file not in the file list */
1112 /* remove file from file list */
1113 if (prev_file == NULL)
1114 file_base = file->cf_next;
1116 prev_file->cf_next = file->cf_next;
1118 /* free env variables */
1119 env_list_destroy(file->cf_env_list);
1121 /* finally free file itself */
1122 Free_safe(file->cf_user);
1129 save_file(cf_t * arg_file)
1130 /* Store the informations relatives to the executions
1131 * of tasks at a defined frequency of system's running time */
1134 cf_t *start_file = NULL;
1136 if (arg_file != NULL)
1137 start_file = arg_file;
1139 start_file = file_base;
1142 for (file = start_file; file; file = file->cf_next) {
1144 debug("Saving %s...", file->cf_user);
1146 /* save the file safely : save it to a temporary name, then rename() it */
1147 /* chown the file to root:root : this file should only be read and
1148 * modified by fcron (not fcrontab) */
1149 save_file_safe(file, file->cf_user, "fcron", rootuid, rootgid, now);
1151 if (arg_file != NULL)
1152 /* we have to save only a single file */