/* ::[[ @(#) brik.c 1.55 89/07/12 03:31:06 ]]:: */
#ifndef LINT
 static char sccsid[]="::[[ @(#) brik.c 1.55 89/07/12 03:31:06 ]]::";
#endif

#define DATESTAMP "1989/07/11"
#define VERSION   "2.0"

/*
(c) Copyright 1989 Rahul Dhesi, All rights reserved.  Permission is
granted to use, copy, and distribute this file under the terms of the
GNU General Public License version 1.0.
*/

/*
Checksum: 2990338384      (check or update this with "brik")
*/

static char copyright[] =
"\
Copyright 1989 Rahul Dhesi, All rights reserved.  Use and distribution is\n\
permitted under the terms of the GNU General Public License version 1.0.\n\
";

/*
The following code assumes the ASCII character set and 8-bit bytes.
*/

#ifndef OK_STDIO
# include <stdio.h>
# define OK_STDIO
#endif

#include "brik.h"          /* configuration options */
#include "assert.h"

typedef unsigned long tcrc; /* type of crc value -- same as in addbfcrc.c */

#ifdef GENTAB
void mkcrctab PARMS ((void));
#endif /* GENTAB */

#ifdef STDINCLUDE
# include <stdlib.h>
# include <string.h>
#else
 FILE *fopen PARMS ((char *filename, char *mode));
 char *nextfile PARMS ((int, char *, int));
 char *strcat PARMS ((char *, char *));
 char *strchr PARMS ((char *, char));
 char *strcpy PARMS ((char *, char *));
 char *strncat PARMS ((char *, char *, int));
 int strcmp PARMS ((char *, char *));
 int strlen PARMS ((char *));
 void exit PARMS ((int status));
#endif /* STDINCLUDE */

/* twilight zone functions -- may or may not be standard */
int main PARMS ((int, char **));
int getopt PARMS ((int, char **, char *));

/* our own functions */
FILE *efopen PARMS ((char *filename, char *mode, int errlevel));
char suffix PARMS ((void));
char *nextfile PARMS ((int, char *, int));
void dofname PARMS ((char *));
void dofptr PARMS ((FILE *, char *));
int lowerit PARMS ((int));
void printhdr PARMS ((void));
void readnames PARMS ((FILE *));
void updatefile PARMS ((FILE *, long, tcrc, char *));
void whole_check PARMS ((FILE *, char *));
tcrc findcrc PARMS ((FILE *, char *, int *));
tcrc xatol PARMS ((char *));
void addbfcrc PARMS ((char *, int));
void brktst PARMS ((void));
void hdrcrc PARMS ((FILE *, char *));
void longhelp PARMS ((void));
void shorthelp PARMS ((void));
void showerr PARMS ((char *, int));

/* the following constants can be changed if you know what you are doing */
#define ERRLIMIT  127      /* exit(n) returns n not exceeding this */
#define LINESIZE  8192     /* handle lines of up to this length */
#define ERRBUFSIZ 1024     /* buffer size for perror() message */
#define BACKSIZE  1024     /* how much to seek back looking for header */
#define BINTABSIZ 256      /* size of binary char test table */
#ifdef CTRLZ_CHECK
# define Z_THRESHOLD 10    /* see how CTRLZ_CHECK is used later */
#endif /* CTRLZ_CHECK */

/* the following constants should not be changed */
#define MYNL      10       /* newline for CRC calculation */
#define PATTERN   "Checksum:"    /* look for this header */
#define CHARSINCRC 10      /* characters in CRC */
#define CMTCH     '#'      /* begins comment in CRC list */

/* error levels */
#define LVL_WARN  0
#define LVL_ERR   1
#define LVL_FATAL 2

#ifdef  USEINDEX
# define strchr   index
#endif  /* USEINDEX */

#ifdef NOTRAIL_B              /* avoid trailing "b" in mode string */
# define BRIK_RD  "r"
# define BRIK_RW  "r+"
# define BRIK_RDB "r"
#else
# define BRIK_RD  "r"
# define BRIK_RW  "r+"
# define BRIK_RDB "rb"
#endif   /* NOTRAIL_B */

