/* * $Id: dbck.c,v 0.23 1994/06/18 21:05:43 ceder Exp $ * Copyright (C) 1991, 1992, 1993, 1994 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. */ /* * dbck.c - A simple database checker and corrector. * * Author: Per Cederqvist. */ static char *rcsid = "$Id: dbck.c,v 0.23 1994/06/18 21:05:43 ceder Exp $"; #include "rcs.h" USE(rcsid); #ifdef HAVE_STDLIB_H # include <stdlib.h> #endif #include <stdio.h> #ifdef HAVE_STDARG_H # include <stdarg.h> #endif #include <time.h> #include <sys/types.h> #include "misc-types.h" #include "s-string.h" #include "kom-types.h" #include "tmp-limits.h" #include "lyskomd.h" #include "log.h" #include "server/smalloc.h" #include "misc-parser.h" #include "cache.h" #include "config.h" #include "debug.h" #include "dbck-cache.h" #include "param.h" #include "server-config.h" #include "manipulate.h" Info kom_info; /* Not used, but needed by server-config.o. */ struct kom_par param; /* This is set to TRUE if init_cache finds out that the last part of the database is missing. */ Bool truncated_texts = FALSE; char datafilename[1024]; /* Full pathname to the database file */ char backupfilename[1024]; /* Full pathname to the backup file */ char textfilename[1024]; char textbackupfilename[1024]; /* unshrinked text-file. */ #define TEXTBACKUPFILE_NAME "db/backup-texts" int vflag=0; /* Verbose - list statistics also. */ int iflag=0; /* Interactive - prompt user and repair. */ int rflag=0; /* Repair simple error without confirmation. */ int gflag=0; /* Garbage collect text-file. */ int sflag=0; /* Statistic flag. */ /* The following variable corresponds to the -c flag, and is present here due to a bug in lyskomd 1.6.1. */ int unset_change_name_is_error=0; int modifications = 0; typedef struct { int created_confs; } Person_scratchpad; static const Person_scratchpad EMPTY_PERSON_SCRATCHPAD = { 0 }; static Person_scratchpad *person_scratchpad[MAX_CONF]; #ifndef NDEBUG int buglevel = 0; #endif #if defined(HAVE_VFPRINTF) && defined(HAVE_STDARG_H) extern void log (const char * format, ...) { va_list AP; va_start(AP, format); vfprintf(stdout, format, AP); va_end(AP); } #else extern void log (format, a, b, c, d, e, f, g) const char * format; int a, b, c, d, e, f, g; { fprintf(stdout, format, a, b, c, d, e, f, g); } #endif #if defined(HAVE_VFPRINTF) && defined(HAVE_STDARG_H) extern void restart_kom (const char * format, ...) { va_list AP; va_start(AP, format); vfprintf(stdout, format, AP); va_end(AP); exit(1); } #else extern void restart_kom (format, a, b, c, d, e, f, g) const char * format; int a, b, c, d, e, f, g; { fprintf(stdout, format, a, b, c, d, e, f, g); exit(1); } #endif void register_jubel(Pers_no pno, Text_no divis, Text_no tno) { /* Not needed in dbck.c, but called from conf-file.c. */ } static Person_scratchpad * alloc_person_scratchpad(void) { Person_scratchpad *p; p = smalloc(sizeof(Person_scratchpad)); *p = EMPTY_PERSON_SCRATCHPAD; return p; } static Bool is_comment_to(Text_no comment, Text_stat *parent) { int i; for ( i = 0; i < parent->no_of_misc; i++ ) { switch( parent->misc_items[ i ].type ) { case comm_in: if ( parent->misc_items[ i ].datum.commented_in == comment ) return TRUE; break; default: break; } } return FALSE; } static Bool is_commented_in(Text_no parent, Text_stat *child) { int i; for ( i = 0; i < child->no_of_misc; i++ ) { switch( child->misc_items[ i ].type ) { case comm_to: if ( child->misc_items[ i ].datum.comment_to == parent ) return TRUE; break; default: break; } } return FALSE; } static Bool is_footnote_to(Text_no footnote, Text_stat *parent) { int i; for ( i = 0; i < parent->no_of_misc; i++ ) { switch( parent->misc_items[ i ].type ) { case footn_in: if ( parent->misc_items[ i ].datum.footnoted_in == footnote ) return TRUE; break; default: break; } } return FALSE; } static Bool is_footnoted_in(Text_no parent, Text_stat *child) { int i; for ( i = 0; i < child->no_of_misc; i++ ) { switch( child->misc_items[ i ].type ) { case footn_to: if ( child->misc_items[ i ].datum.footnote_to == parent ) return TRUE; break; default: break; } } return FALSE; } static Member * locate_member(Pers_no pers_no, Conference * conf_c) { Member * member; int i; for(member = conf_c->members.members, i = conf_c->members.no_of_members; i > 0; i--, member++) { if ( member->member == pers_no ) { return member; } } return NULL; } /* * Delete a misc_info. * If it is a recpt, cc_recpt, comm_to or footn_to delete any * loc_no, rec_time, sent_by or sent_at that might follow it. * * Note that the Misc_info is not reallocated. */ static void delete_misc (Text_stat *tstat, Misc_info *misc) /* Pointer to first misc_item to delete. */ { int del = 1; /* Number of items to delete. */ /* Always delete at least one item. */ Bool ready; /* Check range of misc */ if (misc < tstat->misc_items || misc >= tstat->misc_items + tstat->no_of_misc ) { restart_kom("delete_misc() - misc out of range"); } ready = FALSE; while (ready == FALSE && misc + del < tstat->misc_items + tstat->no_of_misc ) { switch ( misc[ del ].type ) { case loc_no: case rec_time: case sent_by: case sent_at: del++; break; case recpt: case cc_recpt: case footn_to: case footn_in: case comm_to: case comm_in: ready = TRUE; break; #ifndef COMPILE_CHECKS default: restart_kom("delete_misc() - illegal misc found.\n"); #endif } } tstat->no_of_misc -= del; /* Move items beyond the deleted ones. */ while ( misc < tstat->misc_items + tstat->no_of_misc ) { misc[ 0 ] = misc[ del ]; misc++; } } static int confirm(char *question) { if ( iflag ) { fputs(question, stdout); fputs(" (y/n) ", stdout); while(1) switch(getchar()) { case 'y': case 'Y': return 1; case 'n': case 'N': case EOF: return 0; default: break; } } else return 0; } static long check_misc_infos(Text_no tno, Text_stat *tstat) { const Misc_info * misc = tstat->misc_items; Misc_info * previous; Misc_info_group group; Conference *c; Text_stat *t; long error=0; while (previous = (Misc_info *)misc, group = parse_next_misc(&misc, tstat->misc_items + tstat->no_of_misc), group.type != m_end_of_list && group.type != m_error ) { switch ( group.type ) { case m_recpt: c = cached_get_conf_stat (group.recipient); if ( c == NULL && group.recipient == 0 ) { log ("Conference 0 is recipient to text %lu.\n", (unsigned long)tno); if (rflag || confirm("Repair by deleting misc_item? ")) { delete_misc(tstat, previous); mark_text_as_changed(tno); modifications++; log("Repaired: Conference 0 is no longer a recipient.\n"); misc = previous; } else error++; break; } if ( c == NULL ) break; /* Check loc_no */ if ( group.local_no < c->texts.first_local_no ) { log("Text %lu: Recipient %lu<%lu> loc_no is less than %lu\n", (unsigned long)tno, (unsigned long)group.recipient, (unsigned long)group.local_no, (unsigned long)c->texts.first_local_no); error++; } else if ( c->texts.first_local_no + c->texts.no_of_texts - 1 < group.local_no ) { log("Text %lu: Recipient %lu<%lu> loc_no > %lu\n", (unsigned long)tno, (unsigned long)group.recipient, (unsigned long)group.local_no, (unsigned long)(c->texts.first_local_no + c->texts.no_of_texts - 1)); error++; } else if ( c->texts.texts[group.local_no - c->texts.first_local_no] != tno ) { log("Text %lu: Recipient %lu<%lu>: %s to %lu.\n", (unsigned long)tno, (unsigned long)group.recipient, (unsigned long)group.local_no, "that local number is mapped", (unsigned long)c->texts.texts[group.local_no - c->texts.first_local_no]); error++; } break; case m_cc_recpt: c = cached_get_conf_stat (group.cc_recipient); if ( c == NULL && group.cc_recipient == 0 ) { log ("Conference 0 is cc_recipient to text %lu.\n", (unsigned long)tno); if (rflag || confirm("Repair by deleting misc_item? ")) { delete_misc(tstat, previous); mark_text_as_changed(tno); modifications++; log("Repaired: Conf 0 is no longer a cc_recipient.\n"); misc = previous; } else error++; break; } if ( c == NULL ) break; /* Check loc_no */ if ( group.local_no < c->texts.first_local_no ) { log("Text %lu: CC_Recipient %lu<%lu> is less than %lu\n", (unsigned long)tno, (unsigned long)group.cc_recipient, (unsigned long)group.local_no, (unsigned long)c->texts.first_local_no); error++; } else if ( c->texts.first_local_no + c->texts.no_of_texts - 1 < group.local_no ) { log("Text %lu: CC_Recipient %lu<%lu> loc_no > %lu\n", (unsigned long)tno, (unsigned long)group.cc_recipient, (unsigned long)group.local_no, (unsigned long)(c->texts.first_local_no + c->texts.no_of_texts - 1)); error++; } else if ( c->texts.texts[group.local_no - c->texts.first_local_no] != tno ) { log("Text %lu: CC_Recipient %lu<%lu>: %s to %lu.\n", (unsigned long)tno, (unsigned long)group.cc_recipient, (unsigned long)group.local_no, "that local number is mapped", (unsigned long)c->texts.texts[group.local_no - c->texts.first_local_no]); error++; } break; case m_comm_to: t = cached_get_text_stat(group.comment_to); if ( t == NULL ) { log("Text %lu is a comment to %lu, which doesn't exist.\n", (unsigned long)tno, (unsigned long)group.comment_to); if (rflag || confirm("Repair by deleting misc_item? ")) { delete_misc(tstat, previous); mark_text_as_changed(tno); modifications++; log("Repaired: Comment-link deleted.\n"); misc = previous; } else error++; error++; } else if (!is_comment_to(tno, t)) { log("Text %lu is a comment to %lu, but not the reverse.\n", (unsigned long)tno, (unsigned long)group.comment_to); error++; } break; case m_comm_in: t = cached_get_text_stat(group.commented_in); if ( t == NULL ) { log("Text %lu is commented in %lu, which doesn't exist.\n", (unsigned long)tno, (unsigned long)group.commented_in); if (rflag || confirm("Repair by deleting misc_item? ")) { delete_misc(tstat, previous); mark_text_as_changed(tno); modifications++; log("Repaired: Comment-link deleted.\n"); misc = previous; } else error++; } else if (!is_commented_in(tno, t)) { log("Text %lu is a comment to %lu, but not the reverse.\n", (unsigned long)tno, (unsigned long)group.commented_in); error++; } break; case m_footn_to: t = cached_get_text_stat(group.footnote_to); if ( t == NULL ) { log("Text %lu is a footnote to %lu, which doesn't exist.\n", (unsigned long)tno, (unsigned long)group.footnote_to); if (rflag || confirm("Repair by deleting misc_item? ")) { delete_misc(tstat, previous); mark_text_as_changed(tno); modifications++; log("Repaired: Footnote-link deleted.\n"); misc = previous; } else error++; } else if (!is_footnote_to(tno, t)) { log("Text %lu is a footnote to %lu, but not the reverse.\n", (unsigned long)tno, (unsigned long)group.footnote_to); error++; } break; case m_footn_in: t = cached_get_text_stat(group.footnoted_in); if ( t == NULL ) { log("Text %lu is footnoted in %lu, which doesn't exist.\n", (unsigned long)tno, (unsigned long)group.footnoted_in); if (rflag || confirm("Repair by deleting misc_item? ")) { delete_misc(tstat, previous); mark_text_as_changed(tno); modifications++; log("Repaired: Footnote-link deleted.\n"); misc = previous; } else error++; } else if (!is_footnoted_in(tno, t)) { log("Text %lu is a footnot to %lu, but not the reverse.\n", (unsigned long)tno, (unsigned long)group.footnoted_in); error++; } break; default: log("check_misc_infos(): parse_next_misc returned type %lu\n", (unsigned long)group.type); break; } } if ( group.type == m_error ) { log("Text %lu has a bad misc_info_list.\n", (unsigned long)tno); error++; } return error; } static long check_texts(void) { Text_no ct = 0; Text_stat *ctp=NULL; long errors = 0; Text_no number_of_texts = 0; unsigned long bytes=0; unsigned long max_bytes=0; Text_no max_text=0; while ( (ct = traverse_text(ct)) != 0 ) { number_of_texts++; ctp = cached_get_text_stat( ct ); if ( ctp == NULL ) { log("Text %lu nonexistent.\n", ct); errors++; } else { bytes += ctp->no_of_chars; if ( ctp->no_of_chars > max_bytes ) { max_bytes = ctp->no_of_chars; max_text = ct; } /* no_of_marks is not yet checked. */ errors += check_misc_infos(ct, ctp); } } if (vflag) { if ( number_of_texts == 0 ) log("WARNING: No texts found.\n"); else { log("Total of %lu texts (total %lu bytes, avg. %lu bytes/text).\n", (unsigned long)number_of_texts, (unsigned long)bytes, (unsigned long)(bytes/number_of_texts)); log("Longest text is %lu (%lu bytes).\n", (unsigned long)max_text, (unsigned long)max_bytes); } } return errors; } static Bool adjust_text_list(Text_list *text_list) { unsigned long zeroes; unsigned long i; for (zeroes = 0; zeroes < text_list->no_of_texts && text_list->texts[ zeroes ] == 0; zeroes++) ; if ( zeroes > 0 ) { text_list->no_of_texts -= zeroes; text_list->first_local_no += zeroes; for ( i = 0; i < text_list->no_of_texts; i++) text_list->texts[ i ] = text_list->texts[ i + zeroes ]; text_list->texts = srealloc(text_list->texts, (text_list->no_of_texts * sizeof(Text_no))); } return zeroes > 0; } static int check_created_texts(Pers_no pno, Text_list *created) { unsigned long i; Text_stat *t; int error=0; for ( i=0; i < created->no_of_texts; i++ ) { if (created->texts[i] != 0) { t = cached_get_text_stat(created->texts[i]); if ( t != NULL && t->author != pno) { log("Person %lu is author of text %lu whose author is %lu.\n", (unsigned long)pno, (unsigned long)created->texts[i], (unsigned long)t->author); error++; } if ( t == NULL ) { log("Person %lu is author of text %lu, which doesn't exist.\n", (unsigned long)pno, (unsigned long)created->texts[i]); if ( rflag || confirm("Repair by setting to text_no to 0 in local map")) { created->texts[i] = 0; mark_person_as_changed(pno); modifications++; log("Repaired: created_texts corrected.\n"); } else error++; } } } if ( created->no_of_texts > 0 && created->texts[0] == 0 ) { log("Person %lu has a bad created_texts array. Starts with a 0.\n", (unsigned long)pno); if ( rflag || confirm ("Repair by adjusting created_texts")) { adjust_text_list(created); mark_person_as_changed(pno); modifications++; log("Repaired: created_texts adjusted.\n"); } else error++; } return error; } static int check_membership(Pers_no pno, const Membership *mship) { int error=0; Conference *conf; int i; Local_text_no last=0; conf = cached_get_conf_stat(mship->conf_no); if ( conf == NULL ) { log("Person %lu is a member in the non-existing conference %lu.\n", (unsigned long)pno, (unsigned long)mship->conf_no); error++; } else { /* Check read texts */ if ( mship->last_text_read > conf->texts.first_local_no + conf->texts.no_of_texts - 1) { log("Person %lu %s %lu in conf %lu, which only has %lu texts.\n", (unsigned long)pno, "has read text", (unsigned long)mship->last_text_read, (unsigned long)mship->conf_no, (unsigned long)(conf->texts.first_local_no + conf->texts.no_of_texts - 1)); error++; } for ( last = i = 0; i < mship->no_of_read; i++) { if ( mship->read_texts[i] <= last ) { log("Person %lu's membership in %lu %s %lu<%lu> <= %lu.\n", (unsigned long)pno, (unsigned long)mship->conf_no, "is corrupt: read text number", (unsigned long)mship->read_texts[i], (unsigned long)i, (unsigned long)last); error++; } last = mship->read_texts[i]; } /* Check that he is a member */ if ( locate_member(pno, conf) == NULL ) { log("Person %lu is a member in %lu in which he isn't a member.\n", (unsigned long)pno, (unsigned long)mship->conf_no); error++; } } return error; } static int check_membership_list(Pers_no pno, const Membership_list *mlist) { int errors=0; int i; for (i = 0; i < mlist->no_of_confs; i++) errors += check_membership(pno, &mlist->confs[i]); return errors; } static int check_persons(void) { Pers_no cp = 0; Person *pstat=NULL; Conference *cstat=NULL; long errors = 0; Pers_no number_of_persons=0; while ( (cp = traverse_person(cp)) != 0 ) { number_of_persons++; pstat = cached_get_person_stat (cp); cstat = cached_get_conf_stat (cp); if ( pstat == NULL ) { log("Person %lu nonexistent.\n", (unsigned long)cp); errors++; } else if (cstat == NULL) { log("Person %lu has no conference.\n", (unsigned long)cp); errors++; } else if (!cstat->type.letter_box) { log("Person %lu's conference is not a letter_box.\n", (unsigned long)cp); errors++; } else { errors += (check_created_texts(cp, &pstat->created_texts) + check_membership_list(cp, &pstat->conferences)); } if (unset_change_name_is_error == 1 && pstat->privileges.change_name == 0) { log("Person %lu has no change_name capability.\n", (unsigned long)cp); if (rflag || confirm("Grant him the capability")) { pstat->privileges.change_name = 1; mark_person_as_changed(cp); modifications++; } else errors++; } } if (vflag) log("Total of %lu persons.\n", number_of_persons); return errors; } static Bool is_recipient(Conf_no conf_no, Text_stat * t_stat) { int i; for ( i = 0; i < t_stat->no_of_misc; i++ ) { switch( t_stat->misc_items[ i ].type ) { case recpt: if ( t_stat->misc_items[ i ].datum.recipient == conf_no ) { return TRUE; } break; case cc_recpt: if ( t_stat->misc_items[ i ].datum.cc_recipient == conf_no ) { return TRUE; } break; case rec_time: case comm_to: case comm_in: case footn_to: case footn_in: case sent_by: case sent_at: case loc_no: break; #ifndef COMPILE_CHECKS default: restart_kom("is_recipient(): illegal misc_item\n"); #endif } } return FALSE; } static int check_texts_in_conf(Conf_no cc, Text_list *tlist) { unsigned long i; Text_stat *t; int error=0; for ( i=0; i < tlist->no_of_texts; i++ ) { if (tlist->texts[i] != 0) { t = cached_get_text_stat(tlist->texts[i]); if ( t == NULL ) { log("Text %lu<%lu> in conference %lu is non-existent.\n", (unsigned long)tlist->texts[i], (unsigned long)i + tlist->first_local_no, (unsigned long)cc); if (rflag || confirm("Repair by setting Text_no to 0 in the map?") ) { tlist->texts[i]=0; mark_conference_as_changed(cc); modifications++; log("Repaired: %lu is no longer a recipient.\n", (unsigned long)cc); } else error++; } else { if ( !is_recipient(cc, t) ) { log("Text %lu<%lu> in conference %lu %s.\n", (unsigned long)tlist->texts[i], (unsigned long)i + tlist->first_local_no, (unsigned long)cc, "doesn't have the conference as recipient"); if (confirm("Repair by setting Text_no to 0 in the map?") ) { tlist->texts[i]=0; mark_conference_as_changed(cc); modifications++; log("Repaired: %lu is no longer a recipient.\n", (unsigned long)cc); } else error++; } } } } if ( tlist->no_of_texts > 0 && tlist->texts[0] == 0 ) { log("Conference %lu has a bad Text_list. Starts with a 0.\n", (unsigned long)cc); if ( rflag || confirm ("Repair by adjusting text_list")) { adjust_text_list(tlist); mark_conference_as_changed(cc); modifications++; log("Repaired: text_list adjusted.\n"); } else error++; } return error; } static Membership * locate_membership(Conf_no conf_no, Person * pers_p) { Membership * confp; int i; for(confp = pers_p->conferences.confs, i = pers_p->conferences.no_of_confs; i > 0; i--, confp++) { if ( confp->conf_no == conf_no ) { return confp; } } return NULL; } static int check_member(Conf_no cc, Member *memb) { Person *pp; int error=0; pp = cached_get_person_stat(memb->member); if ( pp == NULL ) { log("Person %lu, supposedly a member in conf %lu, is nonexistent.\n", (unsigned long)memb->member, (unsigned long)cc); error++; } else { if ( locate_membership(cc, pp) == NULL ) { log("Person %lu is not a member in conf %lu.\n", (unsigned long)memb->member, (unsigned long)cc); error++; } } return error; } static int check_member_list(Conf_no cc, const Member_list *mlist) { int errors=0; int i; for (i = 0; i < mlist->no_of_members; i++) errors += check_member(cc, &mlist->members[i]); return errors; } static int check_confs(void) { Conf_no cc = 0; Person *pstat=NULL; Conference *cstat=NULL; long errors = 0; Conf_no number_of_confs = 0; while ( (cc = traverse_conference(cc)) != 0 ) { number_of_confs++; cstat = cached_get_conf_stat (cc); if ( cstat == NULL ) { log("Conference %lu nonexistent.\n", (unsigned long)cc); errors++; } else { if (cstat->type.letter_box) { pstat = cached_get_person_stat(cc); if (pstat == NULL) { log("Mailbox %lu has no person.\n", (unsigned long)cc); errors++; } } else /* not letter_box */ { /* Remember that the creator might no longer exist. */ if ( person_scratchpad[ cstat->creator ] != NULL ) ++person_scratchpad[ cstat->creator ]->created_confs; } errors += (check_texts_in_conf(cc, &cstat->texts) + check_member_list(cc, &cstat->members)); } } if ( vflag ) log("Total of %lu conferences.\n", (unsigned long)number_of_confs); return errors; } static void init_person_scratch(void) { Pers_no pno = 0; while( (pno = traverse_person(pno)) != 0 ) { person_scratchpad[pno] = alloc_person_scratchpad(); } } static long post_check_persons(void) { long errors = 0; Pers_no pers_no = 0; Person *pstat; while ( (pers_no = traverse_person(pers_no)) != 0 ) { if ( (pstat = cached_get_person_stat(pers_no)) == NULL ) { log("%s(): can't cached_get_person_stat(%d).\n", "INTERNAL DBCK ERROR: post_check_persons", pers_no); } } return errors; } /* * Returns 0 if the database seems to be correct. */ static long check_data_base(void) { long errors; init_person_scratch(); errors = check_texts() + check_persons() + check_confs(); return errors + post_check_persons(); } static void init_data_base(char *dbase_dir) { sprintf(datafilename, "%s/%s", dbase_dir, param.datafile_name); sprintf(backupfilename, "%s/%s", dbase_dir, param.backupfile_name); sprintf(textfilename, "%s/%s", dbase_dir, param.textfile_name); sprintf(textbackupfilename, "%s/%s", dbase_dir, param.textbackupfile_name); if ( vflag ) { log("Database = %s\n", datafilename); log("Backup = %s\n", backupfilename); log("Text = %s\n", textfilename); log("Textback = %s\n", textbackupfilename); } if ( init_cache() == FAILURE ) restart_kom("Can't find database.\n"); } static void garb_text_file(void) { Text_no tno = 0; String text; log("Renaming %s to %s\n", textfilename, textbackupfilename); rename(textfilename, textbackupfilename); log("Writing texts to (new) %s\n", textfilename); fflush(stdout); fflush(stderr); cache_open_new_text_file(); while ( (tno = traverse_text(tno)) != 0 ) { text = cached_get_text(tno); cached_flush_text(tno, text); free_tmp(); } log("Writing datafile with new indexes.\n"); fflush(stdout); fflush(stderr); cache_sync(); log("Ready."); } static void print_statistics(void) { Text_stat *ts; Text_no t; int *hist; int i; hist = calloc(param.text_len, sizeof(int)); if (hist == NULL) { perror("dbck: print_statistics(): can't calloc()"); return; } for (t=0; (t=traverse_text(t)) != 0;) { ts = cached_get_text_stat(t); if (ts == NULL) { log("print_statistics(): Can't get text_stat.\n"); return; } hist[ts->no_of_chars]++; } log("Length Frequency\n"); for(i=0; i<param.text_len; i++) if(hist[i] != 0) log("%8d %d\n", i, hist[i]); } /* Stop "no previous prototype" warning from gcc 2.0 */ int main(int, char**); int main (int argc, char **argv) { int i; int errors; char *default_config_file; char *config_file; BUGDECL; for (i = 1; i < argc && argv[i][0] == '-'; i++) { switch (argv[i][1]) { #ifndef NDEBUG case 'd': buglevel++; break; #endif case 'i': /* Running interactively. */ iflag++; /* Will ask user and try to repair. */ break; case 'r': /* Repair simple errors wihtout asking. */ rflag++; break; case 'v': /* Verbose: report more than errors. */ vflag++; break; case 'g': /* Garbage collect: compress text-file. */ gflag++; break; case 's': /* Statistics: text length et c. */ sflag++; break; case 'c': /* Consider an unset change_name an error. */ unset_change_name_is_error = 1; break; default: restart_kom("usage: %s %s [config_file]\n", argv[0], "[-d ...] [-i] [-r] [-v] [-g] [-s] [-c]"); } } /* Read in the configuration file. */ default_config_file = smalloc(strlen(DEFAULT_DBASE_DIR) + strlen(CONFIG_FILE) + 2); sprintf(default_config_file, "%s/%s", DEFAULT_DBASE_DIR, CONFIG_FILE); if (i < argc) config_file = argv[i++]; else config_file = default_config_file; read_configuration(config_file); sfree(default_config_file); if (i != argc) restart_kom("usage: %s %s [config_file]\n", argv[0], "[-d ...] [-i] [-r] [-v] [-g] [-s] [-c]"); s_set_storage_management(smalloc, srealloc, sfree); init_data_base(param.dbase_dir); errors = check_data_base(); if (truncated_texts == TRUE) modifications++; if ( iflag ) log("Total of %d error%s remains.\n", errors, errors == 1 ? "" : "s"); else if ( vflag && errors > 0 ) log("%d error%s found.\n", errors, errors == 1 ? "" : "s"); if ( modifications > 0 ) { log("%d modification%s made. Syncing...\n", modifications, modifications == 1 ? "" : "s"); fflush(stdout); fflush(stderr); cache_sync(); log("ready.\n"); } if ( sflag ) print_statistics(); if ( gflag ) { if ( modifications == 0 && errors == 0 ) { log("No errors found. Compressing textfile.\n"); fflush(stdout); fflush(stderr); garb_text_file(); log("ready.\n"); } else log("Compression not done since errors was found.\n"); } return errors != 0; }