--- /dev/null
+############################
+# 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/)
+
+
+
+
--- /dev/null
+see README in directory doc/ .
\ No newline at end of file
--- /dev/null
+
+/*
+ * 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;
+
+}
--- /dev/null
+/*
+ * 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; \
+}
--- /dev/null
+
+/*
+ * 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 ;
+
+ }
+}
--- /dev/null
+/*
+ * 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 **************************************** */
+/* ****************************************************************** */
+
--- /dev/null
+
+/*
+ * 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);
+ }
+ }
+
+ }
+
+}
+
--- /dev/null
+/*
+ * 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;
+ }
+
+}
--- /dev/null
+/*
+ * 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 */
+
--- /dev/null
+
+/*
+ * 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;
+}
--- /dev/null
+/*
+ * 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__ */
--- /dev/null
+
+/*
+ * 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);
+
+ }
+}
--- /dev/null
+/*
+ * 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__ */
+
--- /dev/null
+
+/*
+ * 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");
+}
+
--- /dev/null
+
+/*
+ * 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);
+}
+
+
--- /dev/null
+#!/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
--- /dev/null
+#!/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
--- /dev/null
+#!/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
+
--- /dev/null
+#!/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
--- /dev/null
+
+/*
+ * 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);
+}
+
+
+
+
+
+