#define  whitespace(x)     (strchr(" \t\n",(x)) != NULL)
/* format strings for printing CRCs and filenames etc. */
static char ok[] =      "ok ";
static char bad[] =     "BAD";
static char blank[] =   "   ";
static char fmtstr[] = "%10lu%c %s %s\n";
static char hdrfmt[] = "Checksum: %10lu  %s\n";

static char version[] = VERSION;
char bintab[BINTABSIZ];    /* binary char test table */
int patlen;                /* length of PATTERN */
int errcount = 0;          /* count of errors */
int gen1 = 0;              /* generate CRCs for all files */
int gen2 = 0;              /* generate CRCs for files with headers */
int silent = 0;            /* be silent, just set error status */
int quiet = 0;             /* talks less, but not completely silent */
int verbose = 0;           /* be verbose, print message for good files too */
int updfile = 0;           /* update file by inserting CRC */
int check1 = 0;            /* whether to check header crcs */
int check2 = 0;            /* whether to check whole file crcs */
int fromfile = 0;          /* read filenames from a file */
int binary = 0;            /* manipulate binary file */
int trailing = 0;          /* include trailing empty lines */
int prthdr = 0;            /* print Checksum: XXXXXXXXXX header */
int autocheck = 0;         /* brik must decide if text or binary */
int is_stdin = 0;          /* current file is stdin */
int doubtful = 0;          /* text but doubtful */

#ifdef DEBUG
int debugging = 0;
#endif

/* opens file, prints error message if can't open */
FILE *efopen (fname, mode, level)
char *fname;               /* filename to open */
char *mode;                /* mode, e.g. "r" or "r+" */
int level;                 /* error level */
{
   FILE *fptr;
   fptr = fopen (fname, mode);
   if (fptr == NULL)
      showerr (fname, level);
   return (fptr);
}

/* LOWERIT is a function or macro that returns lowercase of a character */
#ifndef LOWERIT
# ifdef AVOID_MACROS
#  define LOWERIT    lowerit
# else
#  define LOWERIT(c)        ((c)>='A' && (c)<='Z' ? ('a'-'A')+(c) : (c))
# endif
#endif

/* Function needed by SEEKFIX code even if a macro is available */
int lowerit (c) int c;  /* returns lowercase of an ASCII character */
{
  if (c >= 'A' && c <= 'Z') return (('a'-'A') + c);
  else return (c);
}

/* STRNICMP is a case-insensitive strncmp */
#ifndef STRNICMP
int STRNICMP (s1, s2, n)
register char *s1, *s2;
int n;
{
   assert (n >= 0);
   assert (LOWERIT('X') == 'x');
   assert (LOWERIT('*') == '*');

   for ( ; LOWERIT(*s1) == LOWERIT(*s2);  s1++, s2++) {
      if (--n == 0 || *s1 == '\0')
         return(0);
   }
   return(LOWERIT(*s1) - LOWERIT(*s2));
}
#endif /* STRNICMP */

#ifdef AVOID_MACROS
# define BRINCMP     STRNICMP
#else
# define BRINCMP(s1,s2,n) (LOWERIT(*(s1))!=LOWERIT(*(s2))||STRNICMP(s1,s2,n))
#endif


#define xdigit(x)    ((x) >= '0' && (x) <= '9')

/*
xatol is given a string that (supposedly) begins with a string
of digits.  It returns a corresponding positive numeric value.
*/
tcrc xatol (str)
char *str;
{
   tcrc retval;
   retval = 0L;
   while (xdigit(*str)) {
      retval = retval * 10L + (*str-'0');
      str++;
   }
   return (retval);
}

