/* * $Id: simple-cache.c,v 0.90 2000/04/28 20:49:31 ceder Exp $ * Copyright (C) 1991-1999 Lysator Academic Computer Association. * * This file is part of the LysKOM server. * * LysKOM 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 1, or (at your option) * any later version. * * LysKOM 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 LysKOM; see the file COPYING. If not, write to * Lysator, c/o ISY, Linkoping University, S-581 83 Linkoping, SWEDEN, * or the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, * MA 02139, USA. * * Please mail bug reports to bug-lyskom@lysator.liu.se. */ /* * This module contains some simple simulations of the routines in * cache.c. * * Extracted from ram-cache.c and rewritten by ceder. * * New database format with texts in their own file by Inge Wallin. * * New save algorithm by ceder. * Attempt at newer algorithm by byers (FASTSAVE) */ #ifdef HAVE_CONFIG_H # include #endif static const char * rcsid = "$Id: simple-cache.c,v 0.90 2000/04/28 20:49:31 ceder Exp $"; #include "rcs.h" USE(rcsid); #include #include #ifdef HAVE_STDLIB_H # include #endif #include #ifdef HAVE_UNISTD_H # include #endif #ifdef HAVE_STRING_H # include #endif #ifndef SEEK_END # include #endif #ifndef SEEK_END # define SEEK_SET 0 # define SEEK_END 2 #endif #include #include #include "ldifftime.h" #include "exp.h" #include "misc-types.h" #include "s-string.h" #include "kom-types.h" #include "cache-node.h" #include "cache.h" #include "parser.h" #include "ram-parse.h" #include "ram-output.h" #include "server/smalloc.h" #include "kom-memory.h" #include "lyskomd.h" #include "debug.h" #include "kom-errno.h" #include "log.h" #include "com.h" #include "async.h" #include "connections.h" #include "send-async.h" #include "param.h" #include "kom-config.h" #include "admin.h" #include "unused.h" #include "local-to-global.h" #include "server-time.h" /* * Possible improvements: +++ * When there are consecutive items in file A that shall be copied * to file B, copy them in one transfer (up to a certain limit). * * In pre_sync: compute size of, and allocate disk space for file B. */ /* * All functions that can fail sets kom_errno to a suitable value * if they fail. */ static Small_conf ** small_conf_arr; static Cache_node_mcb * pers_mcb; static Cache_node_mcb * conf_mcb; static Conf_no next_free_num = 1; static Cache_node_mcb * text_mcb; static Text_no next_text_num = 1; /* * The elements in the following lists with same index refers to the same * conference. */ static int no_of_match_info; EXPORT Matching_info *match_table = NULL; static FILE *text_file= NULL; static FILE *file_a = NULL; /* Current file. */ static FILE *file_b = NULL; /* File under construction. */ #ifdef FASTSAVE static FILE *file_b_r = NULL; /* Read from file under construction */ #endif /* * Four state variables for the background save. */ static enum { sync_idle, sync_save_conf, sync_save_pers, sync_save_text, sync_error, sync_wait, sync_ready } sync_state; /* The state machine sync_state works like this: * * Old state action new state * none sync_part called sync_idle * for the first time * sync_idle SYNC_INTERVAL sync_save_conf * sync_save_conf all confs saved sync_save_pers * sync_save_pers all persons saved sync_save_text * sync_save_text all texts saved sync_ready * sync_ready sync_part called sync_idle * any error occurs sync_error * sync_error sync_part called sync_wait * sync_wait SYNC_RETRY_INTERVAL sync_save_conf */ #ifndef FASTSAVE /* sync_next should be split in next_text_to_sync and next_conf_to_sync to avoid warnings about the different sizes of Conf_no and Text_no. Since that is indeed done if FASTSAVE is set, don't bother doing it in the old code that will anyhow soon be replaced. */ static unsigned long sync_next; #endif static Conf_no highest_conf_no; static Text_no highest_text_no; BUGDECL; /* Define LOGACCESSES if you want to be able to log all accesses to the data base. */ #ifdef LOGACCESSES typedef enum { lt_restart, lt_text_stat, lt_text_mass, lt_conf_stat, lt_pers_stat, lt_text_def, lt_conf_def, lt_pers_def, lt_create_text, lt_garb_text, lt_delete_text, lt_create_conf, lt_delete_conf, lt_create_pers, lt_delete_pers, lt_lock_conf, lt_unlock_conf, lt_lock_pers, lt_unlock_pers, lt_get_highest, /* Get highest Local_text_no for a conf. */ lt_get_conf_type /* Note: mark_*_as_changed is not logged. */ } Log_type; static FILE *logfile = NULL; static int syncing_or_saving = 0; static int garb_running = 0; static void log_access(Log_type t, int id) { extern int putw(int, FILE *); if (garb_running + syncing_or_saving == 0) { putc(t, logfile); putw(id, logfile); } } #define LOGACC(a,b) {if (logfile) log_access(a, b);} #else #define LOGACC(a,b) #endif /* Local functions */ static Success build_matching_info(void); static Success rebuild_matching_info_entry(Conf_no conf_no); static Matching_info *find_matching_info(Conf_no conf_no); /* Macros */ #define TRACE2(format, arg) if ( buglevel > 2 ) printf(format, arg) #define TRACE1(format) if ( buglevel > 2 ) printf(format) #define TRACESTR(str) if ( buglevel > 2 ) s_puts(str) static Person * read_person(FILE *fp, long pos, long UNUSED(size)) /* FIXME: should sanity-check the size */ { Person *p; long dummy; p = alloc_person(); fseek(fp, pos+1, SEEK_SET); /* Skip 'P' */ dummy = fparse_long(fp); if ( fparse_person(fp, p) != OK ) { free_person(p); return NULL; } else return p; } static Conference * read_conference(FILE *fp, long pos, long UNUSED(size)) /* FIXME: should sanity-check the size */ { Conference *c; long dummy; c = alloc_conference(); fseek(fp, pos+1, SEEK_SET); /* Skip 'C' */ dummy = fparse_long(fp); if ( fparse_conference(fp, c) != OK ) { free_conference(c); return NULL; } else return c; } static Text_stat * read_text_stat(FILE *fp, long pos, long UNUSED(size)) /* FIXME: should sanity-check the size */ { Text_stat *t; long dummy; t = alloc_text_stat(); fseek(fp, pos+1, SEEK_SET); /* Skip 'T' */ dummy = fparse_long(fp); if ( fparse_text_stat(fp, t) != OK ) { free_text_stat(t); return NULL; } else return t; } static void pers_set_mru(Pers_no pers_no) { set_mru(pers_mcb, pers_no); } static void text_set_mru(Text_no text_no) { set_mru(text_mcb, text_no); } static void conf_set_mru(Conf_no conf_no) { set_mru(conf_mcb, conf_no); } static Cache_node * get_pers_node(Pers_no pers_no) { if (pers_no >= next_free_num || pers_no < 1) return NULL; return get_cache_node(pers_mcb, pers_no); } static void unlink_text_lru (Cache_node *node) { unlink_lru (node, &text_mcb->lru, &text_mcb->mru); } static void unlink_conf_lru (Cache_node *node) { unlink_lru (node, &conf_mcb->lru, &conf_mcb->mru); } static void unlink_pers_lru (Cache_node *node) { unlink_lru (node, &pers_mcb->lru, &pers_mcb->mru); } static Cache_node * get_conf_node(Conf_no conf_no) { if (conf_no >= next_free_num || conf_no < 1) return NULL; return get_cache_node(conf_mcb, conf_no); } static Cache_node * get_text_node(Text_no text_no) { if (text_no >= next_text_num || text_no < 1) return NULL; return get_cache_node(text_mcb, text_no); } /* * Name caching routines */ /* * change_name changes the cached conference name. It is only called when * a conference name is changed or a conference is deleted. */ void cached_change_name( Conf_no name_num, String new_name ) { if ( name_num < 1 || name_num >= next_free_num ) restart_kom("cached_change_name(%d, ----): next_free_num==%d\n", name_num, next_free_num); s_clear( &small_conf_arr[name_num]->name ); s_strcpy( &small_conf_arr[name_num]->name, new_name); rebuild_matching_info_entry(name_num); } extern Conf_type cached_get_conf_type (Conf_no conf_no) { if ( conf_no < 1 || conf_no >= next_free_num ) restart_kom("cached_get_conf_type(%d): next_free_num==%d", conf_no, next_free_num); if ( small_conf_arr [ conf_no ] == NULL ) restart_kom("cached_get_conf_type(%d): conference does not exist.\n", conf_no); LOGACC(lt_get_conf_type, conf_no); return small_conf_arr [ conf_no ]->type; } extern Conf_no cached_get_conf_supervisor(Conf_no conf_no) { if (conf_no < 1 || conf_no >= next_free_num) restart_kom("cached_get_conf_supervisor(%d): next_free_num==%d\n", conf_no, next_free_num); if (small_conf_arr[conf_no] == NULL) restart_kom("cached_get_conf_supervisor(%d):" " conference does not exist.\n", conf_no); LOGACC(lt_get_conf_supervisor, conf_no); return small_conf_arr[conf_no]->supervisor; } /* * Return number of conferences present. (Actually, return a number * at least as large as the number of conferences present). */ extern Conf_no cached_no_of_existing_conferences(void) { return next_free_num; /* This is too large, but who cares? */ /* Actually, this is used in lookup_regexp (and maybe other places) to allocate an array this large, and that is pretty stupid if many conferences have been deleted. But it is no big deal. Not yet, anyhow... */ } /* * Various function calls to tell the cache that something is changed. */ void mark_person_as_changed(Pers_no pers_no) { Cache_node *node; node = get_pers_node(pers_no); TRACE2("Person %d is changed\n", pers_no); if ( node == NULL || node->s.exists == 0) restart_kom("mark_person_as_changed(%d): nonexistent.\n", pers_no); node->s.dirty = 1; pers_set_mru( pers_no ); } /* * Mark the conference as dirty, so that it will be written to * the disk. * * Also update all fields in the Small_conf except then name, so that * they are always current. * * NOTE: You must call cached_change_name when the name changes. * It is not necessary to call cached_change_name after * cached_create_conf. */ void mark_conference_as_changed(Conf_no conf_no) { Cache_node *node; Conference *conf_c; node = get_conf_node (conf_no); TRACE2("Conf. %d is changed\n", conf_no); if ( node == NULL || node->s.exists == 0) restart_kom("mark_conference_as_changed(%d): nonexistent.\n", conf_no); node->s.dirty = 1; conf_set_mru( conf_no ); conf_c = (Conference *) node->ptr; small_conf_arr[conf_no]->highest_local_no = l2g_first_appendable_key(&conf_c->texts) - 1; small_conf_arr[conf_no]->nice = conf_c->nice; small_conf_arr[conf_no]->type = conf_c->type; small_conf_arr[conf_no]->supervisor = conf_c->supervisor; } void mark_text_as_changed( Text_no text_no ) { Cache_node *node; node = get_text_node (text_no); TRACE2("Text %lu is changed.\n", text_no); if ( text_no < 1 || text_no >= next_text_num || node == NULL || node->s.exists == 0) { restart_kom("mark_text_as_changed(%lu): nonexistent.\n", text_no); } node->s.dirty = 1; text_set_mru (text_no); } /* * Person-related calls */ extern Success cached_create_person( Pers_no person ) { Cache_node *node; TRACE2("Person %d is being created.\n", person); if ( person < 1 || person >= next_free_num ) { restart_kom("cached_create_person(%d): next_free_num == %d.\n", person, next_free_num); } if ( get_pers_node(person) != NULL ) { restart_kom("cached_create_person(%d): Person existed.\n", person); } create_cache_node (pers_mcb, person); node = get_pers_node (person); if ( node == NULL ) restart_kom("cached_create_person(): couldn't get cache_node.\n"); node->ptr = alloc_person(); node->s.dirty = 1; node->s.exists = 1; pers_set_mru( person ); LOGACC(lt_create_pers, person); return OK; } extern Person * cached_get_person_stat( Pers_no person ) { Cache_node *node; TRACE2("cached_get_person_stat %d\n", person); if ( person == 0 ) { err_stat = 0; kom_errno = KOM_CONF_ZERO; return NULL; } if ( person >= next_free_num ) { err_stat = person; kom_errno = KOM_UNDEF_PERS; return NULL; } node = get_pers_node (person); if ( node == NULL || node->s.exists == 0 ) { err_stat = person; kom_errno = KOM_UNDEF_PERS; return NULL; } LOGACC(lt_pers_stat, person); if ( node->ptr != NULL ) { pers_set_mru( person ); ++pers_mcb->hits; return node->ptr; } if ( node->snap_shot != NULL ) { node->ptr = copy_person (node->snap_shot); pers_set_mru (person); ++pers_mcb->hits; return node->ptr; } node->ptr = read_person(file_a, node->pos, node->size); ++pers_mcb->misses; pers_set_mru (person); return node->ptr; } /* * Conference-related calls */ static int no_of_allocated_small_confs = 0; static void free_small_conf (Small_conf *sc) { if ( sc != NULL ) { --no_of_allocated_small_confs; s_clear ( &sc->name ); sfree (sc); } } static void init_small_conf(Small_conf *sc) { sc->name = EMPTY_STRING; init_conf_type(&sc->type); sc->supervisor = 0; sc->highest_local_no = 0; sc->nice = param.default_nice; } static Small_conf * alloc_small_conf(void) { Small_conf *s; s = smalloc(sizeof(Small_conf)); init_small_conf(s); ++no_of_allocated_small_confs; return s; } /* * Create a conference. * * Set up a Conference and cache the name in the small_conf_array. */ extern Conf_no cached_create_conf (String name) { Conference * conf_c; Conf_no conf_no; Cache_node *node; TRACE1("cached_create_conf( "); TRACESTR(name); TRACE1(" )\n"); if ( next_free_num >= param.max_conf ) { err_stat = next_free_num; kom_errno = KOM_INDEX_OUT_OF_RANGE; return 0; } conf_no = next_free_num++; create_cache_node (conf_mcb, conf_no); node = get_conf_node (conf_no); if ( node == NULL ) restart_kom("cached_create_conf(): failed to allocate cache_node.\n"); node->s.exists = 1; node->s.dirty = 1; node->ptr = conf_c = alloc_conference(); conf_set_mru(conf_no); zero_init_cache_node (pers_mcb, conf_no); small_conf_arr[ conf_no ] = alloc_small_conf(); conf_c->name = EMPTY_STRING; s_strcpy(&conf_c->name, name); cached_change_name( conf_no, name); TRACE2("Created conference number %d\n", conf_no); LOGACC(lt_create_conf, conf_no); return conf_no; } extern Success cached_delete_conf( Conf_no conf ) { Cache_node *node; if ( conf == 0 ) { err_stat = conf; kom_errno = KOM_CONF_ZERO; return FAILURE; } if ( conf >= next_free_num ) { err_stat = conf; kom_errno = KOM_UNDEF_CONF; return FAILURE; } node = get_conf_node (conf); if ( node == NULL || node->s.exists == 0 ) { err_stat = conf; kom_errno = KOM_UNDEF_CONF; return FAILURE; } if ( node->lock_cnt > 0 ) kom_log("WNG: cached_delete_conf(%d): lock_cnt === %d\n", conf, node->lock_cnt); free_small_conf(small_conf_arr[conf]); small_conf_arr[conf] = NULL; free_conference(node->ptr); node->ptr = NULL; node->s.exists = 0; LOGACC(lt_delete_conf, conf); rebuild_matching_info_entry(conf); return OK; } Success cached_delete_person(Pers_no pers) { Cache_node *node; if ( pers == 0 ) { err_stat = pers; kom_errno = KOM_CONF_ZERO; return FAILURE; } if ( pers >= next_free_num ) { kom_log("cached_delete_person(%lu): next_free_num == %lu\n", (unsigned long)pers, (unsigned long)next_free_num); err_stat = pers; kom_errno = KOM_UNDEF_PERS; return FAILURE; } node = get_pers_node (pers); if ( pers >= next_free_num || node == NULL || node->s.exists == 0 ) { kom_log("cached_delete_person(): attempt to delete void person.\n"); err_stat = pers; kom_errno = KOM_UNDEF_PERS; return FAILURE; } if ( node->lock_cnt > 0 ) kom_log("cached_delete_pers(%lu): lock_cnt === %lu\n", (unsigned long)pers, (unsigned long)node->lock_cnt); LOGACC(lt_delete_pers, pers); free_person (node->ptr); node->ptr = NULL; node->s.exists = 0; return OK; } Success cached_delete_text(Text_no text) { Cache_node *node; if ( text == 0 ) { err_stat = text; kom_errno = KOM_TEXT_ZERO; return FAILURE; } node = get_text_node (text); if ( text >= next_text_num || node == NULL || node->s.exists == 0 ) { kom_log("cached_delete_text(): attempt to delete void text %lu.\n", text); err_stat = text; kom_errno = KOM_NO_SUCH_TEXT; return FAILURE; } if ( node->lock_cnt > 0 ) kom_log("cached_delete_text(%lu): lock_cnt === %d\n", text, node->lock_cnt); free_text_stat(node->ptr); node->ptr = NULL; node->s.exists = 0; #ifdef LOGACCESSES if (garb_running) { LOGACC(lt_garb_text, text); } else LOGACC(lt_delete_text, text); #endif return OK; } extern Conference * cached_get_conf_stat (Conf_no conf_no) { Cache_node *node; TRACE2("cached_get_conf_stat %d\n", conf_no); if ( conf_no == 0 ) { err_stat = conf_no; kom_errno = KOM_CONF_ZERO; return NULL; } node = get_conf_node (conf_no); if ( conf_no >= next_free_num || node == NULL || node->s.exists == 0 ) { err_stat = conf_no; kom_errno = KOM_UNDEF_CONF; return NULL; } LOGACC(lt_conf_stat, conf_no); if ( node->ptr != NULL ) { conf_set_mru (conf_no); ++conf_mcb->hits; return node->ptr; } if ( node->snap_shot != NULL ) { node->ptr = copy_conference (node->snap_shot); conf_set_mru (conf_no); ++conf_mcb->hits; return node->ptr; } node->ptr = read_conference(file_a, node->pos, node->size); ++conf_mcb->misses; conf_set_mru (conf_no); return node->ptr; } /* * Return TRUE if conf_no exists. */ Bool cached_conf_exists(Conf_no conf_no) { if (conf_no == 0 || conf_no >= next_free_num ) return FALSE; return small_conf_arr[conf_no] != NULL ? TRUE : FALSE; } /* * Calls to handle texts */ /* * +++ Should return Success. */ extern String cached_get_text( Text_no text ) { String the_string; Text_stat *t_stat; TRACE2("cached_get_text %lu\n", text); if ( (t_stat = cached_get_text_stat (text)) == NULL ) return EMPTY_STRING; else { LOGACC(lt_text_mass, text); the_string.string = tmp_alloc( t_stat->no_of_chars ); the_string.len = t_stat->no_of_chars; fseek(text_file, t_stat->file_pos, SEEK_SET); if ( fread(the_string.string, sizeof(char), the_string.len, text_file) != (size_t)the_string.len ) { kom_log("WARNING: cached_get_text: premature end on text %lu\n", text); return EMPTY_STRING; } return the_string; } } extern Text_stat * /* NULL on error */ cached_get_text_stat( Text_no text ) { Cache_node *node; TRACE2("cached_get_text_stat(%lu); next_text_num == ", text); TRACE2("%lu\n", (unsigned long)next_text_num); if ( text == 0 ) { err_stat = text; kom_errno = KOM_TEXT_ZERO; return NULL; } node = get_text_node (text); if ( text >= next_text_num || node == NULL || node->s.exists == 0 ) { TRACE1("cached_get_text_stat: no such text.\n"); err_stat = text; kom_errno = KOM_NO_SUCH_TEXT; return NULL; } LOGACC(lt_text_stat, text); if ( node->ptr != NULL ) { TRACE1("Found in ptr.\n"); text_set_mru( text ); ++text_mcb->hits; return node->ptr; } if ( node->snap_shot != NULL ) { TRACE1("Found in snap_shot\n"); node->ptr = copy_text_stat(node->snap_shot); text_set_mru (text); ++text_mcb->hits; return node->ptr; } TRACE1("Found in file A.\n"); node->ptr = read_text_stat(file_a, node->pos, node->size); text_set_mru (text); ++text_mcb->misses; return node->ptr; } /* * The text is set up with an empty misc-field. The misc field is * then initialized by create_text. */ extern Text_no cached_create_text(const String message) { Text_no tno; Cache_node *node; long file_pos; tno = next_text_num++; TRACE2("cached_create_text (len=%lu)\n", message.len); if ( tno >= param.max_text ) { err_stat = tno; kom_errno = KOM_INDEX_OUT_OF_RANGE; next_text_num = param.max_text; return 0; } if (fseek(text_file, 0, SEEK_END) != 0) { kom_log("ERROR: cannot seek to end of text_file: %s\n", strerror(errno)); clearerr(text_file); return 0; } file_pos = ftell(text_file); if (fwrite(message.string, 1, message.len, text_file) != (size_t)message.len) { if (errno != ENOSPC) kom_log("WARNING: cached_create_text: Couldn't write text %lu: %s\n", tno, strerror(errno)); err_stat = 0; kom_errno = KOM_TEMPFAIL; clearerr(text_file); return 0; } if (fflush(text_file) != 0) { if (errno != ENOSPC) kom_log("WARNING: cached_create_text: Couldn't fflush text %lu: %s\n", tno, strerror(errno)); err_stat = 0; kom_errno = KOM_TEMPFAIL; clearerr(text_file); return 0; } if (fsync(fileno(text_file)) != 0) { if (errno != ENOSPC) kom_log("WARNING: cached_create_text: Couldn't fsync text %lu: %s\n", tno, strerror(errno)); err_stat = 0; kom_errno = KOM_TEMPFAIL; clearerr(text_file); return 0; } create_cache_node(text_mcb, tno); node = get_text_node (tno); if ( node == NULL ) restart_kom("cached_create_text(): couldn't create cache-node.\n"); node->s.exists = 1; node->s.dirty = 1; node->ptr = alloc_text_stat(); ((Text_stat *)node->ptr)->no_of_misc = 0; ((Text_stat *)node->ptr)->misc_items = NULL; ((Text_stat *)node->ptr)->no_of_marks = 0; ((Text_stat *)node->ptr)->no_of_lines = 0; ((Text_stat *)node->ptr)->no_of_chars = 0; ((Text_stat *)node->ptr)->file_pos = file_pos; text_set_mru( tno ); LOGACC(lt_create_text, tno); TRACE2("cached_create_text -> %lu\n", tno); return tno; } EXPORT Text_no traverse_text(Text_no seed) { Cache_node *node; seed++; while ( seed < next_text_num ) { node = get_text_node (seed); if ( node != NULL && node->s.exists != 0 ) break; seed++; } return (seed >= next_text_num) ? 0 : seed ; } #if 0 /* This is not used, but should work OK. */ Pers_no traverse_person(Pers_no seed) { Cache_node *node; seed++; while ( seed < next_free_num ) { node = get_pers_node (seed); if (node != NULL && node->s.exists != 0 ) break; seed++; } return (seed >= next_free_num) ? 0 : seed ; } #endif Conf_no traverse_conference(Conf_no seed) { Cache_node *node; seed++; while ( seed < next_free_num ) { node = get_conf_node (seed); if (node != NULL && node->s.exists != 0 ) break; seed++; } return (seed >= next_free_num) ? 0 : seed ; } extern Garb_nice cached_get_garb_nice (Conf_no conf_no) { return small_conf_arr [ conf_no ]->nice; } extern String cached_get_name (Conf_no conf_no) { return small_conf_arr [ conf_no ]->name; } extern Local_text_no cached_get_highest_local_no (Conf_no conf_no) { LOGACC(lt_get_highest, conf_no); return small_conf_arr[ conf_no ]->highest_local_no; } extern Small_conf * cached_get_small_conf_stat (Conf_no conf_no) { TRACE2("cached_get_small_conf_stat %d\n", conf_no); if (conf_no == 0) { err_stat = conf_no; kom_errno = KOM_CONF_ZERO; return NULL; } if (conf_no >= next_free_num || small_conf_arr[conf_no] == NULL) { err_stat = conf_no; kom_errno = KOM_UNDEF_CONF; return NULL; } return small_conf_arr[conf_no]; } /* Lock a person struct in memory. Increase a referenc count. */ void cached_lock_person(Pers_no pers_no) { Cache_node *node; LOGACC(lt_lock_pers, pers_no); node = get_pers_node(pers_no); if ( node == NULL || node->s.exists == 0 ) restart_kom("cached_lock_person(%d): nonexistent.\n", pers_no); if ( node->ptr == NULL ) { Person *pers_stat_ptr; pers_stat_ptr = cached_get_person_stat( pers_no ); if ( pers_stat_ptr == NULL ) restart_kom("cached_lock_person(%d): couldn't read in person.\n", pers_no); if ( pers_stat_ptr != node->ptr ) restart_kom("%s(%d): pers_stat_ptr == %lu, node->ptr == %lu.\n", "cached_lock_person", pers_no, (unsigned long)pers_stat_ptr, (unsigned long)node->ptr); } node->lock_cnt++; } /* Decrease reference count. If zero, unlock person. */ void cached_unlock_person(Pers_no pers_no) { Cache_node *node; LOGACC(lt_unlock_pers, pers_no); node = get_pers_node (pers_no); if ( node == NULL ) restart_kom("cached_unlock_person(): couldn't get cache-node.\n"); if ( node->lock_cnt <= 0 ) { kom_log("cached_unlock_person(%d): lock_cnt == %d.\n", pers_no, node->lock_cnt); node->lock_cnt = 0; } else node->lock_cnt--; } /* Lock a conf struct in memory. Increase a reference count. */ void cached_lock_conf(Conf_no conf_no) { Cache_node *node; node = get_conf_node(conf_no); if ( node == NULL) restart_kom("cached_lock_conf(): can't get cache-node.\n"); if ( node->s.exists == 0 ) restart_kom("cached_lock_conf(%d): nonexistent.\n", conf_no); LOGACC(lt_lock_conf, conf_no); if ( node->ptr == NULL ) { Conference *conference_ptr; conference_ptr = cached_get_conf_stat( conf_no ); if ( conference_ptr == NULL ) restart_kom("cached_lock_conf(%d): couldn't read in conf.\n", conf_no); if ( conference_ptr != node->ptr ) restart_kom("%s(%d): conference_ptr == %lu, node->ptr == %lu.\n", "cached_lock_conf", conf_no, (unsigned long)conference_ptr, (unsigned long)node->ptr); } node->lock_cnt++; } /* Decrease reference count. If zero, unlock conf. */ void cached_unlock_conf(Conf_no conf_no) { Cache_node *node; LOGACC(lt_unlock_conf, conf_no); node = get_conf_node(conf_no); if ( node == NULL ) restart_kom("cached_unlock_conf(): can't get node.\n"); if ( node->lock_cnt <= 0 ) { kom_log("cached_unlock_conf(%d): lock_cnt == %d.\n", conf_no, node->lock_cnt); node->lock_cnt = 0; } else node->lock_cnt--; } /* * And here comes some functions to deal with lookup_names. */ /* Free the _contents_ of a match_table. The table itself i _not_ freed. */ static void free_match_table(Matching_info *mtch) { if ( mtch == NULL ) return; while ( mtch->conf_no != 0 ) { free_tokens( mtch->tokens ); /* mtch->name is not freed since it points into conf_arr[]. */ ++mtch; } } /* Find the entry for CONF_NO in match_table. Return something if there is a nonempty name or a list of tokens in the entry. The caller is responsible to checking that the name of the returned entry is nonempty. The reason for this is that cached_delete_conf clears the name before rebuilding the matching_info, and rebuild_matching_info_entry needs to get the entry to free the token list when it is called from cached_delete_conf */ static int find_matching_info_compare(const void *a, const void *b) { Matching_info *info_a = (Matching_info *)a; Matching_info *info_b = (Matching_info *)b; return info_a->conf_no - info_b->conf_no; } static Matching_info * find_matching_info(Conf_no conf_no) { Matching_info *info, key; /* FIXME: Do a binary search here! The search must be able to FIXME: deal with NULL entries. We could do this with a regular FIXME: bearch and a final check that the entry has a non-NULL FIXME: tokens field. */ key.conf_no = conf_no; info = bsearch(&key, match_table, no_of_match_info, sizeof(Matching_info), find_matching_info_compare); if (info == NULL) return NULL; else if (s_empty(info->name) && info->tokens == NULL) return NULL; else return info; } /* Rebuild the matching info entry for a conference. If the conference does not exist in the table, it will be added. If it is not a new conference (conf_no higher than the highest in the table) the entire table will be rebuilt. */ static Success rebuild_matching_info_entry(Conf_no conf_no) { Matching_info *mtch; Cache_node *node; mtch = find_matching_info(conf_no); if (mtch != NULL) { /* The entry was found. Free the old data */ free_tokens(mtch->tokens); /* Add or remove the new entry, depending on if the conf exists */ if (small_conf_arr[conf_no] != NULL) { /* Renamed a conference */ mtch->name = small_conf_arr[conf_no]->name; mtch->tokens = tokenize(small_conf_arr[conf_no]->name, s_fcrea_str(WHITESPACE)); } else { /* Deleted a conference. Clear everything but the conf_no */ /* FIXME: We should compress the table by moving the entries FIXME: Above the deleted one down one step. This will save FIXME: a little time in lookup, and make realloc the next FIXME: time someone creates a conference not have to move FIXME: memory. */ mtch->name = EMPTY_STRING; mtch->tokens = NULL; mtch->priority = 0; } } else { /* The entry was not found in match_table. This can mean one of two things... (1) The name was deleted earlier. This happens when a name is deleted, build_match_info is called, and then this function is called on the same conference. This is an error, of sorts, but can be safely ignored. (2) We are being called on a new conference. When this happens, make sure the new conference has a conf_no higher than the highest in the array, then add it to the end (if it has a conf_no not higher than the last entry, rebuild the entire thing.) */ /* Check that the conference actually exists. The cache node must exist, the node must have the exists flag set and the name must be in small_conf_arr (the last bit is really just a precaution to avoid a crash later on.) */ node = get_conf_node(conf_no); if (node == NULL || node->s.exists == 0 || s_empty(small_conf_arr[conf_no]->name)) { return OK; } /* The conference exists. Check if it's conf_no is higher than the last current entry in the list */ if (conf_no <= match_table[no_of_match_info - 1].conf_no) { kom_log("Rebuilding entire match_table. It doesn't look right.\n"); return build_matching_info(); } /* Add an entry to the match table */ match_table = srealloc(match_table, (no_of_match_info + 2) * sizeof(Matching_info)); match_table[no_of_match_info].name = small_conf_arr[conf_no]->name; match_table[no_of_match_info].tokens = tokenize(small_conf_arr[conf_no]->name, s_fcrea_str(WHITESPACE)); match_table[no_of_match_info].priority = 7; match_table[no_of_match_info].conf_no = conf_no; /* Bump the number of match infos and enter the ending dummy */ no_of_match_info += 1; match_table[no_of_match_info].name = EMPTY_STRING; match_table[no_of_match_info].tokens = NULL; match_table[no_of_match_info].priority = 0; match_table[no_of_match_info].conf_no = 0; } return OK; } static Success build_matching_info(void) { Conf_no i; Matching_info *mtch; Conf_no *conf; free_match_table(match_table); match_table = srealloc(match_table, next_free_num * sizeof(Matching_info)); no_of_match_info = 0; mtch = match_table; for ( i = 1; i < next_free_num; i++ ) { if ( small_conf_arr[ i ] != NULL && ! s_empty ( small_conf_arr[ i ]->name ) ) { mtch->name = small_conf_arr[ i ]->name; mtch->tokens = tokenize(mtch->name, s_fcrea_str(WHITESPACE)); mtch->priority = 7; mtch->conf_no = i; ++mtch; ++conf; ++no_of_match_info; } } mtch->name = EMPTY_STRING; mtch->tokens = NULL; mtch->priority = 0; mtch->conf_no = 0; return OK; } /* Map conference name to number */ extern Success cached_lookup_name(const String name, Conf_list_old *result) { Parse_info tmp; int i; /* FIXME: It is a waste of resources to include persons here if this lookup is performed due to a lookup-z-name that only requests conferences, and vice versa. */ tmp = parse(name, match_table, FALSE, FALSE, s_fcrea_str(WHITESPACE), DEFAULT_COLLAT_TAB); if ( tmp.no_of_matches == -1 ) { kom_errno = KOM_INTERNAL_ERROR; err_stat = 0; return FAILURE; } if ( tmp.no_of_matches == 1 && tmp.indexes[ 0 ] == -1 ) { /* Return the entire list. */ /* FIXME: This allocates too much data if some conferences have been deleted. */ result->no_of_conf_nos = 0; result->conf_nos = tmp_alloc(no_of_match_info * sizeof(Conf_no)); result->type_of_conf = tmp_alloc(no_of_match_info * sizeof(Conf_type)); for ( i = 0; i < no_of_match_info; i++ ) { if (s_empty(match_table[i].name)) continue; result->conf_nos[result->no_of_conf_nos] = match_table[i].conf_no; result->type_of_conf[result->no_of_conf_nos] = small_conf_arr[match_table[i].conf_no]->type; result->no_of_conf_nos++; } } else { /* Return the conferences whose conf_nos are in indexes[]. */ result->no_of_conf_nos = tmp.no_of_matches; result->conf_nos = tmp_alloc(tmp.no_of_matches * sizeof(Conf_no)); result->type_of_conf = tmp_alloc(tmp.no_of_matches * sizeof(Conf_type)); for ( i = 0; i < tmp.no_of_matches; i++ ) { result->conf_nos[ i ] = match_table[ tmp.indexes[ i ] ].conf_no; result->type_of_conf[ i ] = small_conf_arr[ match_table[ tmp.indexes[ i ] ].conf_no ]->type; } } sfree(tmp.indexes); return OK; } static Bool is_clean(const char *fn) { FILE *fp; if ( (fp = fopen(fn, "rb")) == NULL ) return FALSE; if ( getc(fp) == 'C' && getc(fp) == 'L' && getc(fp) == 'E' && getc(fp) == 'A' && getc(fp) == 'N' ) { fclose(fp); return TRUE; } else { fclose(fp); return FALSE; } } static long get_version(const char *fn) { FILE *fp; long version; if ( (fp = fopen(fn, "rb")) == NULL) return -1; fseek(fp, 5, SEEK_SET); if (getc(fp) == '\n') { fclose(fp); return 0; } version = fparse_long(fp); fclose(fp); return version; } static void sync_output_header(FILE* fp, const char *state) { fprintf(fp, "%s:%05ld\n", state, 2L); /* DIRTY-FLAG and VERSION*/ fprintf(fp, "%020lu\n", (unsigned long)current_time); } static void pre_sync(void) { Text_no tno_iter; Conf_no cno_iter; Cache_node *node; async_sync_db(); /* Mark up what to save.*/ BUG(("Sync starting\n")); highest_text_no = next_text_num; highest_conf_no = next_free_num; #ifndef FASTSAVE for ( cno_iter = 1; cno_iter < highest_conf_no; cno_iter++ ) { node = get_conf_node(cno_iter); if ( node == NULL ) continue; if (node->s.exists == 0) { unlink_conf_lru(node); destruct_cache_node(conf_mcb, cno_iter); } else { if (node->s.dirty != 0) { free_conference(node->snap_shot); if ( node->lock_cnt == 0 ) { unlink_conf_lru(node); node->snap_shot = node->ptr; node->ptr = NULL; } else node->snap_shot = copy_conference(node->ptr); node->s.dirty = 0; } } } for ( cno_iter = 1; cno_iter < highest_conf_no; cno_iter++ ) { node = get_pers_node(cno_iter); if ( node == NULL ) continue; if (node->s.exists == 0) { unlink_pers_lru(node); destruct_cache_node(pers_mcb, cno_iter); } else { if (node->s.dirty != 0) { free_person(node->snap_shot); if ( node->lock_cnt == 0 ) { unlink_pers_lru(node); node->snap_shot = node->ptr; node->ptr = NULL; } else node->snap_shot = copy_person(node->ptr); node->s.dirty = 0; } } } for ( tno_iter = 1; tno_iter < highest_text_no; tno_iter++ ) { node = get_text_node(tno_iter); if ( node == NULL ) continue; if (node->s.exists == 0) { unlink_text_lru(node); destruct_cache_node(text_mcb, tno_iter); } else { if (node->s.dirty != 0) { free_text_stat(node->snap_shot); if ( node->lock_cnt == 0 ) { unlink_text_lru(node); node->snap_shot = node->ptr; node->ptr = NULL; } else node->snap_shot = copy_text_stat(node->ptr); node->s.dirty = 0; } } } #endif /* All marking is done. Now open file B. */ if (is_clean(param.datafile_name)) { if (is_clean(param.backupfile_name)) { if (rename(param.backupfile_name, param.backupfile_name_2) != 0) { kom_log("pre_sync: can't do extra backup.\n"); } } if (rename(param.datafile_name, param.backupfile_name) != 0) restart_kom("pre_sync: can't backup.\n"); } else kom_log("pre_sync: datafile not clean. No backup taken.\n"); if ( file_b != NULL ) { kom_log("pre_sync: Save in progress aborted.\n"); fclose(file_b); #ifdef FASTSAVE file_b = NULL; #endif } if ( (file_b = fopen(param.datafile_name, "wb") ) == NULL ) { kom_log("WARNING: pre_sync: can't open file to save in.\n"); sync_state = sync_wait; return; } #ifdef FASTSAVE if ( (file_b_r = fopen(param.datafile_name, "rb") ) == NULL ) { fclose(file_b); file_b = NULL; kom_log("WARNING: pre_sync: can't open file to save in for reading.\n"); sync_state = sync_wait; return; } #endif sync_output_header(file_b, "DIRTY"); fprintf(file_b, "#C %d\n", highest_conf_no); fprintf(file_b, "#T %ld\n", highest_text_no); fprintf(file_b, "I"); foutput_info(file_b, &kom_info); fprintf(file_b, "\n"); sync_state = sync_save_conf; #ifdef FASTSAVE next_text_to_sync = 1; next_conf_to_sync = 1; #else sync_next = 1; #endif } static void copy_file(FILE *from, FILE *to, long from_pos, long len, long no) { static char buf[BUFSIZ]; long result; long num; long new_num; long num_ix; long chunk_len; long orig_len = len; int first_chunk = 1; if (len < 3) { restart_kom("copy_file: insane len %ld\n", len); } /* Include the terminating newline in the length. */ ++len; if ( fseek(from, from_pos, SEEK_SET) == -1 ) { sync_state = sync_error; restart_kom("sync: copy_file(): src fseek failed.\n"); return; } if ( fseek(to, 0, SEEK_END) == -1 ) { sync_state = sync_error; kom_log("sync: copy_file(): dst fseek failed.\n"); return; } first_chunk = 1; while (len > 0) { chunk_len = len; if (chunk_len > BUFSIZ) chunk_len = BUFSIZ; if ((result = fread(buf, 1, chunk_len, from)) != chunk_len) { restart_kom("%s.\nfrom_pos = %ld, len = %ld, result = %ld\n", "sync: copy_file(): fread failed", from_pos, len, result); sync_state = sync_error; return; } if (first_chunk) { /* Check the start of the first chunk. */ if (buf[0] != 'T' && buf[0] != 'C' && buf[0] != 'P') { restart_kom("Found char %d at pos %ld; expected T, C or P\n", buf[0], from_pos); } if (buf[1] != ' ') { restart_kom("Expected space after T, C or P but got " "%d at %ld\n", buf[1], from_pos); } num = 0; for (num_ix = 2; num_ix < chunk_len && buf[num_ix] >= '0' && buf[num_ix] <= '9'; ++num_ix) { new_num = 10 * num + buf[num_ix] - '0'; if (new_num / 10 != num) { restart_kom("copy_file: number overflow at %ld\n", from_pos); } num = new_num; } if (num != no) { restart_kom("copy_file: expected %ld, got %ld; " "no sanity at %ld\n", no, num, from_pos); } if (num_ix >= chunk_len) { restart_kom("copy_file: to little data at %ld\n", from_pos); } if (buf[num_ix] != ' ') { restart_kom("copy_file: expected space after number " "%ld at %ld; got %d\n", num, from_pos, buf[num_ix]); } first_chunk = 0; } /* The last chunk should end with a newline. */ if (len == result) { if (buf[len-1] != '\n') { restart_kom("Failed to find a newline at %ld + %ld - 1\n", from_pos, orig_len); } /* Don't emit the newline here. */ --len; --chunk_len; if (len == 0) return; } /* Write this chunk. */ if (fwrite(buf, 1, chunk_len, to) != (size_t)chunk_len) { sync_state = sync_error; kom_log("sync: copy_file(): fwrite failed.\n"); return; } len -= chunk_len; } } static void save_one_conf(void) { Cache_node *cn; #ifdef FASTSAVE if (next_conf_to_sync < highest_conf_no) #else if (sync_next < highest_conf_no) #endif { cn = get_conf_node (sync_next); if ( cn == NULL ) { } else { #ifdef FASTSAVE cn->saved_pos = cn->pos; cn->pos = ftell(file_b); cn->s.saved_dirty = cn->s.dirty; #else cn->pos_b = ftell(file_b); #endif if ( cn->snap_shot != NULL ) { fprintf(file_b, "C %lu ", sync_next); foutput_conference(file_b, cn->snap_shot); #ifdef FASTSAVE free_conference( cn->snap_shot ); #endif } else if ( cn->s.dirty == 0 && cn->ptr != NULL ) { fprintf(file_b, "C %lu", sync_next); foutput_conference(file_b, cn->ptr); } else { copy_file(file_a, file_b, cn->pos, cn->size - 1, sync_next); } putc('\n', file_b); #ifdef FASTSAVE cn->saved_size = cn->size; cn->size = ftell(file_b) - cn->pos; cn->s.dirty = 0; #else cn->size_b = ftell(file_b) - cn->pos_b; #endif } #ifdef FASTSAVE next_conf_to_sync += 1; #else sync_next++; #endif } else /* All conferences are written. */ { #ifdef FASTSAVE sync_stat = sync_save_conf; #else sync_next = 1; sync_state = sync_save_pers; #endif } } static void write_pers(FILE *fp, Person *p, int pers_no) { fprintf(fp, "P %d %dH", pers_no, PASSWD_LEN); fwrite(p->pwd, PASSWD_LEN, 1, fp); foutput_person(fp, p); } static void save_one_pers(void) { Cache_node *cn; #ifdef FASTSAVE restart_kom("Attempt to save one person in FASTSAVE mode (can't happen.)"); #endif if (sync_next < highest_conf_no) { cn = get_pers_node (sync_next); if ( cn == NULL ) { } else { cn->pos_b = ftell(file_b); if ( cn->snap_shot != NULL ) { write_pers(file_b, cn->snap_shot, sync_next); } else if ( cn->s.dirty == 0 && cn->ptr != NULL ) { write_pers(file_b, cn->ptr, sync_next); } else { copy_file(file_a, file_b, cn->pos, cn->size - 1, sync_next); } putc('\n', file_b); cn->size_b = ftell(file_b) - cn->pos_b; } sync_next++; } else /* All persons are written. */ { sync_next = 1; sync_state = sync_save_text; } } static void post_sync(void) { Text_no tno_iter; Conf_no cno_iter; Cache_node *node; async_sync_db(); if ( file_a == NULL ) kom_log("WARNING: post_sync(): file_a == NULL. This is only normal %s", "if this is the first sync ever on this data file.\n"); else fclose(file_a); if ( ( file_a = fopen(param.datafile_name, "rb") ) == NULL ) { kom_log("post_sync: can't open the file I just saved.\n"); sync_state = sync_wait; return; } for ( cno_iter = 1; cno_iter < highest_conf_no; cno_iter++ ) { node = get_conf_node(cno_iter); if ( node != NULL ) { node->pos = node->pos_b; node->size = node->size_b; free_conference(node->snap_shot); node->snap_shot = NULL; } } for ( cno_iter = 1; cno_iter < highest_conf_no; cno_iter++ ) { node = get_pers_node(cno_iter); if ( node != NULL ) { node->pos = node->pos_b; node->size = node->size_b; free_person(node->snap_shot); node->snap_shot = NULL; } } for ( tno_iter = 1; tno_iter < highest_text_no; tno_iter++ ) { node = get_text_node(tno_iter); if ( node != NULL ) { node->pos = node->pos_b; node->size = node->size_b; free_text_stat(node->snap_shot); node->snap_shot = NULL; } } } static void save_one_text(void) { Cache_node *cn; long offset; long offset2; #ifdef FASTSAVE while (next_text_to_sync < highest_text_no) #else while (sync_next < highest_text_no) #endif { cn = get_text_node(sync_next); if ( cn == NULL ) { #ifdef FASTSAVE next_text_to_sync += 1; #else sync_next++; #endif continue; } else { #ifdef FASTSAVE cn->saved_pos = cn->pos; cn->pos = ftell(file_b); cn->s.saved_dirty = cn->s.dirty; #else cn->pos_b = ftell(file_b); #endif if ( cn->snap_shot != NULL ) { fprintf(file_b, "T %lu", sync_next); foutput_text_stat(file_b, cn->snap_shot); #ifdef FASTSAVE free_text_stat( cn->snap_shot ); #endif } else if ( cn->s.dirty == 0 && cn->ptr != NULL ) { fprintf(file_b, "T %lu", sync_next); foutput_text_stat(file_b, cn->ptr); } else { copy_file(file_a, file_b, cn->pos, cn->size - 1, sync_next); } putc('\n', file_b); #ifdef FASTSAVE cn->size = ftell(file_b) - cn->pos; next_text_to_sync += 1; cn->s.dirty = 0; #else cn->size_b = ftell(file_b) - cn->pos_b; sync_next++; #endif break; } /*NOTREACHED*/ restart_kom("Unreachable statement reached."); } /* If all texts are written, do some clean-up. */ #ifdef FASTSAVE if (next_text_to_sync == highest_text_no) #else if (sync_next == highest_text_no) #endif { if ( ferror(file_b) != 0 ) { kom_log ("save_one_text(): ferror() detected.\n"); sync_state = sync_error; return; } offset = ftell(file_b); /* Make sure that the entire file resides on disk. This test seems to be necessary. The data file has been corrupted at least once. */ if (offset == -1) { kom_log ("save_one_text(): ftell returned -1.\n"); sync_state = sync_error; return; } rewind(file_b); if ( ferror(file_b) != 0 ) { kom_log ("save_one_text(): rewind failed.\n"); sync_state = sync_error; return; } sync_output_header(file_b, "CLEAN"); if ( ferror(file_b) != 0 ) { kom_log ("save_one_text(): Set state to CLEAN failed.\n"); sync_state = sync_error; return; } if (fflush(file_b) != 0) { kom_log ("save_one_text(): fflush failed.\n"); sync_state = sync_error; return; } if ( ferror(file_b) != 0 ) { kom_log ("save_one_text(): ferror after fflush failed.\n"); sync_state = sync_error; return; } if ( fclose(file_b) != 0 ) { file_b = NULL; kom_log("Sync: fclose() failed in save_one_text. Retrying.\n"); remove(param.datafile_name); sync_state = sync_wait; return; } #ifdef FASTSAVE if ( fclose(file_b_r) != 0 ) { file_b_r = NULL; kom_log("Sync: fclose() of reader failed in save_one_text. Retrying.\n"); remove(param.datafile_name); sync_state = sync_wait; return; } file_b_r = NULL; #endif file_b = fopen(param.datafile_name, "rb"); if (file_b == NULL) { kom_log("save_one_text(): failed to reopen file.\n"); remove (param.datafile_name); sync_state = sync_wait; return; } if (fseek(file_b, 0, SEEK_END) != 0) { kom_log("save_one_text(): fseek failed.\n"); sync_state = sync_error; return; } offset2 = ftell (file_b); if ( offset2 != offset ) { kom_log ("save_one_text(): ftell confused (%ld and %ld).\n", offset, offset2); sync_state = sync_error; return; } fclose (file_b); file_b = NULL; sync_state = sync_ready; BUG(("Sync ready\n")); post_sync(); } } /* * Sync_part() should be called often as long as it returns FALSE. * Sync_part() returns TRUE when everything is written to a file. * Sync_part() should be called once in a while even when it returns * TRUE (because sync() checks to see if it is time to start yet * another save.) */ Bool sync_part(void) { static time_t last_sync_start = NO_TIME; if ( last_sync_start == NO_TIME ) { last_sync_start = current_time; sync_state = sync_idle; } #ifdef LOGACCESSES syncing_or_saving = 1; #endif switch(sync_state) { case sync_save_conf: save_one_conf(); break; #ifndef FASTSAVE case sync_save_pers: save_one_pers(); break; #endif case sync_save_text: save_one_text(); break; case sync_ready: sync_state = sync_idle; return TRUE; case sync_idle: if (param.never_save) { return TRUE; } if (ldifftime(current_time, last_sync_start) < 60 * param.sync_interval) { #ifdef LOGACCESSES syncing_or_saving = 0; #endif return TRUE; } last_sync_start = current_time; pre_sync(); break; case sync_wait: if (ldifftime(current_time, last_sync_start) < 60*param.sync_retry_interval) { #ifdef LOGACCESSES syncing_or_saving = 0; #endif return TRUE; } last_sync_start = current_time; pre_sync(); break; case sync_error: kom_log("sync: Error saving new file. Retrying.\n"); fclose(file_b); file_b = NULL; #ifdef FASTSAVE if (file_b_r != NULL) { fclose(file_b_r); file_b_r = NULL; } #endif remove(param.datafile_name); sync_state = sync_wait; break; default: restart_kom("sync(): sync_state==%d", sync_state); } if ( file_b != NULL && ferror(file_b) != 0) sync_state = sync_error; #ifdef LOGACCESSES syncing_or_saving = 0; #endif return FALSE; } static void setup_small_conf(Conf_no conf_no, Conference *conf_c) { small_conf_arr[conf_no] = alloc_small_conf(); s_strcpy(&small_conf_arr[conf_no]->name, conf_c->name); small_conf_arr[conf_no]->type = conf_c->type; small_conf_arr[conf_no]->supervisor = conf_c->supervisor; small_conf_arr[conf_no]->highest_local_no = l2g_first_appendable_key(&conf_c->texts) - 1; small_conf_arr[conf_no]->nice = conf_c->nice; } extern Success init_cache(void) { long i; Cache_node *node; Conference tmp_conf; Person tmp_pers; Text_stat tmp_text; long datafile_version; long pos, num; Bool done = FALSE; Bool read_text_no = FALSE; Bool read_conf_no = FALSE; int c = 0; time_t saved_time = 0; small_conf_arr = smalloc(sizeof(*small_conf_arr) * param.max_conf); pers_mcb = create_cache_node_mcb(100, param.max_conf); conf_mcb = create_cache_node_mcb(100, param.max_conf); text_mcb = create_cache_node_mcb(100, param.max_text); init_conference(&tmp_conf); init_person(&tmp_pers); init_text_stat(&tmp_text); #ifdef LOGACCESSES if (param.logaccess_file) { logfile = fopen(param.logaccess_file, "a"); if (logfile) kom_log("Logging db accesses to %s.\n", param.logaccess_file); else kom_log("Failed to open db log file %s. Not logging.\n", param.logaccess_file); } #endif LOGACC(lt_restart, current_time); for (i = 0; i < param.max_conf; i++) small_conf_arr[i] = NULL; for (i = 1; i < param.max_conf; i++) zero_init_cache_node(pers_mcb, i); for (i = 1; i < param.max_conf; i++) zero_init_cache_node(conf_mcb, i); for (i = 1; i < param.max_text; i++) zero_init_cache_node(text_mcb, i); datafile_version = -1; if ( (text_file = fopen(param.textfile_name, "a+b")) == NULL ) { restart_kom("%s \"%s\". errno = %d\n", "ERROR: init_cache: can't open text file", param.textfile_name, errno); } if (is_clean(param.datafile_name)) { if ((file_a = fopen(param.datafile_name, "rb")) == NULL) { kom_log("WARNING: init_cache: can't open datafile.\n"); kom_errno = KOM_INTERNAL_ERROR; err_stat = 0; return FAILURE; } kom_log("MSG: init_cache: using datafile.\n"); datafile_version = get_version(param.datafile_name); } else if (is_clean(param.backupfile_name)) { if ((file_a = fopen(param.backupfile_name, "rb")) == NULL) { kom_log("WARNING: init_cache: can't open backupfile.\n"); kom_errno = KOM_INTERNAL_ERROR; err_stat = 0; return FAILURE; } kom_log("MSG: init_cache: using backup file.\n"); datafile_version = get_version(param.backupfile_name); } else { /* Don't attempt to use backupfile_name_2 automatically. If that file is ever needed something is really broken; manual intervention is needed to assess the damage. */ kom_log("WARNING: init_cache: can't find old data base.\n"); kom_errno = KOM_INTERNAL_ERROR; err_stat = 0; return FAILURE; } switch (datafile_version) { case 0: restart_kom("Database is version 0. Please convert it with dbck.\n"); break; case 1: fseek(file_a, 12, SEEK_SET); restart_kom("You need to run dbck to convert your datafile to version 2.\n"); break; case 2: /* * Read timestamp */ fseek(file_a, 12, SEEK_SET); saved_time = fparse_long(file_a); if (saved_time > current_time) { restart_kom("Saved time is later than current time. Exiting.\n"); } kom_log("Database saved on %s", /* ctime returns a trailing newline. */ ctime(&saved_time)); break; default: restart_kom("Can't read database version %ld. Giving up.\n", datafile_version); } set_input_format(datafile_version); /* * Read conferences */ for ( i = 1; !done ; i++ ) /* CONFS */ { fskipwhite(file_a); switch(c = getc(file_a)) { case EOF: done = TRUE; break; case '@': case '+': restart_kom("init_cache(): old type record in new type file\n"); break; case '#': fskipwhite(file_a); switch(getc(file_a)) { case 'C': highest_conf_no = fparse_long(file_a); next_free_num = highest_conf_no; read_conf_no = TRUE; break; case 'T': highest_text_no = fparse_long(file_a); next_text_num = highest_text_no; read_text_no = TRUE; break; default: restart_kom("init_cache(): Bad number in database\n"); } break; case '-': fskipwhite(file_a); switch(getc(file_a)) { case 'C': num = fparse_long(file_a); node = get_conf_node(num); if (node) { node->s.exists = 0; node->pos = -1; } break; case 'P': num = fparse_long(file_a); node = get_pers_node(num); if (node) { node->s.exists = 0; node->pos = -1; } break; case 'T': num = fparse_long(file_a); node = get_text_node(num); if (node) { node->s.exists = 0; node->pos = -1; } break; default: restart_kom("init_cache(): bad remove block in data file\n"); } case 'I': if (fparse_info(file_a, &kom_info) != OK) restart_kom("init_cache(): fparse_info() failed.\n"); break; case 'C': pos = ftell(file_a) - 1; /* Don't forget the '+' */ num = fparse_long(file_a); LOGACC(lt_conf_def, num); if (num < 1) { restart_kom("ERROR: init_cache(), bad conf_no %ld" " at record %ld\n", num, i); } node = get_conf_node(num); if (!node) { create_cache_node(conf_mcb, num); node = get_conf_node(num); } node->s.exists = 1; node->pos = pos; if ( fparse_conference(file_a, &tmp_conf) != OK ) restart_kom("init_cache(): fparse_conference(%ld) failed.\n", i); node->size = ftell(file_a) - node->pos; setup_small_conf(num, &tmp_conf); clear_conference(&tmp_conf); break; case 'P': pos = ftell(file_a) - 1; /* Don't forget the '+' */ num = fparse_long(file_a); LOGACC(lt_pers_def, num); node = get_pers_node(num); if (!node) { create_cache_node(pers_mcb, num); node = get_pers_node(num); } node->s.exists = 1; node->pos = pos; if ( fparse_person(file_a, &tmp_pers) != OK ) restart_kom("init_cache: fparse_person failed. i==%ld\n", i); node->size = ftell(file_a) - node->pos; clear_person(&tmp_pers); break; case 'T': pos = ftell(file_a) - 1; /* Don't forget the '+' */ num = fparse_long(file_a); LOGACC(lt_text_def, num); node = get_text_node(num); if (!node) { create_cache_node(text_mcb, num); node = get_text_node(num); } node->s.exists = 1; node->pos = pos; if ( fparse_text_stat(file_a, &tmp_text) != OK ) restart_kom("init_cache(): fparse_text_stat failed. i==%ld\n", i); node->size = ftell(file_a) - node->pos; clear_text_stat(&tmp_text); break; default: restart_kom("init_cache(): Unknown key %c (%d) " "in data file at %lu\n", c, c, (unsigned long)ftell(file_a)); break; } } if (read_conf_no == FALSE || read_text_no == FALSE) { restart_kom("init_cache(): highest text or conf no not read!\n"); } build_matching_info(); kom_log("Read %d confs/persons and %ld texts\n", next_free_num, next_text_num); return OK; } extern void cache_sync_all(void) { if (param.never_save) return; pre_sync(); while ( sync_part() != TRUE ) ; } void free_all_cache (void) { unsigned int i; Cache_node *node; #ifdef LOGACCESSES if (logfile) fclose(logfile); #endif for ( i = 1; i < next_free_num; i++ ) { node = get_conf_node(i); if ( node != NULL ) { if ( node->snap_shot != NULL ) { free_conference (node->snap_shot); node->snap_shot = NULL; } if ( node->ptr != NULL ) { free_conference (node->ptr); node->ptr = NULL; } } destruct_cache_node (conf_mcb, i); node = get_pers_node(i); if ( node != NULL ) { if ( node->snap_shot != NULL ) { free_person (node->snap_shot); node->snap_shot = NULL; } if ( node->ptr != NULL ) { free_person (node->ptr); node->ptr = NULL; } } destruct_cache_node (pers_mcb, i); if ( small_conf_arr[i] != NULL ) { free_small_conf (small_conf_arr[i]); small_conf_arr[i] = NULL; } } for ( i = 1; i < next_text_num; i++ ) { node = get_text_node(i); if ( node != NULL ) { if ( node->snap_shot != NULL ) { free_text_stat (node->snap_shot); node->snap_shot = NULL; } if ( node->ptr != NULL ) { free_text_stat (node->ptr); node->ptr = NULL; } } destruct_cache_node (text_mcb, i); } free_match_table(match_table); free_cache_node_mcb(conf_mcb); free_cache_node_mcb(text_mcb); free_cache_node_mcb(pers_mcb); sfree(small_conf_arr); sfree (match_table); } /* Is it allowed to delete this node from the cache? It is, unless the node is locked, dirty, or contains a snap-shot. */ static Bool throwable_p(Cache_node *node) { return ((node->s.dirty == 0 || node->s.exists == 0) && node->snap_shot == NULL && node->lock_cnt == 0) ? TRUE : FALSE; } static void limit_pers(void) { Cache_node *node; Cache_node *next_node; int i; node = pers_mcb->mru; /* Skip first CACHE_PERSONS clean persons. */ for ( i = 0; node != NULL && i < param.cache_persons; i++ ) { while (node != NULL && !throwable_p(node)) node = node->next; if ( node != NULL ) node = node->next; } /* Delete any remaining clean persons */ while ( node != NULL ) { next_node = node->next; if (throwable_p(node)) { unlink_pers_lru(node); free_person (node->ptr); node->ptr = NULL; /* +++ delete cache-node if non-existent. */ } node = next_node; } } static void limit_conf(void) { Cache_node *node; Cache_node *next_node; int i; node = conf_mcb->mru; /* Skip first CACHE_CONFERENCES clean confs. */ for ( i = 0; node != NULL && i < param.cache_conferences; i++ ) { while (node != NULL && !throwable_p(node)) node = node->next; if ( node != NULL ) node = node->next; } /* Delete any remaining clean confs. */ while ( node != NULL ) { next_node = node->next; if (throwable_p(node)) { unlink_conf_lru(node); free_conference (node->ptr); node->ptr = NULL; /* FIXME: +++ delete if non-existent. */ } node = next_node; } } static void limit_text_stat(void) { Cache_node *node; Cache_node *next_node; int i; node = text_mcb->mru; /* Skip first CACHE_TEXT_STATS clean text_stats. */ for ( i = 0; node != NULL && i < param.cache_text_stats; i++ ) { while (node != NULL && !throwable_p(node)) node = node->next; if (node != NULL) node = node->next; } /* Delete any remaining clean text_stats. */ while ( node != NULL ) { next_node = node->next; if (throwable_p(node)) { unlink_text_lru(node); free_text_stat (node->ptr); node->ptr = NULL; /* +++ delete if non-existent. */ } node = next_node; } } /* * Limit the number of 'clean' cache entries. */ EXPORT void cache_limit_size(void) { limit_pers(); limit_conf(); limit_text_stat(); } EXPORT void dump_cache_mem_usage(FILE *fp) { fprintf(fp, "---simple-cache.c:\n"); fprintf(fp, "\tSmall_confs: %d\n", no_of_allocated_small_confs); } EXPORT void dump_cache_stats(FILE *fp) { fprintf(fp, "---simple-cache.c:\n"); fprintf(fp, "\tPersons (cache size: %d):\n", param.cache_persons); fprintf(fp, "\t hits: %lu\n\t miss: %lu\n", pers_mcb->hits, pers_mcb->misses); fprintf(fp, "\tConferences (cache size: %d):\n", param.cache_conferences); fprintf(fp, "\t hits: %lu\n\t miss: %lu\n", conf_mcb->hits, conf_mcb->misses); fprintf(fp, "\tText_stats (cache size: %d):\n", param.cache_text_stats); fprintf(fp, "\t hits: %lu\n\t miss: %lu\n", text_mcb->hits, text_mcb->misses); } EXPORT Text_no query_next_text_num(void) { return next_text_num; } EXPORT void tell_cache_garb_text(int running) { #ifdef LOGACCESSES garb_running = running; #else /* Kluge to remove compiler warning The compiler should be able to optimize this away */ running = running; #endif }