/* * FCRON - periodic command scheduler * * Copyright 2000-2007 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. */ /* $Id: conf.c,v 1.73 2007/06/03 17:52:34 thib Exp thib $ */ #include "fcron.h" #include "conf.h" #include "database.h" int read_file(const char *file_name, cf_t *cf); int add_line_to_file(cl_t *cl, cf_t *cf, uid_t runas, char *runas_str, time_t t_save); int read_strn(int fd, char **str, short int size); int read_type(int fd, short int *type, short int *size); void synchronize_file(char *file_name); /* 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_t *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); 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; if ( strcmp(dir_name, ".") == 0 ) explain("updating configuration from %s", fcrontabs); else explain("updating 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 removing files which are no longer wanted */ for (list_cur = rm_list; list_cur; list_cur = list_cur->next ) { explain("removing file %s", list_cur->str + 3); delete_file(list_cur->str + 3); /* len("rm.") = 3 */ if ( remove(list_cur->str + 3) != 0 && errno != ENOENT ) error_e("Could not remove %s", list_cur->str + 3); if ( remove(list_cur->str) != 0 && errno != ENOENT ) error_e("Could not remove %s", list_cur->str); } /* then add normal files, if any, to database */ for (list_cur = file_list; list_cur; list_cur = list_cur->next ) { errno = 0; if ( getpwnam(list_cur->str) #ifdef SYSFCRONTAB || strcmp(list_cur->str, SYSFCRONTAB) == 0 #endif ) { explain("adding file %s", list_cur->str); synchronize_file(list_cur->str); } else error_e("ignoring file \"%s\" : not in passwd file.", list_cur->str); } /* finally add new files */ for (list_cur = new_list; list_cur; list_cur = list_cur->next ) { /* len("new.") = 4 : */ errno = 0; if ( getpwnam(list_cur->str + 4) #ifdef SYSFCRONTAB || strcmp(list_cur->str + 4, SYSFCRONTAB) == 0 #endif ) { explain("adding new file %s", list_cur->str + 4); synchronize_file(list_cur->str); } else error_e("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_t *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_nextexe) to their current value */ cf_t *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_t *old = NULL; cl_t *old_l = NULL; cl_t *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) ); old = cur_f; /* load new file */ Alloc(cur_f, cf_t); if ( read_file(file_name, cur_f) != 0 ) { /* an error as occured */ return; } /* assign old pointer to the old file, and move it to the first * place of the list : delete_file() only remove the first * occurrence of the file which has the name given in argument */ if (prev != NULL) { prev->cf_next = old->cf_next; old->cf_next = file_base; file_base = old; } else /* this is the first file in the list : no need to move it */ ; /* 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 && ( ( is_freq(new_l->cl_option) && new_l->cl_timefreq == old_l->cl_timefreq ) || ( is_td(new_l->cl_option) && memcmp( &(new_l->cl_mins), &(old_l->cl_mins), size)==0 && is_dayor(new_l->cl_option) == is_dayor(old_l->cl_option) ) ) ) { if ( new_l->cl_runfreq == old_l->cl_runfreq ) new_l->cl_remain = old_l->cl_remain; /* check if there is a change about the tz diff */ if ( (new_l->cl_file->cf_tzdiff != old_l->cl_file->cf_tzdiff) && (old_l->cl_nextexe - old_l->cl_file->cf_tzdiff + new_l->cl_file->cf_tzdiff > now) ) new_l->cl_nextexe = old_l->cl_nextexe - old_l->cl_file->cf_tzdiff + new_l->cl_file->cf_tzdiff; else new_l->cl_nextexe = old_l->cl_nextexe; insert_nextexe(new_l); if (debug_opt) { struct tm *ftime; ftime = localtime(&new_l->cl_nextexe); debug(" from last conf: %s next exec %d/%d/%d" " wday:%d %02d:%02d (system time)", new_l->cl_shell, (ftime->tm_mon + 1), ftime->tm_mday, (ftime->tm_year + 1900), ftime->tm_wday, ftime->tm_hour, ftime->tm_min); } break; } } /* remove old file from the list */ delete_file(user); /* insert new file in the list */ cur_f->cf_next = file_base; file_base = cur_f; /* save final file */ save_file(cur_f); /* 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 */ Alloc(cur_f, cf_t); if ( read_file(file_name, cur_f) != 0 ) { /* an error as occured */ return; } /* 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); /* 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_t); if ( read_file(file_name, cur_f) != 0 ) { /* an error as occured */ return; } /* insert the file in the list */ cur_f->cf_next = file_base; file_base = cur_f; } } int read_strn(int fd, char **str, short int size) /* read a "size"-length string in a binary fcrontab file */ { if ( (*str = calloc(size + 1, sizeof(char))) == NULL ) goto err; if ( read(fd, *str, size) < size ) goto err; (*str)[size] = '\0'; return OK; err: if (*str) free(*str); *str = NULL; return ERR; } int read_type(int fd, short int *type, short int *size) /* read the type and size of the next field in a binary fcrontab file */ { if ( read(fd, type, sizeof(short int)) < sizeof(short int) ) goto err; if ( read(fd, size, sizeof(short int)) < sizeof(short int) ) goto err; return OK; err: return ERR; } /* macros for read_file() */ /* read "size" bytes from file "ff", put them in "to", and check for errors */ #define Read(TO, SIZE, ERR_STR) \ { \ if ( read(fileno(ff), &(TO), SIZE) < SIZE ) { \ error_e(ERR_STR); \ goto err; \ } \ } #define Read_strn(TO, SIZE, ERR_STR) \ { \ if ( read_strn(fileno(ff), &(TO), SIZE) != OK ) { \ error_e(ERR_STR); \ goto err; \ } \ } int read_file(const char *file_name, cf_t *cf) /* read a formated fcrontab. return ERR on error, OK otherwise */ { FILE *ff = NULL; cl_t *cl = NULL; env_t *env = NULL; char buf[LINE_LEN]; long int bufi = 0; time_t t_save = 0; uid_t runas = 0; char *runas_str = NULL; struct stat file_stat; struct passwd *pass = NULL; short int type = 0, size = 0; int rc; #ifdef WITH_SELINUX int flask_enabled = is_selinux_enabled(); int retval; struct av_decision avd; const char *user_name; #endif /* open file */ if ( (ff = fopen(file_name, "r")) == NULL ) { warn_e("Could not read %s (may have just been removed)", file_name); goto err; } /* check if this file is owned by root : otherwise, all runas fields * of this field should be set to the owner */ rc = fstat(fileno(ff), &file_stat); if ( rc != 0 ) { error_e("Could not stat %s", file_name); goto err; } #ifdef WITH_SELINUX if(flask_enabled && fgetfilecon(fileno(ff), &cf->cf_file_context) < 0 ) { error_e("Could not get context of %s", file_name); goto err; } #endif if ( strncmp(file_name,"new.", 4) == 0 ) { if ( file_stat.st_uid == rootuid ) { /* file is owned by root : no test needed : set runas to rootuid */ runas = rootuid; } else { /* this is a standard user's new fcrontab : set the runas field to * the owner of the file */ runas = file_stat.st_uid; if ( (pass = getpwuid(file_stat.st_uid)) == NULL ) { error_e("Could not getpwuid(%d)", file_stat.st_uid); goto err; } runas_str = strdup2(pass->pw_name); } cf->cf_user = strdup2(file_name + 4); } else { if(!cf->cf_user) cf->cf_user = strdup2(file_name); if ( file_stat.st_uid == rootuid ) { /* file is owned by root : either this file has already been parsed * at least once by fcron, or it is root's fcrontab */ runas = rootuid; } else { error("Non-new file %s owned by someone else than root",file_name); goto err; } } #ifdef WITH_SELINUX /* * Since crontab files are not directly executed, * crond must ensure that the crontab file has * a context that is appropriate for the context of * the user cron job. It performs an entrypoint * permission check for this purpose. */ #ifdef SYSFCRONTAB if(!strcmp(cf->cf_user, SYSFCRONTAB)) user_name = "system_u"; else #endif /* def SYSFCRONTAB */ user_name = cf->cf_user; if(flask_enabled) { if(get_default_context(user_name, NULL, &cf->cf_user_context)) error_e("NO CONTEXT for user \"%s\"", cf->cf_user_context); retval = security_compute_av(cf->cf_user_context, cf->cf_file_context , SECCLASS_FILE, FILE__ENTRYPOINT, &avd); if(retval || ((FILE__ENTRYPOINT & avd.allowed) != FILE__ENTRYPOINT)) { syslog(LOG_ERR, "ENTRYPOINT FAILED for user \"%s\" " "(CONTEXT %s) for file CONTEXT %s" , cf->cf_user, cf->cf_user_context, cf->cf_file_context); goto err; } } #endif debug("User %s Entry", file_name); bzero(buf, sizeof(buf)); /* get version of fcrontab file: it permits to daemon not to load * a file which he won't understand the syntax, for example * a file using a depreciated format generated by an old fcrontab, * if the syntax has changed */ if ( read_type(fileno(ff), &type, &size) != OK || type != S_HEADER_T || read(fileno(ff), &bufi, size) < size || bufi != S_FILEVERSION ) { error("File %s is not valid: ignored.", file_name); error("This file may have been generated by an old version of fcron."); error("In that case, you should try to use the converter given in the " "source package, or install it again using fcrontab."); goto err; } if ( read_type(fileno(ff), &type, &size) != OK || type != S_USER_T ) { error("Invalid binary fcrontab (no USER field)"); goto err; } /* get the owner's name */ /* we set cf->cf_user before for SE Linux, so we need to free it here */ free(cf->cf_user); if ( read_strn(fileno(ff), &cf->cf_user, size) != OK ) { error("Cannot read user's name : file ignored"); goto err; } if ( runas != rootuid ) { /* we use file owner's name for more security (see above) */ /* free the value obtained by read_strn() (we need to read it anyway * to set the file ptr to the next thing to read) */ free(cf->cf_user); cf->cf_user = runas_str; } /* get the time & date of the saving */ /* a new file generated by fcrontab has t_save set to 0 */ if ( read_type(fileno(ff), &type, &size) != OK || type != S_TIMEDATE_T || read(fileno(ff), &t_save, size) < size ) { error("could not get time and date of saving"); goto err; } Alloc(cl, cl_t); /* main loop : read env variables, and lines */ while ( read_type(fileno(ff), &type, &size) == OK ) { /* action is determined by the type of the field */ switch ( type ) { case S_ENVVAR_T: /* read a env variable and add it to the env var list */ Alloc(env, env_t); Read_strn(env->e_val, size, "Error while reading env var"); /* Read_strn go to "err" on error */ debug(" Env: \"%s\"", env->e_val ); env->e_next = cf->cf_env_base; cf->cf_env_base = env; break; case S_TZDIFF_T: /* time diff between local (real) and system hour */ Read(bufi, size, "Error while reading tzdiff field"); cf->cf_tzdiff = (signed char) bufi; break; case S_TZ_T: /* read the timezone (string) in which the line should run */ Read_strn(cl->cl_tz, size, "Error while reading timezone field"); break; case S_SHELL_T: Read_strn(cl->cl_shell, size, "Error while reading shell field"); break; case S_RUNAS_T: Read_strn(cl->cl_runas, size, "Error while reading runas field"); break; case S_MAILTO_T: Read_strn(cl->cl_mailto, size, "Error while reading mailto field"); break; case S_NEXTEXE_T: Read(bufi, size, "Error while reading nextexe field"); cl->cl_nextexe = (time_t) bufi; break; case S_FIRST_T: Read(bufi, size, "Error while reading first field"); cl->cl_first = (time_t) bufi; break; case S_OPTION_T: if (size < OPTION_SIZE) /* set the options not defined in the savefile to default */ set_default_opt(cl->cl_option); Read(cl->cl_option, size, "Error while reading option field"); break; case S_NUMEXE_T: Read(cl->cl_numexe, size, "Error while reading numexe field"); break; case S_LAVG_T: Read(cl->cl_lavg, size, "Error while reading lavg field"); break; case S_UNTIL_T: Read(bufi, size, "Error while reading until field"); cl->cl_until = (time_t) bufi; break; case S_NICE_T: Read(cl->cl_nice, size, "Error while reading nice field"); break; case S_RUNFREQ_T: Read(bufi, size, "Error while reading runfreq field"); cl->cl_runfreq = (unsigned short) bufi; break; case S_REMAIN_T: Read(bufi, size, "Error while reading remain field"); cl->cl_remain = (unsigned short) bufi; break; case S_TIMEFREQ_T: Read(bufi, size, "Error while reading timefreq field"); cl->cl_timefreq = (time_t) bufi; break; case S_MINS_T: Read(cl->cl_mins, size, "Error while reading mins field"); break; case S_HRS_T: Read(cl->cl_hrs, size, "Error while reading hrs field"); break; case S_DAYS_T: Read(cl->cl_days, size, "Error while reading days field"); break; case S_MONS_T: Read(cl->cl_mons, size, "Error while reading mons field"); break; case S_DOW_T: Read(cl->cl_dow, size, "Error while reading dow field"); break; case S_ENDLINE_T: if (add_line_to_file(cl, cf, runas, runas_str, t_save) == 0) Alloc(cl, cl_t); break; /* default case in "switch(type)" */ default: error("Error while loading %s : unknown field type %d (ignored)", file_name, type); /* skip the data corresponding to the unknown field */ { /* we avoid using fseek(), as it seems not to work correctly * on some systems when we use read() on the FILE stream */ int i; for (i = 0; i < size; i++) if ( getc(ff) == EOF ) goto err; } } } /* free last cl Alloc : unused */ free(cl); /* check for an error */ if ( ferror(ff) != 0 ) error("file %s is truncated : you should reinstall it with fcrontab", file_name); fclose(ff); return OK; err: if ( ff != NULL) fclose(ff); if ( cl != NULL && cl->cl_next == NULL ) { /* line is not yet in the line list of the file : free it */ if ( cl->cl_shell ) free(cl->cl_shell); if ( cl->cl_runas) free(cl->cl_runas); if ( cl->cl_mailto) free(cl->cl_mailto); free(cl); } /* check if we have started to read the lines and env var */ if ( cl != NULL ) { /* insert the line in the file list in order to be able to free * the memory using delete_file() */ cf->cf_next = file_base; file_base = cf; delete_file(cf->cf_user); } else if (cf->cf_user != NULL) free(cf->cf_user); return ERR; } int add_line_to_file(cl_t *cl, cf_t *cf, uid_t runas, char *runas_str, time_t t_save) /* check if the line is valid, and if yes, add it to the file cf */ { time_t slept = now - t_save; if ( cl->cl_shell == NULL || cl->cl_runas == NULL || cl->cl_mailto == NULL ) { error("Line is not valid (empty shell, runas or mailto field)" " : ignored"); bzero(cl, sizeof(cl)); if (cl->cl_shell) free(cl->cl_shell); if (cl->cl_runas) free(cl->cl_runas); if (cl->cl_mailto) free(cl->cl_mailto); return 1; } /* set runas field if necessary (to improve security) */ if (runas != rootuid) { if (strcmp(cl->cl_runas, runas_str) != 0) warn("warning: runas(%s) is not owner (%s): overridden.", cl->cl_runas, runas_str); Set(cl->cl_runas, runas_str); } /* we need that here because the user's name contained in the * struct cf_t may be required */ cl->cl_file = cf; /* check if the mailto field is valid */ if ( cl->cl_mailto && (*(cl->cl_mailto) == '-' || strcspn(cl->cl_mailto, " \t\n") != strlen(cl->cl_mailto) ) ) { error("mailto field \'%s\' is not valid : set to owner %s.", cl->cl_mailto, cl->cl_file->cf_user); free(cl->cl_mailto); cl->cl_mailto = strdup2(cl->cl_file->cf_user); } /* check if the job hasn't been stopped during execution and insert * it in lavg or serial queue if it was in one at fcron's stops */ if (cl->cl_numexe > 0) { cl->cl_numexe = 0; if ( is_lavg(cl->cl_option) ) { if ( ! is_strict(cl->cl_option) ) add_lavg_job(cl, -1); } else if ( is_serial(cl->cl_option) || is_serial_once(cl->cl_option) ) add_serial_job(cl, -1); else { /* job has been stopped during execution : * launch it again */ warn("job %s did not finish : running it again.", cl->cl_shell); set_serial_once(cl->cl_option); add_serial_job(cl, -1); } } if ( is_td(cl->cl_option) ) { /* set the time and date of the next execution */ if ( cl->cl_nextexe <= now ) { if ( cl->cl_nextexe == 0 ) /* the is a line from a new file */ set_next_exe(cl, NO_GOTO, -1); else if (cl->cl_runfreq == 1 && is_notice_notrun(cl->cl_option)) set_next_exe_notrun(cl, SYSDOWN); else if ( is_bootrun(cl->cl_option) && t_save != 0 && cl->cl_runfreq != 1) { if ( cl->cl_remain > 0 && --cl->cl_remain > 0 ) { debug(" cl_remain: %d", cl->cl_remain); } else { /* run bootrun jobs */ cl->cl_remain = cl->cl_runfreq; debug(" boot-run %s", cl->cl_shell); if ( ! is_lavg(cl->cl_option) ) { set_serial_once(cl->cl_option); add_serial_job(cl, -1); } else add_lavg_job(cl, -1); } set_next_exe(cl, STD, -1); } else { if ( is_notice_notrun(cl->cl_option) ) { /* set next exe and mail user */ struct tm *since2 = localtime(&cl->cl_nextexe); struct tm since; int tz_changed = 0; tz_changed = switch_timezone(orig_tz_envvar, cl->cl_tz); memcpy(&since, since2, sizeof(since)); set_next_exe(cl, NO_GOTO, -1); mail_notrun(cl, SYSDOWN, &since); if ( tz_changed > 0 ) switch_back_timezone(orig_tz_envvar); } else set_next_exe(cl, NO_GOTO, -1); } } else /* value of nextexe is valid : just insert line in queue */ insert_nextexe(cl); } else { /* is_td(cl->cl_option) */ /* standard @-lines */ if ( is_volatile(cl->cl_option) ) { /* cl_first is always saved for a volatile line */ cl->cl_nextexe = now + cl->cl_first; } else cl->cl_nextexe += slept; if ( cl->cl_timefreq < 10 ) { error("Invalid timefreq for %s: set to 1 day", cl->cl_shell); cl->cl_timefreq = 3600*24; } insert_nextexe(cl); } if (debug_opt) { struct tm *ftime; ftime = localtime( &(cl->cl_nextexe) ); debug(" cmd %s next exec %d/%d/%d wday:%d %02d:%02d:%02d" " (system time)", cl->cl_shell, (ftime->tm_mon + 1), ftime->tm_mday, (ftime->tm_year + 1900), ftime->tm_wday, ftime->tm_hour, ftime->tm_min, ftime->tm_sec); } /* add the current line to the list, and allocate a new line */ if ( (cl->cl_id = next_id++) >= ULONG_MAX - 1) next_id = 0; cl->cl_next = cf->cf_line_base; cf->cf_line_base = cl; return 0; } void delete_file(const char *user_name) /* free a file if user_name is not null * otherwise free all files */ { cf_t *file; cf_t *prev_file = NULL; cl_t *line; cl_t *cur_line; env_t *env = NULL; env_t *cur_env = NULL; struct job_t *j = NULL; struct job_t *prev_j; int i, k; struct cl_t **s_a = NULL; file = file_base; while ( file != NULL) { if (strcmp(user_name, file->cf_user) != 0) { prev_file = file; file = file->cf_next; continue; } for ( i = 0; i < exe_num; i++) if ( exe_array[i].e_line != NULL && exe_array[i].e_line->cl_file == file ) { /* we set the e_line to NULL, as so we know in wait_chld() * and wait_all() the corresponding file has been removed. * Plus, we decrement serial_running and lavg_serial_running * as we won't be able to do it at the end of the job */ if ( ( is_serial(exe_array[i].e_line->cl_option) || is_serial_once(exe_array[i].e_line->cl_option) ) && ! is_lavg(exe_array[i].e_line->cl_option) ) serial_running--; else if ( is_serial(exe_array[i].e_line->cl_option) && is_lavg(exe_array[i].e_line->cl_option) ) lavg_serial_running--; exe_array[i].e_line = NULL; } /* free lavg queue entries */ i = 0; while (i < lavg_num) if ( lavg_array[i].l_line->cl_file == file ) { debug("removing %s from lavg queue", lavg_array[i].l_line->cl_shell); lavg_array[i].l_line->cl_numexe--; if (i < --lavg_num) { lavg_array[i] = lavg_array[lavg_num]; lavg_array[lavg_num].l_line = NULL; } else lavg_array[i].l_line = NULL; } else i++; /* free serial queue entries */ for ( i = 0; i < serial_array_size; i++) if (serial_array[i] != NULL && serial_array[i]->cl_file == file ) { if ( ! s_a ) s_a = calloc(serial_array_size, sizeof(cl_t *)); debug("removing %s from serial queue", serial_array[i]->cl_shell); serial_num--; serial_array[i]->cl_numexe--; serial_array[i] = NULL; } /* remove from queue and move the rest of the jobs to get * a queue in order without empty entries */ if ( ! s_a ) goto end_of_serial_recomputing; if ( (k = serial_array_index + serial_num) >= serial_array_size ) k -= serial_array_size; for ( i = k = 0; i < serial_array_size; i++) { if ( serial_array_index + i < serial_array_size ) { if ( (s_a[k] = serial_array[serial_array_index + i]) != NULL) k++; } else if( (s_a[k] = serial_array[serial_array_index + i - serial_array_size]) != NULL) k++; } free(serial_array); serial_array = s_a; serial_array_index = 0; end_of_serial_recomputing: /* free lines */ cur_line = file->cf_line_base; while ( (line = cur_line) != NULL) { cur_line = line->cl_next; /* remove from the main queue */ prev_j = NULL; for ( j = queue_base; j != NULL; j = j->j_next ) if ( j->j_line == line ) { if (prev_j != NULL) prev_j->j_next = j->j_next; else queue_base = j->j_next; free(j); break; } else prev_j = j; /* free line itself */ free(line->cl_shell); free(line->cl_runas); free(line->cl_mailto); free(line); } /* delete_file() MUST remove only the first occurrence : * this is needed by synchronize_file() */ break ; } if (file == NULL) /* file not in the file list */ return; /* remove file from file 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_val); free(env); } /* finally free file itself */ free(file->cf_user); free(file); } void save_file(cf_t *arg_file) /* Store the informations relatives to the executions * of tasks at a defined frequency of system's running time */ { cf_t *file = NULL; cf_t *start_file = NULL; if (arg_file != NULL) start_file = arg_file; else start_file = file_base; for (file = start_file; file; file = file->cf_next) { debug("Saving %s...", file->cf_user); /* save the file safely : save it to a temporary name, then rename() it */ /* chown the file to root:root : this file should only be read and * modified by fcron (not fcrontab) */ save_file_safe(file, file->cf_user, "fcron", rootuid, rootgid, now); if (arg_file != NULL) /* we have to save only a single file */ break ; } }