main (argc, argv)
int argc;
char **argv;
{
   int i;
   int c;                        /* next option letter */
   int count = 0;                /* count of required options seen */
   char *infname;                /* name of file to read filenames from */
   FILE *infptr;                 /* open file ptr for infname */

   extern int optind;            /* from getopt: next arg to process */
   extern int opterr;            /* used by getopt */

   opterr = 1;                   /* so getopt will print err msg */
   argv[0] = "brik";             /* for getopt to use */

   patlen = strlen (PATTERN);

#ifdef DEBUG
   while ((c = getopt (argc, argv, "cCgGasqvWHfbThd")) != EOF)
#else
   while ((c = getopt (argc, argv, "cCgGasqvWHfbTh")) != EOF)
#endif
   {
      switch (c) {
         case 'a':   autocheck++; binary = 0; trailing = 0; break;
         case 'c':   check1++; count++; break;
         case 'C':   check2++; count++; break;
         case 'g':   gen1++; count++; break;
         case 'G':   gen2++; count++; break;
         case 's':   silent++; verbose = 0; break;
         case 'q':   quiet++; verbose = 0; break;
         case 'v':   verbose++; silent = 0; break;
         case 'W':   updfile++; break;
         case 'f':   fromfile++; break;
         case 'b':   binary++; autocheck = 0; trailing = 0; break;
         case 'T':   trailing++; binary = 0; autocheck = 0; break;
         case 'H':   prthdr++; break;
#ifdef DEBUG
         case 'd':   debugging++; break;
#endif
         case 'h':   longhelp();
         case '?':   shorthelp();
      }
   }

   if (count != 1)
      shorthelp();

   if (binary && (check1 || gen1)) {
      fprintf (stderr, "brik: fatal: Can't read or update CRC header in binary mode\n");
      exit (1);
   }

   if ((updfile || prthdr) && !gen1) {
      fprintf (stderr, "brik: fatal: Use of -W and -H requires -g\n");
      exit (1);
   }

#if 0
   if (gen1 || gen2 && !updfile)
      silent = 0;
#endif

   if (gen1)
      autocheck = 0;

#ifdef GENTAB
   /* generate CRC table */
   mkcrctab();
#endif /* GENTAB */

   /* initialize binary char test table */
   for (i = 0;  i < BINTABSIZ;  i++) {
      if ( (i < 7) || (i > 13 && i < 26) || (i > 126)) /*ASCII binary chars*/
         bintab[i] = 1;
      else
         bintab[i] = 0;
   }

   i = optind;

   if (fromfile) {                  /* read filenames from file */
      if (i >= argc) {              /* need filenames after -f */
         fprintf (stderr, "brik: fatal: Filename(s) needed after -f\n");
         exit (1);
      }
      for (; i < argc;  i++) {
         infname = argv[i];
         if (strcmp(infname, "-") == 0) { /* "-" means stdin */
            readnames (stdin);
         } else {
#ifdef WILDCARD
            extern char *nextfile();
            nextfile (0, infname, 0);     /* initialize fileset 0 */
            while ((infname = nextfile(1, (char *) NULL, 0)) != NULL) {
               infptr = efopen (infname, BRIK_RD, LVL_ERR);
               readnames (infptr);
               fclose (infptr);
            }
#else
            infptr = efopen (infname, BRIK_RD, LVL_ERR);
            readnames (infptr);
            fclose (infptr);
#endif /* WILDCARD */
         }
      }
   } else {                         /* read filenames from command line */
      if (i >= argc) {
#ifndef BIN_STDIN_OK
         if (binary && !check2) {
            fprintf (stderr, "brik: fatal: Can't handle stdin in binary mode\n");
            exit (1);
         }
#endif
         is_stdin = 1;
         dofptr (stdin, "stdin");      /* if no files, read stdin */
      } else {
         for (;  i < argc;  i ++) {
#ifdef WILDCARD
            extern char *nextfile();
            char *one_name;               /* a matching filename */
            nextfile (0, argv[i], 0);     /* initialize fileset 0 */
            while ((one_name = nextfile(1, (char *) NULL, 0)) != NULL)
               dofname (one_name);
#else
            dofname (argv[i]);
#endif /* WILDCARD */
         }
      }
   }
errexit:
   if (errcount > ERRLIMIT)
      errcount = ERRLIMIT;       /* don't overflow status code */
   exit (errcount);
   return (errcount);            /* to keep turbo c and lint happy */
}

