/* * $Id: simple-cache.c,v 0.117 2005/12/18 22:18:15 ceder Exp $ * Copyright (C) 1991-2005 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 report bugs at http://bugzilla.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 #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 "timewrap.h" #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-io.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 "conf-file.h" #include "param.h" #include "kom-config.h" #include "admin.h" #include "unused.h" #include "local-to-global.h" #include "server-time.h" #include "eintr.h" #include "timeval-util.h" #include "stats.h" #include "services.h" #include "manipulate.h" /* * Possible improvements: * * FIXME (bug 167): 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). */ /* * 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 Conf_no existing_confs = 0; static Cache_node_mcb * text_mcb; static Text_no next_text_num = 1; static int no_of_match_info; EXPORT Matching_info *match_table = NULL; static FILE *text_file= NULL; static struct dbfile *file_a = NULL; /* Current file. */ static struct dbfile *file_b = NULL; /* File under construction. */ #ifdef FASTSAVE static struct dbfile *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 = 0; static Text_no highest_text_no = 0; BUGDECL; #ifdef DEBUG_CALLS /* This is used by cache_sync_start() and cache_sync_finish(). */ static int block_after_pre_sync = 0; #endif /* Define LOGACCESSES if you want to be able to log all accesses to the data base. */ #ifdef LOGACCESSES enum log_type { 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. */ }; static FILE *logfile = NULL; static int syncing_or_saving = 0; static int garb_running = 0; static void log_access(enum 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 static Static_server_info boottime_info = { 0, 0, EMPTY_STRING_i, 0, 0, 0, 0, 0 }; /* 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(struct dbfile *fp, long pos, long size) { Person *p; long pers_no; long new_pos; if (fparse_set_pos(fp, pos+1) != OK) /* Skip 'P' */ return NULL; p = alloc_person(); pers_no = fparse_long(fp); if ( fparse_person(fp, p) != OK ) { free_person(p); return NULL; } new_pos = dbfile_ftell(fp); if (new_pos < 0) { kom_log("ftell failed: %s. Ignored.\n", strerror(errno)); } else if (new_pos != pos + size) { restart_kom("Person %ld at %ld should be %ld bytes, " "but %ld bytes was read\n", pers_no, pos, size, new_pos - pos); } return p; } static Conference * read_conference(struct dbfile *fp, long pos, long size) { Conference *c; long conf_no; long new_pos; if (fparse_set_pos(fp, pos+1) != OK) /* Skip 'C' */ return NULL; c = alloc_conference(); conf_no = fparse_long(fp); if ( fparse_conference(fp, c) != OK ) { free_conference(c); return NULL; } new_pos = dbfile_ftell(fp); if (new_pos < 0) { kom_log("ftell failed: %s. Ignored.\n", strerror(errno)); } else if (new_pos != pos + size) { restart_kom("Conference %ld at %ld should be %ld bytes, " "but %ld bytes was read\n", conf_no, pos, size, new_pos - pos); } return c; } static Text_stat * read_text_stat(struct dbfile *fp, long pos, long UNUSED(size)) /* FIXME (bug 170): sanity-check the size */ { Text_stat *t; long dummy; if (fparse_set_pos(fp, pos+1) != OK) /* Skip 'T' */ return NULL; t = alloc_text_stat(); 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 existing_confs; } /* * 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]->keep_commented = conf_c->keep_commented; 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); } /* * Store and retrieve the highest number used. */ static int write_number_file(void) { FILE *fp; if ((fp = i_fopen(param.numberfile_tmp_name, "w")) == NULL) { kom_log("opening %s: %s\n", param.numberfile_tmp_name, strerror(errno)); return -1; } fprintf(fp, "Text_no: %lu Conf_no: %lu End.\n", (unsigned long)next_text_num, (unsigned long)next_free_num); if (fflush(fp) != 0) { kom_log("fflush to %s failed\n", param.numberfile_tmp_name); i_fclose(fp); return -1; } if (ferror(fp) != 0) { kom_log("fprintf to %s failed\n", param.numberfile_tmp_name); i_fclose(fp); return -1; } if (i_fclose(fp) != 0) { kom_log("fclose %s failed\n", param.numberfile_tmp_name); return -1; } if (i_rename(param.numberfile_tmp_name, param.numberfile_name) != 0) { kom_log("failed to rename %s to %s: %s\n", param.numberfile_tmp_name, param.numberfile_name, strerror(errno)); return -1; } return 0; } static void read_number_file(void) { FILE *fp; unsigned long txt = 0; unsigned long cno = 0; Text_no text_no; Conf_no conf_no; if ((fp = i_fopen(param.numberfile_name, "r")) == NULL) { if (errno == ENOENT) kom_log("WARN: %s: No such file\n", param.numberfile_name); else restart_kom("opening %s: %s\n", param.numberfile_name, strerror(errno)); return; } if (fscanf(fp, "Text_no: %lu Conf_no: %lu", &txt, &cno) != 2 || getc(fp) != ' ' || getc(fp) != 'E' || getc(fp) != 'n' || getc(fp) != 'd' || getc(fp) != '.') { kom_log("WARN: %s: broken file (ignored)\n", param.numberfile_name); i_fclose(fp); return; } i_fclose(fp); text_no = txt; conf_no = cno; if (text_no > next_text_num) { kom_log("WARN: Texts %lu - %lu were lost.\n", (unsigned long)next_text_num, (unsigned long)text_no - 1); next_text_num = text_no; } if (conf_no > next_free_num) { kom_log("WARN: Confs %lu - %lu were lost.\n", (unsigned long)next_free_num, (unsigned long)conf_no - 1); next_free_num = conf_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; sc->keep_commented = param.default_keep_commented; } 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++; if (write_number_file() < 0) { next_free_num--; err_stat = 0; kom_errno = KOM_TEMPFAIL; return 0; } ++existing_confs; 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; --existing_confs; 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 */ /* * FIXME (bug 171): cached_get_text() 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 if (t_stat->generation == 0) { LOGACC(lt_text_mass, text); the_string.string = tmp_alloc( t_stat->no_of_chars ); the_string.len = t_stat->no_of_chars; if (fseek(text_file, t_stat->file_pos, SEEK_SET) != 0) { kom_log("Failed to seek to %ld in text mass file: %s.\n", t_stat->file_pos, strerror(errno)); return EMPTY_STRING; } 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; } else { #warning Generation > 0 not yet handled restart_kom("Only generation 0 is handled.\n"); } } 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 do_create_text. */ extern Text_no cached_create_text(const String message) { Text_no tno; Cache_node *node; long file_pos; tno = next_text_num++; #warning Generation > 0 not yet handled 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 (write_number_file() < 0) { next_text_num--; err_stat = 0; kom_errno = KOM_TEMPFAIL; 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)->generation = 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 Garb_nice cached_get_keep_commented(Conf_no conf_no) { return small_conf_arr[conf_no]->keep_commented; } 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 for 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; /* A simple subtraction could be used, but this is more future-proof if we have to increase the size of Conf_no in the future. */ if (info_a->conf_no < info_b->conf_no) return -1; else if (info_a->conf_no > info_b->conf_no) return 1; else return 0; } static Matching_info * find_matching_info(Conf_no conf_no) { Matching_info *info, key; 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 (bug 173): We should compress the table by moving the entries above the deleted one down one step. This will save a little time in lookup, and make realloc the next time someone creates a conference not have to move memory. Warning: doing this might interfere with the comments refered to by bug 172. */ mtch->name = EMPTY_STRING; mtch->tokens = NULL; } } 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 */ /* We need space for the new entry and for the sentinel. */ 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].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].conf_no = 0; } return OK; } static Success build_matching_info(void) { Conf_no i; Matching_info *mtch; Conf_no size; free_match_table(match_table); size = cached_no_of_existing_conferences() + 1; match_table = srealloc(match_table, size * sizeof(Matching_info)); no_of_match_info = 0; mtch = match_table; for ( i = 1; i < next_free_num && no_of_match_info < size; 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->conf_no = i; ++mtch; ++no_of_match_info; } } if (no_of_match_info >= size) restart_kom("Internal error: build_matching_info: " "no_of_match_info=%ld but size=%ld\n", (long)no_of_match_info, (long)size); mtch->name = EMPTY_STRING; mtch->tokens = NULL; 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; Conf_no cno_tmp; /* FIXME (bug 174): 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 (bug 175): 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; cno_tmp = match_table[i].conf_no; result->conf_nos[result->no_of_conf_nos] = cno_tmp; result->type_of_conf[result->no_of_conf_nos] = small_conf_arr[cno_tmp]->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++ ) { cno_tmp = match_table[ tmp.indexes[i] ].conf_no; result->conf_nos[i] = cno_tmp; result->type_of_conf[i] = small_conf_arr[cno_tmp]->type; } } sfree(tmp.indexes); return OK; } static Bool is_clean(const char *fn) { FILE *fp; if ( (fp = i_fopen(fn, "rb")) == NULL ) return FALSE; if ( getc(fp) == 'C' && getc(fp) == 'L' && getc(fp) == 'E' && getc(fp) == 'A' && getc(fp) == 'N' ) { i_fclose(fp); return TRUE; } else { i_fclose(fp); return FALSE; } } static void pre_sync(void) { Text_no tno_iter; Conf_no cno_iter; Cache_node *node; async_sync_db(); /* Mark up what to save.*/ VBUG(("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 (i_rename(param.backupfile_name, param.backupfile_name_2) != 0) { kom_log("pre_sync: can't do extra backup.\n"); } } if (i_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"); dbfile_delete(file_b); } if ((file_b = dbfile_open_write(param.datafile_name, "DIRTY", NULL)) == NULL) { kom_log("WARNING: pre_sync: can't open file %s to save in: %s.\n", param.datafile_name, strerror(errno)); sync_state = sync_wait; return; } #ifdef FASTSAVE if ((file_b_r = dbfile_open_read(param.datafile_name, "CLEAN")) == NULL) { dbfile_delete(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 fprintf(file_b->fp, "#C %d\n", highest_conf_no); fprintf(file_b->fp, "#T %ld\n", highest_text_no); fprintf(file_b->fp, "I"); foutput_info(file_b, &kom_info); foutput_newline(file_b); 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(struct dbfile *from, struct dbfile *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 ( fparse_set_pos(from, from_pos) != OK ) { sync_state = sync_error; restart_kom("sync: copy_file(): src fseek failed.\n"); return; } if ( fseek(to->fp, 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->fp)) != 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->fp) != (size_t)chunk_len) { sync_state = sync_error; kom_log("sync: copy_file(): fwrite failed.\n"); return; } len -= chunk_len; } } static void write_conf(struct dbfile *fp, Conference *c, int conf_no) { fprintf(fp->fp, "C %d", conf_no); foutput_conference(fp, c); } static void save_one_conf(void) { Cache_node *cn; #ifdef DEBUG_CALLS if (block_after_pre_sync) return; #endif #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 = dbfile_ftell(file_b); cn->s.saved_dirty = cn->s.dirty; #else cn->pos_b = dbfile_ftell(file_b); #endif if ( cn->snap_shot != NULL ) { write_conf(file_b, cn->snap_shot, sync_next); #ifdef FASTSAVE free_conference( cn->snap_shot ); #endif } else if ( cn->s.dirty == 0 && cn->ptr != NULL ) { write_conf(file_b, cn->ptr, sync_next); } else if ( file_a->format == file_b->format ) { copy_file(file_a, file_b, cn->pos, cn->size - 1, sync_next); } else { Conference *tmp = read_conference(file_a, cn->pos, cn->size); if (tmp == NULL) { restart_kom("Failed to read old conference at %ld.\n", cn->pos); } write_conf(file_b, tmp, sync_next); free_conference(tmp); } foutput_newline(file_b); #ifdef FASTSAVE cn->saved_size = cn->size; cn->size = dbfile_ftell(file_b) - cn->pos; cn->s.dirty = 0; #else cn->size_b = dbfile_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(struct dbfile *fp, Person *p, int pers_no) { fprintf(fp->fp, "P %d %dH", pers_no, PASSWD_LEN); fwrite(p->pwd, PASSWD_LEN, 1, fp->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 = dbfile_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 if ( file_a->format == file_b->format ) { copy_file(file_a, file_b, cn->pos, cn->size - 1, sync_next); } else { Person *tmp = read_person(file_a, cn->pos, cn->size); if (tmp == NULL) { restart_kom("Failed to read old person at %ld.\n", cn->pos); } write_pers(file_b, tmp, sync_next); free_person(tmp); } foutput_newline(file_b); cn->size_b = dbfile_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 dbfile_delete(file_a); if ((file_a = dbfile_open_read(param.datafile_name, "CLEAN")) == 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 write_text(struct dbfile *fp, Text_stat *t, int text_no) { fprintf(fp->fp, "T %d", text_no); foutput_text_stat(fp, t); } 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 = dbfile_ftell(file_b); cn->s.saved_dirty = cn->s.dirty; #else cn->pos_b = dbfile_ftell(file_b); #endif if ( cn->snap_shot != NULL ) { write_text(file_b, cn->snap_shot, sync_next); #ifdef FASTSAVE free_text_stat( cn->snap_shot ); #endif } else if ( cn->s.dirty == 0 && cn->ptr != NULL ) { write_text(file_b, cn->ptr, sync_next); } else if ( file_a->format == file_b->format ) { copy_file(file_a, file_b, cn->pos, cn->size - 1, sync_next); } else { Text_stat *tmp = read_text_stat(file_a, cn->pos, cn->size); if (tmp == NULL) { restart_kom("Failed to read old text_stat at %ld.\n", cn->pos); } write_text(file_b, tmp, sync_next); free_text_stat(tmp); } foutput_newline(file_b); #ifdef FASTSAVE cn->size = dbfile_ftell(file_b) - cn->pos; next_text_to_sync += 1; cn->s.dirty = 0; #else cn->size_b = dbfile_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 ( dbfile_ferror(file_b) != 0 ) { kom_log ("save_one_text(): ferror() detected.\n"); sync_state = sync_error; return; } offset = dbfile_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; } if (dbfile_change_magic(file_b, "CLEAN") != OK) { sync_state = sync_error; return; } if (dbfile_delete(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 (dbfile_delete(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 = dbfile_open_read(param.datafile_name, "CLEAN"); 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->fp, 0, SEEK_END) != 0) { kom_log("save_one_text(): fseek failed.\n"); sync_state = sync_error; return; } offset2 = dbfile_ftell (file_b); if ( offset2 != offset ) { kom_log ("save_one_text(): ftell confused (%ld and %ld).\n", offset, offset2); sync_state = sync_error; return; } dbfile_delete(file_b); file_b = NULL; sync_state = sync_ready; VBUG(("Sync ready\n")); post_sync(); } } /* * Sync_part() should be called often as long as it returns 0. If it * returns anything else, there is no need to call it again for that * many seconds, but it is harmless to call it more often than that. */ struct timeval sync_part(void) { static struct timeval last_sync_start = {0, 0}; struct timeval timeleft; if (timeval_zero(last_sync_start)) { 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 timeval_ctor(1, 0); case sync_idle: if (param.never_save) return timeval_ctor(60, 0); if (timeval_remaining(&timeleft, param.sync_interval, last_sync_start, current_time)) { #ifdef LOGACCESSES syncing_or_saving = 0; #endif return timeleft; } last_sync_start = current_time; pre_sync(); break; case sync_wait: if (timeval_remaining(&timeleft, param.sync_retry_interval, last_sync_start, current_time)) { #ifdef LOGACCESSES syncing_or_saving = 0; #endif return timeleft; } last_sync_start = current_time; pre_sync(); break; case sync_error: kom_log("sync: Error saving new file. Retrying.\n"); dbfile_delete(file_b); file_b = NULL; #ifdef FASTSAVE if (file_b_r != NULL) { i_fclose(file_b_r); file_b_r = NULL; } #endif remove(param.datafile_name); /* Send a message to all clients if we fail to save the database. */ async_send_message( 0, 0, s_fcrea_str( "A serious error occurred while saving the database. Tell\n" "the administrator to check the server. This could be caused\n" "by insufficient disc space."), FALSE); sync_state = sync_wait; break; default: restart_kom("sync(): sync_state==%d", sync_state); } if ( file_b != NULL && dbfile_ferror(file_b) != 0) sync_state = sync_error; #ifdef LOGACCESSES syncing_or_saving = 0; #endif return timeval_ctor(0, 0); } 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; small_conf_arr[conf_no]->keep_commented = conf_c->keep_commented; } extern Success init_cache(void) { Conf_no ic; Text_no it; Cache_node *node; Conference tmp_conf; Person tmp_pers; Text_stat tmp_text; long pos, num; Bool done = FALSE; Bool read_text_no = FALSE; Bool read_conf_no = FALSE; int c = 0; struct timeval saved_time; long record; boottime_info.boot_time = current_time.tv_sec; 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 = i_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 (ic = 0; ic < param.max_conf; ic++) small_conf_arr[ic] = NULL; for (ic = 1; ic < param.max_conf; ic++) zero_init_cache_node(pers_mcb, ic); for (ic = 1; ic < param.max_conf; ic++) zero_init_cache_node(conf_mcb, ic); for (it = 1; it < param.max_text; it++) zero_init_cache_node(text_mcb, it); if ((text_file = i_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 = dbfile_open_read(param.datafile_name, "CLEAN")) == 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"); boottime_info.db_status = s_fcrea_str("clean"); } else if (is_clean(param.backupfile_name)) { if ((file_a = dbfile_open_read(param.backupfile_name, "CLEAN")) == 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"); boottime_info.db_status = s_fcrea_str("backup"); } 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 (file_a->format) { case 0: case 1: restart_kom("You need to run dbck to convert your datafile to version 2 or 3.\n"); break; case 2: case 3: /* * Read timestamp */ fparse_set_pos(file_a, 12); saved_time.tv_sec = fparse_long(file_a); saved_time.tv_usec = 0; if (timeval_greater(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.tv_sec)); boottime_info.save_time = saved_time.tv_sec; break; default: restart_kom("Can't read database version %d. Giving up.\n", file_a->format); } for (record = 1; !done ; record++) { fskipwhite(file_a); switch(c = dbfile_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(dbfile_getc(file_a)) { case 'C': next_free_num = fparse_long(file_a); read_conf_no = TRUE; break; case 'T': next_text_num = fparse_long(file_a); read_text_no = TRUE; break; default: restart_kom("init_cache(): Bad number in database\n"); } break; case '-': fskipwhite(file_a); switch(dbfile_getc(file_a)) { case 'C': num = fparse_long(file_a); node = get_conf_node(num); if (node) { if (node->s.exists) { update_stat(STAT_CONFS, -1); node->s.exists = 0; node->pos = -1; } } break; case 'P': num = fparse_long(file_a); node = get_pers_node(num); if (node) { if (node->s.exists) { update_stat(STAT_PERSONS, -1); node->s.exists = 0; node->pos = -1; } } break; case 'T': num = fparse_long(file_a); node = get_text_node(num); if (node) { if (node->s.exists) { update_stat(STAT_TEXTS, -1); 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 = dbfile_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, record); } node = get_conf_node(num); if (!node) { create_cache_node(conf_mcb, num); node = get_conf_node(num); update_stat(STAT_CONFS, 1); ++existing_confs; } node->s.exists = 1; node->pos = pos; if ( fparse_conference(file_a, &tmp_conf) != OK ) restart_kom("init_cache(): fparse_conference() failed" " at record %ld.\n", record); node->size = dbfile_ftell(file_a) - node->pos; setup_small_conf(num, &tmp_conf); clear_conference(&tmp_conf); break; case 'P': pos = dbfile_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); update_stat(STAT_PERSONS, 1); } node->s.exists = 1; node->pos = pos; if ( fparse_person(file_a, &tmp_pers) != OK ) restart_kom("init_cache: fparse_person failed at" " record %ld.\n", record); node->size = dbfile_ftell(file_a) - node->pos; clear_person(&tmp_pers); break; case 'T': pos = dbfile_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); update_stat(STAT_TEXTS, 1); } node->s.exists = 1; node->pos = pos; if ( fparse_text_stat(file_a, &tmp_text) != OK ) restart_kom("init_cache(): fparse_text_stat failed at" " record %ld.\n", record); node->size = dbfile_ftell(file_a) - node->pos; clear_text_stat(&tmp_text); break; default: restart_kom("init_cache(): Unknown key %c (%d) " "in data file at %ld\n", c, c, dbfile_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); read_number_file(); boottime_info.highest_text_no = next_text_num - 1; boottime_info.highest_conf_no = next_free_num - 1; boottime_info.existing_texts = read_stat_value(STAT_TEXTS); boottime_info.existing_confs = read_stat_value(STAT_CONFS); boottime_info.existing_persons = read_stat_value(STAT_PERSONS); return OK; } extern void cache_sync_all(void) { if (param.never_save) return; #ifdef DEBUG_CALLS if (block_after_pre_sync) restart_kom("cache_sync_all: block_after_pre_sync is set!\n"); #endif pre_sync(); while (timeval_zero(sync_part())) ; } #ifdef DEBUG_CALLS /* Do pre_sync(), but stop after that. Nothing will actually be saved until cache_sync_finish() is called. You must call cache_sync_finish() before shutting down the server or attempting to use sync_kom(). */ extern Success cache_sync_start(void) { CHK_CONNECTION(FAILURE); block_after_pre_sync = 1; pre_sync(); return OK; } /* This should only be called afrer a call to cache_sync_start(). Stop being blocked and save everything. */ extern Success cache_sync_finish(void) { CHK_CONNECTION(FAILURE); block_after_pre_sync = 0; while (timeval_zero(sync_part())) ; return OK; } #endif void free_all_cache (void) { unsigned int i; Cache_node *node; #ifdef LOGACCESSES if (logfile) i_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; --existing_confs; } } 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); i_fclose(text_file); if (file_a != NULL) { dbfile_delete(file_a); file_a = NULL; } } /* 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; /* FIXME (bug 95): 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 (bug 95): 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; /* FIXME (bug 95): 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); fprintf(fp, "\tExisting confs: %d\n", existing_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 Conf_no query_next_conf_no(void) { return next_free_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 } Success get_boottime_info(Static_server_info *result) { CHK_CONNECTION(FAILURE); *result = boottime_info; return OK; }