]> granicus.if.org Git - fcron/commitdiff
Initial revision
authorthib <thib>
Sun, 14 May 2000 17:25:41 +0000 (17:25 +0000)
committerthib <thib>
Sun, 14 May 2000 17:25:41 +0000 (17:25 +0000)
20 files changed:
Makefile.in [new file with mode: 0644]
README [new file with mode: 0644]
allow.c [new file with mode: 0644]
bitstring.h [new file with mode: 0644]
conf.c [new file with mode: 0644]
config.h.in [new file with mode: 0644]
database.c [new file with mode: 0644]
fcron.c [new file with mode: 0644]
fcron.h [new file with mode: 0644]
fcrontab.c [new file with mode: 0644]
fcrontab.h [new file with mode: 0644]
fileconf.c [new file with mode: 0644]
global.h [new file with mode: 0644]
job.c [new file with mode: 0644]
log.c [new file with mode: 0644]
script/boot-install [new file with mode: 0755]
script/boot-uninstall [new file with mode: 0755]
script/gen-doc [new file with mode: 0755]
script/sysVinit-launcher [new file with mode: 0755]
subs.c [new file with mode: 0644]

diff --git a/Makefile.in b/Makefile.in
new file mode 100644 (file)
index 0000000..f8289de
--- /dev/null
@@ -0,0 +1,131 @@
+############################
+# fcron's Makefile  ########
+############################
+
+# ********************************************************* #
+# *** Begin of configurable stuffs ************************ #
+
+
+# Where should we install it ?
+DESTSBIN= /usr/sbin/
+DESTBIN= /usr/bin/
+DESTMAN= /usr/man/
+FCRONTABS=/var/spool/fcron/
+ETC=/etc/
+
+
+# Optimize or debug ?
+#      -DDEBUG         even more verbose
+#      -DCHECKJOBS     send a mail containing the exact shell command
+#                      for each execution of each job.
+#OPTIM=                -DDEBUG -g -DFOREGROUND
+OPTIM=         -DDEBUG -DCHECKJOBS -Wall -Wpointer-arith -Wstrict-prototypes
+#OPTIM=                -O2 -Wall
+#OPTIM=                -O3 -mcpu=i686 -Wall
+
+
+# Options
+#      -DFOREGROUND [0|1]    default run in foreground ?
+#      -DFCRONTABS path       default path of users' config file
+#
+OPTION = 
+
+
+# Want other flags ?
+#OTHERFLAGS=
+
+
+# Want a nonstandard CC ?
+CC= gcc
+
+
+# Any include ?
+INCLUDE=       -I.
+
+
+# The name of the BSD like install program
+#INSTALL = installbsd
+INSTALL= install
+
+
+# *** End of configurable stuffs ************************** #
+# ********************************************************* #
+
+####################################
+# Should not be changed under this #
+####################################
+
+VERSION= 0.8.0
+CFLAGS= $(INCLUDE) $(OPTIM) $(OTHERFLAGS) $(OPTION) -DVERSION=\"$(VERSION)\" -DFCRONTABS=\"$(FCRONTABS)\" -DETC=\"$(ETC)\"
+OBJSD = fcron.o subs.o database.o job.o log.o conf.o
+OBJS= fcrontab.o fileconf.o subs.o log.o allow.c
+HEADERSD = fcron.h config.h global.h
+HEADERS = fcrontab.h config.h global.h
+
+all: fcron fcrontab
+
+fcron: $(OBJSD)
+       $(CC) $(CFLAGS) -o $@ $(OBJSD)
+
+fcrontab: $(OBJS)
+       $(CC) $(CFLAGS) -o $@ $(OBJS)
+
+fcrontab.o: fcrontab.c $(HEADERS)
+       $(CC) $(CFLAGS) -c fcrontab.c 
+
+fileconf.o:  fileconf.c $(HEADERS)
+       $(CC) $(CFLAGS) -c fileconf.c 
+
+allow.o:  allow.c $(HEADERS)
+       $(CC) $(CFLAGS) -c allow.c 
+
+%.o: %.c $(HEADERSD)
+       $(CC) $(CFLAGS) -c $<
+
+install: all
+       $(INSTALL) -m 111 -o root -s fcron $(DESTSBIN)/
+       $(INSTALL) -m 4111 -o root -s fcrontab $(DESTBIN)/
+       $(INSTALL) -m 700 -o root files/fcron.allow $(ETC)/
+       $(INSTALL) -m 700 -o root files/fcron.deny $(ETC)/
+       $(INSTALL) -m 644 -o root man/fcron.8 $(DESTMAN)/man8/
+       $(INSTALL) -m 644 -o root man/fcrontab.1 $(DESTMAN)/man1/
+       $(INSTALL) -m 644 -o root man/fcrontab.5 $(DESTMAN)/man5/
+       $(INSTALL) -m 644 -o root man/bitstring.3 $(DESTMAN)/man3/
+       ./sysVinit-install "$(CFLAGS)" $(INSTALL)
+       mkdir -p /usr/doc/fcron-$(VERSION)
+       $(INSTALL) -m 644 -o root doc/* /usr/doc/fcron-$(VERSION)/
+       mkdir -p $(FCRONTABS)
+       chmod 700 $(FCRONTABS)
+
+uninstall:
+       rm -f $(DESTSBIN)/fcron
+       ./sysVinit-uninstall
+
+clean:
+       rm -f *.o
+       rm -f fcron fcrontab
+
+vclean: clean
+       find ./ -name "*~" -exec rm -f {} \;
+       rm -f fcron*tar.gz
+
+doc-changes: doc/ man/
+       touch doc-changes
+
+doc: doc-changes
+       ./gen-doc $(VERSION)
+       touch doc-changes
+
+tar: vclean doc
+
+       @(find ./ -type f | sed -e "s:^\./:fcron-$(VERSION)/:" > MANIFEST)
+       @(cd ..; ln -s fcron fcron-$(VERSION))
+       (cd ..; tar -czvf fcron-$(VERSION).src.tar.gz `cat fcron/MANIFEST`)
+       @(cd ..; rm -f fcron-$(VERSION))
+
+       @(cd ..; cp -f fcron-$(VERSION).src.tar.gz old-fcron-pkg/)
+       @(cd ..; mv -f fcron-$(VERSION).src.tar.gz fcron/)
+
+
+
+
diff --git a/README b/README
new file mode 100644 (file)
index 0000000..c36fa08
--- /dev/null
+++ b/README
@@ -0,0 +1 @@
+see README in directory doc/ .
\ No newline at end of file
diff --git a/allow.c b/allow.c
new file mode 100644 (file)
index 0000000..a8b0c73
--- /dev/null
+++ b/allow.c
@@ -0,0 +1,109 @@
+
+/*
+ * FCRON - periodic command scheduler 
+ *
+ *  Copyright 2000 Thibault Godouet <sphawk@free.fr>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ * 
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * 
+ *  The GNU General Public License can also be found in the file
+ *  `LICENSE' that comes with the fcron source distribution.
+ */
+
+/* ALLOW.C */
+
+#include "fcrontab.h"
+
+
+int
+in_file(char *str, char *file)
+    /* return -1 if file can't be opened
+     *        0 if string is not in file,
+     *        1 if it is in file
+     *   and  2 if file contains "all" string */
+{
+    char buf[LINE_LEN];
+    FILE *f = NULL;
+    char *start = NULL;
+
+    if ( access(file, F_OK) != 0 )
+       /* file does not exist */
+       return -1;
+
+    if ( (f = fopen(file, "r")) == NULL ) {
+       die_e("could not open %s", file);
+       return -1;
+    }
+
+    while ( fgets(buf, sizeof(buf), f) != NULL ) {
+
+       /* skip leading and trailing blanks, comments */
+       start = buf;
+       while ( *start != '\0' && isspace(*start) )
+           start++;
+       if ( *start == '#' || *start == '\0' )
+           continue;
+       remove_blanks(start);
+
+       if ( strcmp(str, start) == 0 )
+           return 1;
+       if ( strcmp(start, "all") == 0 )
+           return 2;
+    }
+
+    /* if execution gets here, string is not in file */
+    return 0;
+
+
+}
+
+int
+is_allowed(char *user)
+    /* return 1 if user is allowed to use this soft
+     * otherwise return 0 */
+{
+    char allow = 0;
+    char deny = 0;
+
+    /* check if user is in passwd file */
+    if ( ! getpwnam(user) )
+       return 1;
+
+    /* check if user is in fcron.allow and/or in fcron.deny files */
+    allow = in_file(user, ETC "/" FCRON_ALLOW);
+    deny = in_file(user, ETC "/" FCRON_DENY);
+
+    if ( allow == -1 && deny == -1 )
+       /* neither fcron.allow nor fcron.deny exist ( or can be opened ) :
+        * we consider that user is allowed */
+       return 1;
+
+    if ( allow == -1 && deny == 0 )
+       return 1;
+    
+    if ( deny == -1 && allow == 1 )
+       return 1;
+
+    if ( allow == 1 )
+       if ( deny != 1 )
+           return 1;
+    if ( allow == 2 )
+       if ( deny == 0 )
+           return 1;
+
+    /* if we gets here, user is not allowed */
+    return 0;
+
+}
diff --git a/bitstring.h b/bitstring.h
new file mode 100644 (file)
index 0000000..d054de3
--- /dev/null
@@ -0,0 +1,122 @@
+/*
+ * Copyright (c) 1989 The Regents of the University of California.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Paul Vixie.
+ *
+ * Redistribution and use in source and binary forms are permitted
+ * provided that the above copyright notice and this paragraph are
+ * duplicated in all such forms and that any documentation,
+ * advertising materials, and other materials related to such
+ * distribution and use acknowledge that the software was developed
+ * by the University of California, Berkeley.  The name of the
+ * University may not be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
+ *
+ *     @(#)bitstring.h 5.2 (Berkeley) 4/4/90
+ */
+
+typedef        unsigned char bitstr_t;
+
+/* internal macros */
+                               /* byte of the bitstring bit is in */
+#define        _bit_byte(bit) \
+       ((bit) >> 3)
+
+                               /* mask for the bit within its byte */
+#define        _bit_mask(bit) \
+       (1 << ((bit)&0x7))
+
+/* external macros */
+                               /* bytes in a bitstring of nbits bits */
+#define        bitstr_size(nbits) \
+       ((((nbits) - 1) >> 3) + 1)
+
+                               /* allocate a bitstring */
+#define        bit_alloc(nbits) \
+       (bitstr_t *)malloc(1, \
+           (unsigned int)bitstr_size(nbits) * sizeof(bitstr_t))
+
+                               /* allocate a bitstring on the stack */
+#define        bit_decl(name, nbits) \
+       (name)[bitstr_size(nbits)]
+
+                               /* is bit N of bitstring name set? */
+#define        bit_test(name, bit) \
+       ((name)[_bit_byte(bit)] & _bit_mask(bit))
+
+                               /* set bit N of bitstring name */
+#define        bit_set(name, bit) \
+       (name)[_bit_byte(bit)] |= _bit_mask(bit)
+
+                               /* clear bit N of bitstring name */
+#define        bit_clear(name, bit) \
+       (name)[_bit_byte(bit)] &= ~_bit_mask(bit)
+
+                               /* clear bits start ... stop in bitstring */
+#define        bit_nclear(name, start, stop) { \
+       register bitstr_t *_name = name; \
+       register int _start = start, _stop = stop; \
+       register int _startbyte = _bit_byte(_start); \
+       register int _stopbyte = _bit_byte(_stop); \
+       if (_startbyte == _stopbyte) { \
+               _name[_startbyte] &= ((0xff >> (8 - (_start&0x7))) | \
+                                     (0xff << ((_stop&0x7) + 1))); \
+       } else { \
+               _name[_startbyte] &= 0xff >> (8 - (_start&0x7)); \
+               while (++_startbyte < _stopbyte) \
+                       _name[_startbyte] = 0; \
+               _name[_stopbyte] &= 0xff << ((_stop&0x7) + 1); \
+       } \
+}
+
+                               /* set bits start ... stop in bitstring */
+#define        bit_nset(name, start, stop) { \
+       register bitstr_t *_name = name; \
+       register int _start = start, _stop = stop; \
+       register int _startbyte = _bit_byte(_start); \
+       register int _stopbyte = _bit_byte(_stop); \
+       if (_startbyte == _stopbyte) { \
+               _name[_startbyte] |= ((0xff << (_start&0x7)) & \
+                                   (0xff >> (7 - (_stop&0x7)))); \
+       } else { \
+               _name[_startbyte] |= 0xff << ((_start)&0x7); \
+               while (++_startbyte < _stopbyte) \
+                       _name[_startbyte] = 0xff; \
+               _name[_stopbyte] |= 0xff >> (7 - (_stop&0x7)); \
+       } \
+}
+
+                               /* find first bit clear in name */
+#define        bit_ffc(name, nbits, value) { \
+       register bitstr_t *_name = name; \
+       register int _byte, _nbits = nbits; \
+       register int _stopbyte = _bit_byte(_nbits), _value = -1; \
+       for (_byte = 0; _byte <= _stopbyte; ++_byte) \
+               if (_name[_byte] != 0xff) { \
+                       _value = _byte << 3; \
+                       for (_stopbyte = _name[_byte]; (_stopbyte&0x1); \
+                           ++_value, _stopbyte >>= 1); \
+                       break; \
+               } \
+       *(value) = _value; \
+}
+
+                               /* find first bit set in name */
+#define        bit_ffs(name, nbits, value) { \
+       register bitstr_t *_name = name; \
+       register int _byte, _nbits = nbits; \
+       register int _stopbyte = _bit_byte(_nbits), _value = -1; \
+       for (_byte = 0; _byte <= _stopbyte; ++_byte) \
+               if (_name[_byte]) { \
+                       _value = _byte << 3; \
+                       for (_stopbyte = _name[_byte]; !(_stopbyte&0x1); \
+                           ++_value, _stopbyte >>= 1); \
+                       break; \
+               } \
+       *(value) = _value; \
+}
diff --git a/conf.c b/conf.c
new file mode 100644 (file)
index 0000000..b8e7cc8
--- /dev/null
+++ b/conf.c
@@ -0,0 +1,618 @@
+
+/*
+ * FCRON - periodic command scheduler 
+ *
+ *  Copyright 2000 Thibault Godouet <sphawk@free.fr>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ * 
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * 
+ *  The GNU General Public License can also be found in the file
+ *  `LICENSE' that comes with the fcron source distribution.
+ */
+
+/* CONF.C */
+
+#include "fcron.h"
+
+int read_file(const char *file_name, CF *cf);
+char *read_str(FILE *f, char *buf, int max);
+void synchronize_file(char *file_name);
+
+
+/* this is used to create a list of files to remove, to add */
+typedef struct list_t {
+    char *str;
+    struct list_t *next;
+} list_t;
+
+
+void
+reload_all(const char *dir_name)
+    /* save all current configuration, remove it from the memory,
+     * and reload from dir_name */
+{
+    CF *f = NULL;
+
+    explain("Removing current configuration from memory");
+
+    f = file_base;
+    while ( f != NULL ) {
+       if ( f->cf_running > 0 ) {
+           wait_all( &f->cf_running );
+           save_file(f, NULL);
+       }
+       delete_file(f->cf_user);    
+
+       /* delete_file remove the f file from the list :
+        * next file to remove is now pointed by file_base. */
+       f = file_base;
+    }
+    
+    synchronize_dir(dir_name);
+
+}
+
+
+void
+synchronize_dir(const char *dir_name)
+    /* 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,
+     * finally add new files. */
+{
+
+    list_t *rm_list = NULL;
+    list_t *new_list = NULL;
+    list_t *file_list = NULL;    
+    list_t *list_cur = NULL;
+    DIR *dir;
+    struct dirent *den;
+
+    explain("update configuration from '%s'", dir_name);
+
+    if ((dir = opendir("."))) {
+       while ((den = readdir(dir))) {
+
+           if (strncmp(den->d_name, "rm.", 3) == 0) {
+               /* this is a file to remove from database */
+               Alloc(list_cur, list_t);
+               list_cur->str = strdup2(den->d_name);
+               list_cur->next = rm_list;
+               rm_list = list_cur;
+           } else
+
+               if (strncmp(den->d_name, "new.", 4) == 0) {
+                   /* this is a file to append to database */
+                   Alloc(list_cur, list_t);
+                   list_cur->str = strdup2(den->d_name);
+                   list_cur->next = new_list;
+                   new_list = list_cur;
+               } else 
+
+                   if (strchr(den->d_name, '.') != NULL)
+                       continue;
+                   else 
+                       /* this is a normal file : if file_base is not null,
+                        * so if a database has already been created, we
+                        * ignore it */
+                       if ( file_base == NULL ) {
+                           Alloc(list_cur, list_t);
+                           list_cur->str = strdup2(den->d_name);
+                           list_cur->next = file_list;
+                           file_list = list_cur;
+                       }
+           
+       }
+       closedir(dir);
+    } else
+       die("Unable to open current dir!");
+            
+
+    /* proceed to adds or removes */
+
+    /* begin by adding normal files, if any, to database */
+    for (list_cur = file_list; list_cur; list_cur = list_cur->next ) {
+       if ( getpwnam(list_cur->str) ) {
+           debug("adding file '%s'", list_cur->str);       
+           synchronize_file(list_cur->str);
+       }
+       else
+           warn("ignoring file '%s' : not in passwd file.", list_cur->str);
+    }
+
+    /* then remove files which are no longer wanted */
+    for (list_cur = rm_list; list_cur; list_cur = list_cur->next ) {
+       debug("removing file '%s'", list_cur->str + 3);
+       delete_file(list_cur->str + 3);  /* len("rm.") = 3 */
+       remove(list_cur->str + 3);
+       remove(list_cur->str);
+    }
+    
+    /* finally add new files */
+    for (list_cur = new_list; list_cur; list_cur = list_cur->next ) {
+       if ( getpwnam(list_cur->str + 4) ) { /* len("new.") = 4 */
+           debug("adding new file '%s'", list_cur->str + 4);
+           synchronize_file(list_cur->str);  
+       }
+       else
+           warn("ignoring file '%s' : not in passwd file.", 
+                (list_cur->str + 4));
+    }
+
+    /* free lists */
+    {
+       list_t *l = NULL;
+       list_t *next = NULL;
+       
+       next = rm_list;
+       while( (l = next) != NULL ) {
+           next = l->next;
+           free(l->str);
+           free(l);
+       }
+           
+       next = new_list;
+       while( (l = next) != NULL ) {
+           next = l->next;
+           free(l->str);
+           free(l);
+       }
+
+       next = file_list;
+       while( (l = next) != NULL ) {
+           next = l->next;
+           free(l->str);
+           free(l);
+       }
+
+    }
+
+}
+
+
+void
+synchronize_file(char *file_name)
+{
+
+    CF *cur_f = NULL;
+    char *user = NULL;
+
+
+    if (strchr(file_name, '.') != NULL ) {
+       /* this is a new file : we have to check if there is an old
+        * version in database in order to keep a maximum of fields
+        * (cl_remain, cl_nextexe) to their current value */
+
+       CF *prev = NULL;
+
+       /* set user name  */
+       /* we add 4 to file_name pointer because of the "new."
+        * string at the beginning of a new file */         
+       user = (file_name + 4);
+
+       for (cur_f = file_base; cur_f; cur_f = cur_f->cf_next) {
+           if ( strcmp(user, cur_f->cf_user) == 0 )
+               break;
+           prev = cur_f;
+       }
+
+       if (cur_f != NULL) {
+           /* an old version of this file exist in database */
+
+           CF *old = NULL;
+           CL *old_l = NULL;
+           CL *new_l = NULL;
+           /* size used when comparing two line : 
+            * it's the size of all time table (mins, days ...) */
+           const size_t size=( bitstr_size(60) + bitstr_size(24) + 
+                               bitstr_size(32) + bitstr_size(12) +
+                               bitstr_size(7) );
+           
+           /* assign old pointer to the old file, and remove it 
+            * from the list */
+           old = cur_f;
+
+           if (prev != NULL)
+               prev->cf_next = cur_f->cf_next;
+           else 
+               /* this is the first file in the list */
+               file_base = cur_f->cf_next;
+
+           /* load new file */
+           Alloc(cur_f, CF);
+           if ( read_file(file_name, cur_f) != 0 ) {
+               /* an error as occured */
+               free(cur_f);
+               return;
+           }
+
+           /* set cf_user field ( len("new.")=4 ) */
+           cur_f->cf_user = strdup2(user);
+
+           /* compare each lines between the new and the old 
+            * version of the file */
+           for (new_l = cur_f->cf_line_base; new_l; new_l = new_l->cl_next)
+               for(old_l = old->cf_line_base; old_l; old_l = old_l->cl_next) {
+
+                   /* compare the shell command and the fields 
+                      from cl_mins down to cl_runfreq or the timefreq */
+                   if ( strcmp(new_l->cl_shell, old_l->cl_shell) == 0 &&
+                        new_l->cl_timefreq == old_l->cl_timefreq &&
+                        memcmp(new_l->cl_mins, old_l->cl_mins, size) == 0
+                       ) {
+                       
+                       new_l->cl_remain = old_l->cl_remain; 
+                       new_l->cl_nextexe = old_l->cl_nextexe; 
+
+                       if (debug_opt) {
+                           if (new_l->cl_nextexe > 0) {
+                               struct tm *ftime;
+                               ftime = localtime(&new_l->cl_nextexe);
+                               debug("  from last conf: %s next exec %d/%d/%d"
+                                     " wday:%d %02d:%02d", new_l->cl_shell,
+                                     (ftime->tm_mon + 1), ftime->tm_mday,
+                                     (ftime->tm_year + 1900), ftime->tm_wday,
+                                     ftime->tm_hour, ftime->tm_min);
+                           } 
+                           if (new_l->cl_remain > 0)
+                               debug("  from last conf: %s cl_remain: %ld", 
+                                     new_l->cl_shell, new_l->cl_remain);
+                       }
+                       
+                       break;
+
+                   } 
+               }
+
+           /* insert new file in the list */
+           cur_f->cf_next = file_base;
+           file_base = cur_f;
+
+           /* save final file */
+           save_file(cur_f, user);
+
+           /* delete new.user file */
+           if ( remove(file_name) != 0 )
+               error_e("could not remove %s", file_name);
+
+       }
+
+       else {
+           /* no old version exist in database : load this file
+            * as a normal file, but change its name */
+           user = (file_name + 4);
+       
+           Alloc(cur_f, CF);
+
+           if ( read_file(file_name, cur_f) != 0 ) {
+               /* an error as occured */
+               free(cur_f);
+               return;
+           }
+
+           /* set cf_user field */
+           cur_f->cf_user = strdup2(user);
+
+           /* insert the file in the list */
+           cur_f->cf_next = file_base;
+           file_base = cur_f;
+
+           /* save as a normal file */
+           save_file(cur_f, user);
+
+           /* delete new.user file */
+           if ( remove(file_name) != 0 )
+               error_e("could not remove %s", file_name);
+       }
+
+    }
+
+    else {
+       /* this is a normal file */
+       
+       Alloc(cur_f, CF);
+
+       if ( read_file(file_name, cur_f) != 0 ) {
+           /* an error as occured */
+           free(cur_f);
+           return;
+       }
+
+       /* set cf_user field */
+       user = file_name;
+       cur_f->cf_user = strdup2(user);
+
+       /* insert the file in the list */
+       cur_f->cf_next = file_base;
+       file_base = cur_f;
+
+    }
+
+}
+
+
+char *
+read_str(FILE *f, char *buf, int max)
+    /* return a pointer to string read from file f
+     * if it is non-zero length */
+{
+    int i;
+
+    for (i = 0; i < max; i++)
+       if ( (buf[i] = fgetc(f)) == '\0')
+           break;
+
+    if ( strlen(buf) == 0 )
+       return NULL;
+    else
+       return strdup2(buf);
+
+}
+
+
+int
+read_file(const char *file_name, CF *cf)
+    /* read a formated fcrontab.
+       return 1 on error, 0 otherwise */
+{
+    FILE *ff = NULL;
+    CL *cl = NULL;
+    env_t *env = NULL;
+    char buf[LINE_LEN];
+    time_t ti;
+    char c;
+    int i;
+
+
+    /* open file */
+    if ( (ff = fopen(file_name, "r")) == NULL ) {
+       error_e("Could not read %s", file_name);
+       return 1;
+    }
+
+    ti = time(NULL);
+    debug("User %s Entry", file_name);
+    bzero(buf, sizeof(buf));
+
+    /* get version of fcrontab file: it permit to daemon not to load
+     * a file which he won't understand the syntax, for exemple
+     * a file using a depreciated format generated by an old fcrontab,
+     * if the syntax has changed */
+    if (fgets(buf, sizeof(buf), ff) == NULL ||
+       strncmp(buf, "fcrontab-"FILEVERSION"\n",
+               sizeof("fcrontab-"FILEVERSION"\n")) != 0) {
+
+       error("File is not valid: ignored.");
+       error("Maybe this file has been generated by an old version"
+             " of fcron. In this case, you should install it again"
+             " using fcrontab.");
+       return 1;
+    }    
+
+
+    /* check if there is a mailto field in file */
+    if ( (c = getc(ff)) == 'm' ) {
+       /* there is one : read it */
+
+       for (i = 0; i < sizeof(buf); i++)
+           if ( (buf[i] = fgetc(ff)) == '\0')
+               break;
+       cf->cf_mailto = strdup2(buf);
+
+       debug("  MailTo: '%s'", cf->cf_mailto);
+    } else
+       /* if there is no mailto field, the first letter is not a 'm',
+        * but a 'e' : there is no need to lseek */
+       ;
+           
+    /* read env variables */
+    Alloc(env, env_t);
+    while( (env->e_name = read_str(ff, buf, sizeof(buf))) != NULL ) {
+       env->e_val = read_str(ff, buf, sizeof(buf));
+       debug("  Env: '%s=%s'", env->e_name, env->e_val );
+       
+       env->e_next = cf->cf_env_base;
+       cf->cf_env_base = env;
+       Alloc(env, env_t);
+    }
+    /* free last alloc : unused */
+    free(env);
+       
+    /* read lines */
+    Alloc(cl, CL);
+    while ( fread(cl, sizeof(CL), 1, ff) == 1 ) {
+
+       cl->cl_shell = read_str(ff, buf, sizeof(buf));
+
+       debug("  Command '%s'", cl->cl_shell);
+       
+       if ( cl->cl_timefreq == 0 ) {
+           /* set the time and date of the next execution  */
+           if ( cl->cl_nextexe <= ti )
+               set_next_exe(cl, 1);
+       } else {
+
+           debug("   remain: %ld  timefreq: %ld", cl->cl_remain,
+                 cl->cl_timefreq);
+       }           
+
+       /* check if the task has not been stopped during execution */
+       if (cl->cl_pid > 0) {
+           if (cl->cl_mailpid > 0) {
+               /* job has terminated normally, but mail has not
+                * been sent */
+               warn("output of job '%s' has not been mailed "
+                       "due to daemon's stop", cl->cl_shell);
+               cl->cl_pid = cl->cl_mailfd = cl->cl_mailpid = 0;
+           } else {
+               /* job has been stopped during execution :
+                * launch it again */
+               warn("job '%s' has not terminated : will be executed"
+                       " again now.", cl->cl_shell);
+               /* we don't set cl_nextexe to 0 because this value is 
+                * reserved to the entries based on frequency */
+               cl->cl_nextexe = 1;
+               cl->cl_pid = cl->cl_mailfd = cl->cl_mailpid = 0;
+           }
+       }
+           
+       cl->cl_next = cf->cf_line_base;
+       cf->cf_line_base = cl;
+       Alloc(cl, CL);
+    }
+    /* free last calloc : unused */
+    free(cl);
+    
+    fclose(ff);
+
+    return 0;
+
+}
+
+
+void
+delete_file(const char *user_name)
+    /* free a file if user_name is not null 
+     *   otherwise free all files */
+{
+    CF *file;
+    CF *prev_file=NULL;
+    CL *line;
+    CL *cur_line;
+    env_t *env = NULL;
+    env_t *cur_env = NULL;
+
+    file = file_base;
+    while ( file != NULL) {
+       if (strcmp(user_name, file->cf_user) == 0) {
+
+           /* free lines */
+           cur_line = file->cf_line_base;
+           while ( (line = cur_line) != NULL) {
+               cur_line = line->cl_next;
+               free(line->cl_shell);
+               free(line);
+           }
+           break ;
+       } else {
+           prev_file = file;
+           file = file->cf_next;
+       }
+    }
+    
+    if (file == NULL)
+       /* file not in list */
+       return;
+    
+    /* remove file from list */
+    if (prev_file == NULL)
+       file_base = file->cf_next;
+    else
+       prev_file->cf_next = file->cf_next;
+
+    /* free env variables */
+    cur_env = file->cf_env_base;
+    while  ( (env = cur_env) != NULL ) {
+       cur_env = env->e_next;
+       free(env->e_name);
+       free(env->e_val);
+       free(env);
+    }
+
+    /* finally free file itself */
+    free(file->cf_user);
+    free(file->cf_mailto);
+    free(file);
+
+}
+
+
+void
+save_file(CF *file, char *path)
+    /* Store the informations relatives to the executions
+     * of tasks at a defined frequency of system's running time */
+{
+    CF *cf = NULL;
+    CL *cl= NULL;
+    FILE *f = NULL;
+    char check[FNAME_LEN];
+    CF *start = NULL;
+    int fd = 0;
+    env_t *env = NULL;
+
+
+    if (file != NULL)
+       start = file;
+    else
+       start = file_base;
+       
+    
+    for (cf = start; cf; cf = cf->cf_next) {
+
+       debug("Saving %s...", cf->cf_user);
+
+       /* create a file in order to detect unattended stop */
+       snprintf(check, sizeof(check), "%s.saving", cf->cf_user);
+       fd = open (check, O_CREAT);
+
+       /* open file for writing */
+       if ( path == NULL ) {
+           if ( (f = fopen(cf->cf_user, "w")) == NULL )
+               error_e("save");
+       }
+       else
+           if ( (f = fopen(path, "w")) == NULL )
+               error_e("save");
+
+       /* save file : */
+
+       /* put version of frontab file: it permit to daemon not to load
+        * a file which he won't understand the syntax, for exemple
+        * a file using a depreciated format generated by an old fcrontab,
+        * if the syntax has changed */
+       fprintf(f, "fcrontab-" FILEVERSION "\n");
+
+       /*   mailto, */
+       if ( cf->cf_mailto != NULL ) {
+           fprintf(f, "m");
+           fprintf(f, "%s%c", cf->cf_mailto, '\0');
+       } else
+           fprintf(f, "e");
+
+       /*   env variables, */
+       for (env = cf->cf_env_base; env; env = env->e_next) {
+           fprintf(f, "%s%c", env->e_name, '\0');
+           fprintf(f, "%s%c", env->e_val, '\0');
+       }
+       fprintf(f, "%c", '\0');
+
+       /*   finally, lines. */
+       for (cl = cf->cf_line_base; cl; cl = cl->cl_next) {
+           if ( fwrite(cl, sizeof(CL), 1, f) != 1 )
+               error_e("save");
+           fprintf(f, "%s%c", cl->cl_shell, '\0');
+
+       }
+    
+       fclose(f);
+
+       close(fd);
+       remove(check);
+       
+       if (file != NULL)
+           break ;
+
+    }
+}
diff --git a/config.h.in b/config.h.in
new file mode 100644 (file)
index 0000000..ca62980
--- /dev/null
@@ -0,0 +1,112 @@
+/*
+ * FCRON - periodic command scheduler 
+ *
+ *  Copyright 2000 Thibault Godouet <sphawk@free.fr>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ * 
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * 
+ *  The GNU General Public License can also be found in the file
+ *  `LICENSE' that comes with the fcron source distribution.
+ */
+
+/* config.h */
+
+
+/* *********************************************************** */
+/* This file is destined to be edited to match your preference */
+/* *********************************************************** */
+
+
+/* ****************************************************************** */
+/* beginning of configurable stuff ********************************** */
+
+
+/* NOTE : if you change anything here, check if the doc should not
+          be updated
+*/
+
+
+/* *** options which can be set in Makefile *** */
+
+#ifndef VERSION
+#define VERSION        "1.0.0"   /* default version if not defined in Makefile */
+#endif
+
+#ifndef FCRONTABS        /* directory where is stored users' fcrontabs */
+#define FCRONTABS      "/var/spool/fcron/"
+#endif
+
+#ifndef DEBUG
+#define DEBUG           0  /* 1 if you want debug mode */
+#endif
+
+#ifndef FOREGROUND
+#define FOREGROUND      0  /* 1 if you want foreground mode by default */
+#endif
+
+#ifndef SAVE
+#define SAVE            1800  /* save every n seconds */
+#endif
+
+
+/* *** paths *** */
+
+#define FCRON_ALLOW      "fcron.allow"
+
+#define FCRON_DENY       "fcron.deny"
+
+#define SENDMAIL       "/usr/lib/sendmail" /* mail command */
+
+#define SENDMAIL_ARGS  "-Ffcron", "-odi"   /* args of mail command */
+
+#define EDITOR          "/usr/bin/vi"       /* default editor */
+
+#define SHELL           "/bin/sh"           /* default shell */
+
+#define PIDFILE                "/var/run/fcron.pid" /* where is located pid file ? */
+
+
+/* *** memory *** */
+
+#define MAXLINES       256  /* max lines in non-root fcrontabs */
+
+#define LINE_LEN    1024     /* max line length in user's config file */
+#define FNAME_LEN 512        /* max length of a file name */
+#define ENV_LEN 50           /* max length of an env variable name */
+
+#define MAX_MSG 150           /* max length of a log message */
+
+
+
+/* *** system dependent *** */
+
+#define ERR     -1           
+#define OK       0
+
+#define EXIT_ERR   1         /* code returned by fcron/fcrontab on error */
+#define EXIT_OK    0         /* code returned  on normal exit */
+
+/* Syslog facility and priorities messages will be logged to (see syslog(3)) */
+#define SYSLOG_FACILITY LOG_CRON
+#define EXPLAIN_LEVEL LOG_NOTICE  /* informational messages */
+#define WARNING_LEVEL LOG_WARNING /* warning messages */
+#define COMPLAIN_LEVEL LOG_ERR    /* error messages */
+#define DEBUG_LEVEL LOG_DEBUG     /* only used when DEBUG is defined */
+
+
+
+/* end of configurable stuff **************************************** */
+/* ****************************************************************** */
+
diff --git a/database.c b/database.c
new file mode 100644 (file)
index 0000000..7e89314
--- /dev/null
@@ -0,0 +1,476 @@
+
+/*
+ * FCRON - periodic command scheduler 
+ *
+ *  Copyright 2000 Thibault Godouet <sphawk@free.fr>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ * 
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * 
+ *  The GNU General Public License can also be found in the file
+ *  `LICENSE' that comes with the fcron source distribution.
+ */
+
+/* DATABASE.C */
+
+#include "fcron.h"
+
+int is_leap_year(int year);
+int get_nb_mdays(int year, int mon);
+void goto_non_matching(CL *line, struct tm *tm);
+
+
+
+void
+test_jobs(time_t t2)
+  /* determine which jobs need to be run, and run them. */
+{
+    CF *file;
+    CL *line;
+    //time_t ti = 0;
+    //int adjust = 0;
+
+    debug("Looking for jobs to execute ...");
+
+    for (file = file_base; file; file = file->cf_next) {
+       debug("FILE %s:", file->cf_user);
+
+       for (line = file->cf_line_base; line; line = line->cl_next) {
+
+           if ( (line->cl_nextexe <= t2) && (line->cl_nextexe != 0) ) {
+
+               set_next_exe(line, 0);
+
+               if (--(line->cl_remain) > 0) {
+                   debug("    cl_Remain: %d", line->cl_remain);
+                   continue ;
+               }
+
+               if (line->cl_pid > 0) {
+                   warn("    process already running: %s '%s'", 
+                        file->cf_user,
+                        line->cl_shell
+                       );
+                   line->cl_remain = line->cl_runfreq;
+               } 
+               else {
+                   line->cl_remain = line->cl_runfreq;
+                   
+                   run_job(file, line);
+                   
+               }
+           }
+
+           /* jobs based on timefreq */
+           else if (line->cl_timefreq > 0 && line->cl_remain <= 0 ) {
+
+               /* time may has changed since the begin time t2
+                * if system load average is high : we handle it here */
+               //ti = time(NULL);
+               //adjust = ti - t2;
+
+               //line->cl_remain = line->cl_timefreq - adjust;
+               line->cl_remain = line->cl_timefreq;
+
+               if (line->cl_pid > 0) {
+                   warn("    process already running: %s '%s'", 
+                        file->cf_user,
+                        line->cl_shell
+                       );
+               } else {
+
+                   run_job(file, line);
+                   
+               }
+           }
+                           
+
+       }
+       
+    }
+
+}
+
+
+void
+wait_chld(void)
+  /* wait_chld() - check for job completion */
+{
+    CF *file;
+    CL *line;
+    int pid;
+    int status;
+
+    ////////
+    debug("wait_chld");
+    ///////
+
+    while ( (pid=wait3(&status, WNOHANG, NULL)) > 0 ) {
+
+       for (file = file_base; file; file = file->cf_next) {
+
+           if (file->cf_running) {
+
+               for (line = file->cf_line_base; line; line = line->cl_next) {
+
+                   if (pid < 0 || pid == line->cl_pid)
+                       end_job(file, line, status);
+                   else if ( pid == line->cl_mailpid )
+                       end_mailer(line, status);
+
+               }
+           }
+       }
+    }
+
+}
+
+
+void
+wait_all(int *counter)
+   /* return after all jobs completion. */
+{
+    CF *file;
+    CL *line;
+    pid_t pid;
+    int status;
+
+    debug("Waiting for all jobs");
+
+    while ( (*counter > 0) && (pid=wait3(&status, 0, NULL)) > 0 ) {
+
+       for (file = file_base; file; file = file->cf_next) {
+
+           if (file->cf_running) {
+
+               for (line = file->cf_line_base; line; line = line->cl_next) {
+
+                   if (pid < 0 || pid == line->cl_pid)
+                       end_job(file, line, status);
+                   else if ( pid == line->cl_mailpid )
+                       end_mailer(line, status);
+
+               }
+           }
+       }
+    }    
+
+}
+
+
+int
+is_leap_year(int year)
+  /* return 1 if it's a leap year otherwise return 0 */
+{
+    return ( (year % 4 == 0) &&
+            ( (year % 100 != 0) || (year % 400 == 0) ) );
+
+}
+
+
+int
+get_nb_mdays(int year, int mon)
+  /* return the number of days in a given month of a given year */
+{
+    if (mon == 1) { /* is February ? */
+       if ( is_leap_year(year) )
+           return 29;
+       else
+           return 28;
+    }
+    else if (mon <= 6)
+       if (mon % 2 == 0)
+           return 31;
+       else
+           return 30;
+    else if (mon % 2 == 0)
+       return 30;
+    else 
+       return 31;
+
+}
+
+
+void
+set_wday(struct tm *date)
+  /* we know that 01/01/2000 was a Saturday ( day number 6 )
+   * so we count the number of days since 01/01/2000,
+   * and add this number modulo 7 to the wday number */
+{
+    long nod = 0;
+    register int i;
+
+    /* we add the number of days of each previous years */
+    for (i =  (date->tm_year - 1); i >= 100 ; i--)
+       nod += ( is_leap_year(i+1900) ) ? 366 : 365;
+
+    /*     and month */
+    for (i =  (date->tm_mon - 1); i >= 0; i--)
+       nod += get_nb_mdays( (date->tm_year + 1900), i);
+
+    /* then we add the number of days passed in the current month */
+    nod += (date->tm_mday - 1); /* (mday is set from 1 to 31) */
+
+    date->tm_wday = (nod % 7) + 6;
+
+    if ( date->tm_wday >= 7 )
+       date->tm_wday -= 7;
+
+    debug("   dow of %d-%d-%d : %d", (date->tm_mon + 1), date->tm_mday, 
+         ( date->tm_year + 1900 ), date->tm_wday); 
+
+} 
+
+
+void
+goto_non_matching(CL *line, struct tm *ftime)
+    /* search the first the nearest time and date that does
+     * not match the line */
+{
+    register int i;
+    
+    /* check if we are in an interval of execution */
+    if( ! ( bit_test(line->cl_mins, ftime->tm_min) &&
+           bit_test(line->cl_hrs, ftime->tm_hour) &&
+           bit_test(line->cl_days, ftime->tm_mday) &&
+           bit_test(line->cl_dow, ftime->tm_wday) &&
+           bit_test(line->cl_mons, ftime->tm_mon)
+       ) )
+       return;
+    
+
+    for(i=ftime->tm_min; bit_test(line->cl_mins, i) && (i<60); i++);
+    if (i == 60) {
+       ftime->tm_min = 0;
+       if (ftime->tm_hour++ == 24) {
+           ftime->tm_hour = 0;
+           if(ftime->tm_mday++ == 
+              get_nb_mdays((ftime->tm_year+1900),ftime->tm_mon)) {
+               ftime->tm_mday = 0;
+               if(ftime->tm_mon++ == 12) {
+                   ftime->tm_mon = 0;
+                   ftime->tm_year++;
+               }
+           }
+       }
+
+       set_wday(ftime);
+       /* mktime set the wday */
+       //mktime(ftime);
+
+       if( ! ( bit_test(line->cl_hrs, ftime->tm_hour) &&
+               bit_test(line->cl_days, ftime->tm_mday) &&
+               bit_test(line->cl_dow, ftime->tm_wday) &&
+               bit_test(line->cl_mons, ftime->tm_mon)
+           ) )
+           goto exit_fct;
+
+    }
+         
+  exit_fct:
+    debug("   '%s' first non matching %d/%d/%d wday:%d %02d:%02d",
+         line->cl_shell, (ftime->tm_mon + 1), ftime->tm_mday,
+         (ftime->tm_year + 1900), ftime->tm_wday,
+         ftime->tm_hour, ftime->tm_min);
+    return;
+
+}
+
+
+void 
+set_next_exe(CL *line, char is_new_line)
+  /* set the cl_nextexe of a given CL */
+{
+
+    if (line->cl_timefreq == 0) {
+
+       struct tm *ft;
+       struct tm ftime;
+       time_t now;
+       register int i;
+       int max;
+
+
+       now = time(NULL);
+       ft = localtime(&now);
+
+       /* localtime() function seem to return every time the same pointer :
+          it resets our previous changes, so we need to prevent it
+          ( localtime() is used in the debug() function) */
+       memcpy(&ftime, ft, sizeof(struct tm));
+
+       ftime.tm_sec = 0;
+       ftime.tm_isdst = -1;
+
+       if ( line->cl_runfreq == 1 && ! is_new_line )
+           goto_non_matching(line, &ftime);
+       else
+           /* to prevent multiple execution in the same minute */
+           ftime.tm_min += 1;
+
+    
+      setMonth:
+       for (i = ftime.tm_mon; (bit_test(line->cl_mons, i)==0) && (i<12); i++);
+       if (i >= 12) {
+           ftime.tm_year++;
+           ftime.tm_mon = 0;
+           ftime.tm_mday = 1;
+           ftime.tm_hour = 0;
+           ftime.tm_min = 0;
+           goto setMonth;
+       }
+       if (ftime.tm_mon !=  i) {
+           ftime.tm_mon = i;
+           ftime.tm_mday = 1;
+           ftime.tm_hour = 0;
+           ftime.tm_min = 0;
+       }           
+
+       /* set the number of days in that month */
+       max = get_nb_mdays( (ftime.tm_year + 1900), ftime.tm_mon);
+
+      setDay:
+       for (i=ftime.tm_mday; (bit_test(line->cl_days, i)==0)&&(i<=max); i++);
+       if (i > max) {
+           ftime.tm_mon++;
+           ftime.tm_mday = 1;
+           ftime.tm_hour = 0;
+           ftime.tm_min = 0;
+           goto setMonth;
+       }
+       if ( ftime.tm_mday != i ) {
+           ftime.tm_mday = i;
+           ftime.tm_hour = 0;
+           ftime.tm_min = 0;       
+       }
+
+       set_wday(&ftime);
+       /* mktime set the wday */
+       //mktime(&ftime);
+
+        /* check if the day of week is OK */
+       if ( bit_test(line->cl_dow, ftime.tm_wday) == 0 ) {
+           ftime.tm_mday++;
+           ftime.tm_hour = 0;
+           ftime.tm_min = 0;
+           goto setDay;
+       }
+   
+      setHour:
+       for (i=ftime.tm_hour; (bit_test(line->cl_hrs, i)==0) && (i<24); i++);
+       if (i >= 24) {
+           ftime.tm_mday++;
+           ftime.tm_hour = 0;
+           ftime.tm_min = 0;
+           goto setDay;
+       }
+       if ( ftime.tm_hour != i ) {
+           ftime.tm_hour = i;
+           ftime.tm_min = 0;
+       }
+   
+
+       for (i=ftime.tm_min; (bit_test(line->cl_mins, i)==0) && (i<60); i++);
+       if (i >= 60) {
+           ftime.tm_hour++;
+           ftime.tm_min = 0;
+           goto setHour;
+       }
+       ftime.tm_min = i;
+   
+    
+       line->cl_nextexe = mktime(&ftime);
+
+       debug("   cmd: '%s' next exec %d/%d/%d wday:%d %02d:%02d",
+             line->cl_shell, (ftime.tm_mon + 1), ftime.tm_mday,
+             (ftime.tm_year + 1900), ftime.tm_wday,
+             ftime.tm_hour, ftime.tm_min);
+       
+
+    }
+
+}
+
+
+long
+time_to_sleep(short lim)
+  /* return the time to sleep until next task have to be executed. */
+{
+
+    CF *file;
+    CL *line;
+    /* we set tts to a big value, unless some problems can occurs
+     * with files without any line */
+    time_t tts = lim;
+    time_t cur;
+    time_t now;
+
+    now = time(NULL);
+
+    for (file = file_base; file; file = file->cf_next) {
+
+       for (line = file->cf_line_base; line; line = line->cl_next) {
+
+           if (line->cl_nextexe > 0)
+               cur = (line->cl_nextexe > now) ? (line->cl_nextexe - now) : 0;
+           else
+               cur = line->cl_remain;
+
+           if (cur < tts)
+               tts = cur;
+
+           if (tts == 0)
+               return 0;
+
+       }
+
+    }
+
+    debug("Time to sleep: %lds", tts);
+
+    return tts;
+}
+
+
+
+void
+update_time_remaining(long dt)
+  /* update the remaining time of tasks run at a certain frequency */
+{
+    CF *file;
+    CL *line;
+
+    debug("Updating time remaining ...");
+    
+    for (file = file_base; file; file = file->cf_next) {
+
+       debug("File %s", file->cf_user);
+    
+       for (line = file->cf_line_base; line; line = line->cl_next) {
+           if (line->cl_timefreq > 0) {
+
+               if ( (line->cl_remain - dt) >= 0 )
+                   line->cl_remain -= dt;
+               else
+                   line->cl_remain = 0;
+
+               debug("  '%s' cl_remain = %d", line->cl_shell,
+                     line->cl_remain);
+           }
+       }
+
+    }
+  
+}
+
diff --git a/fcron.c b/fcron.c
new file mode 100644 (file)
index 0000000..1cabd5e
--- /dev/null
+++ b/fcron.c
@@ -0,0 +1,518 @@
+/*
+ * FCRON - periodic command scheduler 
+ *
+ *  Copyright 2000 Thibault Godouet <sphawk@free.fr>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ * 
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * 
+ *  The GNU General Public License can also be found in the file
+ *  `LICENSE' that comes with the fcron source distribution.
+ */
+
+ /* fcron.c */
+
+#include "fcron.h"
+
+void main_loop(void);
+void info(void);
+void usage(void);
+void xexit(int exit_value);
+void sighup_handler(int x);
+void sigterm_handler(int x);
+void sigchild_handler(int x);
+void sigusr1_handler(int x);
+int parseopt(int argc, char *argv[]);
+void get_lock(void);
+
+
+char debug_opt = DEBUG;       /* set to 1 if we are in debug mode */
+char foreground = FOREGROUND; /* set to 1 when we are on foreground, else 0 */
+char  *cdir = FCRONTABS;       /* the dir where are stored users' fcrontabs */
+int daemon_uid;                 
+pid_t daemon_pid;               
+char *prog_name = NULL;         
+char sig_conf = 0;             /* is 1 when we got a SIGHUP */ 
+char sig_chld = 0;            /* is 1 when we got a SIGCHLD */  
+CF *file_base;                /* point to the first file of the list */
+int jobs_running = 0;         /* number of jobs which are running */
+time_t t1;                    /* the time at which sleep began */
+
+
+void
+info(void)
+    /* print some informations about this program :
+     * version, license */
+{
+    fprintf(stderr,
+           "fcron " VERSION " - periodic command scheduler\n"
+           "Copyright 2000 Thibault Godouet <sphawk@free.fr>\n"
+           "This program is free software; you can redistribute it\n"
+           " and/or modify it under the terms of the GNU General Public\n"
+           " License. See file LICENSE distributed with this program\n"
+           " for more informations\n"
+       );
+
+    exit(EXIT_OK);
+
+}
+
+
+void
+usage()
+  /*  print a help message about command line options and exit */
+{
+    fprintf(stderr, "\nfcron " VERSION "\n\n"
+           "fcron [-d] [-f] [-b]\n"
+           "fcron -h\n"
+           "  -d     --debug          Set Debug mode.\n"
+           "  -f     --foreground     Stay in foreground.\n"
+           "  -b     --background     Go to background.\n"
+           "  -h     --help           Show this help message.\n"
+       );
+    
+    exit(EXIT_ERR);
+}
+
+
+void 
+xexit(int exit_value) 
+    /* exit after having freed memory and removed lock file */
+{
+    CF *f = NULL;
+    extern time_t t1;
+    time_t t2 = time(NULL);
+    time_t dt = 0;
+    
+    dt = t2 - t1;
+
+    if (dt > 0) {
+
+       debug("slept: %lds", dt);
+       
+       update_time_remaining(dt);
+    }
+
+    /* we save all files now and after having waiting for all
+     * job being executed because we might get a SIGKILL
+     * if we don't exit quickly */
+    save_file(NULL, NULL);
+
+    f = file_base;
+    while ( f != NULL ) {
+       if ( f->cf_running > 0 ) {
+           wait_all( &f->cf_running );
+           save_file(f, NULL);
+       }
+       delete_file(f->cf_user);    
+
+       /* delete_file remove the f file from the list :
+        * next file to remove is now pointed by file_base. */
+       f = file_base;
+    }
+
+    remove(PIDFILE);
+    
+    explain("Exiting with code %d", exit_value);
+    exit (exit_value);
+
+}
+
+void
+get_lock()
+    /* check if another fcron daemon is running : in this case, die.
+     * if not, write our pid to /var/run/fcron.pid in order to lock,
+     * and to permit fcrontab to read our pid and signal us */
+{
+    static FILE *fp = NULL;
+
+    if ( ! fp ) {
+       int     fd, otherpid, foreopt;
+
+       foreopt = foreground;
+       foreground = 1;
+
+       if (((fd = open(PIDFILE, O_RDWR|O_CREAT, 0644)) == -1 )
+           || ((fp = fdopen(fd, "r+"))) == NULL)
+           die_e("can't open or create " PIDFILE);
+       
+    
+       if ( flock(fd, LOCK_EX|LOCK_NB) != 0 ) {
+           if ((errno == EAGAIN) || (errno == EACCES))
+               errno = EWOULDBLOCK;
+           fscanf(fp, "%d", &otherpid);
+           die("can't lock " PIDFILE ", running daemon's pid may be %d",
+               otherpid);
+       }
+
+       (void) fcntl(fd, F_SETFD, 1);
+
+       foreground = foreopt;
+
+    }
+
+    rewind(fp);
+    fprintf(fp, "%d\n", daemon_pid);
+    fflush(fp);
+    (void) ftruncate(fileno(fp), ftell(fp));
+
+    /* abandon fd and fp even though the file is open. we need to
+     * keep it open and locked, but we don't need the handles elsewhere.
+     */
+
+
+}
+
+
+int
+parseopt(int argc, char *argv[])
+  /* set options */
+{
+
+    char c;
+    int i;
+    static struct option opt[] =
+    {
+       {"debug",0,NULL,'d'},
+       {"foreground",0,NULL,'f'},
+       {"background",0,NULL,'b'},
+       {"help",0,NULL,'h'},
+       {"version",0,NULL,'V'},
+       {0,0,0,0}
+    };
+    extern char *optarg;
+    extern int optind, opterr, optopt;
+
+    /* constants and variables defined by command line */
+
+    while(1) {
+       c = getopt_long(argc, argv, "dfbhV", opt, NULL);
+       if (c == -1) break;
+       switch (c) {
+
+       case 'V':
+           info(); break;
+
+       case 'h':
+           usage(); break;
+
+       case 'd':
+           debug_opt = 1;
+           foreground = 1;
+
+       case 'f':
+           foreground = 1; break;
+
+       case 'b':
+           foreground = 0; break;
+
+       case 'c':
+           cdir = optarg; break;
+
+       case ':':
+           error("(setopt) Missing parameter");
+           usage();
+
+       case '?':
+           usage();
+
+       default:
+           warn("(setopt) Warning: getopt returned %c", c);
+       }
+    }
+
+    if (optind < argc) {
+       for (i = optind; i<=argc; i++)
+           error("Unknown argument '%s'", argv[i]);
+       usage();
+    }
+
+    return OK;
+
+}
+
+
+void
+sigterm_handler(int x)
+  /* exit safely */
+{
+    debug("");
+    explain("SIGTERM signal received");
+    xexit(EXIT_OK);
+}
+
+void
+sighup_handler(int x)
+  /* update configuration */
+{
+    signal(SIGHUP, sighup_handler);
+    debug("");
+    explain("SIGHUP signal received");
+    /* we don't call the synchronize_dir() function directly,
+       because it may cause some problems if this signal
+       is not received during the sleep
+    */
+    sig_conf = 1;
+}
+
+void
+sigchild_handler(int x)
+  /* call wait_chld() to take care of finished jobs */
+{
+    debug("");
+    debug("SIGCHLD signal received.");
+    
+    sig_chld = 1;
+
+    (void)signal(SIGCHLD, sigchild_handler);
+}
+
+
+void
+sigusr1_handler(int x)
+  /* reload all configurations */
+{
+    signal(SIGUSR1, sigusr1_handler);
+    debug("");
+    explain("SIGUSR1 signal received");
+    /* we don't call the synchronize_dir() function directly,
+       because it may cause some problems if this signal
+       is not received during the sleep
+    */
+    sig_conf = 2;
+}
+
+
+int
+main(int argc, char **argv)
+{
+    int i;
+
+    /* this program belongs to root : we set default permission mode
+     * to  600 for security reasons */
+    umask(066);
+
+    /* parse options */
+
+    if (strrchr(argv[0],'/')==NULL) prog_name = argv[0];
+    else prog_name = strrchr(argv[0],'/')+1;
+
+    daemon_uid = getuid();
+
+    /* we have to set daemon_pid before the fork because it's
+     * used in die() and die_e() functions */
+    daemon_pid = getpid();
+
+    parseopt(argc, argv);
+
+    /* change directory */
+
+    if (chdir(cdir) != 0)
+       die_e("Could not change dir to " FCRONTABS);
+    
+
+    if (foreground == 0) {
+
+       /*
+        * close stdin and stdout (stderr normally redirected by caller).
+        * close unused descriptors
+        * optional detach from controlling terminal
+        */
+
+       int fd;
+       pid_t pid;
+
+       /* check if another fcron daemon is running */
+       get_lock();
+
+       switch ( pid = fork() ) {
+       case -1:
+           die_e("fork");
+           break;
+       case 0:
+           /* child */
+           break;
+       default:
+           /* parent */
+           printf("\n%s[%d] " VERSION " : started.\n\n",
+                  prog_name, pid);
+
+           exit(0);
+       }
+
+       daemon_pid = getpid();
+
+       if ((fd = open("/dev/tty", O_RDWR)) >= 0) {
+           ioctl(fd, TIOCNOTTY, 0);
+           close(fd);
+       }
+
+       fclose(stdin);
+       fclose(stdout);
+
+       if ( (i = open("/dev/null", O_RDWR)) < 0)
+           die_e("open: /dev/null:");
+       dup2(i, 0);
+       dup2(i, 1);
+
+
+       if(debug_opt) {
+           /* wait until child death and log his return value
+            * on error */
+           int status;
+               
+           switch ( pid = fork() ) {
+           case -1:
+               die_e("fork");
+               break;
+           case 0:
+               /* child */
+               daemon_pid = getpid();
+               break;
+           default:
+               /* parent */
+               while ( wait4(pid, &status, 0, NULL) != pid ) ;
+               if ( ! WIFEXITED(status) )
+                   error("fcron[%d] has exited with status %d",
+                         pid, WEXITSTATUS(status));
+               if ( WIFSIGNALED(status) )
+                   error("fcron[%d] has exited due to signal %d",
+                         pid, WTERMSIG(status));
+
+               exit(0);
+
+           }
+       }
+
+
+    }
+
+    /* if we are in foreground, check if another fcron daemon
+     * is running, otherwise update value of pid in lock file */
+    get_lock();
+    
+    explain("%s[%d] " VERSION " started", prog_name, daemon_pid);
+
+    (void)signal(SIGTERM, sigterm_handler);
+    (void)signal(SIGHUP, sighup_handler);
+    (void)signal(SIGCHLD, sigchild_handler);
+    (void)signal(SIGUSR1, sigusr1_handler);
+
+    main_loop();
+
+    /* never reached */
+    return EXIT_OK;
+}
+
+
+void main_loop()
+  /* main loop - get the time to sleep until next job execution,
+   *             sleep, and then test all jobs and execute if needed. */
+{
+    extern time_t t1;     /* time at the beginning of sleeping */
+    time_t t2 = 0;        /* time at the end of sleeping */
+    time_t t3 = 0;        /* time at the previous reception of SIGCHLD */
+    long int dt = 0;      /* time we slept */
+    time_t save = SAVE;   /* time remaining until next save */
+    struct timeval tv;    /* we use usec field to get more precision */
+    time_t stime = 0;     /* time to sleep until next job
+                          * execution */
+
+    debug("Entering main loop");
+
+    t1 = time(NULL);
+
+    synchronize_dir(".");
+
+    /* synchronize save with jobs execution */
+    save += (60 - (t1 % 60));
+    stime = time_to_sleep(save);
+
+    for (;;) {
+
+       sig_chld = 0;
+
+      sleep:
+       sleep(stime - 1);
+       gettimeofday(&tv, NULL);
+       usleep( 1000000 - tv.tv_usec );
+
+       if (sig_chld > 0) {
+
+           /* sleep has been stopped too early :
+            * sleep the remaining time */
+           wait_chld();
+           sig_chld = 0;
+
+           if ( t3 < t1 ) t3 = t1;
+           dt = time(NULL) - t3;
+
+           if ( dt > 0 ) {
+               /* update stime value */
+               dt = stime - dt;
+               debug("remain %d s of sleeping", dt);
+               stime = dt;
+               t3 = time(NULL);
+
+           } else
+               /* we have slept less than 1 second : old stime
+                * is still valid */
+               ;
+
+           goto sleep;
+
+       }
+
+       t2 = time(NULL);
+       dt = t2 - t1;
+
+       if (dt > 0) {
+
+           debug("\n");
+           debug("slept: %lds", dt);
+
+           update_time_remaining(dt);
+
+           if (sig_conf > 0) {
+
+               if (sig_conf == 1)
+                   /* update configuration */
+                   synchronize_dir(".");
+               else {
+                   /* reload all configuration */
+                   update_time_remaining(dt);              
+                   reload_all(".");
+               }               
+               sig_conf = 0;
+           }
+
+           test_jobs(t2);
+
+           if ( (save = ( (save-dt) >= 0 ) ? save-dt : 0) == 0) {
+               save = SAVE - (t2 % 60);
+               /* save all files */
+               save_file(NULL, NULL);
+           }
+
+       }
+
+       if ( sig_chld == 1 )
+           wait_chld();
+
+       stime = time_to_sleep(save);
+
+       t1 = t2;
+    }
+
+}
diff --git a/fcron.h b/fcron.h
new file mode 100644 (file)
index 0000000..80ab70e
--- /dev/null
+++ b/fcron.h
@@ -0,0 +1,100 @@
+/*
+ * FCRON - periodic command scheduler 
+ *
+ *  Copyright 2000 Thibault Godouet <sphawk@free.fr>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ * 
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * 
+ *  The GNU General Public License can also be found in the file
+ *  `LICENSE' that comes with the fcron source distribution.
+ */
+
+/* fcron.h */
+
+#ifndef __FCRONH__
+#define __FCRONH__
+
+#include "global.h"
+
+#include <dirent.h>
+#include <fcntl.h>
+#include <grp.h>
+#include <sys/ioctl.h>
+#include <sys/time.h>
+
+
+/* global variables */
+extern char debug_opt;
+extern char foreground;
+extern char *cdir;
+extern int daemon_uid;
+extern pid_t daemon_pid;
+extern char *prog_name;
+extern char sig_hup;
+extern CF *file_base;
+extern int jobs_running;
+/* end of global variables */
+
+
+/* functions prototypes */
+
+/* fcron.c */
+extern void xexit(int exit_value);
+/* end of fcron.c */
+
+/* log.c */
+extern void xcloselog(void);
+extern char *make_msg(char *fmt, va_list args);
+extern void explain(char *fmt, ...);
+extern void explain_e(char *fmt, ...);
+extern void warn(char *fmt, ...);
+extern void warn_e(char *fmt, ...);
+extern void error(char *fmt, ...);
+extern void error_e(char *fmt, ...);
+extern void die(char *fmt, ...);
+extern void die_e(char *fmt, ...);
+extern void Debug(char *fmt, ...);
+/* end of log.c */
+
+/* subs.c */
+extern int remove_blanks(char *str);
+extern char *strdup2(const char *);
+/* end of subs.c */
+
+/* database.c */
+extern void test_jobs(time_t t2);
+extern void wait_chld(void);
+extern void wait_all(int *counter);
+extern time_t time_to_sleep(short lim);
+extern void set_next_exe(CL *line, char is_new_line);
+extern void update_time_remaining(long dt);
+/* end of database.c */
+
+/* conf.c */
+extern void reload_all(const char *dir_name);
+extern void synchronize_dir(const char *dir_name);
+extern void delete_file(const char *user_name);
+extern void save_file(CF *file_name, char *path);
+/* end of conf.c */
+
+/* job.c */
+extern void run_job(CF *file, CL *line);
+extern void end_job(CF *file, CL *line, int status);
+extern void end_mailer(CL *line, int status);
+/* end of job.c */
+
+
+#endif /* __FCRONH */
+
diff --git a/fcrontab.c b/fcrontab.c
new file mode 100644 (file)
index 0000000..3135d8b
--- /dev/null
@@ -0,0 +1,660 @@
+
+/*
+ * FCRON - periodic command scheduler 
+ *
+ *  Copyright 2000 Thibault Godouet <sphawk@free.fr>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ * 
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * 
+ *  The GNU General Public License can also be found in the file
+ *  `LICENSE' that comes with the fcron source distribution.
+ */
+
+/* FCRONTAB.C */
+
+/* 
+ * The goal of this program is simple : giving a user interface to fcron
+ * daemon, by allowing each user to see, modify, append or remove his 
+ * fcrontabs. 
+ * Fcron daemon use a specific formated format of file, so fcrontab generate
+ * that kind of file from human readable files. In order allowing users to
+ * see and modify their fcrontabs, the source file is always saved with the
+ * formated one. 
+ * Fcrontab makes a temporary formated file, and then sends a signal 
+ * to the daemon to force it to update its configuration, remove the temp
+ * file and save a new and final formated file.
+ * That way, not the simple, allows the daemon to keep a maximum of 
+ * informations like the time remaining before next execution, or the date
+ * and hour of next execution.
+ */
+
+#include "fcrontab.h"
+
+
+void info(void);
+void usage(void);
+void sig_daemon(void);
+pid_t read_pid(void);
+
+
+/* command line options */
+char rm_opt = 0;
+char list_opt = 0;
+char edit_opt = 0;
+char ignore_prev = 0;
+int file_opt = 0;
+char debug_opt = DEBUG;
+char *user = NULL;
+char  *cdir = FCRONTABS;
+
+char need_sig = 0;           /* do we need to signal fcron daemon */
+
+char *orig_dir = NULL;
+CF *file_base = NULL;
+char buf[FNAME_LEN];
+char file[FNAME_LEN];
+
+/* needed by log part : */
+char *prog_name = NULL;
+char foreground = 1;
+pid_t daemon_pid = 0;
+
+
+void
+info(void)
+    /* print some informations about this program :
+     * version, license */
+{
+    fprintf(stderr,
+           "fcrontab " VERSION " - user interface to daemon fcron\n"
+           "Copyright 2000 Thibault Godouet <sphawk@free.fr>\n"
+           "This program is free software; you can redistribute it\n"
+           " and/or modify it under the terms of the GNU General Public\n"
+           " License. See file LICENSE distributed with this program\n"
+           " for more informations\n"
+       );
+
+    exit(EXIT_OK);
+
+}
+
+
+void
+usage(void)
+  /*  print a help message about command line options and exit */
+{
+    fprintf(stderr, 
+           "fcrontab [-u user] [-n] file\n"
+           "fcrontab [-u user] { -l | -r | -e [-n] }\n"
+           "fcrontab -h\n"
+           "  -u user    specify user name.\n"
+           "  -l         list user's current fcrontab.\n"
+           "  -r         remove user's current fcrontab.\n"
+           "  -e         edit user's current fcrontab.\n"
+           "  -n         ignore previous version of file.\n"
+           "  -d         set up debug mode.\n"
+           "  -h         display this help message.\n"
+           "\n"
+       );
+    
+    exit(EXIT_ERR);
+}
+
+
+pid_t
+read_pid(void)
+    /* return fcron daemon's pid if running.
+     * otherwise return 0 */
+{
+    FILE *fp = NULL;
+    pid_t pid = 0;
+    
+    if ((fp = fopen(PIDFILE, "r")) != NULL) {
+       fscanf(fp, "%d", &pid);    
+       fclose(fp);
+    }
+
+    return pid;
+}
+
+
+void
+sig_daemon(void)
+    /* send SIGHUP to daemon to tell him configuration has changed */
+    /* SIGHUP is sent once 10s before the next minute to avoid
+     * some bad users to block daemon by sending it SIGHUP all the time */
+{
+    time_t t = 0;
+    int sl = 0;
+    FILE *fp = NULL;
+    int        fd = 0;
+    struct tm *tm = NULL;
+
+
+    t = time(NULL);
+    tm = localtime(&t);
+    
+    if ( (sl = 60 - (t % 60) - 10) < 0 ) {
+       snprintf(buf, sizeof(buf), "%02dh%02d", tm->tm_hour, tm->tm_min + 2);
+       sl = 60 - (t % 60) + 50;
+    } else
+       snprintf(buf, sizeof(buf), "%02dh%02d", tm->tm_hour, tm->tm_min + 1);
+
+    fprintf(stderr, "Modifications will be take into account"
+           " at %s.\n", buf);
+
+
+    /* try to create a lock file */
+    if ((fd = open(FCRONTABS "/fcrontab.sig", O_RDWR|O_CREAT, 0644)) == -1
+       || ((fp = fdopen(fd, "r+")) == NULL) )
+       die_e("can't open or create " PIDFILE); 
+    
+    if ( flock(fd, LOCK_EX|LOCK_NB) != 0 ) {
+       debug("fcrontab is already waiting for signalling the daemon : exit");
+       return;
+    }
+
+
+    (void) fcntl(fd, F_SETFD, 1);
+
+    /* abandon fd and fp even though the file is open. we need to
+     * keep it open and locked, but we don't need the handles elsewhere.
+     */
+
+    switch ( fork() ) {
+    case -1:
+       remove(FCRONTABS "/fcrontab.sig");
+       die_e("could not fork : daemon as not been signaled");
+       break;
+    case 0:
+       /* child */
+       break;
+    default:
+       /* parent */
+       return;
+    }
+
+    sleep(sl);
+    
+    remove(FCRONTABS "/fcrontab.sig");
+
+    if ( (daemon_pid = read_pid()) == 0 )
+       /* daemon is not running any longer : we exit */
+       return ;
+
+    if ( kill(daemon_pid, SIGHUP) != 0)
+       die_e("could not send SIGHUP to daemon (pid %d)", daemon_pid);
+
+}
+
+
+
+void
+xexit(int exit_val)
+    /* launch signal if needed and exit */
+{
+    if ( need_sig == 1 ) {
+       /* check if daemon is running */
+       if ( (daemon_pid = read_pid()) != 0 )
+           sig_daemon();
+       else
+           fprintf(stderr, "fcron is not running :\n  modifications will"
+                   " be take into account at its next execution.\n");
+    }
+
+    exit(exit_val);
+
+}
+
+
+void
+copy(char *orig, char *dest)
+    /* copy orig file to dest */
+{
+    FILE *from = NULL, *to = NULL;
+    char c;
+
+    if ( (from = fopen(orig, "r")) == NULL
+        || (to = fopen(dest, "w")) == NULL) {
+       perror("copy");
+       return ;
+    }
+
+    while ( (c = getc(from)) != EOF )
+       if ( putc(c, to) == EOF ) {
+           fprintf(stderr, "Error while copying file. Aborting.\n");
+           xexit(ERR);
+       }
+
+    fclose(from);
+    fclose(to);
+    
+}
+
+
+int
+remove_fcrontab(char rm_orig)
+    /* remove user's fcrontab and tell daemon to update his conf */
+{
+
+    if ( rm_orig )
+       explain("removing %s's fcrontab", user);
+
+    /* remove source and formated file */
+    if ( (rm_orig && remove(buf)) != 0 || remove(user) != 0) {
+           
+       /* try to remove the temp file in case he has not
+        * been read by fcron daemon */
+       snprintf(buf, sizeof(buf), "new.%s", user);
+       if ( remove(buf) != 0 ) {
+
+           if ( errno == ENOENT )
+               return ENOENT;
+           else
+               perror(buf);            
+       }
+
+    }
+
+    /* finally create a file in order to tell the daemon
+     * a file was removed, and launch a signal to daemon */
+    { 
+       FILE *f;
+       snprintf(buf, sizeof(buf), "rm.%s", user);
+       f = fopen(buf, "w");
+       fclose(f);
+
+       need_sig = 1;
+
+    }
+
+    return OK;
+
+}
+
+void
+make_file(char *file, char *user)
+{
+
+    explain("installing file '%s' for user %s", file, user);
+
+    /* read file and create a list in memory */
+    if ( read_file(file, user) != ERR ) {
+       
+       if ( file_base->cf_line_base == NULL ) {
+           /* no entries */
+           explain("%s's fcrontab contains no entries", user);
+           remove_fcrontab(0);
+       }
+       else {
+           if (ignore_prev == 1)
+               /* if user wants to ignore previous version, we remove it *
+                * ( fcron daemon remove files no longer wanted before
+                *   adding new ones ) */
+               remove_fcrontab(0);
+
+           /* write that list in a temp file on disk */
+           snprintf(buf, sizeof(buf), "new.%s", user);
+           save_file(buf);
+
+           /* copy original file to FCRONTABS dir */
+           snprintf(buf, sizeof(buf), "%s.orig", user);
+           copy(file, buf);
+
+       } 
+
+       /* free memory used to store the list */
+       delete_file(user);
+
+       /* tell daemon to update the conf */
+       need_sig = 1;
+
+    } else
+       xexit(EXIT_ERR);
+    
+}
+
+
+void
+list_file(char *file)
+{
+    FILE *f = NULL;
+    char c;
+
+    explain("listing %s's fcrontab", user);
+
+    if ( (f = fopen(file, "r")) == NULL ) {
+       if ( errno == ENOENT ) {
+           explain("user %s has no fcrontab.", user);
+           return ;
+       }
+       else
+           die_e("User %s could not read file '%s'", user, file);
+    }
+    else {
+
+       while ( (c = getc(f)) != EOF )
+           putchar(c);
+
+       fclose(f);
+
+    }
+
+}
+
+void
+edit_file(char *buf)
+    /* copy file to a temp file, edit that file, and install it
+       if necessary */
+{
+    char *editor = NULL;
+    pid_t pid;
+    int status;
+    struct stat st;
+    time_t mtime = 0;
+    char tmp[FNAME_LEN];
+    FILE *f, *fi;
+    int file = 0;
+    char c;
+
+    explain("fcrontabs : editing %s's fcrontab", user);        
+
+    if ( (editor = getenv("VISUAL")) == NULL )
+       if( (editor = getenv("EDITOR")) == NULL )
+           editor = EDITOR;
+       
+    sprintf(tmp, "/tmp/fcrontab.%d", getpid());
+
+    if ( (file = open(tmp, O_CREAT|O_EXCL|O_WRONLY, 0600)) == -1 )
+       die_e("could not create file %s", tmp);
+    if ( (fi = fdopen(file, "w")) == NULL )
+       die_e("could not fdopen");
+
+    /* copy user's fcrontab (if any) to a temp file */
+    if ( (f = fopen(buf, "r")) == NULL ) {
+       if ( errno != ENOENT )
+           die_e("could not open file %s", buf);
+       else
+           fprintf(stderr, "no fcrontab for %s - using an empty one\n",
+                   user);
+    }
+    else { 
+       /* copy original file to temp file */
+       while ( (c=getc(f)) != EOF )
+           putc(c, fi);
+       fclose(f);
+    }
+
+    if ( fchown(file, getuid(), getgid()) != 0 )
+       die_e("could not chown %s", tmp);
+    
+    fclose(fi);
+    close(file);
+
+    if ( stat(tmp, &st) == 0 )
+       mtime = st.st_mtime;
+    else
+       die_e("could not stat '%s'", buf);
+    
+
+    switch ( pid = fork() ) {
+    case 0:
+       /* child */
+       if (setuid(getuid()) < 0) {
+           perror("setuid(getuid())");
+           xexit(EXIT_ERR);
+       }
+       execlp(editor, editor, tmp, NULL);
+       perror(editor);
+       xexit(EXIT_ERR);
+
+    case -1:
+       perror("fork");
+       xexit(EXIT_ERR);
+
+    default:
+       /* parent */
+       break ;
+    }
+           
+    /* only reached by parent */
+    wait4(pid, &status, 0, NULL);
+    if ( ! WIFEXITED(status) ) {
+       fprintf(stderr, "Editor exited abnormally:"
+               " fcrontab is unchanged.\n");
+       xexit(EXIT_ERR);
+    }
+
+    /* check if file has been modified */
+    if ( stat(tmp, &st) != 0 )
+       die_e("could not stat %s", tmp);
+    
+    else if ( st.st_mtime > mtime )
+       make_file(tmp, user);
+
+    else
+       fprintf(stderr, "Fcrontab is unchanged :"
+               " no need to install it.\n"); 
+
+    if ( remove(tmp) != 0 )
+       error("could not remove %s", tmp);
+           
+    xexit (EXIT_OK);
+}
+
+
+
+void
+parseopt(int argc, char *argv[])
+  /* set options */
+{
+
+    char c;
+    extern char *optarg;
+    extern int optind, opterr, optopt;
+
+    /* constants and variables defined by command line */
+
+    while(1) {
+       c = getopt(argc, argv, "u:lrednhV");
+       if (c == -1) break;
+       switch (c) {
+
+       case 'V':
+           info(); break;
+
+       case 'h':
+           usage(); break;
+
+       case 'u':
+           user = strdup2(optarg) ; 
+           if (getuid() != 0) {
+               fprintf(stderr, "must be privileged to use -u\n");
+               xexit(EXIT_ERR);
+           }
+           break;
+
+       case 'd':
+           debug_opt = 1; break;
+
+       case 'l':
+           list_opt = 1;
+           rm_opt = 0;
+           edit_opt = 0;
+           break;
+
+       case 'r':
+           list_opt = 0;
+           rm_opt = 1;
+           edit_opt = 0;
+           break;
+
+       case 'e':
+           list_opt = 0;
+           rm_opt = 0;
+           edit_opt = 1;
+           break;
+
+       case 'n':
+           ignore_prev = 1;
+           break;
+           
+       case ':':
+           fprintf(stderr, "(setopt) Missing parameter");
+           usage();
+
+       case '?':
+           usage();
+
+       default:
+           fprintf(stderr, "(setopt) Warning: getopt returned %c", c);
+       }
+    }
+
+    if ( user == NULL )
+       if ((user = getenv("USER")) == NULL) {
+           fprintf(stderr, "Could not get user name.\n");
+           xexit(EXIT_ERR);
+       }
+
+    if ( ! is_allowed(user) ) {
+       die("User '%s' is not allowed to use %s. Aborting.",
+           user, prog_name);       
+
+    }
+
+    if (optind < argc)
+       file_opt = optind;
+}
+
+
+int
+main(int argc, char **argv)
+{
+
+    memset(buf, 0, sizeof(buf));
+    memset(file, 0, sizeof(file));
+
+    if (strrchr(argv[0],'/')==NULL) prog_name = argv[0];
+    else prog_name = strrchr(argv[0],'/')+1;
+
+    /* interpret command line options */
+    parseopt(argc, argv);
+
+    if ( seteuid(0) != 0 )
+       die_e("Could not set uid to root");
+    if ( setegid(0) != 0 )
+       die_e("Could not set gid to root");
+
+    /* this program is seteuid root : we set default permission mode
+     * to  600 for security reasons */
+    umask(066);
+
+    /* get current dir */
+    if ( (orig_dir = getcwd(NULL, 0)) == NULL )
+       perror("getcwd");
+
+    /* change directory */
+    if (chdir(cdir) != 0) {
+       perror("Could not chdir to " FCRONTABS );
+       xexit (EXIT_ERR);
+    }
+
+    snprintf(buf, sizeof(buf), "%s.orig", user);
+
+    /* determine what action should be taken */
+    if ( file_opt && ! list_opt && ! rm_opt && ! edit_opt ) {
+
+       if ( strcmp(argv[file_opt], "-") == 0 ) {
+
+           /* install what we get through stdin */
+
+           FILE *tmp_file = NULL;
+           char tmp[FNAME_LEN];
+           register char c;
+                   
+           sprintf(tmp, "/tmp/fcrontab.%d", getpid());
+           if( (tmp_file = fopen(tmp, "w")) == NULL )
+               fprintf(stderr, "Could not open '%s': %s\n", tmp,
+                       strerror(errno));
+
+           while ( (c = getc(stdin)) != EOF )
+               putc(c, tmp_file);
+
+           fclose(tmp_file);
+
+           if ( chown(tmp, getuid(), getgid()) != 0 )
+               die_e("could not chown %s", tmp);
+
+           make_file(tmp, user);
+       
+           remove(tmp);
+
+           xexit ( EXIT_OK );
+
+       }
+
+       else {
+
+           if ( *argv[file_opt] != '/' )
+               /* this is just the file name, not the path : complete it */
+               snprintf(file,sizeof(file),"%s/%s",orig_dir,argv[file_opt]);
+           else
+               strncpy(file, argv[file_opt], sizeof(file));
+
+           make_file(file, user);
+       
+           xexit ( EXIT_OK );
+
+       }
+
+    } else if ( list_opt + rm_opt + edit_opt != 1 )
+       usage();
+
+
+    /* remove user's entries */
+    if ( rm_opt == 1 ) {
+
+       if ( remove_fcrontab(1) == ENOENT )
+           fprintf(stderr, "no fcrontab for %s\n", user);
+
+       xexit (EXIT_OK);
+    }
+
+
+
+    /* list user's entries */
+    if ( list_opt == 1 ) {
+
+       list_file(buf);
+
+       xexit(EXIT_OK);
+
+    }
+
+
+
+    /* edit user's entries */
+    if ( edit_opt == 1 ) {
+
+       edit_file(buf);
+
+       xexit(EXIT_OK);
+
+    }
+
+    /* never reach */
+    return EXIT_OK;
+}
diff --git a/fcrontab.h b/fcrontab.h
new file mode 100644 (file)
index 0000000..0ddc3f4
--- /dev/null
@@ -0,0 +1,73 @@
+/*
+ * FCRON - periodic command scheduler 
+ *
+ *  Copyright 2000 Thibault Godouet <sphawk@free.fr>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ * 
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * 
+ *  The GNU General Public License can also be found in the file
+ *  `LICENSE' that comes with the fcron source distribution.
+ */
+
+/* fcrontab.h */
+
+#ifndef __FCRONTABH__
+#define __FCRONTABH__
+
+#include "global.h"
+
+
+/* macros */
+#define Skip_blanks(ptr) \
+        while((*ptr == ' ') || (*ptr == '\t')) \
+       ptr++;
+
+/* global variables */
+extern char debug_opt;
+extern CF *file_base;
+
+/* prototype definition */
+
+/* fileconf.c */
+extern int read_file(char *file_name, char *user);
+extern void delete_file(const char *user_name);
+extern void save_file(char *path);
+/* end of fileconf.c */
+
+/* subs.c */
+extern int remove_blanks(char *str);
+extern char *strdup2(const char *);
+/* end of subs.c */
+
+/* log.c */
+extern void xcloselog(void);
+extern char *make_msg(char *fmt, va_list args);
+extern void explain(char *fmt, ...);
+extern void explain_e(char *fmt, ...);
+extern void warn(char *fmt, ...);
+extern void warn_e(char *fmt, ...);
+extern void error(char *fmt, ...);
+extern void error_e(char *fmt, ...);
+extern void die(char *fmt, ...);
+extern void die_e(char *fmt, ...);
+extern void Debug(char *fmt, ...);
+/* end of log.c */
+
+/* allow.c */
+extern int is_allowed(char *user);
+/* end of allow.c */
+
+
+#endif /* __FCRONTABH__ */
diff --git a/fileconf.c b/fileconf.c
new file mode 100644 (file)
index 0000000..f5771af
--- /dev/null
@@ -0,0 +1,714 @@
+
+/*
+ * FCRON - periodic command scheduler 
+ *
+ *  Copyright 2000 Thibault Godouet <sphawk@free.fr>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ * 
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * 
+ *  The GNU General Public License can also be found in the file
+ *  `LICENSE' that comes with the fcron source distribution.
+ */
+
+/* FILECONF.C */
+
+#include "fcrontab.h"
+
+int get_line(char *str, size_t size, FILE *file, int *line);
+char *get_time(char *ptr, time_t *time, int line, char *file_name);
+char *read_num(char *ptr, int *num, int max, const char **names);
+void read_freq(char *ptr, CF *cf, int line, char *file_name);
+void read_arys(char *ptr, CF *cf, int line, char *file_name);
+char *read_field(char *ptr, bitstr_t *ary, int max, const char **names,
+              int line, char *file_name);
+void read_env(char *ptr, CF *cf, int line);
+char *get_string(char *ptr);
+
+
+/* warning : all names must have the same length */
+const char *dows_ary[] = {
+    "sun", "mon", "tue", "wed", "thu", "fri", "sat",
+    NULL
+};
+
+/* warning : all names must have the same length */
+const char *mons_ary[] = {
+    "jan", "feb", "mar", "apr", "may", "jun", 
+    "jul", "aug", "sep", "oct", "nov", "dec",
+    NULL
+};
+
+
+char *
+get_string(char *ptr)
+    /* read string pointed by ptr, remove blanks and manage
+     * string placed in quotes */
+    /* return NULL on mismatched quotes */
+{
+    char quote = 0;
+    int length = 0;
+
+    if ( *ptr == '\"' || *ptr == '\'' ) {
+       quote = *ptr;
+       ptr++;
+    }
+
+    length = remove_blanks(ptr);
+
+    if ( quote != 0 ) {
+       if ( *(ptr + length - 1) == quote )
+           *(ptr + length - 1) = '\0';
+       else
+           /* mismatched quotes */
+           return NULL;
+    }
+    
+
+    return strdup2(ptr);
+
+}
+
+
+int
+get_line(char *str, size_t size, FILE *file, int *line)
+    /* similar to fgets, but increase line if necessary,
+     * and continue over an "\" followed by an "\n" char */
+{
+    size_t size_max = size - 1 ;
+    register int i=0;
+
+    while (i < size_max ) {
+
+       switch ( *(str + i) = getc(file) ) {
+
+       case '\n':
+           /* check if the \n char is preceded by a "\" char : 
+            *  in this case, suppress the "\", don't copy the \n,
+            *  and continue */
+           if ( *(str + i - 1) == '\\') {
+               i--;
+               (*line)++;
+               continue;
+           }
+           else {
+               *(str + i) = '\0';
+               return OK;
+           }
+           break;
+               
+       case EOF:
+           *(str + i) = '\0';
+           /* we couldn't return EOF ( equal to ERR by default ) 
+            * nor ERR, which is used for another error */
+           return 999;
+
+       default:
+           i++;
+
+       }
+
+    }
+
+    /* line is too long : goto next line and return ERR */
+    while ((*str = getc(file)) != '\n' && *str != EOF )
+       ;
+    (*line)++;
+    return ERR;
+
+}
+
+int
+read_file(char *file_name, char *user)
+    /* read file "name" and append CF list */
+{
+    CF *cf = NULL;
+    FILE *file = NULL;
+    char buf[LINE_LEN];
+    int max_lines;
+    int line = 1;
+    int max_entries = MAXLINES;
+    int entries=0;
+    char *ptr = NULL;
+    int ret;
+    
+    bzero(buf, sizeof(buf));
+
+    /* open file */
+
+    /* check if user is allowed to read file */
+    if ( access(file_name, R_OK) != 0 )
+       die_e("User %s can't read file '%s'", user, file_name);
+    else if ( (file = fopen(file_name, "r")) == NULL ) {
+       fprintf(stderr, "Could not open '%s': %s\n", file_name,
+               strerror(errno));
+       return ERR;
+    }
+
+    Alloc(cf, CF);
+
+    if ( debug_opt )
+       fprintf(stderr, "FILE %s\n", file_name);
+
+    if (strcmp(user, "root") == 0)
+       max_entries = 65535;
+
+    /* max_lines acts here as a security counter to avoid endless loop. */
+    max_lines = max_entries * 10;
+
+    while ( entries <= max_entries && line <= max_lines ) {
+
+       if ( (ret = get_line(buf, sizeof(buf), file, &line)) == OK)
+           ;
+       else if ( ret == ERR ) {
+           fprintf(stderr, "Line %d of %s is too long (more than %d):"
+                   " skipping line.\n",line, file_name, sizeof(buf));
+           continue;
+       } else
+           /* EOF : no more lines */
+           break;
+       
+       ptr = buf;
+       Skip_blanks(ptr);
+
+       switch(*ptr) {
+       case '#':
+       case '\0':
+           /* comments or empty line: skipping */
+           line++;
+           continue;
+       case '@':
+           if (debug_opt)
+               fprintf(stderr, "      %s\n", buf);
+           read_freq(ptr, cf, line, file_name);
+           entries++;
+           break;
+       case '&':
+           if (debug_opt)
+               fprintf(stderr, "      %s\n", buf);
+           read_arys(ptr, cf, line, file_name);
+           entries++;
+           break;
+       default:
+           if ( isdigit(*ptr) || *ptr == '*' ) {
+               if (debug_opt)
+                   fprintf(stderr, "      %s\n", buf);
+               read_arys(ptr, cf, line, file_name);
+               entries++;
+           } else
+               read_env(ptr, cf, line);
+       }
+
+       line++; 
+
+    }
+
+    cf->cf_user = user;
+    cf->cf_next = file_base;
+    file_base = cf;
+
+    fclose(file);
+    
+    return OK;
+
+}
+
+void
+read_env(char *ptr, CF *cf, int line)
+    /* append env variable list.
+     * (remove blanks) */
+{
+    char name[ENV_LEN];
+    env_t *env = NULL;
+    int j=0;
+    char *val = NULL;
+
+    bzero(name, sizeof(name));
+
+    /* copy env variable's name */
+    while ( isalnum(*ptr) && *ptr != '=' && j < sizeof(name)) {
+       name[j++] = *ptr;
+       ptr++;
+    }
+    name[j] = '\0';
+
+    /* skip '=' and spaces around */
+    while ( isspace(*ptr) || *ptr == '=' )
+       ptr++;
+
+    /* get value */
+    if ( ( val = get_string(ptr)) == NULL ) {
+       fprintf(stderr, "Error at line %d (mismatched"
+               " quotes): skipping line.\n", line);
+       return;
+    }
+
+    if (debug_opt)
+       fprintf(stderr, "  Env : '%s=%s'\n", name, val);
+
+    /* we ignore USER's assignment */
+    if ( strcmp(name, "USER") == 0 )
+       return;
+
+    /* the MAILTO assignment is, in fact, an fcron option :
+     *  we don't store it in the same way. */
+    if ( strcmp(name, "MAILTO") == 0 )
+       cf->cf_mailto = val;
+    
+    else {
+
+       Alloc(env, env_t);      
+
+       env->e_name = strdup2(name);
+       env->e_val = val;
+       env->e_next = cf->cf_env_base;
+       cf->cf_env_base = env;
+    }
+    
+    return;
+
+}
+
+
+char *
+get_time(char *ptr, time_t *time, int line, char *file_name )
+    /* convert time read in string in time_t format */
+{
+    time_t sum;
+    
+    while( (*ptr != ' ') && (*ptr != '\t') && (*ptr != '\0') ) { 
+
+       sum = 0;
+
+       while ( isdigit(*ptr) ) {
+           sum *= 10;
+           sum += *ptr - 48;
+           ptr++;
+       }
+
+       /* interpret multipliers */
+       switch (*ptr) {
+       case 'm':               /* months */
+           sum *= 4;
+       case 'w':               /* weeks */
+           sum *= 7;
+       case 'd':               /* days */
+           sum *= 24;
+       case 'h':               /* hours */
+           sum *= 60;
+           ptr++;
+       default:                /* minutes */
+           sum *= 60;
+           
+       }
+
+       *time += sum;
+
+    }
+
+    Skip_blanks(ptr);
+    return ptr;
+}
+
+
+
+void
+read_freq(char *ptr, CF *cf, int line, char *file_name)
+    /* read a freq entry, and append a line to cf */
+{
+    CL *cl=NULL;
+    
+    Alloc(cl, CL);
+
+    ptr++;
+    /* get cl_remain field */
+    ptr = get_time(ptr, &(cl->cl_remain), line, file_name);
+
+    Skip_blanks(ptr);
+
+    /* then cl_timefreq */
+    ptr = get_time(ptr, &(cl->cl_timefreq), line, file_name);
+    if ( cl->cl_timefreq == 0) {
+       fprintf(stderr, "Error at line %d of file %s (no freq"
+               " specified): skipping line.\n", line, file_name);
+       free(cl);
+       return;
+    }
+
+    if ( cl->cl_remain == 0 )
+       /* cl_remain is not specified : set it to cl_timefreq value */
+       cl->cl_remain = cl->cl_timefreq;
+
+    /* get cl_shell field ( remove trailing blanks ) */
+    if ( (cl->cl_shell = get_string(ptr)) == NULL ) {
+       fprintf(stderr, "Error at line %d of file %s (mismatched"
+               " quotes): skipping line.\n", line, file_name);
+       free(cl);
+       return;
+    }
+
+    cl->cl_next = cf->cf_line_base;
+    cf->cf_line_base = cl;
+
+    if ( debug_opt )
+       fprintf(stderr, "  Cmd '%s', timefreq %ld, remain %ld\n",
+               cl->cl_shell, cl->cl_timefreq, cl->cl_remain);
+
+}
+
+
+
+#define R_field(ptr, ary, max, aryconst, descrp) \
+  if((ptr = read_field(ptr, ary, max, aryconst, line, file_name)) == NULL) { \
+      if (debug_opt) \
+          fprintf(stderr, "\n"); \
+      fprintf(stderr, "Error while reading " descrp " field line %d" \
+             " of file %s: ignoring line.\n", line, file_name); \
+      free(cl); \
+      return; \
+  }
+
+void
+read_arys(char *ptr, CF *cf, int line, char *file_name)
+    /* read a run freq number plus a normal fcron line */
+{
+    CL *cl = NULL;
+
+    Alloc(cl, CL);
+
+
+    /* set cl_remain if not specified or 
+     * if set to 1 to skip unnecessary tests */
+    if ( *ptr != '&' )
+       /* cl_remain not specified : set it to 0 */
+       cl->cl_remain = 0;
+    else {
+       ptr++;
+       /* get remain number */
+       while ( isdigit(*ptr) ) {           
+           cl->cl_remain *= 10;
+           cl->cl_remain += *ptr - 48;
+           ptr++;
+       }
+
+       Skip_blanks(ptr);
+    }
+
+    cl->cl_runfreq = cl->cl_remain;
+
+    if (debug_opt)
+       fprintf(stderr, "     ");
+
+    /* get the fields (check for errors) */
+    R_field(ptr, cl->cl_mins, 60, NULL, "minutes");
+    R_field(ptr, cl->cl_hrs, 24, NULL, "hours");
+    R_field(ptr, cl->cl_days, 32, NULL, "days");
+    R_field(ptr, cl->cl_mons, 12, mons_ary, "months");
+    R_field(ptr, cl->cl_dow, 8, dows_ary, "days of week");
+
+    if (debug_opt)
+       /* if debug_opt is set, we print informations in read_field function,
+        *  but no end line : we print it here */
+       fprintf(stderr, " remain %ld\n", cl->cl_remain);
+
+    /* get the shell command (remove trailing blanks) */
+    if ( (cl->cl_shell = get_string(ptr)) == NULL ) {
+       fprintf(stderr, "Error at line %d of file %s (mismatched"
+               " quotes): skipping line.\n", line, file_name);
+       free(cl);
+       return;
+    }
+
+    cl->cl_next = cf->cf_line_base;
+    cf->cf_line_base = cl;
+
+    if ( debug_opt )
+       fprintf(stderr, "  Cmd '%s'\n", cl->cl_shell);
+
+
+}
+
+char *
+read_num(char *ptr, int *num, int max, const char **names)
+    /* read a string's number and return it under int format.
+     *  Also check if that number is less than max */
+{
+    *num = 0;
+
+    if ( isalpha(*ptr) ) {
+       int i;
+
+       /* set string to lower case */
+       for ( i = 0; i < strlen(names[0]); i++ )
+           *(ptr+i) = tolower( *(ptr+i) );
+
+       for (i = 0; names[i]; ++i)
+           if (strncmp(ptr, names[i], strlen(names[i])) == 0) {
+               *num = i;
+               ptr += strlen(names[i]);
+               return ptr;
+               break;      
+           }
+
+       /* string is not in name list */
+       return NULL;
+
+    } else {
+
+       if ( max == 12 )
+           /* month are defined by user from 1 to 12 */
+           max = 13;
+
+       while ( isdigit(*ptr) ) { 
+           *num *= 10; 
+           *num += *ptr - 48; 
+
+           if (*num >= max) 
+               return NULL;
+
+           ptr++; 
+
+       } 
+
+       if ( max == 13 )
+           /* this number is part of the month field.
+            * user set it from 1 to 12, but we manage it internally
+            * as a number from 0 to 11 : we remove 1 to *num */
+           *num = *num - 1;
+
+    }
+
+    return ptr;
+}
+
+
+char *
+read_field(char *ptr, bitstr_t *ary, int max, const char **names,
+          int line, char *file_name)
+    /* read a field like "2,5-8,10-20/2,21-30~25" and fill ary */
+{
+    int start = 0;
+    int stop = 0;
+    int step = 0;
+    int rm = 0;
+    int i = 0;
+
+    while ( (*ptr != ' ') && (*ptr != '\t') && (*ptr != '\0') ) {
+       
+       start = stop = step = 0 ;
+
+       /* there may be a "," */
+       if ( *ptr == ',' )
+           ptr++;
+
+       if ( *ptr == '*' ) {
+           /* we have to fill everything (may be modified by a step ) */
+           start = 0;
+           stop = max - 1;
+           ptr++;
+       } else {
+
+           if ( (ptr = read_num(ptr, &start, max, names)) == NULL )
+               return NULL;
+
+           if (*ptr == ',' || *ptr == ' ' || *ptr == '\t') {
+               /* this is a single number : set up array and continue */
+               if (debug_opt)
+                   fprintf(stderr, " %d", start);
+               bit_set(ary, start);
+               continue;
+           }
+
+           /* check for a dash */
+           else if ( *ptr == '-' ) {
+               ptr++;
+               if ( (ptr = read_num(ptr, &stop, max, names)) == NULL )
+                   return NULL;
+           } else
+               /* syntax error */
+               return NULL;
+
+       }
+
+       /* check for step size */
+       if ( *ptr == '/' ) {
+           ptr++;
+           if ((ptr = read_num(ptr, &step, max, names)) == NULL || step == 0)
+               return NULL;
+       } else
+           /* step undefined : default is 0 */
+           step = 1;
+       
+       /* fill array */        
+       if (debug_opt)
+           fprintf(stderr, " %d-%d/%d", start, stop, step);
+
+       for (i = start;  i <= stop;  i += step)
+           bit_set(ary, i);
+
+       /* finally, remove unwanted values */
+       while ( *ptr == '~' ) {
+           ptr++;
+           rm = 0;
+           if ( (ptr = read_num(ptr, &rm, max, names)) == NULL )
+               return NULL;
+
+           if (debug_opt)
+               fprintf(stderr, " ~%d", rm);        
+           bit_clear(ary, rm);
+
+           /* if we remove one value of Sunday, remove the other */
+           if (max == 8 && rm == 0) {
+               bit_clear(ary, 7);
+           if (debug_opt)
+               fprintf(stderr, " ~%d", 7);         
+           }
+           else if (max == 8 && rm == 7) {
+               bit_clear(ary, 0);
+           if (debug_opt)
+               fprintf(stderr, " ~%d", 0);                 
+           }
+
+       }
+
+    }
+
+    /* Sunday is both 0 and 7 : if one is set, set the other */
+    if ( max == 8 ) {
+       if ( bit_test(ary, 0) )
+           bit_set(ary, 7);
+       else if ( bit_test(ary, 7) )
+           bit_set(ary, 0);
+    }
+
+    Skip_blanks(ptr);
+
+    if (debug_opt)
+       fprintf(stderr, " #");
+
+    return ptr;
+}
+
+
+void
+delete_file(const char *user_name)
+    /* free a file if user_name is not null 
+     *   otherwise free all files */
+{
+    CF *file = NULL;
+    CF *prev_file = NULL;
+    CL *line = NULL;
+    CL *cur_line = NULL;
+    env_t *env = NULL;
+    env_t *cur_env = NULL;
+
+    file = file_base;
+    while ( file != NULL) {
+       if (strcmp(user_name, file->cf_user) == 0) {
+
+           /* free lines */
+           cur_line = file->cf_line_base;
+           while ( (line = cur_line) != NULL) {
+               cur_line = line->cl_next;
+               free(line->cl_shell);
+               free(line);
+           }
+           break ;
+
+       } else {
+           prev_file = file;
+           file = file->cf_next;
+       }
+    }
+    
+    if (file == NULL)
+       /* file not in list */
+       return;
+    
+    /* remove file from list */
+    if (prev_file == NULL)
+       file_base = file->cf_next;
+    else
+       prev_file->cf_next = file->cf_next;
+
+    /* free env variables */
+    cur_env = file->cf_env_base;
+    while  ( (env = cur_env) != NULL ) {
+       cur_env = env->e_next;
+       free(env->e_name);
+       free(env->e_val);
+       free(env);
+    }
+
+    /* finally free file itself */
+    free(file->cf_user);
+    free(file->cf_mailto);
+    free(file);
+
+}
+
+
+void
+save_file(char *path)
+    /* Store the informations relatives to the executions
+     * of tasks at a defined frequency of time of system's
+     * run */
+{
+    CF *file = NULL;
+    CL *line = NULL;
+    FILE *f = NULL;
+    env_t *env = NULL;
+
+    if (debug_opt)
+       fprintf(stderr, "Saving ...\n");
+    
+    for (file = file_base; file; file = file->cf_next) {
+
+       /* open file */
+       if ( (f = fopen(path, "w")) == NULL )
+           perror("save");
+
+       /* save file : */
+
+       /* put program's version : it permit to daemon not to load
+        * a file which he won't understand the syntax, for exemple
+        * a file using a depreciated format generated by an old fcrontab,
+        * if the syntax has changed */
+       fprintf(f, "fcrontab-" FILEVERSION "\n");
+
+       /*   mailto, */
+       if ( file->cf_mailto != NULL ) {
+           fprintf(f, "m");
+           fprintf(f, "%s%c", file->cf_mailto, '\0');
+       } else
+           fprintf(f, "e");
+
+       /*   env variables, */
+       for (env = file->cf_env_base; env; env = env->e_next) {
+           fprintf(f, "%s%c", env->e_name, '\0');
+           fprintf(f, "%s%c", env->e_val, '\0');
+       }
+       fprintf(f, "%c", '\0');
+
+       /*   finally, lines. */
+       for (line = file->cf_line_base; line; line = line->cl_next) {
+           if ( fwrite(line, sizeof(CL), 1, f) != 1 )
+               perror("save");
+           fprintf(f, "%s%c", line->cl_shell, '\0');
+       }
+    
+       fclose(f);
+
+    }
+}
diff --git a/global.h b/global.h
new file mode 100644 (file)
index 0000000..cab04f3
--- /dev/null
+++ b/global.h
@@ -0,0 +1,110 @@
+/*
+ * FCRON - periodic command scheduler 
+ *
+ *  Copyright 2000 Thibault Godouet <sphawk@free.fr>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ * 
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * 
+ *  The GNU General Public License can also be found in the file
+ *  `LICENSE' that comes with the fcron source distribution.
+ */
+
+/* global.h */
+
+
+#ifndef __GLOBALH__
+#define __GLOBALH__
+
+#include <ctype.h>
+#include <errno.h>
+#include <getopt.h>
+#include <pwd.h>
+#include <signal.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/file.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <syslog.h>
+#include <time.h>
+#include <unistd.h>
+#include <fcntl.h>
+
+#include "bitstring.h"         
+
+#include "config.h"
+
+
+
+/* none configurable constants */
+#define FILEVERSION "001"  /* syntax's version of fcrontabs : 
+                           * must have a length of 3 characters */
+
+
+
+/* macros */
+#define arysize(ary)   (sizeof(ary)/sizeof((ary)[0]))
+
+#define Alloc(ptr, type) \
+        if( (ptr = calloc(1, sizeof(type))) == NULL ) { \
+            fprintf(stderr, "Could not calloc."); \
+            exit(EXIT_ERR); \
+        }
+
+#define debug if(debug_opt) Debug
+
+typedef struct env_t {
+    char         *e_name;       /* env name                             */
+    char         *e_val;        /* env value                            */
+    struct env_t *e_next;
+} env_t ;
+
+typedef struct CF {
+    struct CF    *cf_next;
+    struct CL    *cf_line_base;
+    char        *cf_user;      /* user-name                            */
+    char         *cf_mailto;    /* mail output's to mail_user           */
+    struct env_t *cf_env_base;  /* list of all env variables to set     */
+    int                 cf_running;    /* number of jobs running               */
+} CF;
+
+/* warning : do not change the order of the members of this structure
+ *   because some tests made are dependent to that order */
+typedef struct CL {
+    struct CL    *cl_next;
+    char        *cl_shell;     /* shell command                        */
+    pid_t       cl_pid;        /* running pid, 0, or armed (-1)        */
+    pid_t       cl_mailpid;    /* mailer pid or 0                      */
+    int                 cl_mailfd;     /* running pid is for mail              */
+    int                 cl_mailpos;    /* 'empty file' size                    */
+    /* see bitstring(3) man page for more details */
+    bitstr_t    bit_decl(cl_mins, 60);  /* 0-59                        */
+    bitstr_t    bit_decl(cl_hrs, 24);  /* 0-23                         */
+    bitstr_t    bit_decl(cl_days, 32); /* 1-31                         */
+    bitstr_t    bit_decl(cl_mons, 12); /* 0-11                         */
+    bitstr_t    bit_decl(cl_dow, 8);   /* 0-7, 0 and 7 are both Sunday */
+    time_t       cl_timefreq;    /* Run every n min                     */
+    short int    cl_runfreq;     /* Run once every n matches            */
+    time_t       cl_remain;      /* remaining until next execution      */
+    time_t       cl_nextexe;     /* time and date of the next execution */
+} CL;
+
+
+
+#endif /* __GLOBALH__ */
+
diff --git a/job.c b/job.c
new file mode 100644 (file)
index 0000000..64537fa
--- /dev/null
+++ b/job.c
@@ -0,0 +1,323 @@
+
+/*
+ * FCRON - periodic command scheduler 
+ *
+ *  Copyright 2000 Thibault Godouet <sphawk@free.fr>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ * 
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * 
+ *  The GNU General Public License can also be found in the file
+ *  `LICENSE' that comes with the fcron source distribution.
+ */
+
+/* JOB.C */
+
+#include "fcron.h"
+
+int temp_file(void);
+void xwrite(int fd, char *string);
+void launch_mailer(CF *file, CL *line);
+int change_user(const char *user, short dochdir);
+
+
+int
+change_user(const char *user, short dochdir)
+{
+    struct passwd *pas;
+
+    /* Obtain password entry and change privileges */
+
+    if ((pas = getpwnam(user)) == NULL) 
+        die("failed to get uid for %s", user);
+    
+    setenv("USER", pas->pw_name, 1);
+    setenv("HOME", pas->pw_dir, 1);
+    setenv("SHELL", pas->pw_shell, 1);
+
+    /* Change running state to the user in question */
+
+    if (initgroups(user, pas->pw_gid) < 0)
+       die_e("initgroups failed: %s", user);
+
+    if (setregid(pas->pw_gid, pas->pw_gid) < 0) 
+       die("setregid failed: %s %d", user, pas->pw_gid);
+    
+    if (setreuid(pas->pw_uid, pas->pw_uid) < 0) 
+       die("setreuid failed: %s %d", user, pas->pw_uid);
+
+    if (dochdir) {
+       if (chdir(pas->pw_dir) < 0) {
+           error("chdir failed: %s '%s'", user, pas->pw_dir);
+           if (chdir("/") < 0) {
+               error("chdir failed: %s '%s'", user, pas->pw_dir);
+               die("chdir failed: %s '/'", user);
+           }
+       }
+    }
+    return(pas->pw_uid);
+}
+
+
+
+void 
+run_job(CF *file, CL *line)
+    /* fork(), redirect outputs to a temp file, and execl() the task */ 
+{
+
+    pid_t pid = 0;
+    char *shell = NULL;
+    char *home = NULL;
+    env_t *env = NULL;
+
+    /* create temporary file for stdout and stderr of the job */
+    line->cl_mailfd = temp_file();
+
+    /* write mail header */
+    xwrite(line->cl_mailfd,"To: ");
+    xwrite(line->cl_mailfd, file->cf_user);
+    xwrite(line->cl_mailfd, "\nSubject: Output of fcron job: '");
+    xwrite(line->cl_mailfd, line->cl_shell);
+    xwrite(line->cl_mailfd,"'\n\n");
+    line->cl_mailpos = lseek(line->cl_mailfd, 0, SEEK_END);
+
+    switch ( pid = fork() ) {
+
+    case 0:
+       /* child */
+
+       foreground = 0;
+       if (change_user(file->cf_user, 1) < 0)
+           return ;
+
+       /* stdin is already /dev/null, setup stdout and stderr */
+       if ( close(1) != 0 )
+           die_e("Can't close file descriptor %d",1);
+       if ( close(2) != 0 )
+           die_e("Can't close file descriptor %d",2);
+
+       if ( file->cf_mailto != NULL && strcmp(file->cf_mailto, "") == 0 ) {
+           if ( close(line->cl_mailfd) != 0 )
+               die_e("Can't close file descriptor %d", line->cl_mailfd);
+           if ( (line->cl_mailfd = open("/dev/null", O_RDWR)) < 0 )
+               die_e("open: /dev/null:");
+       }
+
+       if (dup2(line->cl_mailfd, 1) != 1 || dup2(line->cl_mailfd, 2) != 2)
+           die_e("dup2() error");    /* dup2 also clears close-on-exec flag */
+
+       foreground = 1; 
+       /* now, errors will be mailed to the user (or to /dev/null) */
+
+       xcloselog();
+
+       /* set env variables */
+       for ( env = file->cf_env_base; env; env = env->e_next)
+           if ( setenv(env->e_name, env->e_val, 1) != 0 )
+               error("could not setenv()");
+
+       if ( (home = getenv("HOME")) != NULL )
+           if (chdir(home) != 0)
+               error_e("Could not chdir to HOME dir '%s'", home);
+
+       if ( (shell = getenv("SHELL")) == NULL )
+           shell = SHELL;
+       else if ( access(shell, X_OK) != 0 ) {
+           if (errno == ENOENT)
+               error("shell '%s' : no file or directory. SHELL set to " SHELL,
+                     shell);
+           else
+               error_e("shell '%s' not valid : SHELL set to " SHELL, shell);
+           shell = SHELL;
+       }
+
+#ifdef CHECKJOBS
+       /* this will force to mail a message containing at least the exact
+        * and complete command executed for each execution of all jobs */
+       debug("Execing '%s -c %s'", shell, line->cl_shell);
+#endif /* CHECKJOBS */
+
+       execl(shell, shell, "-c", line->cl_shell, NULL);
+
+       /* execl returns only on error */
+       die_e("execl() '%s -c %s' error", shell, line->cl_shell);
+
+       /* execution never gets here */
+
+    case -1:
+       error_e("Fork error : could not exec '%s'", line->cl_shell);
+       break;
+
+    default:
+       /* parent */
+
+       ////////
+       debug("run job - parent");
+       ////////t
+
+       line->cl_pid = pid;
+       file->cf_running += 1;
+       jobs_running++;
+
+       explain("  Job `%s' started (pid %d)", line->cl_shell, line->cl_pid);
+
+    }
+
+}
+
+void 
+end_job(CF *file, CL *line, int status)
+    /* if task have made some output, mail it to user */
+{
+
+    char mail_output;
+    char *m;
+
+    if ( lseek(line->cl_mailfd, 0, SEEK_END) > line->cl_mailpos ) {
+       if ( file->cf_mailto != NULL && file->cf_mailto[0] == '\0' )
+           /* there is a mail output, but it will not be mail */
+           mail_output = 2;
+       else
+           /* an output exit : we will mail it */
+           mail_output = 1;
+    }
+    else
+       /* no output */
+       mail_output = 0;
+
+    m= (mail_output == 1) ? " (mailing output)" : "";
+    if (WIFEXITED(status) && WEXITSTATUS(status)==0)
+       explain("Job `%s' terminated%s", line->cl_shell, m);
+    else if (WIFEXITED(status))
+       explain("Job `%s' terminated (exit status: %d)%s",
+               line->cl_shell, WEXITSTATUS(status), m);
+    else if (WIFSIGNALED(status))
+       error("Job `%s' terminated due to signal %d%s",
+                line->cl_shell, WTERMSIG(status), m);
+    else /* is this possible? */
+       error("Job `%s' terminated abnormally %s", line->cl_shell, m);
+
+    if (mail_output == 1) launch_mailer(file, line);
+
+    /* if MAILTO is "", temp file is already closed */
+    if ( mail_output != 2 && close(line->cl_mailfd) != 0 )
+       die_e("Can't close file descriptor %d", line->cl_mailfd);
+
+    line->cl_pid = 0;
+    file->cf_running -= 1;
+    jobs_running--;
+
+}
+
+void
+launch_mailer(CF *file, CL *line)
+    /* mail the output of a job to user */
+{
+    char *mailto = NULL;
+
+    switch ( line->cl_mailpid = fork() ) {
+    case 0:
+       /* child */
+
+       foreground = 0;
+
+       /* set stdin to the job's output */
+       if ( close(0) != 0 )
+           die_e("Can't close file descriptor %d",0);
+           
+
+       if (dup2(line->cl_mailfd,0)!=0) die_e("Can't dup2()");
+       if (lseek(0,0,SEEK_SET)!=0) die_e("Can't lseek()");
+
+       xcloselog();
+
+       /* determine which will be the mail receiver */
+       if ( (mailto = file->cf_mailto) == NULL )
+           mailto = file->cf_user;
+
+       /* change permissions */
+       if (change_user(file->cf_user, 1) < 0)
+           return ;
+
+       /* run sendmail with mail file as standard input */
+       execl(SENDMAIL, SENDMAIL, SENDMAIL_ARGS, mailto, NULL);
+       die_e("Can't exec " SENDMAIL);
+       break;
+
+    case -1:
+       error_e("Could not exec '%s'", line->cl_shell);
+       break;
+
+    default:
+       /* parent */
+
+    }
+}
+
+void
+end_mailer(CL *line, int status)
+    /* take care of a finished mailer */
+{
+    if (WIFEXITED(status) && WEXITSTATUS(status)!=0)
+       error("Tried to mail output of job `%s', "
+                "but mailer process (" SENDMAIL ") exited with status %d",
+                line->cl_shell, WEXITSTATUS(status) );
+    else if (!WIFEXITED(status) && WIFSIGNALED(status))
+       error("Tried to mail output of job `%s', "
+                "but mailer process (" SENDMAIL ") got signal %d",
+                line->cl_shell, WTERMSIG(status) );
+    else if (!WIFEXITED(status) && !WIFSIGNALED(status))
+       error("Tried to mail output of job `%s', "
+                "but mailer process (" SENDMAIL ") terminated abnormally"
+                , line->cl_shell);
+
+    line->cl_mailpid = 0;
+}
+
+
+int
+temp_file(void)
+    /* Open a temporary file and return its file descriptor */
+{
+   const int max_retries=50;
+   char *name;
+   int fd,i;
+
+   i=0;
+   name=NULL;
+   do {
+      i++;
+      free(name);
+      name=tempnam(NULL,NULL);
+      if (name==NULL) die("Can't find a unique temporary filename");
+      fd=open(name,O_RDWR|O_CREAT|O_EXCL|O_APPEND,S_IRUSR|S_IWUSR);
+      /* I'm not sure we actually need to be so persistent here */
+   } while (fd==-1 && errno==EEXIST && i<max_retries);
+   if (fd==-1) die_e("Can't open temporary file");
+   if (unlink(name)) die_e("Can't unlink temporary file");
+   free(name);
+   fcntl(fd,F_SETFD,1);   /* set close-on-exec flag */
+   return fd;
+}
+
+
+void
+xwrite(int fd, char *string)
+  /* Write (using write()) the string "string" to temporary file "fd".
+   * Don't return on failure */
+{
+   if (write(fd,string,strlen(string))==-1)
+      die_e("Can't write to temporary file");
+}
+
diff --git a/log.c b/log.c
new file mode 100644 (file)
index 0000000..19b4a78
--- /dev/null
+++ b/log.c
@@ -0,0 +1,251 @@
+
+/*
+ * FCRON - periodic command scheduler 
+ *
+ *  Copyright 2000 Thibault Godouet <sphawk@free.fr>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ * 
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * 
+ *  The GNU General Public License can also be found in the file
+ *  `LICENSE' that comes with the fcron source distribution.
+ */
+
+/* log.c */
+
+/* This code is inspired by Anacron's sources of
+   Itai Tzur <itzur@actcom.co.il> ( thanks to him ) */
+
+
+#include "fcron.h"
+
+static void xopenlog(void);
+
+static char truncated[]=" (truncated)";
+static int log_open=0;
+
+
+static void
+xopenlog(void)
+{
+    if (!log_open) {
+       openlog(prog_name, LOG_PID, SYSLOG_FACILITY);
+       log_open=1;
+    }
+}
+
+
+void
+xcloselog()
+{
+    if (log_open) closelog();
+    log_open=0;
+}
+
+
+/* Construct the message string from its parts */
+char *
+make_msg(char *fmt, va_list args)
+{
+    int len;
+    char *msg = NULL;
+   
+    if ( (msg = calloc(1, MAX_MSG + 1)) == NULL )
+       return NULL;
+    /* There's some confusion in the documentation about what vsnprintf
+     * returns when the buffer overflows.  Hmmm... */
+    len=vsnprintf(msg, MAX_MSG + 1, fmt, args);
+    if (len>=MAX_MSG)
+       strcpy(msg+sizeof(msg)-sizeof(truncated), truncated);
+
+    return msg;
+}
+
+
+/* Log a message, described by "fmt" and "args", with the specified 
+ * "priority". */
+static void
+log(int priority, char *fmt, va_list args)
+{
+    char *msg;
+
+    if ( (msg = make_msg(fmt, args)) == NULL)
+       return;
+
+    xopenlog();
+    syslog(priority, "%s", msg);
+
+    if (foreground == 1) {
+       time_t t = time(NULL);
+       struct tm *ft;
+       char date[30];
+
+       ft = localtime(&t);
+       date[0] = '\0';
+       strftime(date, sizeof(date), "%H:%M:%S", ft);
+       fprintf(stderr, "%s %s\n", date, msg);
+
+    }
+
+    free(msg);
+}
+
+
+/* Same as log(), but also appends an error description corresponding
+ * to "errno". */
+static void
+log_e(int priority, char *fmt, va_list args)
+{
+    int saved_errno;
+    char *msg;
+
+    saved_errno=errno;
+
+    if ( (msg = make_msg(fmt, args)) == NULL )
+       return ;
+
+    xopenlog();
+    syslog(priority, "%s: %s", msg, strerror(saved_errno));
+
+    if (foreground == 1) {
+       time_t t = time(NULL);
+       struct tm *ft;
+       char date[30];
+
+       ft = localtime(&t);
+       date[0] = '\0';
+       strftime(date, sizeof(date), "%H:%M:%S", ft);
+       fprintf(stderr, "%s %s: %s\n", date, msg, strerror(saved_errno));
+    }
+}
+
+
+/* Log an "explain" level message */
+void
+explain(char *fmt, ...)
+{
+    va_list args;
+
+    va_start(args, fmt);
+    log(EXPLAIN_LEVEL, fmt, args);
+    va_end(args);
+}
+
+
+/* Log an "explain" level message, with an error description */
+void
+explain_e(char *fmt, ...)
+{
+    va_list args;
+
+    va_start(args, fmt);
+    log_e(EXPLAIN_LEVEL, fmt, args);
+    va_end(args);
+}
+
+
+/* Log a "warning" level message */
+void
+warn(char *fmt, ...)
+{
+    va_list args;
+
+    va_start(args, fmt);
+    log(WARNING_LEVEL, fmt, args);
+    va_end(args);
+}
+
+
+/* Log a "warning" level message, with an error description */
+void
+warn_e(char *fmt, ...)
+{
+    va_list args;
+
+    va_start(args, fmt);
+    log_e(WARNING_LEVEL, fmt, args);
+    va_end(args);
+}
+
+
+/* Log a "complain" level message */
+void
+error(char *fmt, ...)
+{
+    va_list args;
+
+    va_start(args, fmt);
+    log(COMPLAIN_LEVEL, fmt, args);
+    va_end(args);
+}
+
+
+/* Log a "complain" level message, with an error description */
+void
+error_e(char *fmt, ...)
+{
+    va_list args;
+
+    va_start(args, fmt);
+    log_e(COMPLAIN_LEVEL, fmt, args);
+    va_end(args);
+}
+
+
+/* Log a "complain" level message, and exit */
+void
+die(char *fmt, ...)
+{
+    va_list args;
+
+    va_start(args, fmt);
+    log(COMPLAIN_LEVEL, fmt, args);
+    va_end(args);
+    if (getpid() == daemon_pid) error("Aborted");
+
+    exit(EXIT_ERR);
+
+}  
+
+
+/* Log a "complain" level message, with an error description, and exit */
+void
+die_e(char *fmt, ...)
+{
+   va_list args;
+   int err_no = 0;
+
+   err_no = errno;
+
+   va_start(args, fmt);
+   log_e(COMPLAIN_LEVEL, fmt, args);
+   va_end(args);
+   if (getpid() == daemon_pid) error("Aborted");
+
+   exit(err_no);
+
+}  
+
+
+void
+Debug(char *fmt, ...)
+{
+    va_list args;
+
+    va_start(args, fmt);
+    log(DEBUG_LEVEL, fmt, args);
+    va_end(args);
+}
+
+
diff --git a/script/boot-install b/script/boot-install
new file mode 100755 (executable)
index 0000000..1fefbf1
--- /dev/null
@@ -0,0 +1,41 @@
+#!/bin/sh
+# Install fcron under SysV system.
+#
+
+# take two arguments : first is the compilation line arguments, in order
+#    to determine if fcron should be installed with debugs options,
+#    and the second is the name of the BSD-like install program
+
+PATH="/sbin:/usr/sbin:/bin:/usr/bin:/usr/X11R6/bin"
+
+startdir=$pwd
+
+if test $# -ne 2; then
+    echo "Too few arguments"
+    exit 1
+fi
+
+if test -f /etc/rc.d/rc.M; then
+    # Slackware
+    echo "fcron -b" >> /etc/rc.d/rc.local
+else
+
+    if echo $1 > grep "-DDEBUG"; then
+      $2 -m 755 -o root sysVinit-launcher-debug /etc/rc.d/init.d/fcron
+    else
+      $2 -m 755 -o root sysVinit-launcher /etc/rc.d/init.d/fcron
+    fi
+    
+    cd /etc/rc.d/rc3.d/
+    ln -f -s ../init.d/fcron S40fcron
+    cd /etc/rc.d/rc4.d/
+    ln -f -s ../init.d/fcron S40fcron
+    cd /etc/rc.d/rc5.d/
+    ln -f -s ../init.d/fcron S40fcron
+    
+    cd /etc/rc.d/rc0.d/
+    ln -f -s ../init.d/fcron  K60fcron
+    cd /etc/rc.d/rc6.d/
+    ln -f -s ../init.d/fcron  K60fcron
+    cd $startdir
+fi
diff --git a/script/boot-uninstall b/script/boot-uninstall
new file mode 100755 (executable)
index 0000000..2b5e5e0
--- /dev/null
@@ -0,0 +1,15 @@
+#!/bin/sh
+# Uninstall fcron under SysV system.
+#
+
+PATH="/sbin:/usr/sbin:/bin:/usr/bin:/usr/X11R6/bin"
+
+rm -f /etc/rc.d/init.d/fcron-debug
+rm -f /etc/rc.d/init.d/fcron
+
+rm -f /etc/rc.d/rc3.d/S40fcron
+rm -f /etc/rc.d/rc4.d/S40fcron
+rm -f /etc/rc.d/rc5.d/S40fcron
+
+rm -f /etc/rc.d/rc0.d/K60fcron
+rm -f /etc/rc.d/rc6.d/K60fcron
diff --git a/script/gen-doc b/script/gen-doc
new file mode 100755 (executable)
index 0000000..f543c6f
--- /dev/null
@@ -0,0 +1,23 @@
+#!/bin/sh
+
+cd man
+for i in *
+do
+  echo $i
+  sed -e "s:\"fcron version .* - .*\":\"fcron version $1 - `date +%D`\":" < $i > tmp
+  mv -f tmp $i
+  chown $USER $i
+  rm -f tmp
+  groff -Thtml -mandoc $i > ../doc/$i.html  
+done
+
+cd ../doc
+for i in *
+do
+  echo $i
+  sed -e "s:<-- fcron version .* - .* -->:<-- fcron version $1 - `date +%D` -->:" < $i > tmp
+  mv -f tmp $i
+  chown $USER $i
+  rm -f tmp
+done
+
diff --git a/script/sysVinit-launcher b/script/sysVinit-launcher
new file mode 100755 (executable)
index 0000000..211ad30
--- /dev/null
@@ -0,0 +1,63 @@
+#!/bin/sh
+#
+# description : fcron is a scheduler especially useful for people
+#               who are not running their system all the time.
+# processname: fcron
+# config: /var/spool/fcron/*
+
+FUNCTION=0
+
+# Source function library.
+if test -f /etc/rc.d/init.d/functions; then
+    . /etc/rc.d/init.d/functions
+    FUNCTION=1
+fi
+
+RETVAL=0
+
+
+#  See how we were called.
+case "$1" in
+  start)
+       echo -n "Starting fcron: "
+       if test $FUNCTION -eq 1; then
+           daemon fcron
+       else
+           fcron -b
+       fi
+       RETVAL=$?
+       echo
+       [ $RETVAL -eq 0 ] && touch /var/lock/subsys/fcron
+       ;;
+  stop)
+       echo -n "Shutting down fcron"
+       if test $FUNCTION -eq 1; then
+           killproc fcron
+       else
+           killall -TERM fcron
+       fi
+       RETVAL=$?
+       echo
+       [ $RETVAL -eq 0 ] && rm -f /var/lock/subsys/fcron
+       ;;
+  status)
+       if test $FUNCTION -eq 1; then
+           status fcron
+       fi
+       RETVAL=$?
+       ;;
+  restart)
+       $0 stop
+       $0 start
+       RETVAL=$?
+       ;;
+  reload)
+       killall -HUP fcron
+       RETVAL=$?
+       ;;        
+  *)
+       echo "Usage: fcron {start|stop|status|restart}"
+       exit 1
+esac
+
+exit $RETVAL
diff --git a/subs.c b/subs.c
new file mode 100644 (file)
index 0000000..e2bce12
--- /dev/null
+++ b/subs.c
@@ -0,0 +1,73 @@
+
+/*
+ * FCRON - periodic command scheduler 
+ *
+ *  Copyright 2000 Thibault Godouet <sphawk@free.fr>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ * 
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * 
+ *  The GNU General Public License can also be found in the file
+ *  `LICENSE' that comes with the fcron source distribution.
+ */
+
+/* SUBS.C */
+
+#include "global.h"
+
+
+int
+remove_blanks(char *str)
+    /* remove blanks at the the end of str */
+    /* return the length of the new string */
+{
+    char *c = str;
+
+    /* scan forward to the null */
+    while (*c)
+       c++;
+
+    /* scan backward to the first character that is not a space */
+    do {c--;}
+    while (c >= str && isspace(*c));
+
+    /* if last char is a '\n', we remove it */
+    if ( *c == '\n' )
+       *c = '\0';
+    else
+       /* one character beyond where we stopped above is where the null
+        * goes. */
+       *++c = '\0';
+
+    /* return the new length */
+    return ( c - str );
+    
+}
+
+
+char *
+strdup2(const char *str)
+{
+    char *ptr = malloc(strlen(str) + 1);
+
+    if (ptr)
+        strcpy(ptr, str);
+    return(ptr);
+}
+
+
+
+
+
+