/*
**   Reads names from supplied file pointer and handles them.  Just
**   returns if supplied NULL file pointer.  Will also expand wildcards
**   in names read from this file.
*/
void readnames (infptr)
FILE *infptr;
{
   char buf[LINESIZE];
   if (infptr == NULL)
      return;
   while (fgets (buf, LINESIZE, infptr) != NULL) {
#ifdef WILDCARD
      char *fname;                  /* matching filename */
      extern char *nextfile();
#endif /* WILDCARD */
      buf[strlen(buf)-1] = '\0'; /* zap trailing newline */
#ifdef WILDCARD
      nextfile (0, buf, 1);     /* initialize fileset 1 */
      while ((fname = nextfile(1, (char *) NULL, 1)) != NULL) {
         dofname (fname);
      }
#else
      dofname (buf);
#endif /* WILDCARD */
   }
}

/* do one filename */
void dofname (this_arg)
char *this_arg;
{
   FILE *this_file;
   char *mode;                         /* "r", "rb", "rw", etc. for fopen */
#ifdef BRKTST
   extern void brktst();
   brktst();
#endif

   if (autocheck)
      binary = 0;             /* always begin by assuming text */

   if (strcmp(this_arg,"-") == 0) {
#ifndef BIN_STDIN_OK
      if (binary && !check2) {
         fprintf (stderr, "brik: fatal: Can't handle stdin in binary mode\n");
         exit (1);
      }
#endif

      is_stdin = 1;
      this_file = stdin;
      this_arg = "stdin";
   } else {
      if (updfile) {
         assert (!binary);
         this_file = efopen (this_arg, BRIK_RW, LVL_ERR);
      } else {
         if (binary && !check2) /* check2 reads filenames, not data */
            mode = BRIK_RDB;
         else
            mode = BRIK_RD;
         this_file = efopen (this_arg, mode, LVL_ERR);
      }
   }
   if (this_file == NULL)
      errcount++;
   else {
#ifdef NOCASE
      char *p;
      for (p = this_arg;  *p != '\0';  p++)
         *p = LOWERIT(*p);
#endif
      dofptr (this_file, this_arg);
      if (this_file != NULL)
         fclose (this_file);
   }
}

/* returns appropriate suffix character for CRC, based on global flags */
char suffix()
{
   return ((char) (doubtful ? '*' : (binary ? 'b' : (trailing ? 'T' : ' '))));
}


/*
**   Do one file pointer.  Decides if CRC header will be read or written,
**   or whether just the whole file will be read.
*/
void dofptr (fptr, fname)
FILE *fptr;
char *fname;
{
   int retval;                      /* return value from findcrc */
   if (check2)
      whole_check (fptr, fname);    /* do whole file check from list */
   else if (gen1 || check1)         /* header-based CRC check or update */
      hdrcrc (fptr, fname);
   else {                           /* whole-file CRC calculation */
      extern tcrc crccode;
      assert (gen2);
      printhdr();
      (void) findcrc (fptr, fname, &retval);
      if (!silent) {
         if (!binary && retval == 1)
            doubtful = 1;                 /* tell suffix() it's '*' */
         printf (fmtstr, crccode, suffix(), blank, fname);
      }
   }
   is_stdin = 0;                    /* set, but not reset, by our caller */
   doubtful = 0;                    /* sphagetti code, need to fix later */
}

