From: thib Date: Sun, 14 May 2000 17:25:41 +0000 (+0000) Subject: Initial revision X-Git-Tag: ver1564~663 X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=49d488bffbbf533ff57126fb52833d7544a005b1;p=fcron Initial revision --- 49d488bffbbf533ff57126fb52833d7544a005b1 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); +} + + + + + +