/*
**  CHBYTES.C - Change bytes in a file
**
**  This program searches a file for a sequence of bytes. If they are
**  found, they are replaced with zeros. It was originally developed for
**  a friend who needed a program to call from Paradox to remove printer
**  control sequences from formatted print files. The requirements were
**  1) since it is called by another program, all status has to be returned
**  in the errorlevel with no screen messages allowed, and 2) The file must
**  remain the same length, so the deleted sequences must only be replaced
**  with ASCII NULs.
**
**  Syntax: CHBYTES filename pattern_1 [pattern_2 [...pattern_N]]
**  where:  Each pattern is a comma-separated list of bytes, each of which
**          may be of the following forms:
**          C    - Any single character will be treated as literal.
**          XXh  - "XX" will be interpreted as a hexidecimal number (both
**                 1- and 2-digit hex numbers are allowed).
**          NNNd - "NNN" will be interpreted as a decimal number (both
**                 1-, 2-, and 3-digit decimal numbers are allowed).
**
**  e.g.    CHBYTES printer.fil 12d 1bh,[,3,x
**          would zero out form feeds and the escape sequence "[3x"
**
**  Returns: 0 - Success
**           1 - No filename
**           2 - No arguments
**           3 - Error opening file
**           4 - Not enough memory
**           5 - Bad argument
**           6 - Error reading file
**           7 - Error writing file
**
**  Public domain by Bob Stout
*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>

#ifdef __ZTC__
 int _okbigbuf = 0;
#endif

#define LAST_CHAR(s) (((char *)(s))[strlen((char *)(s)) - 1])

#ifndef max
 #define max(x,y) (((x) >= (y)) ? (x) : (y))
#endif

typedef enum {ERROR = -1, SUCCESS, FALSE = 0, TRUE} LOGICAL;

int bufsize;

struct {
      char pattern[40];                         /* pattern to find      */
      int numbytes;                             /* length of pattern    */
} search[40];

int main (int argc, char *argv[])
{
      FILE *fp  = NULL;
      char *buf = NULL, *getbuf(void);
      fpos_t rpos;
      int i, patterns, max_bytes = 0;
      LOGICAL hex2char(const char *, char *);

      if (2 > argc)                             /* no filename          */
            return 1;
      if (3 > argc)                             /* no argument          */
            return 2;
      if (NULL == (fp = fopen(argv[1], "r+b")))
            return 3;                           /* file open error      */
      if (NULL == (buf = getbuf()))
            return 4;                           /* no memory for buffer */

      patterns = argc - 2;                      /* process arguments    */
      for (i = 2; i < argc; ++i)
      {
            char *p, *ptr;

            if (NULL != (ptr = strtok(argv[i], ",")))
            {
                  p = search[i - 2].pattern;
                  do
                  {
                        search[i - 2].numbytes++;
                        if (1 == strlen(ptr))
                        {
                              *p++ = *ptr;
                              continue;
                        }
                        switch (toupper(LAST_CHAR(ptr)))
                        {
                        case 'D':
                              LAST_CHAR(ptr) = '\0';
                              *p++ = (char)atoi(ptr);
                              break;
                        case 'H':
                              LAST_CHAR(ptr) = '\0';
                              if (ERROR == hex2char(ptr, p++))
                                    return 5;
                              break;
                        default:
                              return 5;
                        }
                  } while (NULL != (ptr = strtok(NULL, ",")));
                  *p = '\0';
                  max_bytes = max(max_bytes, search[i - 2].numbytes);
            }
            else  return 5;
      }

      fgetpos(fp, &rpos);                       /* save where we are    */
      while (1)
      {
            int bytes, n;
            LOGICAL modified;

            if (max_bytes > (bytes = (int)fread(buf, 1, bufsize, fp)))
            {
                  if (0 == bytes && !feof(fp))
                        return 6;               /* something's wrong!   */
                  else  break;                  /* all done!            */
            }
            for (n = 0, modified = FALSE; n < patterns; ++n)
            {
                  /* check each pattern in turn                         */

                  for (i = 0; i < (bytes - max_bytes + 1); ++i)
                  {
                        int j;

                        if (buf[i] != *(search[n].pattern))
                              continue;
                        if (SUCCESS != strncmp(&buf[i],
                              search[n].pattern, search[n].numbytes))
                        {
                              continue;
                        }

                        /* found one! replace it in the buffer          */

                        for (j = 0; j < search[n].numbytes; ++j, ++i)
                              buf[i] = '\0';
                        modified = TRUE;
                  }
            }
            if (modified)                       /* write changes, if any*/
            {
                  fpos_t wpos = rpos;

                  fsetpos(fp, &wpos);
                  if (bytes != (int)fwrite(buf, 1, bytes, fp))
                        return 7;
                  fsetpos(fp, &rpos);
            }
            rpos += bytes - max_bytes + 1;      /* get another buffer   */
            fsetpos(fp, &rpos);
      }
      fclose(fp);
      return SUCCESS;
}

/*
**  Allocate the largest buffer we can
*/

char *getbuf(void)
{
      register char *buffer;

      for (bufsize = 0x4000; bufsize >= 128; bufsize >>= 1)
      {
            if (NULL != (buffer = (char *) malloc(bufsize)))
                  return buffer;
      }
      return NULL;
}

/*
**  Convert ASCII hex char to char
*/

#define xdigit(c) (toupper(c) - (((c) > '9') ? 'A' - 10 : '0'))

LOGICAL hex2char(const char *hex, char *buf)
{
      int ch = 0;
      char *p = (char *)hex;

      while(*p)
      {
            if (!isxdigit(*p))
                  return ERROR;
            ch <<= 4;
            ch  += xdigit(*p);
                ++p;
      }
      *buf = (char)ch;
      return SUCCESS;
}
