Select Git revision
dbck.c 24.38 KiB
/*
* dbck.c - A simple database checker and corrector.
*
* Author: Per Cederqvist.
*/
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <kom-types.h>
#include <config.h>
#include "cache.h"
#include "log.h"
#include "lyskomd.h"
#include "misc-parser.h"
#include "smalloc.h"
#include <debug.h>
#include "dbck-cache.h"
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. */
static const char *dbase_dir = NULL; /* Directory where database resides */
#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 modifications = 0;
typedef struct {
int created_confs;
} Person_scratchpad;
static const Person_scratchpad EMPTY_PERSON_SCRATCHPAD = { 0 };
#include "limits.h"
static Person_scratchpad *person_scratchpad[MAX_CONF];
#ifdef DEBUG
int buglevel = 0;
#endif
extern void
log (const char * format, ...)
{
va_list AP;
va_start(AP, format);
vfprintf(stdout, format, AP);
va_end(AP);
}
extern void
restart_kom (const char * format, ...)
{
va_list AP;
va_start(AP, format);
vfprintf(stdout, format, AP);
va_end(AP);
exit(1);
}
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;
}
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", (u_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",
(u_long)tno, (u_long)group.recipient,
(u_long)group.local_no,
(u_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"
" is greater than %lu\n",
(u_long)tno, (u_long)group.recipient,
(u_long)group.local_no,
(u_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>: that local number "
"is mapped to %lu.\n",
(u_long)tno, (u_long)group.recipient,
(u_long)group.local_no,
(u_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",
(u_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 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",
(u_long)tno, (u_long)group.cc_recipient,
(u_long)group.local_no,
(u_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 is "
"greater than %lu\n",
(u_long)tno, (u_long)group.cc_recipient,
(u_long)group.local_no,
(u_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>: that local "
"number is mapped to %lu.\n",
(u_long)tno, (u_long)group.cc_recipient,
(u_long)group.local_no,
(u_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",
(u_long)tno, (u_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",
(u_long)tno, (u_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",
(u_long)tno, (u_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",
(u_long)tno, (u_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",
(u_long)tno, (u_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",
(u_long)tno, (u_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",
(u_long)tno, (u_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",
(u_long)tno, (u_long)group.footnoted_in);
error++;
}
break;
default:
log("check_misc_infos(): parse_next_misc returned type %lu\n",
(u_long)group.type);
break;
}
}
if ( group.type == m_error )
{
log("Text %lu has a bad misc_info_list.\n", (u_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;
u_long bytes=0;
u_long max_bytes=0;
Text_no max_text=0;
while ( ct = traverse_text(ct) )
{
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, "
"average %lu bytes/text).\n",
(u_long)number_of_texts,
(u_long)bytes,
(u_long)(bytes/number_of_texts));
log("Longest text is %lu (%lu bytes).\n",
(u_long)max_text, (u_long)max_bytes);
}
}
return errors;
}
static Bool
adjust_text_list(Text_list *text_list)
{
u_long zeroes;
u_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)
{
u_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",
(u_long)pno, (u_long)created->texts[i],
(u_long)t->author);
error++;
}
if ( t == NULL )
{
log("Person %lu is author of text %lu, which doesn't exist.\n",
(u_long)pno, (u_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",
(u_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",
(u_long)pno, (u_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 has read text %lu in conf %lu, "
"which only has %lu texts.\n",
(u_long)pno,
(u_long)mship->last_text_read,
(u_long)mship->conf_no,
(u_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 is corrupt:"
" read text number %lu<%lu> <= %lu.\n",
(u_long)pno, (u_long)mship->conf_no,
(u_long)mship->read_texts[i], (u_long)i, (u_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",
(u_long)pno, (u_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) )
{
number_of_persons++;
pstat = cached_get_person_stat (cp);
cstat = cached_get_conf_stat (cp);
if ( pstat == NULL )
{
log("Person %lu nonexistent.\n", (u_long)cp);
errors++;
}
else if (cstat == NULL)
{
log("Person %lu has no conference.\n", (u_long)cp);
errors++;
}
else if (!cstat->type.letter_box)
{
log("Person %lu's conference is not a letter_box.\n",
(u_long)cp);
errors++;
}
else
{
errors += (check_created_texts(cp, &pstat->created_texts)
+ check_membership_list(cp, &pstat->conferences));
}
}
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)
{
u_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",
(u_long)tlist->texts[i],
(u_long)i + tlist->first_local_no,
(u_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",
(u_long)cc);
}
else
error++;
}
else
{
if ( !is_recipient(cc, t) )
{
log("Text %lu<%lu> in conference %lu doesn't "
"have the conference as recipient.\n",
(u_long)tlist->texts[i],
(u_long)i + tlist->first_local_no,
(u_long)cc);
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",
(u_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",
(u_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;
}
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, who is supposed to be a member in conf %lu, "
"is nonexistent.\n",
(u_long)memb->member, (u_long)cc);
error++;
}
else
{
if ( locate_membership(cc, pp) == NULL )
{
log("Person %lu is not a member in conf %lu.\n",
(u_long)memb->member,
(u_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", (u_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", (u_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", (u_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("INTERNAL DBCK ERROR: post_check_persons(): can't "
"cached_get_person_stat(%d).\n", pers_no);
}
if ( person_scratchpad[pers_no]->created_confs
!= pstat->created_confs )
{
log("Person %d has created %d conferences, not %d (as said in "
"his person-stat).\n",
pers_no, person_scratchpad[pers_no]->created_confs,
pstat->created_confs);
if ( rflag || confirm("Repair by altering person-stat? ") )
{
pstat->created_confs
= person_scratchpad[pers_no]->created_confs;
mark_person_as_changed(pers_no);
log("Person-stat corrected.\n");
}
else
errors++;
}
}
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(void)
{
if (dbase_dir == NULL)
dbase_dir = DEFAULT_DBASE_DIR;
sprintf(datafilename, "%s/%s", dbase_dir, DATAFILE_NAME);
sprintf(backupfilename, "%s/%s", dbase_dir, BACKUPFILE_NAME);
sprintf(textfilename, "%s/%s", dbase_dir, TEXTFILE_NAME);
sprintf(textbackupfilename, "%s/%s", dbase_dir, 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");
}
void
garb_text_file(void)
{
Text_no tno = 0;
String text;
log("Renaming %s to %s", textfilename, textbackupfilename);
rename(textfilename, textbackupfilename);
log("Writing texts to (new) %s", 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.");
}
int
main (int argc,
char **argv)
{
int i;
int errors;
BUGDECL;
for (i = 1; i < argc && argv[i][0] == '-'; i++)
{
switch (argv[i][1])
{
#ifdef DEBUG
case 'd':
buglevel++;
break;
#endif
case 'D': /* Database directory */
dbase_dir = argv[i]+2;
break;
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;
default:
restart_kom("usage: dbck [options]\n");
}
}
s_set_storage_management(smalloc, srealloc, sfree);
init_data_base();
errors = check_data_base();
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 ( modifications == 0 && errors == 0 && gflag )
{
log("No errors found. Compressing textfile.\n");
fflush(stdout);
fflush(stderr);
garb_text_file();
log("ready.\n");
}
return errors != 0;
}