/* Does whole file check from a list of files and CRCs */
void whole_check (fptr, listname)
FILE *fptr;                   /* open file ptr of CRC list file */
char *listname;               /* name of CRC list file */
{
   tcrc fcrc;        /* recorded crc */
   char *fname;               /* name of file whose CRC being checked */
   char buf [LINESIZE];       /* line buffer */
   char *p;                   /* temp ptr */
   FILE *orgfile;             /* file pointer for original file to check */
   int lino = 0;              /* line no. in list file for error msg */
   char *mode;                /* mode string for fopen */

   while (fgets (buf, LINESIZE, fptr) != NULL) {
      lino++;
      p = buf;
      if (*p == CMTCH)              /* skip comment lines */
         continue;
      while (*p != '\0' && whitespace(*p))      /* skip whitespace */
         p++;
      if (*p == '\0')
         continue;                              /* skip empty lines */
      if (!xdigit(*p))
         goto badline;
      fcrc = xatol (p); /* recorded CRC */

      while (xdigit(*p))
         p++;                                   /* skip past numeric chars */

      doubtful = binary = trailing = 0;
      if (*p == 'b')                            /* 'b' means binary */
         binary = 1;

      if (*p == 'T')                            /* 'T' means trailing mode */
         trailing = 1;

      if (*p == '*')
         doubtful = 1;                          /* text but doubtful */

      while (*p != '\0' && !whitespace(*p)) /* to whitespace */
         p++;
      while (whitespace(*p))   /* skip whitespace */
         p++;

      if (*p == '\n' || *p == '\0') {     /* if at end of line */
         goto badline;
      }
      fname = p;
#if 0
      while (*p != '\0' && !whitespace(*p))  /* skip to whitespace */
#else
      /* Names CAN contain whitespace, and even newlines, however there
       * is no provision to store names with embedded newlines, so we
       * are out of luck for that case...
       */
      while (*p != '\0' && *p != '\n')
#endif
         p++;
      *p = '\0';                    /* null-terminate filename */

      if (binary)
         mode = BRIK_RDB;
      else
         mode = BRIK_RD;

      orgfile = efopen (fname, mode, LVL_ERR);
      if (orgfile == NULL) {
         errcount++;
      } else {
         int retval;
         tcrc foundcrc;
         assert (!(binary && trailing));
         foundcrc = findcrc (orgfile, fname, &retval);
         if (foundcrc == fcrc) {
            if (verbose)
               printf (fmtstr, foundcrc, suffix(), ok, fname);
         } else {
            if (!silent)
               printf (fmtstr, foundcrc, suffix(), bad, fname);
            errcount ++;
         }
         if (orgfile != NULL)
            fclose (orgfile);
      }
   }
   return;
badline:
   fprintf (stderr,
      "brik: error: Abandoning %s due to badly formatted line %d\n",
      listname, lino);
   return;
}


/*
Initializing the CRC to all one bits avoids failure of detection
should entire data stream get cyclically bit-shifted by one position.
The calculation of the probability of this happening is left as
an exercise for the reader.
*/
#define INITCRC   0xFFFFFFFFL;

/*
**   hdrcrc processes one file given an open file pointer
**   and the filename.  The filename is used for messages etc.
**   It does all manipulation of header-related CRCs, i.e.,
**   checking generating header CRC.  It deals only with text files.
*/
void hdrcrc (fptr, fname)
FILE *fptr;
char *fname;
{
   char buf[LINESIZE];
   int lino = 0;
   char *ptr;
   tcrc fcrc;   /* crc recorded in file */
   extern tcrc crccode;
   int retval;                      /* return value from findcrc */
   long hdrpos;                     /* where we found crc header in file */

   crccode = INITCRC;

   assert (!binary);

#ifndef NIXSEEK
   hdrpos = ftell (fptr);
#endif
   while (fgets (buf, LINESIZE, fptr) != NULL) {
#ifdef BRKTST
      extern void brktst();
      brktst();
#endif
      lino++;
      if (BRINCMP (buf, PATTERN, patlen) == 0) {      /* found header */
#ifdef NIXSEEK
         hdrpos = ftell (fptr);        /* seek posn of line with header */
#endif
         ptr = buf + patlen;           /* point to beyond header */
         while (*ptr != '\0' && whitespace(*ptr))
            ptr++;                     /* skip white space */
         fcrc = xatol (ptr);           /* get stored crc */
         while (xdigit(*ptr))
            ptr++;                     /* skip past digits */
         if (check1) {
            if (*ptr == 'T')           /* if 'T' suffix then */
               trailing = 1;          /* ..include trailing empty lines */
            else
               trailing = 0;
         }

         /* find CRC for rest of file */
         (void) findcrc (fptr, fname, &retval);

         if (gen1) {                   /* generating CRC */
            if (updfile) {             /* if updating file posn */
               updatefile (fptr, hdrpos, crccode, fname); /* then do it */
               if (prthdr && !silent)  /* printing header */
                  printf (hdrfmt, crccode, fname);
            } else {
               if (prthdr && !silent)  /* printing header */
                  printf (hdrfmt, crccode, fname);
               else if (!silent)
                  printf (fmtstr, crccode, suffix(), blank, fname);
            }
         } else {                      /* checking CRC */
            if (fcrc == crccode) {
               if (verbose)
                  printf (fmtstr, crccode, suffix(), ok, fname);
            } else {
               if (!silent)
                  printf (fmtstr, crccode, suffix(), bad, fname);
               errcount ++;
            }
         }
         return;
      } /* end if (BRINCMP (...) ) */
#ifndef NIXSEEK
      hdrpos = ftell (fptr);
#endif
   } /* end of while (fgets(...)) */

   /* reach here if header not found -- this is an error */
   if (!silent)
      printf ("%10s      %s\n", "????", fname);
   errcount++;
   return;
}

