]> granicus.if.org Git - fcron/commitdiff
Added options runatreboot, runonce, rebootreset, and cron-like shortcuts @reboot...
authorthib <thib@wind.(none)>
Wed, 22 Sep 2010 23:40:03 +0000 (00:40 +0100)
committerThibault Godouet <fcron@free.fr>
Thu, 23 Sep 2010 23:11:11 +0000 (00:11 +0100)
Save cl_first for all @-lines (we only did it for volatile lines in the past)
added function mail_notrun_time_t()
added function free_line()
added function job_queue_remove()
added function init_default_line()

16 files changed:
Makefile.in
conf.c
conf.h
configure.in
database.c
database.h
doc/en/faq.sgml
doc/en/fcron.8.sgml
doc/en/fcrontab.5.sgml
doc/fcron-doc.mod.in
fcron.c
fcrondyn.c
fileconf.c
global.h
option.h
save.c

index 7f062ef11f320975c4293af96499ec7fcaafdb38..89348026b3da185b8a13d94572f4265c8b7199a5 100644 (file)
@@ -30,6 +30,7 @@ FCRONTABS     = @FCRONTABS@
 PIDDIR         = @PIDDIR@
 FIFODIR                = @FIFODIR@
 PIDFILE                = @PIDFILE@
+REBOOT_LOCK    = @REBOOT_LOCK@
 FIFOFILE       = @FIFOFILE@
 FCRON_SHELL    = @FCRON_SHELL@
 SENDMAIL       = @SENDMAIL@
@@ -118,7 +119,7 @@ exe_list_test: exe_list.o u_list.o exe_list_test.o log.o subs.o
        $(CC) $(CFLAGS) -o $@  exe_list.o u_list.o exe_list_test.o log.o subs.o $(LIBS)
 
 %.o: $(SRCDIR)/%.c $(HEADERSALL) $(SRCDIR)/%.h
-       $(CC) $(CFLAGS) -DPIDFILE="\"${PIDFILE}\"" \
+       $(CC) $(CFLAGS) -DPIDFILE="\"${PIDFILE}\"" -DREBOOT_LOCK="\"${REBOOT_LOCK}\"" \
        -DFIFOFILE="\"${FIFOFILE}\"" -DETC="\"${ETC}\"" \
        -DFCRON_SHELL="\"${FCRON_SHELL}\"" -DFCRON_CONF="\"${FCRON_CONF}\"" \
        -DFCRONTABS="\"${FCRONTABS}\"" \
diff --git a/conf.c b/conf.c
index 105d16053836c7e57c513b3832886b9c12327065..dd0191f7ede6316aa4bac4cebb817825b3795387 100644 (file)
--- a/conf.c
+++ b/conf.c
 #include "conf.h"
 #include "database.h"
 
-int read_file(const char *file_name, cf_t *cf);
+int read_file(const char *file_name, cf_t *cf, int is_system_startup);
 int add_line_to_file(cl_t *cl, cf_t *cf, uid_t runas, char *runas_str,
-                    time_t t_save);
+                    time_t t_save, int is_system_startup);
 int read_strn(int fd, char **str, short int size);
 int read_type(int fd, short int *type, short int *size);
-void synchronize_file(char *file_name);
+void synchronize_file(char *file_name, int is_system_startup);
+void free_line(cl_t *cl);
 
 
 /* this is used to create a list of files to remove, to add */
@@ -65,13 +66,13 @@ reload_all(const char *dir_name)
        f = file_base;
     }
     
-    synchronize_dir(dir_name);
+    synchronize_dir(dir_name, 0);
 
 }
 
 
 void
-synchronize_dir(const char *dir_name)
+synchronize_dir(const char *dir_name, int is_system_startup)
     /* read dir_name and create three list of files to remove,
      * new files and normal files. Then remove each file
      * listed in the first list, then read normal files,
@@ -149,7 +150,7 @@ synchronize_dir(const char *dir_name)
 #endif
            ) {
            explain("adding file %s", list_cur->str);       
-           synchronize_file(list_cur->str);
+           synchronize_file(list_cur->str, is_system_startup);
        }
        else
            error_e("ignoring file \"%s\" : not in passwd file.", list_cur->str);
@@ -165,7 +166,7 @@ synchronize_dir(const char *dir_name)
 #endif
            ) {
            explain("adding new file %s", list_cur->str + 4);
-           synchronize_file(list_cur->str);  
+           synchronize_file(list_cur->str, is_system_startup);
        }
        else
            error_e("ignoring file %s : not in passwd file.", 
@@ -204,7 +205,7 @@ synchronize_dir(const char *dir_name)
 
 
 void
-synchronize_file(char *file_name)
+synchronize_file(char *file_name, int is_system_startup)
 {
 
     cf_t *cur_f = NULL;
@@ -244,7 +245,7 @@ synchronize_file(char *file_name)
 
            /* load new file */
            Alloc(cur_f, cf_t);
