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