/* update file with CRC -- must be seekable */
void updatefile (fptr, hdrpos, crccode, fname)
FILE *fptr;
long hdrpos;
tcrc crccode;
char *fname;
{
   char buf[LINESIZE];
   int buflen;             /* will hold count of chars in buf */
   int chars_to_print;     /* chars needed to fill in CRC */

   /*
   1 for blank, CHARSINCRC for actual CRC, and possibly
   1 more for 'T' suffix if including trailing empty lines too
   */
   chars_to_print = 1 + CHARSINCRC + (trailing ? 1 : 0);

#ifndef NIXSEEK
   /* hdrpos is already seek position of header */
   if (fseek (fptr, hdrpos, 0) != 0) { /* seek back */
      fprintf(stderr,
         "brik: error: No CRC written, seek failed on %s\n",fname);
      return;
   }

SEEKFIX

   fgets (buf, LINESIZE, fptr);
   if (BRINCMP (buf, PATTERN, patlen) == 0)
      goto foundit;
   fprintf(stderr,
      "brik: error: No CRC written, header lost in %s\n",fname);
   return;
#else
   /* Following code does fseeks in a non-ANSI-conformant way */
   /* hdrpos is seek position *after* header was read.  Need to get back */
   if (hdrpos >= BACKSIZE)
      hdrpos -= BACKSIZE;
   else
      hdrpos = 0L;
   if (fseek (fptr, hdrpos, 0) != 0) {       /* seek back first */
      fprintf(stderr,"brik: error: No CRC written, seek failed on %s\n",fname);
      return;
   }
   /* now seek forward until we see CRC header again */
   hdrpos = ftell (fptr);
   while (fgets (buf, LINESIZE, fptr) != NULL) {
      if (BRINCMP (buf, PATTERN, patlen) == 0)
         goto foundit;
      hdrpos = ftell (fptr);
   }
   fprintf(stderr,"brik: error: No CRC written, header lost in %s\n",fname);
   return;
#endif /* NIXSEEK */

foundit:    /* hdrpos points to line with header */
   if (fseek (fptr, hdrpos, 0) != 0) { /* seek failed */
      fprintf(stderr,"brik: error: No CRC written, seek failed on %s\n",fname);
      return;
   }
SEEKFIX
   /* we are seeked back to the line with the CRC header */

#ifdef CHECKSEEK  /* triple-check seeks */
   {
      char tmpbf1[LINESIZE];
      char tmpbf2[LINESIZE];
      fseek (fptr, hdrpos, 0);
      assert (ftell (fptr) == hdrpos);
SEEKFIX
      fgets (tmpbf1, LINESIZE, fptr);
      fseek (fptr, 0L, 0); fseek (fptr, 0L, 2);    /* exercise seeks */
      fseek (fptr, hdrpos, 0);
      assert (ftell (fptr) == hdrpos);
SEEKFIX
      fgets (tmpbf2, LINESIZE, fptr);
      if (strcmp(tmpbf1,tmpbf2) != 0 || BRINCMP(tmpbf1,PATTERN,patlen) != 0) {
         fprintf (stderr,
            "brik: error: Bad seek on %s, abandoning this file\n", fname);
         return;
      }
      fseek (fptr, hdrpos, 0);
SEEKFIX
   }
#endif /* CHECKSEEK */

#ifdef DEBUG
   if (debugging) {  /* zap newline, print buffer, restore newline */
      int nlpos; char savech;
      nlpos = strlen(buf) - 1;  savech = buf[nlpos];  buf[nlpos] = '\0';
      fprintf (stderr, "read header  [%s]\n", buf);
      buf[nlpos] = savech;
   }
#endif

   buflen = strlen (buf);
#ifdef DEBUG
   if (debugging)  /* need chars_to_print plus one trailing space or newline */
      fprintf(stderr,"need %d chars, have %d\n",chars_to_print+1,buflen-patlen);
#endif
   if (buflen - patlen > chars_to_print) {      /* if enough space */
      char sprbuf[1+CHARSINCRC+1+1+6];  /* blank+CRC+suffix+null+fudge */
      char *ptr;
      int i;
      ptr = &buf[patlen];                 /* point to beyond header */
      sprintf (sprbuf, " %10lu%c", crccode, 'T');
      for (i = 0;  i < chars_to_print;  i++) /* trailing 'T' possibly ignored */
         ptr[i] = sprbuf[i];
      if (ptr[i] != '\n')
         ptr[i] = ' ';           /* terminate with newline or blank */
      fseek (fptr, 0L, 1);       /* after read, must seek before write */
      if (fwrite (buf, 1, buflen, fptr) != buflen) {
         fprintf(stderr,
            "brik: error: Write failed while writing CRC to %s\n",fname);
      } else if (verbose)
         printf (fmtstr, crccode, suffix(), blank, fname);
         /* printf ("%10lu      %s\n", crccode, fname); */
#ifdef DEBUG
      buf[strlen(buf)-1] = '\0'; /* zap trailing newline */
      if (debugging)
         fprintf (stderr, "wrote header [%s]\n", buf);
#endif
   } else {
      fprintf(stderr,"brik: error: Not enough space for CRC in %s\n",fname);
      return;
   }
}