-           if ( read_file(file_name, cur_f) != 0 ) {
+           if ( read_file(file_name, cur_f, is_system_startup) != 0 ) {
                /* an error as occured */
                return;
            }
@@ -290,9 +291,21 @@ synchronize_file(char *file_name)
                                + new_l->cl_file->cf_tzdiff;
                        else
                            new_l->cl_nextexe = old_l->cl_nextexe;
-                       insert_nextexe(new_l);
 
-                       if (debug_opt) {
+                        if (is_runonce(new_l->cl_option) && is_runonce(old_l->cl_option)
+                            && is_hasrun(old_l->cl_option)) {
+                            explain("  from last conf: job '%s' with runonce set has "
+                                    "already run since last system startup: not "
+                                    "re-scheduling.", new_l->cl_shell);
+                            set_hasrun(new_l->cl_option);
+                            /* job has already run: remove from the queue */
+                            job_queue_remove(new_l);
+                        }
+                        else
+                            /* update the position in the queue */
+                           insert_nextexe(new_l);
+
+                       if (debug_opt && ! is_hasrun(new_l->cl_option)) {
                            struct tm *ftime;
                            ftime = localtime(&new_l->cl_nextexe);
                            debug("  from last conf: %s next exec %d/%d/%d"
@@ -330,7 +343,7 @@ synchronize_file(char *file_name)
        
            Alloc(cur_f, cf_t);
 
-           if ( read_file(file_name, cur_f) != 0 ) {
+           if ( read_file(file_name, cur_f, is_system_startup) != 0 ) {
                /* an error as occured */
                return;
            }
@@ -354,7 +367,7 @@ synchronize_file(char *file_name)
        
        Alloc(cur_f, cf_t);
 
-       if ( read_file(file_name, cur_f) != 0 ) {
+       if ( read_file(file_name, cur_f, is_system_startup) != 0 ) {
            /* an error as occured */
            return;
        }
@@ -423,7 +436,7 @@ read_type(int fd, short int *type, short int *size)
         }
 
 int
-read_file(const char *file_name, cf_t *cf)
+read_file(const char *file_name, cf_t *cf, int is_system_startup)
     /* read a formated fcrontab.
        return ERR on error, OK otherwise */
 {
@@ -437,6 +450,7 @@ read_file(const char *file_name, cf_t *cf)
     struct passwd *pass = NULL;
     short int type = 0, size = 0;
     int rc;
+    int has_read_cl_first = 0; /* have we read S_FIRST_T? */
 #ifdef WITH_SELINUX
     int flask_enabled = is_selinux_enabled();
     int retval;
@@ -631,6 +645,7 @@ read_file(const char *file_name, cf_t *cf)
        case S_FIRST_T:
            Read(bufi, size, "Error while reading first field");
            cl->cl_first = (time_t) bufi;
+            has_read_cl_first = 1;
            break;
 
        case S_OPTION_T:
@@ -699,14 +714,21 @@ read_file(const char *file_name, cf_t *cf)
            break;
 
        case S_ENDLINE_T:
-           if (add_line_to_file(cl, cf, runas, runas_str, t_save) == 0)
-               Alloc(cl, cl_t);
+            if (is_freq(cl->cl_option) && ! has_read_cl_first) {
+                /* Up to fcron 3.0.X, cl_first/S_FIRST_T was not saved for all @-lines */
+                cl->cl_first = cl->cl_nextexe;
+            }
+           if (add_line_to_file(cl, cf, runas, runas_str, t_save, is_system_startup) != 0)
+                free_line(cl);
+            Alloc(cl, cl_t);
            break;
 
            /* default case in "switch(type)" */
        default:
            error("Error while loading %s : unknown field type %d (ignored)",
                  file_name, type);
+            free_line(cl);
+            Alloc(cl, cl_t);
            /* skip the data corresponding to the unknown field */
            {
                /* we avoid using fseek(), as it seems not to work correctly
@@ -763,7 +785,7 @@ read_file(const char *file_name, cf_t *cf)
 
 
 int
-add_line_to_file(cl_t *cl, cf_t *cf, uid_t runas, char *runas_str, time_t t_save)
+add_line_to_file(cl_t *cl, cf_t *cf, uid_t runas, char *runas_str, time_t t_save, int is_system_startup)
     /* check if the line is valid, and if yes, add it to the file cf */
 {
     time_t slept = now - t_save;
@@ -772,10 +794,6 @@ add_line_to_file(cl_t *cl, cf_t *cf, uid_t runas, char *runas_str, time_t t_save
         cl->cl_mailto == NULL ) {
        error("Line is not valid (empty shell, runas or mailto field)"
              " : ignored");
-       bzero(cl, sizeof(cl));
-       if (cl->cl_shell) free_safe(cl->cl_shell);
-       if (cl->cl_runas) free_safe(cl->cl_runas);
-       if (cl->cl_mailto) free_safe(cl->cl_mailto);
        return 1;
     }
 
@@ -799,9 +817,13 @@ add_line_to_file(cl_t *cl, cf_t *cf, uid_t runas, char *runas_str, time_t t_save
        Set(cl->cl_mailto,cl->cl_file->cf_user);
     }
 
-    /* check if the job hasn't been stopped during execution and insert
-     * it in lavg or serial queue if it was in one at fcron's stops  */
-    if (cl->cl_numexe > 0) {
+    /* job has been stopped during execution: insert it in lavg or serial queue
+     * if it was in one at fcron's stops.  */
+    /* NOTE: runatreboot is prioritary over jobs that were still running
+     * when fcron stops, because the former will get run quicker as they are not
+     * put into the serial queue. runatreboot jobs will be handled later on. */
+    if (cl->cl_numexe > 0 && ! is_runatreboot(cl->cl_option)) {
+
        cl->cl_numexe = 0;
        if ( is_lavg(cl->cl_option) ) {
            if ( ! is_strict(cl->cl_option) )
@@ -820,10 +842,44 @@ add_line_to_file(cl_t *cl, cf_t *cf, uid_t runas, char *runas_str, time_t t_save
        }
     }
 
-    if ( is_td(cl->cl_option) ) {
+    if (is_system_startup || is_volatile(cl->cl_option)) {
+        clear_hasrun(cl->cl_option);
+    }
+
+    if (is_runonce(cl->cl_option) && is_hasrun(cl->cl_option)) {
+        /* if we get here, then is_system_startup is_volatile are both false */
+        /* do nothing: don't re-schedule or add to the job queue */
+        explain("job '%s' with runonce set has already run since last "
+                "system startup: not re-scheduling.", cl->cl_shell);
+    }
+    else if ( is_td(cl->cl_option) ) {
     
        /* set the time and date of the next execution  */
-       if ( cl->cl_nextexe <= now ) {
+        if ( is_system_startup && is_runatreboot(cl->cl_option) ) {
+
+            if ( is_notice_notrun(cl->cl_option) ) {
+
+                if ( cl->cl_runfreq == 1 ) {
+                    /* %-line */
+                   set_next_exe_notrun(cl, SYSDOWN_RUNATREBOOT);
+                }
+                else {
+                    /* set next exe and mail user */
+                    time_t since = cl->cl_nextexe;
+
+                    cl->cl_nextexe = now;
+                    mail_notrun_time_t(cl, SYSDOWN, since);
+                }
+
+            }
+            else {
+                cl->cl_nextexe = now;
+            }
+
+            insert_nextexe(cl);
+
+        }
+        else if ( cl->cl_nextexe <= now ) {
            if ( cl->cl_nextexe == 0 )
                /* the is a line from a new file */
                set_next_exe(cl, NO_GOTO, -1);
@@ -850,44 +906,72 @@ add_line_to_file(cl_t *cl, cf_t *cf, uid_t runas, char *runas_str, time_t t_save
            }
            else {
                if ( is_notice_notrun(cl->cl_option) ) {
-                   /* set next exe and mail user */
-                   struct tm *since2 = localtime(&cl->cl_nextexe);
-                   struct tm since;
-
-                   int tz_changed = 0;
-                   tz_changed = switch_timezone(orig_tz_envvar, cl->cl_tz);
+                    /* set next exe and mail user */
+                    time_t since = cl->cl_nextexe;
 
-                   memcpy(&since, since2, sizeof(since));
                    set_next_exe(cl, NO_GOTO, -1);
-                   mail_notrun(cl, SYSDOWN, &since);
+                    mail_notrun_time_t(cl, SYSDOWN, since);
 
-                   if ( tz_changed > 0 )
-                       switch_back_timezone(orig_tz_envvar);
                } 
                else
                    set_next_exe(cl, NO_GOTO, -1);
            }
        }
-       else
+       else {
            /* value of nextexe is valid : just insert line in queue */
            insert_nextexe(cl);
+        }
     } else {  /* is_td(cl->cl_option) */
        /* standard @-lines */
-       if ( is_volatile(cl->cl_option) ) {
-           /* cl_first is always saved for a volatile line */
-           cl->cl_nextexe = now + cl->cl_first;
-       } else
-           cl->cl_nextexe += slept;
+        if ( is_system_startup && is_runatreboot(cl->cl_option) ) {
+            cl->cl_nextexe = now;
+        }
+        /* t_save == 0 means this is a new file, hence a new line */
+        else if (t_save == 0
+                 || is_volatile(cl->cl_option)
+                 || ( is_system_startup
+                      && ( is_rebootreset(cl->cl_option)
+                           || is_runonce(cl->cl_option) ) ) ) {
+           /* cl_first is always saved to disk for a volatile line */
+            if ( cl->cl_first == LONG_MAX ) {
+                cl->cl_nextexe = LONG_MAX;
+            }
+            else {
+                cl->cl_nextexe = now + cl->cl_first;
+                if ( cl->cl_nextexe < now ) {
+                    /* there was an integer overflow! */
+                    error("Error while setting next exe time for job %s: cl_nextexe"
+                            " overflowed. now=%lu, cl_timefreq=%lu, cl_nextexe=%lu.",
+                            cl->cl_shell, now, cl->cl_timefreq, cl->cl_nextexe);
+                    error("Setting cl_nextexe to LONG_MAX to prevent an infinite loop.");
+                    cl->cl_nextexe = LONG_MAX;
+                }
+            }
+        }
+        else {
+            if ( cl->cl_nextexe != LONG_MAX ) {
+                cl->cl_nextexe += slept;
+                if ( cl->cl_nextexe < now ) {
+                    /* there was an integer overflow! */
+                    error("Error while setting next exe time for job %s: cl_nextexe"
+                            " overflowed. now=%lu, cl_timefreq=%lu, cl_nextexe=%lu.",
+                            cl->cl_shell, now, cl->cl_timefreq, cl->cl_nextexe);
+                    error("Setting cl_nextexe to LONG_MAX to prevent an infinite loop.");
+                    cl->cl_nextexe = LONG_MAX;
+                }
+            }
+        }
  
        if ( cl->cl_timefreq < 10 ) {
            error("Invalid timefreq for %s: set to 1 day",
                  cl->cl_shell);
            cl->cl_timefreq = 3600*24;
        }
-       insert_nextexe(cl);
+
+        insert_nextexe(cl);
     }      
 
-    if (debug_opt) {
+    if (debug_opt && ! (is_runonce(cl->cl_option) && is_hasrun(cl->cl_option)) ) {
        struct tm *ftime;
        ftime = localtime( &(cl->cl_nextexe) );
        debug("  cmd %s next exec %d/%d/%d wday:%d %02d:%02d:%02d"
@@ -898,13 +982,28 @@ add_line_to_file(cl_t *cl, cf_t *cf, uid_t runas, char *runas_str, time_t t_save
     } 
 
     /* add the current line to the list, and allocate a new line */
-    if ( (cl->cl_id = next_id++) >= ULONG_MAX - 1)
+    if ( (cl->cl_id = next_id++) >= ULONG_MAX - 1) {
+        warn("Line id reached %ld: cycling back to zero.", ULONG_MAX);
        next_id = 0;
+    }
     cl->cl_next = cf->cf_line_base;
     cf->cf_line_base = cl;
     return 0;
 }
 
+void
+free_line(cl_t *cl)
+    /* free a line, including its fields */
+{
+    if (cl != NULL) {
+        free_safe(cl->cl_shell);
+        free_safe(cl->cl_runas);
+        free_safe(cl->cl_mailto);
+        free_safe(cl->cl_tz);
+        free_safe(cl);
+    }
+}
+
 void
 delete_file(const char *user_name)
     /* free a file if user_name is not null 
@@ -1008,11 +1107,8 @@ delete_file(const char *user_name)
                else
                    prev_j = j;
 
-           /* free line itself */
-           free_safe(line->cl_shell);
-           free_safe(line->cl_runas);
-           free_safe(line->cl_mailto);
-           free_safe(line);
+            /* free line itself */
+            free_line(line);
        }
        /* delete_file() MUST remove only the first occurrence :
         * this is needed by synchronize_file() */
diff --git a/conf.h b/conf.h
index 48a80159472404fd59feaadfac5faa6b48074103..c8d8d7c3c29110c499d7ff68d7f84fb2993619ce 100644 (file)
--- a/conf.h
+++ b/conf.h
@@ -28,7 +28,7 @@
 
 /* functions prototypes */
 extern void reload_all(const char *dir_name);
-extern void synchronize_dir(const char *dir_name);
+extern void synchronize_dir(const char *dir_name, int is_system_startup);
 extern void delete_file(const char *user_name);
 extern void save_file(struct cf_t *file_name);
 
index 079d0c2caac87669b6812e1daaa87c0093bd570a..86da65b2d7dd0a3e881f7ca91012e110063e4f48 100644 (file)
@@ -290,8 +290,10 @@ AC_ARG_WITH(piddir,
 )
 AC_MSG_RESULT([$PIDDIR])
 PIDFILE="${PIDDIR}/fcron.pid"
+REBOOT_LOCK="${PIDDIR}/fcron.reboot"
 AC_SUBST(PIDDIR)
 AC_SUBST(PIDFILE)
+AC_SUBST(REBOOT_LOCK)
 
 FIFODIR="${localstatedir}/run"
 AC_MSG_CHECKING(location of fifo files)
index d66a48693c42c553c52ab5556f6e56a78e7b5968..95a4e44d8646f2a504a2dd247967d0e89fb3c6a1 100644 (file)
@@ -41,32 +41,6 @@ void run_serial_job(void);
 void run_lavg_job(lavg_t *l);
 void run_queue_job(cl_t *line);
 
-void
-run_reboot_jobs(void)
-{
-    int reboot = 0;
-    struct job_t *j;
-
-    /* lock exist - skip reboot jobs */
-    if (access(REBOOT_LOCK, F_OK) == 0) {
-        info("@reboot jobs will only be run at computer's startup.");
-        return;
-    }
-    /* lock doesn't exist - create lock, run reboot jobs */
-    if ((reboot = creat(REBOOT_LOCK, S_IRUSR & S_IWUSR)) < 0)
-        error_e("Can't create lock for reboot jobs.");
-    else
-        close(reboot);
-
-    for (u = db->head; u != NULL; u = u->next) {
-        for (e = u->crontab; e != NULL; e = e->next) {
-            if (e->flags & WHEN_REBOOT)
-                job_add(e, u);
-        }
-    }
-    (void) job_runqueue();
-}
-
 void
 test_jobs(void)
   /* determine which jobs need to be run, and run them. */
@@ -77,23 +51,32 @@ test_jobs(void)
     debug("Looking for jobs to execute ...");
 /*      // */
 
-    while ( (j=queue_base) && j->j_line->cl_nextexe <= now ){
-       if ( j->j_line->cl_remain > 0 && --(j->j_line->cl_remain) > 0) {
-           set_next_exe(j->j_line, STD, -1);
+    while ( (j=queue_base) && j->j_line->cl_nextexe <= now ) {
+
+        if ( j->j_line->cl_remain > 0 && --(j->j_line->cl_remain) > 0) {
            debug("    cl_remain: %d", j->j_line->cl_remain);
-           continue ;
        }
+        else {
 
-       j->j_line->cl_remain = j->j_line->cl_runfreq;
+            j->j_line->cl_remain = j->j_line->cl_runfreq;
 
-       if ( is_lavg(j->j_line->cl_option) )
-           add_lavg_job(j->j_line, -1);
-       else if ( is_serial(j->j_line->cl_option) )
-           add_serial_job(j->j_line, -1);
-       else
-           run_normal_job(j->j_line, -1);
+            if ( is_lavg(j->j_line->cl_option) )
+                add_lavg_job(j->j_line, -1);
+            else if ( is_serial(j->j_line->cl_option) )
+                add_serial_job(j->j_line, -1);
+            else
+                run_normal_job(j->j_line, -1);
+
+            set_hasrun(j->j_line->cl_option);
+        }
        
-       set_next_exe(j->j_line, STD, -1);
+        if ( is_runonce(j->j_line->cl_option) && is_hasrun(j->j_line->cl_option) ) {
+            explain("Line %s has runonce set: not re-scheduling it.", j->j_line->cl_shell);
+            job_queue_remove(j->j_line);
+        }
+       else {
+               set_next_exe(j->j_line, STD, -1);
+       }
     }
 
 }
@@ -263,68 +246,79 @@ run_queue_job(cl_t *line)
 
 }
 
+job_t *
+job_queue_remove(cl_t *line)
+    /* remove a job from the queue list
+     * returns a pointer to the previous entry,
+     * or NULL if the line either wasn't in the queue or was the first entry */
+{
+    struct job_t *j;
+    struct job_t *jprev = NULL;
+
+    if (queue_base == NULL)
+        return NULL;
+
+    /* find the job in the list */
+    for (j = queue_base; j != NULL ; jprev = j, j = j->j_next) {
+        if ( j->j_line == line ) {
+            /* remove it from the list */
+            if (jprev != NULL) {
+                jprev->j_next = j->j_next;
+            }
+            else
+                /* first element of the list */
+                queue_base = j->j_next;
+
+            free_safe(j);
+            return jprev;
+        }
+    }
 
+    /* the job wasn't there */
+    return NULL;
+}
 
 void
 insert_nextexe(cl_t *line)
-    /* insert a job the queue list */
+    /* insert a job at the right position in the job queue */
 {
-    struct job_t *newjob;
-
-    if (queue_base != NULL) {
-       struct job_t *j;
-       struct job_t *jprev = NULL;
-       struct job_t *old_entry = NULL;
-
-       /* find the job in the list */
-       for (j = queue_base; j != NULL ; j = j->j_next)
-           if ( j->j_line == line ) {
-               old_entry = j;
-               /* remove it from the list */
-               if (jprev != NULL) {
-                   jprev->j_next = j->j_next;
-                   j = jprev;
-               }
-               else
-                   /* first element of the list */
-                   j = queue_base = j->j_next;
+    struct job_t *newjob = NULL;
+    struct job_t *j = NULL;
+    struct job_t *jprev = NULL;
 
-               break;
-           }
-           else
-               jprev = j;
-
-       jprev = NULL;
-       if (j == NULL || line->cl_nextexe < j->j_line->cl_nextexe)
-           j = queue_base;
-       while (j != NULL && (line->cl_nextexe >= j->j_line->cl_nextexe)) {
-           jprev = j;
-           j = j->j_next;
-       }           
+    Alloc(newjob, job_t);
+    newjob->j_line = line;
+    newjob->j_next = NULL;
 
-       if (old_entry == NULL) {
-           /* this job wasn't in the queue : we append it */
-           Alloc(newjob, job_t);
-           newjob->j_line = line;
-       }
-       else
-           /* this job was already in the queue : we move it */
-           newjob = old_entry;
-
-       newjob->j_next = j;
+    if (queue_base == NULL) {
+       /* no job in queue */
+       queue_base = newjob;
+        return;
+    }
 
-       if (jprev == NULL)
-           queue_base = newjob;
-       else
-           jprev->j_next = newjob;
+    jprev = job_queue_remove(line);
+    j = (jprev)? jprev : queue_base;
 
+    /* check if we should start from queue_base or from jprev
+     * (in some cases, e.g. fcrontab has just been edited, the line should
+     *  be moved *forward* in the queue) */
+    if (jprev == NULL || line->cl_nextexe < jprev->j_line->cl_nextexe ) {
+        j = queue_base;
     }
-    else {
-       /* no job in queue */
-       Alloc(newjob, job_t);
-       newjob->j_line = line;      
-       queue_base = newjob;
+
+    /* a job can only be moved back */
+    while (j != NULL && (line->cl_nextexe >= j->j_line->cl_nextexe)) {
+        jprev = j;
+        j = j->j_next;
     }
+    /* when we get out from the while(), newjob should be added between jprev and j */
+
+    newjob->j_next = j;
+
+    if (jprev == NULL)
+        queue_base = newjob;
+    else
+        jprev->j_next = newjob;
 
 }
 
@@ -1195,7 +1189,28 @@ set_next_exe(cl_t *line, char option, int info_fd)
     }
     else {
        /* this is a job based on system up time */
-       line->cl_nextexe = basetime + line->cl_timefreq;
+
+        if ( line->cl_timefreq == LONG_MAX ) {
+            /* when timefreq is set to LONG_MAX, it means that next time nextexe
+             * is updated we want it to be the furthest away possible so as the job
+             * is never executed again (unless at the next reboot/fcron startup
+             * if the line as the appropriate options set) */
+            /* NOTE: the options runonce/hasrun should be used to achieve this,
+             *       but we keep this here as an extra safety */
+            debug("Setting cl_nextexe to LONG_MAX to prevent the line from running again.");
+            line->cl_nextexe = LONG_MAX;
+        }
+        else {
+            line->cl_nextexe = basetime + line->cl_timefreq;
+            if ( line->cl_nextexe <= basetime ) {
+                /* there was an integer overflow! */
+                error("Error while setting next exe time for job %s: cl_nextexe"
+                        " overflowed. basetime=%lu, cl_timefreq=%lu, cl_nextexe=%lu.",
+                        line->cl_shell, basetime, line->cl_timefreq, line->cl_nextexe);
+                error("Setting cl_nextexe to LONG_MAX to prevent an infinite loop.");
+                line->cl_nextexe = LONG_MAX;
+            }
+        }
 
        ft = localtime( &(line->cl_nextexe) );
 
@@ -1242,7 +1257,7 @@ set_next_exe_notrun(cl_t *line, char context)
      * of the function. */
     tz_changed = switch_timezone(orig_tz_envvar, line->cl_tz);
     
-    if (context == SYSDOWN) {
+    if (context == SYSDOWN || context == SYSDOWN_RUNATREBOOT) {
        /* handle timezone differences */
        previous_period = line->cl_nextexe - (line->cl_file->cf_tzdiff * 3600);
        set_next_exe_opt = NO_GOTO;
@@ -1264,7 +1279,11 @@ set_next_exe_notrun(cl_t *line, char context)
     move_time_to(BEGIN_NEXT_PERIOD, line, &ftime);
     next_period = mktime_no_dst(&ftime) + (line->cl_file->cf_tzdiff * 3600);
 
-    set_next_exe(line, set_next_exe_opt, -1);
+    if ( context == SYSDOWN_RUNATREBOOT )
+        line->cl_nextexe = now;
+    else
+        set_next_exe(line, set_next_exe_opt, -1);
+
     if ( line->cl_nextexe >= next_period ) {
        /* line has not run during one or more period(s) : send a mail */
        mail_notrun(line, context, &last_nextexe);
@@ -1275,6 +1294,26 @@ set_next_exe_notrun(cl_t *line, char context)
 
 }
 
+void
+mail_notrun_time_t(cl_t *line, char context, time_t since_time_t)
+/* Same as mail_notrun() but with 'since' defined as a time_t instead of a struct tm */
+{
+    struct tm *since2 = NULL;
+    struct tm since;
+    int tz_changed = 0;
+
+    since2 = localtime(&line->cl_nextexe);
+    memcpy(&since, since2, sizeof(since));
+
+    tz_changed = switch_timezone(orig_tz_envvar, line->cl_tz);
+
+    mail_notrun(line, SYSDOWN, &since);
+
+    if ( tz_changed > 0 )
+        switch_back_timezone(orig_tz_envvar);
+
+}
+
 void
 mail_notrun(cl_t *line, char context, struct tm *since)
     /* send a mail to tell user a job has not run (and why) */
index c1aaeccc1ec8ef4da304914211c04af95133724b..394e2da469533eb24d6a407f638ba6461716c90b 100644 (file)
@@ -37,10 +37,13 @@ extern void set_next_exe(struct cl_t *line, char option, int info_fd);
 #define NO_GOTO_LOG 2  /* set_next_exe() : NO_GOTO but also log nextexe time */
 #define FROM_CUR_NEXTEXE 4 /* set_next_exe() : compute from nextexe, not now */
 extern void set_next_exe_notrun(struct cl_t *line, char context);
-#define LAVG 1         /* set_next_exe_notrun() : context */
-#define SYSDOWN 2      /* set_next_exe_notrun() : context */
-#define QUEUE_FULL 3   /* set_next_exe_notrun() : context */
+#define LAVG 1                /* set_next_exe_notrun() : context */
+#define SYSDOWN 2             /* set_next_exe_notrun() : context */
+#define QUEUE_FULL 3          /* set_next_exe_notrun() : context */
+#define SYSDOWN_RUNATREBOOT 4 /* set_next_exe_notrun() : context */
 extern void mail_notrun(struct cl_t *line, char context, struct tm *since);
+extern void mail_notrun_time_t(cl_t *line, char context, time_t since_time_t);
+extern job_t * job_queue_remove(cl_t *line);
 extern void insert_nextexe(struct cl_t *line);
 extern void run_normal_job(cl_t *line, int fd);
 extern void add_serial_job(struct cl_t *line, int fd);
index 4b464d99fe34053447e2a196aaa6b93f5c95bf1c..1ad52493114dc834dae954b7649c86e641a0ff78 100644 (file)
@@ -122,7 +122,7 @@ every day at 2:30, it will run at 3:30 the day of this kind of DST change.
       Suppose you have an user called "echo" (weird idea ... :)) ). If you use
 the line '* * * * * echo "Hello!"' in root's fcrontab, "echo" will be
 interpreted as "runas(echo)".</para>
-               <para>To suppress that, put your command in quotes:
+               <para>To avoid that, put your command in quotes:
 <programlisting>* * * * * 'echo "Hello!"'</programlisting> will work as
 expected as quotes are allowed for the shell command but not for the user
 name.</para>
@@ -133,13 +133,13 @@ name.</para>
            <question>
                <para>
        I have a job which usually terminates with a non-zero status. When it
-does, I receive a mail with the exit status even if the command had no output.
-How can I avoid the mail?</para>
+does, I receive a email with the exit status even if the command had no output.
+How can I avoid the email?</para>
            </question>
            <answer>
                <para>
-      You could disable mail entirely by setting the "mail" option to "no". But,
-if you still want to receive the standard output as mail, you can add an command
+      You could disable email entirely by setting the "mail" option to "no". But,
+if you still want to receive the standard output as email, you can add an command
 which always evaluates to 0, like "/bin/true", after your primary command. This
 will not affect your job nor create additional output. For example:</para>
                <programlisting>
@@ -222,7 +222,7 @@ fcron -f -y -o
 </programlisting>
                    </example></para>
                <para>You can also use fcron to run some jobs until the end of
-the connection. For instance, you can make fetchmail retrieve mails more often
+the connection. For instance, you can make fetchmail retrieve emails more often
 during connection: we suppose that it is configured to retrieve mails every
 hour, which launches a dialup connection if necessary, and we want it to check
 for mails every 5 minutes while connected.</para>
@@ -252,9 +252,9 @@ killall -TERM fcron
                <para>If you run fcron in several scripts, or if you run fcron
 as a daemon and want also to run fcron in scripts, then you should use fcron,
 fcrontab and fcrondyn's <option>--configfile</option>.</para>
-               <para>For more details, see fcron's options
-<option>--once</option>, <option>--nosyslog</option>,
-<option>--firstsleep</option> and <option>--configfile</option> in <link
+               <para>For more details, see fcron's command line arguments
+&argonce;, <option>--nosyslog</option>,
+&argfirstsleep; and <option>--configfile</option> in <link
 linkend="fcron.8">&fcron;(8)</link>, and fcrontab's options &optvolatile;,
 &optstdout;, &optfirst; in <link
 linkend="fcrontab.5">&fcrontab;(5)</link></para>
@@ -307,33 +307,33 @@ and set appropriately the paths:</para>
               <para>Has fcron some incompatibilities with Vixie cron?</para>
            </question>
            <answer>
-              <para>As far as I know, fcron supports completely the syntax of
-                 Vixie cron's crontab, excepted the @* syntax (@annually,
-                 @weekly, etc: if you use that, you will have to replace it
-                 with the explicit equivalent given in crontab(5)). So you
-                 should not have much problem with that (otherwise, please
+              <para>As far as I know, fcron supports Vixie cron's crontab syntax
+                 fully. You should be able to use a crontab
+                  with fcron with no modification (if not please
                  contact me at &email;).</para>
               <para>The main problem is about the management of the system (f)crontab.
                  Vixie cron monitors the changes on /etc/crontab every minute,
                  and automatically takes into account the changes if any.
-                 As for now, fcron do not do that. Fcron do not support the
-                 /etc/cron.d/ dir too, as it is just an extension of the /etc/crontab
+                 As for now, fcron does not do that by itself.
+                  Fcron does not support the
+                 /etc/cron.d/ directory either, as it is just an extension to the /etc/crontab
                  file.
-                 But be reassured: /etc/cron.{daily,weekly,monthly} are supported
-                 by fcron (in fact, those dirs are not managed by fcron directly,
-                 but by run-parts, which is independent from fcron).</para>
-              <para>So if you want to replace transparently Vixie cron by fcron,
-                 all you have to do is creating a /usr/bin/crontab link to
-                 /usr/bin/fcrontab, and to reinstall the system (f)crontab
-              with fcrontab /etc/crontab each time you modify it
-                 (if you find something else to do, please tell me!).</para>
+                 However /etc/cron.{daily,weekly,monthly} directories will work
+                 in fcron just fine: they are supported through the run-parts program, just as Vixie cron).</para>
+              <para>So if you want to replace Vixie cron by fcron transparently,
+                 all you have to do is create a /usr/bin/crontab link to
+                 /usr/bin/fcrontab, and reinstall the system (f)crontab
+                 with 'fcrontab /etc/crontab' every time you modify it
+                 (if you needed some more work than that, please let me know!).</para>
               <para>You can also use the script script/check_system_crontabs
-                 to generate a system fcrontab from /etc/(f)crontab and /etc/cron.d/,
-                 and install it automatically. If you choose to do that, take 
+                 to monitor for system (f)crontab changes, i.e. changes to /etc/(f)crontab
+                  and /etc/cron.d/. When it detects a change, it will generate
+                  a new system fcrontab, and install it automatically.
+                  Should you choose to use that script, please take
                  a look at the beginning of the script: you will find insctructions
                  on how to use it -- and a few warnings you should pay attention to.
-                 With this script, the behavior of fcron should be very similar
-                 to Vixie cron's one concerning /etc/crontab and /etc/cron.d/.
+                 With this script, the fcron's behavior should be very similar
+                 to Vixie cron regarding /etc/crontab and /etc/cron.d/.
               </para>
            </answer>
         </qandaentry>
@@ -346,18 +346,22 @@ and set appropriately the paths:</para>
                  Thus, fcron features similar functionalities to anacron, but it
                  has different means to achieve it, in other words other ways to define
                  when a job should run. Fcron is in general much more flexible
-                 than anacron. That said,
-                 there is no strict equivalent to an anacron entry: the best would
-                 probably be to have a look at 
+                 than anacron.
+                  The best thing to do is to have look at
                  <link linkend="fcrontab.5">&fcrontab;(5),</link> and choose the
                  type of line which is the most appropriate for your needs (this is
                  likely to be a @-line or a %-line).</para>
-              <para>If you insist on willing to emulate an anacron entry, as precisely
-                 as possible, you should use a %daily, %monthly or %weekly line. If
-              this doesn't emulate an anacron entry as closely as you would like,
-              you may want to try a %hours or %days line, and/or &optrunfreq;.</para>
-              <para>In any case, you would benefit from using fcron options such as 
-              &optserial; or &optlavg;.</para>
+              <para>However if you do want to emulate an anacron entry of the form:</para>
+              <programlisting>0 delay job-identity /your/command</programlisting>
+              <para>then use something as:</para>
+              <programlisting>@runonce delay /your/command</programlisting>
+              <para>If you use a non-zero anacron period/period_name (i.e. if you
+                  don't want your task to be run every day), then
+                  you should use a %daily, %monthly or %weekly line. If
+               this doesn't emulate an anacron entry as closely as you would like,
+               you may want to try a %hours or %days line, and/or &optrunfreq;.</para>
+               <para>In any case, you could benefit from using fcron options such as
+               &optserial; or &optlavg;.</para>
            </answer>
         </qandaentry>
         <qandaentry>
@@ -365,9 +369,9 @@ and set appropriately the paths:</para>
               <para>How can I emulate a Vixie cron @reboot entry?</para>
            </question>
            <answer>
-              <para>You should use a line similar to the following one:</para>
-              <para>@volatile,first(1) BIG-period /your/command</para>
-              <para>This will run /your/command one minute after every reboot.</para>
+              <para>No need to emulate any more, as
+                <link linkend="fcrontab.5.shortcuts">Vixie cron shortcuts</link>,
+                 including @reboot, are now supported by fcron!</para>
            </answer>
         </qandaentry>
     </qandaset>
index 117d232311d0e7635381d3a324885c7042eab604..5f29744a23c4c1552a09051413f008a53f22f962 100644 (file)
@@ -149,12 +149,12 @@ can run simultaneously on an only system (but each &fcron; process *must* have a
 different spool dir and pid file from the other processes).</para>
                </listitem>
            </varlistentry>
-           <varlistentry>
+           <varlistentry id="fcron.8.once">
                <term><option>-o</option></term>
                <term><option>--once</option></term>
                <listitem>
                    <para>Execute all jobs that need to be run at the time &fcron;
-was started, wait for them, then return. Sets <option>firstsleep</option> to 0.
+was started, wait for them, then return. Sets &argfirstsleep; to 0.
 May be especially useful when used with options <option>-y</option> and
 <option>-f</option> in a script run, for instance, at dialup connection.</para>
                    <para>&seealso; fcrontab's options &optvolatile;,
@@ -169,7 +169,7 @@ May be especially useful when used with options <option>-y</option> and
 in foreground.</para>
                </listitem>
            </varlistentry>
-           <varlistentry>
+           <varlistentry id="fcron.8.firstsleep">
                <term><option>-l</option> <replaceable>time</replaceable></term>
                <term><option>--firstsleep</option>
 <replaceable>time</replaceable></term>
@@ -304,4 +304,4 @@ Local variables:
 mode: sgml
 sgml-parent-document:("fcron-doc.sgml" "book" "chapter" "sect1" "")
 End:
--->
\ No newline at end of file
+-->
index 1a199031f0128d8f4fc6bb00baf5d6fa952743f0..5146cc8b7656e9fdb8f3f5d2cf3e7e521e831c62 100644 (file)
@@ -385,6 +385,112 @@ will run only ONCE either at 2:15, 3:15 OR 4:15.</para>
 
        </refsect2>
 
+       <refsect2 id="fcrontab.5.shortcuts">
+           <title>Vixie cron shortcuts</title>
+           <para>To ensure a good compatibility with Vixie cron, Vixie cron shortcuts are supported. Generally speaking their usage is not recommended as they lack some of the flexibility brought by fcron. Also where the precise time of execution is not critical, the use <link
+linkend="uptent">lines based on elapsed system up time</link> is recommended instead.</para>
+           <para>A task using a Vixie cron shortcut is of the form:</para>
+           <programlisting>shortcut command</programlisting>
+           <para>Below is a list of available shortcuts with their fcron equivalent:</para>
+           <table>
+               <title>Vixie cron shortcuts </title>
+               <tgroup cols="2">
+                   <thead>
+                       <row>
+                           <entry>shortcut: </entry>
+                           <entry>meaning: </entry>
+                           <entry>fcron equivalent: </entry>
+                           <entry>suggested alternative: </entry>
+                       </row>
+                   </thead>
+                   <tbody>
+                       <row>
+                           <entry>@reboot </entry>
+                           <entry>Run once, at startup </entry>
+                           <entry>@runatreboot,runonce 1 </entry>
+                           <entry>   </entry>
+                       </row>
+                       <row>
+                           <entry>@yearly </entry>
+                           <entry>Run once a year </entry>
+                           <entry>0 0 1 1 * </entry>
+                           <entry>@ 12m </entry>
+                       </row>
+                       <row>
+                           <entry>@annually </entry>
+                           <entry>(same as @yearly) </entry>
+                           <entry>0 0 1 1 * </entry>
+                           <entry>@ 12m </entry>
+                       </row>
+                       <row>
+                           <entry>@monthly </entry>
+                           <entry>Run once a month </entry>
+                           <entry>0 0 1 * * </entry>
+                           <entry>@ 1m </entry>
+                       </row>
+                       <row>
+                           <entry>@weekly </entry>
+                           <entry>Run once a week </entry>
+                           <entry>0 0 * * 0 </entry>
+                           <entry>@ 1w </entry>
+                       </row>
+                       <row>
+                           <entry>@daily </entry>
+                           <entry>Run once a day </entry>
+                           <entry>0 0 * * * </entry>
+                           <entry>@ 1d </entry>
+                       </row>
+                       <row>
+                           <entry>@midnight </entry>
+                           <entry>(same as @daily) </entry>
+                           <entry>0 0 * * * </entry>
+                           <entry> </entry>
+                       </row>
+                       <row>
+                           <entry>@hourly </entry>
+                           <entry>Run once an hour </entry>
+                           <entry>0 * * * * </entry>
+                           <entry>@ 1h </entry>
+                       </row>
+                   </tbody>
+               </tgroup>
+           </table>
+
+
+           <para>A few examples:</para>
+           <programlisting># run check_laptop_logs.sh at the first minute of every hour:
+@hourly check_laptop_logs.sh
+# run check_web_server.sh and check_file_server.sh every day at exactly
+# midnight, both at the same time:
+@daily check_web_server.sh
+@daily check_file_server.sh
+# run compress_home_made_app_log_files.sh at exactly midnight
+# on the first day of every month:
+@monthly compress_home_made_app_log_files.sh</programlisting>
+           <para>However you might want to replace those task definitions by something as:</para>
+           <programlisting># run check_laptop_logs.sh after every hour of system up time:
+@ 60 check_laptop_logs.sh
+# run check_web_server.sh and check_file_server.sh every night between midnight
+# and 3am, one by after the other:
+%nightly,serial * 0-3 check_web_server.sh
+%nightly,serial * 0-3 check_file_server.sh
+# Run compress_home_made_app_log_files.sh once a month, only at night
+# when the load is low:
+@monthly,lavg(0.5) * 21-23,0-5 * compress_home_made_app_log_files.sh</programlisting>
+
+
+           <para>Last, but not least, it should be noted that tasks defined using a Vixie cron shortcut will only have the same behaviour as in Vixie cron if they are not modified by some earlier option definition. That will be the case if you import a Vixie cron crontab into fcron without modification, or if you precede the task definition by a &optreset;, e.g.:</para>
+           <programlisting>!serial
+@ 10 fcron_task_1
+@ 25 fcron_task_2
+!reset
+@reboot start_unprivileged_user_program
+@daily cleanup_tmp.sh</programlisting>
+           <para>In the example above, &optserial; would apply to the last two tasks if we hadn't used &optreset;.</para>
+       </refsect2>
+
+
+
        <refsect2>
            <title>Options</title>
            <para>The options can be set either for every line below the
@@ -626,6 +732,18 @@ for most uses.</para>
                    </listitem>
                </varlistentry>
 
+               <varlistentry id="fcrontab.5.rebootreset">
+                   <term>rebootreset</term>
+                   <listitem>
+                       <para><emphasis><type>boolean</type></emphasis>(<constant>false</constant>)</para>
+                       <para>When set to true, fcron will act as if the task was
+a new one every time the OS reboots. This is very similar to the option &optvolatile;
+but based on the OS reboots instead of fcron restarts.
+You may also want to use option &optfirst; if you use fcron that way.</para>
+                       <para>&seealso; options &optfirst;, &optvolatile;, &optrunonce;, &optrunatreboot;.</para>
+                   </listitem>
+               </varlistentry>
+
                <varlistentry id="fcrontab.5.reset">
                    <term>reset</term>
                    <listitem>
@@ -643,6 +761,15 @@ permissions and environment (only root is allowed to use this option).</para>
                    </listitem>
                </varlistentry>
 
+               <varlistentry id="fcrontab.5.runatreboot">
+                   <term>runatreboot</term>
+                   <listitem>
+                       <para><emphasis><type>boolean</type></emphasis>(<constant>false</constant>)</para>
+                       <para>If set to true, the task will be run at system startup (i.e. immediately after the &argfirstsleep; delay -- by default, &firstsleep; seconds -- when the &fcron; daemon starts the first time after the OS has booted). This is in addition to the regular schedule which won't be modified by this option.</para>
+                       <para>&seealso; options &optvolatile;, &optrunonce;, &optrebootreset;.</para>
+                   </listitem>
+               </varlistentry>
+
                <varlistentry id="fcrontab.5.runfreq">
                    <term>runfreq</term>
                    <term>r</term>
@@ -654,6 +781,15 @@ linkend="uptent">lines based on elapsed system up time</link>).</para>
                    </listitem>
                </varlistentry>
 
+               <varlistentry id="fcrontab.5.runonce">
+                   <term>runonce</term>
+                   <listitem>
+                       <para><emphasis><type>boolean</type></emphasis>(<constant>false</constant>)</para>
+                       <para>Do not re-schedule the task after it has run once, until the next OS reboot (if &optvolatile; is not set) or until the next &fcron; daemon restart (if &optvolatile; is set).</para>
+                       <para>&seealso; options &optvolatile;, &optrebootreset;, &optrunatreboot;.</para>
+                   </listitem>
+               </varlistentry>
+
                <varlistentry id="fcrontab.5.serial">
                    <term>serial</term>
                    <term>s</term>
@@ -683,7 +819,7 @@ simultaneously?</para>
                        <para><emphasis><type>boolean</type></emphasis>(<constant>false</constant>)</para>
                        <para>If fcron is running in the foreground, then also
 let jobs print to stderr/stdout instead of mailing or discarding it.</para>
-                       <para>&seealso; fcron's option <option>--once</option>
+                       <para>&seealso; fcron's option &argonce;
 in <link linkend="fcron.8">&fcron;(8)</link>.</para>
                    </listitem>
                </varlistentry>
@@ -742,14 +878,14 @@ the load average. Set until to 0 to remove the timeout.</para>
                    <listitem>
                        <para><emphasis><type>boolean</type></emphasis>(<constant>false</constant>)</para>
                        <para>When set to true, the job is based on a "volatile"
-system up time, i.e. restart counting each time fcron is started, which is
-useful when fcron is started by a script running only, for instance, during a
+system up time, i.e. restart counting each time &fcron; is started, which is
+useful when &fcron; is started by a script running only, for instance, during a
 dialup connection: the "volatile" system up time then refers to the dialup
 connection time. You may also want to use option &optfirst; if you use fcron
 that way.</para>
-                       <para>&seealso; options &optfirst;, &optstdout;, <link
-linkend="uptent">lines based on elapsed system up time</link>, fcron's option
-<option>--once</option> in <link linkend="fcron.8">&fcron;(8)</link>.</para>
+                       <para>&seealso; options &optfirst;, &optstdout;, &optrebootreset;,
+<link linkend="uptent">lines based on elapsed system up time</link>, fcron's command line argument
+&argonce; in <link linkend="fcron.8">&fcron;(8)</link>.</para>
                    </listitem>
                </varlistentry>
            </variablelist>
index a09b301fe6e5c4a24c5506a517a2def846a0be66..cc3750544efbcc5b19cd32830d0a43af9d5b4476 100644 (file)
@@ -3,6 +3,8 @@
 <!-- Stable release: -->
 <!ENTITY % devrelease "IGNORE">
 
+<!-- General information -->
+
 <!ENTITY date "@@Date@">
 <!ENTITY version "@@VERSION_QUOTED@">
 <!ENTITY copyrightdate "2000-2008">
@@ -15,6 +17,8 @@
 <!ENTITY docurlEN '<ulink url="&webpageadr/files/fcron-doc-en-html.tar.gz">&webpageadr/files/fcron-doc-html.tar.gz</ulink>'>
 <!ENTITY docurlFR '<ulink url="&webpageadr/files/fcron-doc-fr-html.tar.gz">&webpageadr/files/fcron-doc-html.tar.gz</ulink>'>
 
+<!-- Compilation variables -->
+
 <!ENTITY editor "@@FCRON_EDITOR@">
 <!ENTITY etc "@@ETC@">
 <!ENTITY exiterr "@@EXIT_ERR@">
@@ -36,6 +40,7 @@
 <!ENTITY shell "@@FCRON_SHELL@">
 <!ENTITY sysfcrontab "@@SYSFCRONTAB@">
 
+<!-- shortcuts -->
 
 <!ENTITY seealso "<emphasis>See also</emphasis>:">
 <!ENTITY voiraussi "<emphasis>Voir aussi</emphasis>&nbsp;:">
@@ -49,6 +54,7 @@
 <!ENTITY fcron.conf "<systemitem>fcron.conf</systemitem>">
 <!ENTITY pam "<application>pam</application>">
 
+<!-- documents -->
 
 <!ENTITY about SYSTEM "readme.sgml">
 <!ENTITY install SYSTEM "install.sgml">
 <!ENTITY fdl SYSTEM "fdl.sgml">
 <!ENTITY gpl SYSTEM "gpl.sgml">
 
+<!-- command line arguments -->
+<!ENTITY argfirstsleep '<link linkend="fcron.8.firstsleep"><varname>--sleeptime</varname></link>'>
+<!ENTITY argonce '<link linkend="fcron.8.once"><varname>--once</varname></link>'>
+
+<!-- fcrontab options -->
 
 <!ENTITY optbootrun '<link linkend="fcrontab.5.bootrun"><varname>bootrun</varname></link>'>
 <!ENTITY optdayand '<link linkend="fcrontab.5.dayand"><varname>dayand</varname></link>'>
 <!ENTITY optnolog '<link linkend="fcrontab.5.nolog"><varname>nolog</varname></link>'>
 <!ENTITY optnoticenotrun '<link linkend="fcrontab.5.noticenotrun"><varname>noticenotrun</varname></link>'>
 <!ENTITY optrandom '<link linkend="fcrontab.5.random"><varname>random</varname></link>'>
+<!ENTITY optrebootreset '<link linkend="fcrontab.5.rebootreset"><varname>rebootreset</varname></link>'>
 <!ENTITY optreset '<link linkend="fcrontab.5.reset"><varname>reset</varname></link>'>
 <!ENTITY optrunas '<link linkend="fcrontab.5.runas"><varname>runas</varname></link>'>
+<!ENTITY optrunatreboot '<link linkend="fcrontab.5.runatreboot"><varname>runatreboot</varname></link>'>
 <!ENTITY optrunfreq '<link linkend="fcrontab.5.runfreq"><varname>runfreq</varname></link>'>
+<!ENTITY optrunonce '<link linkend="fcrontab.5.runonce"><varname>runonce</varname></link>'>
 <!ENTITY optserial '<link linkend="fcrontab.5.serial"><varname>serial</varname></link>'>
 <!ENTITY optserialonce '<link linkend="fcrontab.5.serialonce"><varname>serialonce</varname></link>'>
 <!ENTITY optstdout '<link linkend="fcrontab.5.stdout"><varname>stdout</varname></link>'>
diff --git a/fcron.c b/fcron.c
index f3d195e642a0419f8375e39a020a781651ff2a64..6a72d00336eb869d8b5259b5367e03714f51d3af 100644 (file)
--- a/fcron.c
+++ b/fcron.c
@@ -48,6 +48,7 @@ RETSIGTYPE sigusr1_handler(int x);
 RETSIGTYPE sigusr2_handler(int x);
 int parseopt(int argc, char *argv[]);
 void get_lock(void);
+int is_system_reboot(void);
 void create_spooldir(char *dir);
 
 /* command line options */
@@ -272,6 +273,27 @@ get_lock()
 
 }
 
+int
+is_system_startup(void)
+{
+    int reboot = 0;
+
+    /* lock exist - skip reboot jobs */
+    if (access(REBOOT_LOCK, F_OK) == 0) {
+        explain("@reboot jobs will only be run at computer's startup.");
+        /* don't run @reboot jobs */
+        return 0;
+    }
+    /* lock doesn't exist - create lock, run reboot jobs */
+    if ((reboot = creat(REBOOT_LOCK, S_IRUSR & S_IWUSR)) < 0)
+        error_e("Can't create lock for reboot jobs.");
+    else
+        close(reboot);
+
+    /* run @reboot jobs */
+    return 1;
+}
+
 
 int
 parseopt(int argc, char *argv[])
@@ -671,7 +693,7 @@ check_signal()
 
        if (sig_conf == 1) {
            /* update configuration */
-           synchronize_dir(".");
+           synchronize_dir(".", 0);
            sig_conf = 0;
 #ifdef HAVE_SIGNAL
            signal(SIGHUP, sighup_handler);
@@ -721,7 +743,7 @@ main_loop()
 
     now = time(NULL);
 
-    synchronize_dir(".");
+    synchronize_dir(".", is_system_startup());
 
     /* synchronize save with jobs execution */
     save = now + save_time;
@@ -732,6 +754,7 @@ main_loop()
        /* force first execution after first_sleep sec : execution of jobs
         * during system boot time is not what we want */
        stime = first_sleep;
+    debug("initial sleep time : %ld", stime);
 
     for (;;) {
        
index b1bdb5da43822bccae686a44fcd795ccc16f91c8..c37f685b97c7ef6b8e52efe103b5e4c5677e401a 100644 (file)
@@ -70,6 +70,7 @@ gid_t rootgid = 0;
 /* misc */
 char *user_str;
 uid_t user_uid;
+uid_t user_gid;
 
 /* if you change this structure, please update NUM_CMD value in dyncom.h */
 struct cmd_list_ent cmd_list[NUM_CMD] = {
@@ -648,6 +649,7 @@ main(int argc, char **argv)
     read_conf();
 
     user_uid = getuid();
+    user_gid = getgid();
     if ( (pass = getpwuid(user_uid)) == NULL )
        die("user \"%s\" is not in passwd file. Aborting.", USERNAME);
     user_str = strdup2(pass->pw_name);
index 022ec871de8f8c08065f766909e2f1c8b91b0cb1..aa16a1b167c42e9835e4eb30dd64141038847ead 100644 (file)
@@ -29,6 +29,7 @@
 
 char *get_string(char *ptr);
 int get_line(char *str, size_t size, FILE *file);
+void init_default_line(cl_t *cl, cf_t *cf);
 char *get_time(char *ptr, time_t *time, int zero_allowed);
 char *get_num(char *ptr, int *num, int max, short int decimal,
              const char **names);
@@ -38,9 +39,11 @@ char *read_field(char *ptr, bitstr_t *ary, int max, const char **names);
 void read_freq(char *ptr, cf_t *cf);
 void read_arys(char *ptr, cf_t *cf);
 void read_period(char *ptr, cf_t *cf);
+int read_shortcut(char *ptr, cf_t *cf);
 void read_env(char *ptr, cf_t *cf);
 char *read_opt(char *ptr, cl_t *cl);
 char *check_username(char *ptr, cf_t *cf, cl_t *cl);
+void free_line(cl_t *cl);
 
 
 char need_correction;
@@ -122,7 +125,7 @@ get_line(char *str, size_t size, FILE *file)
                *(str + i) = (char) '\0';
                return OK;
            }
-           break;
+            break;
                
        case EOF:
            *(str + i) = (char) '\0';
@@ -147,6 +150,19 @@ get_line(char *str, size_t size, FILE *file)
 
 }
 
+void
+init_default_line(cl_t *cl, cf_t *cf)
+/* clear all context/options from cl */
+{
+    bzero(cl, sizeof(cl_t));
+    Set(cl->cl_runas, runas);
+    Set(cl->cl_mailto, runas);
+    free_safe(cl->cl_tz);
+    set_default_opt(cl->cl_option);
+    cl->cl_file = cf;
+}
+
+
 int
 read_file(char *filename)
     /* read file "name" and append cf_t list */
@@ -161,7 +177,6 @@ read_file(char *filename)
     int ret;
     
     bzero(buf, sizeof(buf));
-    bzero(&default_line, sizeof(cl_t));
     need_correction = 0;
     line = 1;
     file_name = filename;
@@ -180,11 +195,7 @@ read_file(char *filename)
     Alloc(cf, cf_t);
     cf->cf_env_list = env_list_init();
     cf->cf_user = strdup2(user);
-    default_line.cl_file = cf;
-    default_line.cl_runas = strdup2(runas);
-    default_line.cl_mailto = strdup2(runas);
-    default_line.cl_tz = NULL;
-    set_default_opt(default_line.cl_option);
+    init_default_line(&default_line, cf);
 
     if ( debug_opt )
        fprintf(stderr, "FILE %s\n", file_name);
@@ -217,7 +228,9 @@ read_file(char *filename)
            /* comments or empty line: skipping */
            break;
        case '@':
-           read_freq(ptr, cf);
+            /* if it is not a shortcut line then read_shortcut() won't do anything. */
+            if ( ! read_shortcut(ptr, cf))
+                read_freq(ptr, cf);
            entries++;
            break;
        case '&':
@@ -335,7 +348,7 @@ read_env(char *ptr, cf_t *cf)
     if ( strcmp(name, "MAILTO") == 0 ) {
        if ( strcmp(val, "\0") == 0 ) {
            clear_mail(default_line.cl_option);
-           clear_forcemail(default_line.cl_option);
+           clear_mailzerolength(default_line.cl_option);
         }
        else {
            Set(default_line.cl_mailto, val);
@@ -566,15 +579,46 @@ read_opt(char *ptr, cl_t *cl)
                fprintf(stderr, "  Opt : \"%s\" %d\n", opt_name, i);
        }
 
+       else if(strcmp(opt_name, "rebootreset")==0) {
+           if ( in_brackets && (ptr = get_bool(ptr, &i)) == NULL )
+               Handle_err;
+           if ( i == 0 )
+               clear_rebootreset(cl->cl_option);
+           else
+               set_rebootreset(cl->cl_option);
+            if (debug_opt)
+               fprintf(stderr, "  Opt : \"%s\" %d\n", opt_name, i);
+       }
+
+
+       else if(strcmp(opt_name, "runatreboot")==0){
+           if ( in_brackets && (ptr = get_bool(ptr, &i)) == NULL )
+               Handle_err;
+           if ( i == 0 )
+               clear_runatreboot(cl->cl_option);
+           else
+                set_runatreboot(cl->cl_option);
+            if (debug_opt)
+               fprintf(stderr, "  Opt : \"%s\" %d\n", opt_name, i);
+       }
+
+
+       else if(strcmp(opt_name, "runonce")==0){
+           if ( in_brackets && (ptr = get_bool(ptr, &i)) == NULL )
+               Handle_err;
+           if ( i == 0 )
+               clear_runonce(cl->cl_option);
+           else
+                set_runonce(cl->cl_option);
+            if (debug_opt)
+               fprintf(stderr, "  Opt : \"%s\" %d\n", opt_name, i);
+       }
+
        else if( strcmp(opt_name, "reset")==0 ) {
            if ( in_brackets && (ptr = get_bool(ptr, &i)) == NULL )
                Handle_err;
            if ( i == 1 ) {
-               bzero(cl, sizeof(cl_t));
-               Set(cl->cl_runas, runas);
-               Set(cl->cl_mailto, runas);
-               free_safe(cl->cl_tz);
-               set_default_opt(cl->cl_option);
+                init_default_line(cl, cl->cl_file);
            }
            if (debug_opt)
                fprintf(stderr, "  Opt : \"%s\"\n", opt_name);
@@ -739,7 +783,7 @@ read_opt(char *ptr, cl_t *cl)
                Handle_err;
            if ( i == 0 ) {
                clear_mail(cl->cl_option);
-               clear_forcemail(cl->cl_option);
+               clear_mailzerolength(cl->cl_option);
             }
            else
                set_mail(cl->cl_option);
@@ -773,7 +817,7 @@ read_opt(char *ptr, cl_t *cl)
                buf[i++] = *ptr++;
            if ( strcmp(buf, "\0") == 0 ) {
                clear_mail(cl->cl_option);
-               clear_forcemail(cl->cl_option);
+               clear_mailzerolength(cl->cl_option);
             }
            else {
                    Set(cl->cl_mailto, buf);
@@ -1123,6 +1167,152 @@ check_username(char *ptr, cf_t *cf, cl_t *cl)
 }
 
 
+char *
+read_word(char *ptr, char *buf, size_t buf_size)
+/* copy a word to buf with a max_size of buf_size and return a pointer
+ * to the next char after the word */
+{
+    int i = 0;
+
+    bzero(buf, buf_size);
+
+    while ( isalnum( (int) *ptr)  && i < buf_size )
+        buf[i++] = *ptr++;
+
+    return ptr;
+}
+
+int
+read_shortcut(char *ptr, cf_t *cf)
+/* try to read a shortcut entry, and if it is one then append a line to cf
+ * Return 1 if that was a shortcut entry. If it wasn't, return 0 and make sure
+ * ptr is back to its orig value. */
+{
+    cl_t *cl = NULL;
+    char shortcut[20];
+    char *ptr_orig = ptr;
+
+    Alloc(cl, cl_t);
+    memcpy(cl, &default_line, sizeof(cl_t));
+    cl->cl_runas = strdup2(default_line.cl_runas);
+    cl->cl_mailto = strdup2(default_line.cl_mailto);
+    if ( cl->cl_tz != NULL )
+       cl->cl_tz = strdup2(default_line.cl_tz);
+
+    /* skip the @ */
+    ptr++;
+
+    ptr = read_word(ptr, shortcut, sizeof(shortcut));
+
+    /* time&date by default -- we'll switch to freq if @reboot */
+    set_td(cl->cl_option);
+    cl->cl_remain = cl->cl_runfreq; /* FIXME: necessary?? */
+
+    if ( strcmp(shortcut, "reboot") == 0 ) {
+        set_freq(cl->cl_option);
+        set_runatreboot(cl->cl_option);
+        set_runonce(cl->cl_option);
+        clear_volatile(cl->cl_option);
+        cl->cl_runfreq = 0;
+        cl->cl_first = 0;
+        /* the job will not be rescheduled after the first execution (flag is_hasrun),
+         * we set timefreq to LONG_MAX just in case */
+        cl->cl_timefreq = LONG_MAX;
+
+        if ( debug_opt )
+            fprintf(stderr, "  Shc : @reboot\n");
+    }
+    else if ( strcmp(shortcut, "yearly")  == 0 || strcmp(shortcut, "annually")  == 0 ) {
+        bit_set(cl->cl_mins, 0);
+        bit_set(cl->cl_hrs, 0);
+        bit_set(cl->cl_days, 1);
+        bit_set(cl->cl_mons, 0);
+        bit_nset(cl->cl_dow, 0, 7);
+
+        if ( debug_opt )
+            fprintf(stderr, "  Shc : @yearly\n");
+    }
+    else if ( strcmp(shortcut, "monthly")  == 0 ) {
+        bit_set(cl->cl_mins, 0);
+        bit_set(cl->cl_hrs, 0);
+        bit_set(cl->cl_days, 1);
+        bit_nset(cl->cl_mons, 0, 11);
+        bit_nset(cl->cl_dow, 0, 7);
+
+        if ( debug_opt )
+            fprintf(stderr, "  Shc : @monthly\n");
+    }
+    else if ( strcmp(shortcut, "weekly")  == 0 ) {
+        bit_set(cl->cl_mins, 0);
+        bit_set(cl->cl_hrs, 0);
+        bit_nset(cl->cl_days, 0, 31);
+        bit_nset(cl->cl_mons, 0, 11);
+        bit_set(cl->cl_dow, 0);
+        bit_set(cl->cl_dow, 7); /* 0 and 7 are both sunday */
+
+        if ( debug_opt )
+            fprintf(stderr, "  Shc : @weekly\n");
+    }
+    else if ( strcmp(shortcut, "daily")  == 0 || strcmp(shortcut, "midnight")  == 0 ) {
+        bit_set(cl->cl_mins, 0);
+        bit_set(cl->cl_hrs, 0);
+        bit_nset(cl->cl_days, 0, 31);
+        bit_nset(cl->cl_mons, 0, 11);
+        bit_nset(cl->cl_dow, 0, 7);
+
+        if ( debug_opt )
+            fprintf(stderr, "  Shc : @daily\n");
+    }
+    else if ( strcmp(shortcut, "hourly")  == 0 ) {
+        bit_set(cl->cl_mins, 0);
+        bit_nset(cl->cl_hrs, 0, 23);
+        bit_nset(cl->cl_days, 0, 31);
+        bit_nset(cl->cl_mons, 0, 11);
+        bit_nset(cl->cl_dow, 0, 7);
+
+        if ( debug_opt )
+            fprintf(stderr, "  Shc : @hourly\n");
+    }
+    else {
+        /* this is not a shortcut line but a normal @-line:  */
+        ptr = ptr_orig;
+        return 0;
+    }
+
+    /* check for inline runas */
+    ptr = check_username(ptr, cf, cl);
+
+    /* get cl_shell field ( remove trailing blanks ) */
+    if ( (cl->cl_shell = get_string(ptr)) == NULL ) {
+       fprintf(stderr, "%s:%d: Mismatched quotes: skipping line.\n",
+               file_name, line);
+       goto exiterr;
+    }
+    if ( strcmp(cl->cl_shell, "\0") == 0 ) {
+       fprintf(stderr, "%s:%d: No shell command: skipping line.\n",
+               file_name, line);
+       free_safe(cl->cl_shell);
+       goto exiterr;
+    }
+
+#ifndef USE_SENDMAIL
+    clear_mail(cl->cl_option);
+    clear_mailzerolength(cl->cl_option);
+#endif
+
+    cl->cl_next = cf->cf_line_base;
+    cf->cf_line_base = cl;
+
+    if ( debug_opt )
+       fprintf(stderr, "  Cmd \"%s\" (shortcut)\n", cl->cl_shell);
+    return 1;
+
+  exiterr:
+    free_safe(cl);
+    need_correction = 1;
+    return 1;
+}
+
 void
 read_freq(char *ptr, cf_t *cf)
     /* read a freq entry, and append a line to cf */
@@ -1175,11 +1365,9 @@ read_freq(char *ptr, cf_t *cf)
        goto exiterr;
     }
 
-    if ( is_volatile(cl->cl_option) && cl->cl_first == -1 )
-       /* time before first execution is not specified */
+    if ( cl->cl_first == -1 )
+       /* time before first execution was not specified explicitely */
        cl->cl_first = cl->cl_timefreq;
-    else
-       cl->cl_nextexe = (cl->cl_first == -1 ) ? cl->cl_timefreq : cl->cl_first;
 
     /* check for inline runas */
     ptr = check_username(ptr, cf, cl);
@@ -1212,7 +1400,7 @@ read_freq(char *ptr, cf_t *cf)
     return;
 
   exiterr:
-    free_safe(cl);
+    free_line(cl);
     need_correction = 1;
     return;
 }
@@ -1300,6 +1488,7 @@ read_arys(char *ptr, cf_t *cf)
     if ( strcmp(cl->cl_shell, "\0") == 0 ) {
        fprintf(stderr, "%s:%d: No shell command: skipping line.\n",
                file_name, line);
+       free_safe(cl->cl_shell);
        goto exiterr;
     }
 
@@ -1317,7 +1506,7 @@ read_arys(char *ptr, cf_t *cf)
 
   exiterr:
     need_correction = 1;
-    free_safe(cl);
+    free_line(cl);
     return;
 
 }
@@ -1393,6 +1582,7 @@ read_period(char *ptr, cf_t *cf)
     if ( strcmp(cl->cl_shell, "\0") == 0 ) {
        fprintf(stderr, "%s:%d: No shell command: skipping line.\n",
                file_name, line);
+       free_safe(cl->cl_shell);
        goto exiterr;
     } 
     else if ( cl->cl_shell[0] == '*' || isdigit( (int) cl->cl_shell[0]) )
@@ -1456,7 +1646,7 @@ read_period(char *ptr, cf_t *cf)
 
   exiterr:
     need_correction = 1;
-    free_safe(cl);
+    free_line(cl);
     return;
 
 }
@@ -1653,6 +1843,18 @@ read_field(char *ptr, bitstr_t *ary, int max, const char **names)
     return ptr;
 }
 
+void
+free_line(cl_t *cl)
+    /* free a line, including its fields */
+{
+    if (cl != NULL) {
+        free_safe(cl->cl_shell);
+        free_safe(cl->cl_runas);
+        free_safe(cl->cl_mailto);
+        free_safe(cl->cl_tz);
+        free_safe(cl);
+    }
+}
 
 void
 delete_file(const char *user_name)
@@ -1672,11 +1874,7 @@ delete_file(const char *user_name)
            cur_line = file->cf_line_base;
            while ( (line = cur_line) != NULL) {
                cur_line = line->cl_next;
-               free_safe(line->cl_shell);
-               free_safe(line->cl_mailto);
-               free_safe(line->cl_runas);
-               free_safe(line->cl_tz);
-               free_safe(line);
+                free_line(line);
            }
            break ;
 
index 845e577aa4dd00fc297b0d9d407fef7de133b5ba..7410b1c8cd2e385524ab37fbc07d60986ab332a9 100644 (file)
--- a/global.h
+++ b/global.h
 
 #define Set(VAR, VALUE) \
         { \
-          free(VAR); \
+          free_safe(VAR); \
           VAR = strdup2(VALUE); \
         }
 
@@ -187,7 +187,7 @@ typedef struct cf_t {
 } cf_t;
 
 
-#define OPTION_SIZE 4
+#define OPTION_SIZE 4 /* number of bytes to hold the cl_option bit array */
 #define LAVG_SIZE 3
 /* warning : do not change the order of the members of this structure
  *   because some tests made are dependent to that order */
@@ -200,7 +200,7 @@ typedef struct cl_t {
     char          *cl_runas;      /* determine permissions of the job        */
     char          *cl_mailto;     /* mail output to cl_mailto                */
     char          *cl_tz;         /* time zone of the line                   */
-    long int       cl_id;         /* line's unique id number                 */
+    unsigned long  cl_id;         /* line's unique id number                 */
     time_t         cl_until;      /* timeout of the wait for a lavg value    */
     time_t         cl_first;      /* initial delay preserved for volatile entries */
     time_t         cl_nextexe;    /* time and date of the next execution     */
index a6cb0f9fb9f0028f7661f70abe63189899c406dc..3b47c00a3da8672ebc6eea2a08a295e305f30e4b 100644 (file)
--- a/option.h
+++ b/option.h
@@ -56,6 +56,9 @@
  *           or loaded in memory.
  */
 
+/* WARNING : if you add some options, make sure that OPTION_SIZE is big
+ *           enough in global.h */
+
 /*
   
   The options are :
   24     Should first value be applied at each fcron startup, or before line first exe ?
   25     if fcron is running in foreground, print jobs output to stderr/stdout or mail ?
   26     should the output of the job be emailed to the user only non-zero exit status ?
+  27     rebootreset: if set then apply option first at each system startup
+  28     runatreboot: if set then run the job at each system startup
+  29     runonce: if set then run the job only once
+  30     hasrun: set if the job has been run at least once
 
 */
 
 #define clear_erroronlymail(opt) \
        (_bit_clear(opt, 26))
 
+/*
+  bit 27 : set to 1 : at each system startup, run the job after the delay set
+                      in the option first
+           set to 0 : leave the nextexe time as it
+*/
+#define        is_rebootreset(opt) \
+       (_bit_test(opt, 27))
+#define        set_rebootreset(opt) \
+       (_bit_set(opt, 27))
+#define clear_rebootreset(opt) \
+       (_bit_clear(opt, 27))
+
+/*
+  bit 28 : set to 1 : run the job immediately after the system startup
+           set to 0 : leave the nextexe time as it
+*/
+#define        is_runatreboot(opt) \
+       (_bit_test(opt, 28))
+#define        set_runatreboot(opt) \
+       (_bit_set(opt, 28))
+#define clear_runatreboot(opt) \
+       (_bit_clear(opt, 28))
+
+/*
+  bit 29 : set to 1 : run the job only once until next system reboot
+                      (or next fcron restart if volatile is set)
+           set to 0 : don't limit the number of times the job is run
+*/
+#define        is_runonce(opt) \
+       (_bit_test(opt, 29))
+#define        set_runonce(opt) \
+       (_bit_set(opt, 29))
+#define clear_runonce(opt) \
+       (_bit_clear(opt, 29))
+
+/*
+  bit 30 : set to 1 : the job has run at least once since system reboot
+                      (or since fcron restart if volatile is set)
+           set to 0 : job hasn't run yet
+*/
+#define        is_hasrun(opt) \
+       (_bit_test(opt, 30))
+#define        set_hasrun(opt) \
+       (_bit_set(opt, 30))
+#define clear_hasrun(opt) \
+       (_bit_clear(opt, 30))
+
 #endif /* __OPTIONH__ */
 
diff --git a/save.c b/save.c
index 671e840336b332e8c0e267deac70fc7119252644..3faa43c24fc8b70a897c255f202d5718867131c9 100644 (file)
--- a/save.c
+++ b/save.c
@@ -243,13 +243,9 @@ write_file_to_disk(int fd, struct cf_t *file, time_t time_date)
        Save_str(fd, S_MAILTO_T, line->cl_mailto, write_buf, &write_buf_used);
        Save_strn(fd, S_OPTION_T, (char *)line->cl_option, OPTION_SIZE,
                  write_buf, &write_buf_used);
+       Save_lint(fd, S_NEXTEXE_T, line->cl_nextexe, write_buf, &write_buf_used);
 
        /* the following are saved only if needed */
-       if ( is_volatile(line->cl_option) && is_freq(line->cl_option) ) {
-           Save_lint(fd, S_FIRST_T, line->cl_first, write_buf, &write_buf_used);
-       }
-       else
-           Save_lint(fd, S_NEXTEXE_T, line->cl_nextexe, write_buf, &write_buf_used);
        if ( line->cl_numexe )
            Save_strn(fd, S_NUMEXE_T, (char *)&line->cl_numexe, 1, write_buf, &write_buf_used);
        if ( is_lavg(line->cl_option) )
@@ -272,6 +268,7 @@ write_file_to_disk(int fd, struct cf_t *file, time_t time_date)
                     
        if ( is_freq(line->cl_option) ) {
            /* save the frequency to run the line */
+           Save_lint(fd, S_FIRST_T, line->cl_first, write_buf, &write_buf_used);
            Save_lint(fd, S_TIMEFREQ_T, line->cl_timefreq, write_buf, &write_buf_used);
        }
        else {