void longhelp()
{
printf ("%s\n", copyright);

printf (
"Usage:  brik -cCgGsqvWHfbT [ file ] ...  (must have one of -cCgG) \n\n");

printf ("Brik %s (%s) generates and verifies CRC-32 checksums.  It can\n",
      version, DATESTAMP);

printf ("\
also read or update a \"Checksum: xxxxxxxxxx\" header at the beginning\n\
of a line in which xxxxxxxxxx represents the CRC of all lines in the file\n\
*after* this header.  A filename of \"-\" (or none) means standard input.\n\n\
");

printf ("\
   -g     look for Checksum: header, generate CRC for rest of file\n\
   -c     get CRC from header, verify CRC of rest of file\n\
   -G     generate CRC for entire file (add -b for binary files)\n\
   -C     verify all file CRCs from output of -G (-f is not needed)\n\
   -b     use binary mode -- read file byte by byte, not line by line\n\
   -a     automatically decide whether each file is text or binary\n\
");

#ifdef WILDCARD
printf ("   -f     read filenames (wildcards ok) from specified files\n");
#else
printf ("   -f     read filenames from specified files\n");
#endif

printf ("\
   -v     be verbose, report all results (else only errors are reported)\n\
   -s     be silent, say nothing, just return status code\n\
   -q     be quiet, don't print header for -G\n\
   -W     after generating CRC with -g, write it to original header\n\
   -H     after generating CRC with -g, print header to stdout\n\
   -T     include trailing empty lines, normally ignored (text mode only)\n\
");
exit (0);
}

/*
**   Generates CRC of an open file, from current file position to end
**   Except in -T mode, will ignore all trailing empty lines in text
**   files.  Algorithm for this is:
**      1.   After each nonempty line, save crccode so far.
**      2.   At end of file, if last line was empty, use saved crccode rather
**           than current one.
**   In whole-file mode, if was text mode but binary file, and if auto
**   check is on, will re-open file in binary mode and do it again
**   (except if stdin was being read)
**   Returns 1 in retval if it detected that a text file contained binary
*    characters.
*/

tcrc findcrc (fptr, fname, retval)
FILE *fptr;
char *fname;
int *retval;
{
   int count;
   char buf[LINESIZE];
   extern tcrc crccode;
   int warned = 0;
   tcrc savedcrc; /* save crccode for trailing empty lines */
   int buflen;
   *retval = 0;         /* will become 1 later if needed */

again:      /* restart here if retrying in binary mode */

   savedcrc = crccode = INITCRC;

   if (binary) {                                   /* binary */
      while ((count = fread (buf, 1, LINESIZE, fptr)) > 0) {
#ifdef BRKTST
         extern void brktst(); brktst();
#endif
         addbfcrc (buf, count);
      }
   } else {                                           /* text */
#ifdef CTRLZ_CHECK
      int lines = 0;                /* will count lines */
#endif /* CTRLZ_CHECK */
      buflen = 1;                   /* assume empty lines so far */
      while (fgets (buf, LINESIZE, fptr) != NULL) {
         register char *p;
         char *limit;
#ifdef BRKTST
         extern void brktst(); brktst();
#endif

#ifdef CTRLZ_CHECK
         lines++;    /* count lines */
#endif /* CTRLZ_CHECK */

         buflen = strlen (buf);
         limit = buf + buflen;
         for (p = buf;  p != limit;  p++) {
            if (!warned && BINCHAR(*p)) {
               *retval = 1;
               if (autocheck && !is_stdin)/* restart, now known to be binary */
                  goto restart;
               else {               /* don't restart, just warn */
                  warned = 1;
               }
            }
            if (*p == '\n')
               *p = MYNL;
         }
         addbfcrc (buf, buflen);
         if (buflen != 1)
            savedcrc = crccode;
      }
#ifdef CTRLZ_CHECK
      if (gen2) {
         int z_bin_check PARMS ((FILE *fptr, char *fname));
         if (!warned && lines < Z_THRESHOLD && z_bin_check (fptr, fname)) {
            *retval = 1;
            if (autocheck && !is_stdin)
               goto restart;
         }
      }
#endif
      if (!trailing && buflen == 1)
         crccode = savedcrc;
   }
   if (ferror (fptr))
      fprintf (stderr, "brik: warning: error occurred while reading %s\n", fname);
   return (crccode);

/*
reach here if we were trying to get a text crc but the file was binary, we
are in autocheck mode, and we are not reading stdin.  Now we re-initialize
variables, reopen the file in binary mode, and begin again.
*/
restart:
   binary = 1;
   fclose (fptr);  fptr = efopen (fname, BRIK_RDB, LVL_ERR);
   if (fptr == NULL) {
      errcount++;
      return (crccode);
   } else
      goto again;
}

void printhdr ()
{
   static int firsttime = 1;
   if (firsttime && !quiet && !silent) {
      printf ("%c Whole file CRCs generated by Brik v%s.  Use \"brik -C\" to verify them.\n\n",
         CMTCH, version);
        printf ("%c CRC-32        filename\n", CMTCH);
        printf ("%c ------        --------\n\n", CMTCH);
      firsttime = 0;
   }
}

/*
**   Prints error message via perror().  The message is printed in the
**   format "brik: %s: %s" where the first %s is the level text ("warning",
**   "error", or "fatal") and the second %s is the string supplied by
**   perror().
**
*/

void showerr (errmsg, level)
char *errmsg;
int level;
{
#define ERRSTRMAX  40         /* don't copy more than this many chars */
   static char leveltext[][8] =   {"warning", "error", "fatal"};
   char errbuf[ERRBUFSIZ];       /* buffer for error message */
   strcpy (errbuf, "brik: ");
   assert (level >= LVL_WARN && level <= LVL_FATAL);
   strncat (errbuf, leveltext[level], ERRSTRMAX);
   strcat (errbuf, ": ");
   strncat (errbuf, errmsg, ERRSTRMAX);
   perror (errbuf);
}

void shorthelp()
{
   fprintf (stderr, "%s\n\n%s", "Usage to get help:  brik -h", copyright);
   exit (1);
}
