home *** CD-ROM | disk | FTP | other *** search
/ Media Depot 5 / mediadepotvolume51993.iso / FILES / 02 / GSAR110.ZIP / gsar.c < prev    next >
Encoding:
C/C++ Source or Header  |  1996-08-08  |  29.9 KB  |  943 lines

  1. /* gsar.c ***************************************** updated: 960808.09:28 TT
  2.  *
  3.  * Description : General Search And Replace utility (gsar)
  4.  * Author      : Tormod Tjaberg & Hans Peter Verne
  5.  *
  6.  * Copyright (C) 1992-96 Tormod Tjaberg & Hans Peter Verne,
  7.  * This is free software, distributed under the terms of the
  8.  * GNU General Public License. For details see the file COPYING
  9.  */
  10. #include <stdio.h>
  11. #include <stdlib.h>
  12. #include <string.h>
  13. #include <ctype.h>
  14. #include <stdarg.h>
  15. #include <limits.h>
  16. #include <signal.h>
  17. #include <sys/types.h>
  18. #include <sys/stat.h>
  19. #include "comp_dep.h"
  20. #include "arg_func.h"
  21. #include "gsar.h"
  22.  
  23. #define PROGRAM_NAME "gsar"
  24. #define PROGRAM_VER  "1.10"
  25.  
  26. /* Need setmode to turn my streams into binary Under MS-DOS
  27.  */
  28. #ifdef MSDOS
  29. #include <fcntl.h>
  30. #include <io.h>
  31. #endif
  32.  
  33. #ifdef __UNIX__
  34. #include <unistd.h>
  35. #endif
  36.  
  37. /* Clever little option mimic wildcard expansion from
  38.  * shell ONLY available with the Zortech compiler ver 3.0x and upwards
  39.  * And in the Symantec compiler though it's been renamed
  40.  */
  41. #if defined(__ZTC__)
  42. #include <dos.h>
  43. #ifdef WILDCARDS
  44. WILDCARDS
  45. #endif
  46. #ifdef EXPAND_WILDCARDS
  47. EXPAND_WILDCARDS
  48. #endif
  49. #endif
  50.  
  51. /* Macro to determine if char is a path or drive delimiter
  52.  */
  53. #define IS_PATH_DELIM(c)   ((c) == '\\' || (c) == ':' || (c) == '/')
  54.  
  55. #define LF  0xa
  56. #define CR  0xd
  57.  
  58. #define SETVBUF_SIZ  0x4000      
  59.  
  60. unsigned short nItemsSearch = 0;
  61. unsigned short nItemsReplace = 0;
  62.  
  63. /* if the flag below is set we are not allowed to interrupt!
  64.  */
  65. #if defined(__UNIX__)
  66. /* SunOS does not have this definition, so play it safe */
  67. static int fCriticalPart = 0;
  68. #else
  69. static sig_atomic_t fCriticalPart = 0;
  70. #endif
  71.  
  72. OUTPUT_CTRL Ctrl;
  73.  
  74. char **pFileList;           /* list of files found on the command line */
  75. char *pOutFile = NULL;      /* current output file name make sure it's NULL */
  76. char  SearchBuf[PAT_BUFSIZ] = "";
  77. char  ReplaceBuf[PAT_BUFSIZ] = "";
  78.  
  79. char  fSearchReplace = 0;  /* default to a search initially */
  80. char  fFolded        = 0;  /* fold pattern ie. ignore case */
  81. char  fOverWrite     = 0;  /* overwrite input file */
  82. char  fForce         = 0;  /* force overwrite of existing output file */
  83. char  fBuffers       = 0;  /* just display search & replace buffers */
  84. char  fFilter        = 0;  /* make GSAR act like a filter */
  85.  
  86. /* Usage text and GNU licence information
  87.  */
  88. char *Usage[] =
  89. {
  90.   PROGRAM_NAME ", ver " PROGRAM_VER " -- Copyright (C) 1992-96 Tormod Tjaberg & Hans Peter Verne",
  91.   "",
  92.   "Usage: gsar [options] [infile(s)] [outfile]",
  93.   "Options are:",
  94.   "-s<string> Search string ",
  95.   "-r[string] Replace string. Use '-r' to delete the search string from the file",
  96.   "-i         Ignore case difference when comparing strings",
  97.   "-B         just display search & replace Buffers",
  98.   "-f         Force overwrite of an existing output file",
  99.   "-o         Overwrite the existing input file",
  100.   "-c[n]      show textual Context of match, 'n' is number of bytes in context",
  101.   "-x[n]      show context as a heX dump, 'n' is number of bytes in context",
  102.   "-b         display Byte offsets of matches in file",
  103.   "-l         only List filespec and number of matches (default)",
  104.   "-h         suppress display of filespec when displaying context or offsets",
  105.   "-du        convert a DOS ASCII file to UNIX (strips carriage return)",
  106.   "-ud        convert a UNIX ASCII file to DOS (adds carriage return)",
  107.   "-F         'Filter' mode, input from stdin and eventual output to stdout",
  108.   "-G         display the GNU General Public Licence",
  109.   "",
  110.   "Ctrl characters may be entered by using a ':' in the string followed by the",
  111.   "ASCII value of the character. The value is entered using ':' followed by three",
  112.   "decimal digits or ':x' followed by two hex numbers. To enter ':' use '::'",
  113.   NULL
  114. };
  115.  
  116. char *Licence[] =
  117. {
  118.   "This program is free software; you can redistribute it and/or modify",
  119.   "it under the terms of the GNU General Public License as published by",
  120.   "the Free Software Foundation; either version 2 of the License, or",
  121.   "(at your option) any later version.",
  122.   "",
  123.   "This program is distributed in the hope that it will be useful,",
  124.   "but WITHOUT ANY WARRANTY; without even the implied warranty of",
  125.   "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the",
  126.   "GNU General Public License for more details.",
  127.   "",
  128.   "You should have received a copy of the GNU General Public License",
  129.   "along with this program; if not, write to the Free Software",
  130.   "Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.",
  131.   NULL
  132. };
  133.  
  134.  
  135. /* Display the GNU General Public License
  136.  */
  137. void ShowLicence(void)
  138. {
  139.    int i = 0;
  140.  
  141.    while (Licence[i] != NULL)
  142.    {
  143.       fputs(Licence[i++], Ctrl.fpMsg);
  144.       fputc('\n', Ctrl.fpMsg);
  145.    }
  146. }
  147.  
  148.  
  149. /* Input  : Message to be displayed with the format of standard printf
  150.  * Returns: Nothing, but instead it exits to operating system
  151.  *
  152.  * Terminate program, return to OS with a suitable return value
  153.  * exit() will close all open files. All fatal errors are written
  154.  * to stderr.
  155.  */
  156. void Abort(char *pMessage, ...)
  157. {
  158.    va_list argp;
  159.  
  160.    fprintf(stderr, "gsar: ");
  161.    va_start(argp, pMessage);
  162.    vfprintf(stderr, pMessage, argp);
  163.    va_end(argp);
  164.    fprintf(stderr, "\n");
  165.    exit(EXIT_FAILURE);  /* exit & tell operating system that we've failed */
  166. }
  167.  
  168.  
  169. /* Ctrl-Break handler. Returns to operating system
  170.  */
  171. void CtrlBreak(int Val)
  172. {
  173.    if (fCriticalPart)
  174.       return;
  175.  
  176.    signal(SIGINT, SIG_IGN);
  177.  
  178.    /* Before we die try to clean up as much as possible
  179.     * make sure we don't delete stdout...
  180.     */
  181.    if (fSearchReplace && pOutFile != NULL && !fFilter)
  182.       remove(pOutFile);
  183.  
  184.    exit(EXIT_FAILURE);  /* exit & tell operating system that we've failed */
  185. }
  186.  
  187.  
  188. /* Input  : pActStr - pointer to actual string which is to be duplicated
  189.  * Returns: pDupStr - pointer to malloc'd duplicate
  190.  *          NULL - Out of memory
  191.  *
  192.  * Duplicates a string by malloc'ing and then doing a strcpy
  193.  */
  194. char *DupStr(char *pActStr)
  195. {
  196.    register char *pDupStr;
  197.  
  198.    pDupStr = (char *) malloc(strlen(pActStr) + 1);
  199.    if (pDupStr)
  200.       strcpy(pDupStr, pActStr);
  201.    return pDupStr;
  202. }
  203.  
  204.  
  205. /* Input  : filespec - a filespec which contains a path and a filename
  206.  * Returns: savfilespec - pointer to malloc'd string with path
  207.  *
  208.  * Extracts the path from a filespec
  209.  */
  210. char *ExtractPathFromFSpec(register char *filespec)
  211. {
  212.    register char *p;
  213.    register char *savfilespec;
  214.  
  215.    savfilespec = DupStr(filespec); /* avoid destroying the original */
  216.  
  217.    /* Start at end of string and back up till we find the beginning */
  218.    /* of the filename or a path.               */
  219.    for (p = savfilespec + strlen(savfilespec);
  220.         p != savfilespec && !IS_PATH_DELIM(*(p - 1));
  221.         p--)
  222.       ;
  223.    *p = '\0';
  224.    return savfilespec;
  225. }
  226.  
  227.  
  228. /* Input  : pPath - which has to be properly terminated
  229.  *          pPrefix - tmp filename prefix maximum 4 chars
  230.  * Returns: pointer to static buffer
  231.  *          NULL - unable to generate tmp file name
  232.  *
  233.  * Generates a temporary filename by combining an optional path,
  234.  * a prefix ,a number between 0 & SHRT_MAX and the suffix '.tmp'.
  235.  */
  236. char *TmpName(char *pPath, char *pPrefix)
  237. {
  238.    static char FileSpec[FILENAME_MAX ];
  239.    static char Seed = 0;
  240.  
  241.    static short TmpNum;
  242.    char *pDigits;
  243.    unsigned short i;
  244.    struct stat buf;
  245.  
  246.    *FileSpec = '\0'; /* Null terminate the buffer so strcat will always work */
  247.    if (!Seed)      /* if first time through */
  248.    {
  249.       Seed++;
  250.       TmpNum = -1; /* since we increment this will make the first number 0000 */
  251.    }
  252.  
  253.    if (pPath != NULL)
  254.       strcpy(FileSpec, pPath);     /* first the path  */
  255.    if (pPrefix != NULL)
  256.       strcat(FileSpec, pPrefix);   /* then the prefix */
  257.  
  258.    /* pointer to start of digits in filespec */
  259.    pDigits = FileSpec + strlen(FileSpec);
  260.  
  261.    for (i = 0; i <= SHRT_MAX; i++)     /* try SHRT_MAX times */
  262.    {
  263.       TmpNum++;                        /* TmpNum is static ! */
  264.       sprintf(pDigits, "%04x", TmpNum);/* convert to string */
  265.       strcat(FileSpec, ".tmp");        /* add the suffix */
  266.       if (stat(FileSpec, &buf) != 0)
  267.          return FileSpec;                /* file not found, success */
  268.       *(pDigits) = '\0';               /* pre_XXXX --> pre_ */
  269.    }
  270.    /* unable to create a temporary file ! more than SHRT_MAX files !!*/
  271.    return NULL;
  272. }
  273.  
  274.  
  275. /* Input    : pBuffer pointer to character buffer
  276.  *            nItem number of items in buffer
  277.  *            base  1 = hex , 0 = ASCII
  278.  * Returns  : nothing
  279.  *
  280.  * Prints the contents of a buffer in either ASCII or HEX. The
  281.  * nItems variable is needed since we don't stop on a NUL
  282.  */
  283. void DumpBuffer(char *pBuffer, unsigned short nItem, unsigned char Base)
  284. {
  285.    unsigned short i;                 /* loop counter */
  286.  
  287.    if (!Base)
  288.    {
  289.       for (i = 0; i < nItem; i++)
  290.       {
  291. #ifdef MSDOS   /* MSDOS can display all characters except CTRL chars */
  292.          if (!iscntrl((int) * pBuffer))
  293. #else         /* its __UNIX__ */
  294.          if (isprint((int) * pBuffer))
  295. #endif
  296.             fputc(*pBuffer, Ctrl.fpMsg);
  297.          else
  298.             fputc('.', Ctrl.fpMsg);
  299.  
  300.          pBuffer++;
  301.       }
  302.    }
  303.    else
  304.       for (i = 0; i < nItem; i++)
  305.          fprintf(Ctrl.fpMsg, "%02x ", (unsigned char) * pBuffer++);
  306.  
  307.    fputc('\n', Ctrl.fpMsg);
  308. }
  309.  
  310.  
  311. /* Input    : pArgStr - pointer to command line search or replace string
  312.  *            pBuffer - pointer to buffer to store the parsed string
  313.  * Returns  : actual length of parsed string
  314.  *
  315.  * Takes a string & transforms it into the correct internal representation
  316.  * ie :070OO -> FOO or :x46OO -> FOO
  317.  */
  318. unsigned short GetPattern(char *pArgStr, char *pBuffer)
  319. {
  320.    char  number[4 ];/* array to store number to convert */
  321.    char *pEnd;      /* pointer to the result of the string to long conversion */
  322.    char *pStart;    /* pointer to the start of the buffer */
  323.  
  324.    pStart = pBuffer;
  325.    number[3] = '\0'; /* make sure number buffer is terminated */
  326.  
  327.    while (*pArgStr != '\0')
  328.    {
  329.       if (*pArgStr != ':')
  330.          *pBuffer++ = *pArgStr++;       /* just copy the buffer */
  331.       else
  332.       {
  333.          if (*(pArgStr + 1) == ':')    /* check for multiple :'s */
  334.          {
  335.             *pBuffer++ = *pArgStr;
  336.             pArgStr = pArgStr + 2;       /* position ourselves past the 2 :'s */
  337.          }
  338.          else
  339.          {  /* have we actually got three chars to copy ? */
  340.             if (strlen(++pArgStr) > 2)
  341.             {
  342.                memcpy(number, pArgStr, 3);/* negative numbers are silently ignored*/
  343.                pArgStr += 3;              /* jump past this number in the argument string */
  344.                if ((char) tolower(number[0]) == 'x')
  345.                {                          /* perform hex conversion */
  346.                   number[0] = '0';        /* replace the x with a zero */
  347.                   *pBuffer++ = (char) strtol(number, &pEnd, 16);
  348.                   if (pEnd != number + 3)
  349.                      Abort("command error, not a valid hexadecimal number : %s\n", &number[1]);
  350.                }
  351.                else                       /* decimal conversion */
  352.                {
  353.                   *pBuffer++ = (char) strtol(number, &pEnd, 10);
  354.                   if (pEnd != number + 3)
  355.                      Abort("command error, not a valid decimal number : %s\n", number);
  356.                }
  357.             }
  358.             else                          /* strlen(pArgStr) < 3 so abort */
  359.                Abort("command error, a single colon must be followed by three decimal digits or an 'x' followed by 2 hexadecimal numbers\n");
  360.          }
  361.       }
  362.       if (pBuffer - pStart > PAT_BUFSIZ)
  363.          Abort("command error, length of search or replace buffer must not exceed %d chars", PAT_BUFSIZ);
  364.    }  /* while */
  365.    return pBuffer - pStart;               /* actual length of buffer */
  366. }
  367.  
  368.  
  369. /* Input  : argc - number of arguments on the command line
  370.  *          argv - pointer to the argument vevtor
  371.  * Returns: number of actual filenames found
  372.  *
  373.  * Parses the command line & returns number of filenames found
  374.  */
  375. int GetArgs(int argc, char *argv[])
  376. {
  377.    int   i = 0;
  378.    int   j = 0;          /* counters */
  379.    int   c;              /* switch char */
  380.    char *pEnd;           /* the result of the string to long conversion */
  381.    long  longVal;
  382.  
  383.  
  384.    pFileList = NULL;
  385.  
  386.    if (argc > 1)
  387.       while ((c = GetOpt(argc, argv, "|s::r::iBfoc::x::blhd::u::FG")) != EOF)
  388.       {
  389.          switch (c)
  390.          {
  391.             case '|':
  392.                /* create a vector of file pointers */
  393. #if defined(__UNIX__)
  394.                /* SunOS doesn't allow realloc to be called with NULL :-( 
  395.                 * Insert extra test on unix systems...
  396.                 */
  397.                if (pFileList == NULL)
  398.                   pFileList = (char **) malloc((j + 2) * sizeof(char *));
  399.                else
  400. #endif
  401.                   pFileList = (char **) realloc(pFileList, (j + 2) * sizeof(char *));
  402.                pFileList[j++] = pOptArg;
  403.                pFileList[j] = NULL;   /* The C-standard specifies that argv[argc] == NULL */
  404.                break;
  405.             case MISSING_ARG:
  406.                Abort("command error, the '%c' option requires an argument", (unsigned char) CurOpt);
  407.                break;
  408.             case BAD_CHAR:
  409.                Abort("command error, unknown option '%c'. Type 'gsar' by itself help", (unsigned char) CurOpt);
  410.                break;
  411.             case MISSING_OPT:
  412.                i = 0;
  413.                while (Usage[i] != NULL)
  414.                {
  415.                   fputs(Usage[i++], Ctrl.fpMsg);
  416.                   fputc('\n', Ctrl.fpMsg);
  417.                }
  418.                exit(EXIT_SUCCESS);
  419.                break;
  420.             case 's':
  421.                if (pOptArg == NULL)
  422.                   Abort("command error, the '%c' option requires an argument", (unsigned char) CurOpt);
  423.                
  424.                nItemsSearch = GetPattern(pOptArg, SearchBuf);
  425.                break;
  426.             case 'r':
  427.                if (pOptArg == NULL)
  428.                   nItemsReplace = 0;    /* we are to remove occurrence of -s */
  429.                else
  430.                   nItemsReplace = GetPattern(pOptArg, ReplaceBuf);
  431.                fSearchReplace = 1;     /* only search & replace if -r option */
  432.                break;
  433.             case 'u':                  /* replace LF with CR LF */
  434.                if (pOptArg == NULL)
  435.                   pOptArg = "";
  436.                if (!(*pOptArg == 'd' && *(pOptArg + 1) == '\0'))
  437.                   Abort("command error, unknown option 'u%s'. Type 'gsar' by itself for help", pOptArg);
  438.                fSearchReplace = 1;
  439.                nItemsSearch = 1;
  440.                SearchBuf[0] = LF;
  441.                nItemsReplace = 2;
  442.                ReplaceBuf[0] = CR;
  443.                ReplaceBuf[1] = LF;
  444.                break;
  445.             case 'd':                  /* replace CR LF with LF */
  446.                if (pOptArg == NULL)
  447.                   pOptArg = "";
  448.                if (!(*pOptArg == 'u' && *(pOptArg + 1) == '\0'))
  449.                   Abort("command error, unknown option 'd%s'. Type 'gsar' by itself for help", pOptArg);
  450.                fSearchReplace = 1;
  451.                nItemsSearch = 2;
  452.                SearchBuf[0] = CR;
  453.                SearchBuf[1] = LF;
  454.                nItemsReplace = 1;
  455.                ReplaceBuf[0] = LF;
  456.                break;
  457.             case 'i':
  458.                fFolded = 1;
  459.                break;
  460.             case 'G':
  461.                ShowLicence();
  462.                exit(EXIT_SUCCESS);
  463.                break;
  464.             case 'l':
  465.                Ctrl.fTextual = 0;      /* return to terse display */
  466.                Ctrl.fHex = 0;
  467.                Ctrl.fByteOffset = 0;
  468.                break;
  469.             case 'o':
  470.                fOverWrite = 1;
  471.                break;
  472.             case 'f':
  473.                fForce = 1;
  474.                break;
  475.             case 'F':
  476.                fFilter = 1;
  477.                break;
  478.             case 'B':
  479.                fBuffers = 1;
  480.                break;
  481.             case 'b':
  482.                Ctrl.fByteOffset = 1;    /* display file offset */
  483.                break;
  484.             case 'h':
  485.                Ctrl.fFileSpec = 0;      /* turn off filespec */
  486.                break;
  487.             case 'x':
  488.                Ctrl.fTextual = -1;      /* signal fall through */
  489.                Ctrl.fHex = 1;           /* display context in hex */
  490.             case 'c':
  491.                if (Ctrl.fTextual == -1)  /* entered through case 'x' */
  492.                   Ctrl.fTextual = 0;        /* reset sentinel value  */
  493.                else
  494.                {
  495.                   Ctrl.fHex = 0;        /* entered through case 'c' */
  496.                   Ctrl.fTextual = 1;    /* display textual context */
  497.                }
  498.  
  499.                if (pOptArg == NULL)
  500.                   Ctrl.Context = (Ctrl.fHex == 1) ? HEX_CONTEXT : TXT_CONTEXT;
  501.                else
  502.                {
  503.                   longVal = strtol(pOptArg, &pEnd, 0);
  504.                   if (*pEnd != '\0')
  505.                      Abort("command error, invalid number : %s", pOptArg);
  506.  
  507.                   if (longVal < 16 || longVal > BUFSIZ)
  508.                      Abort("command error, context size must be in the range 16 to %d", BUFSIZ);
  509.  
  510.                   if (longVal > USHRT_MAX)
  511.                      Ctrl.Context = USHRT_MAX;
  512.                   else
  513.                      Ctrl.Context = (unsigned short) longVal;
  514.                }
  515.                break;
  516.             default:
  517.                Abort("internal error, option '%c' not handled in switch", (unsigned char) CurOpt);
  518.                break;
  519.          }
  520.       }
  521.    else
  522.    {
  523.       i = 0;
  524.       while (Usage[i] != NULL)
  525.       {
  526.          fputs(Usage[i++], Ctrl.fpMsg);
  527.          fputc('\n', Ctrl.fpMsg);
  528.       }
  529.       exit(EXIT_SUCCESS);
  530.    }
  531.  
  532.    Ctrl.fVerbose = (Ctrl.fTextual ||
  533.                       Ctrl.fHex ||
  534.                       Ctrl.fByteOffset) ? 1 : 0;
  535.  
  536.    return j;
  537. }
  538.  
  539.  
  540.  
  541. /* Perform search or replace using stdin and stdout ie as a 'filter'
  542.  * When gsar operates as a filter all output ie context, byte filenames
  543.  * etc are all sent to stderr.
  544.  */
  545. void StreamSearchReplace(void)
  546. {
  547.    long  nMatches;
  548.  
  549.    Ctrl.pInputFile = "stdin";      /* proper filename   */
  550.    Ctrl.fpMsg = stderr;            /* redirect messages */
  551.  
  552. #ifdef MSDOS
  553.    /* when MSDOS operates on streams it translates characters and terminates
  554.     * input and output when it encounters a CTRL Z. The code below makes
  555.     * MSDOS treat the streams as binary. Stdin cannot be used since MSDOS
  556.     * ignores the CTRL-Z. If you pipe a binary stream to MSDOS under some
  557.     * compilers you might get a write error (BCC, Zortech)
  558.     */
  559.    if (isatty(fileno(stdin)))
  560.       Abort("error, input from tty is not supported under MSDOS");
  561.    setmode(fileno(stdin), O_BINARY);
  562.    setmode(fileno(stdout), O_BINARY);
  563. #endif
  564.  
  565.    Ctrl.fpIn = stdin;              /* input from stdin  */
  566.    Ctrl.fpOut = stdout;            /* output to stdout  */
  567.  
  568.    if (!fSearchReplace)
  569.    {
  570.       nMatches = BMG_Search(&Ctrl);
  571.  
  572.       if (nMatches > 0)
  573.          fprintf(Ctrl.fpMsg, "%s: %ld match%s found\n",
  574.                  Ctrl.pInputFile, nMatches, (nMatches == 1) ? "" : "es");
  575.    }
  576.    else
  577.    {
  578.       nMatches = BMG_SearchReplace(&Ctrl, ReplaceBuf, nItemsReplace);
  579.  
  580.       if (nMatches == -1)   /* error in writing file */
  581.          Abort("error in writing file to stdout\n");
  582.       else
  583.          if (nMatches > 1)
  584.          {
  585.             fflush(Ctrl.fpOut);
  586.             fprintf(Ctrl.fpMsg, "%s: %ld occurrence%s changed\n",
  587.                     Ctrl.pInputFile, nMatches, (nMatches > 1) ? "s" : "");
  588.          }
  589.    }
  590. }
  591.  
  592. /* Input  : filename
  593.  * Returns: 1 - file is OK, i.e we get a stat on it and it's a regular file
  594.  *          0 - file is NOK
  595.  *
  596.  * Eventual errors are displayed here
  597.  */
  598. char fCheckFile(char *pFileName)
  599. {
  600.    struct stat buf;
  601.  
  602.    if (stat(pFileName, &buf) != 0)
  603.    {
  604.       fprintf(Ctrl.fpMsg, "gsar: unable to open input file '%s'\n", pFileName);
  605.       return 0;
  606.    }
  607.  
  608.    if (!(S_ISREG(buf.st_mode)))
  609.    {
  610.       fprintf(Ctrl.fpMsg, "gsar: warning, not a regular file '%s'\n", pFileName);
  611.       return 0;
  612.    }
  613.  
  614.    return 1;
  615. }
  616.  
  617. /* Performs a BMG search on one or multiple files. The files
  618.  * to process are found in pFileList. Files that cannot be
  619.  * opened are just ignored.
  620.  */
  621. void FileSearch(void)
  622. {
  623.    long  nMatches;
  624.  
  625.    while (*pFileList != NULL)
  626.    {
  627.       Ctrl.pInputFile = *pFileList++;
  628.  
  629.       if (!fCheckFile(Ctrl.pInputFile))
  630.          continue;
  631.  
  632.       if ((Ctrl.fpIn = fopen(Ctrl.pInputFile, "rb")) == NULL)
  633.       {
  634.          fprintf(Ctrl.fpMsg, "gsar: unable to open input file '%s'\n", Ctrl.pInputFile);
  635.          continue;
  636.       }
  637.  
  638.       Ctrl.fpMsg = stdout;
  639.  
  640. #if defined(MSDOS) && (!defined(__ZTC__)) || (defined(__ZTC__) && !defined(__SMALL__))
  641.       /* Don't use setvbuf if we're compiling under Zortech small model
  642.        */
  643.       if (setvbuf(Ctrl.fpIn, NULL, _IOFBF, SETVBUF_SIZ) != 0)
  644.          fprintf(Ctrl.fpMsg, "warning, unable to set up buffering for input file\n");
  645. #endif
  646.  
  647.       nMatches = BMG_Search(&Ctrl);
  648.       fclose(Ctrl.fpIn);
  649.  
  650.       if (nMatches > 0)
  651.          fprintf(Ctrl.fpMsg, "%s: %ld match%s found\n",
  652.                  Ctrl.pInputFile, nMatches, (nMatches == 1) ? "" : "es");
  653.    }
  654. }
  655.  
  656. /* Performs a search and replace on a specific outputfile
  657.  */
  658. void OneSearchReplace(void)
  659. {
  660.    long  nMatches = 0;
  661.    struct stat StatBuf;    /* struct so we can access file information */
  662.  
  663.    Ctrl.fpMsg = stdout;    /* redirect messages */
  664.    Ctrl.pInputFile = NULL; /* initially we haven't found a file */
  665.  
  666.    /* find the input and output file
  667.     */
  668.    while (*pFileList != NULL)
  669.    {
  670.       if (Ctrl.pInputFile == NULL)
  671.          Ctrl.pInputFile = *pFileList;
  672.       else
  673.          if (pOutFile == NULL)
  674.             pOutFile = *pFileList;
  675.  
  676.       pFileList++;
  677.    }
  678.  
  679.    if (!fCheckFile(Ctrl.pInputFile))
  680.    {
  681.       /* Message has already been given */
  682.       exit(EXIT_FAILURE);
  683.    }
  684.    else
  685.       Ctrl.fpIn = fopen(Ctrl.pInputFile, "rb");
  686.  
  687.    if (Ctrl.fpIn == NULL)
  688.       Abort("error, unable to open input file '%s'", Ctrl.pInputFile);
  689.  
  690.    if ((stat(pOutFile, &StatBuf)) == 0 && !fForce)   /* stat got the info ie. file exists */
  691.       Abort("error, output file '%s' already exists. Use the 'f' option to force overwrite", pOutFile);
  692.  
  693.    if ((Ctrl.fpOut = fopen(pOutFile, "wb")) == NULL)
  694.       Abort("error, unable to open output file '%s' ", pOutFile);
  695.  
  696. #if defined(MSDOS) && (!defined(__ZTC__)) || (defined(__ZTC__) && !defined(__SMALL__))
  697.    /* Don't use setvbuf if we're compiling under Zortech small model
  698.     */
  699.    if (setvbuf(Ctrl.fpIn, NULL, _IOFBF, SETVBUF_SIZ) != 0)
  700.       fprintf(Ctrl.fpMsg, "warning, unable to set up buffering for input file\n");
  701.    if (setvbuf(Ctrl.fpOut, NULL, _IOFBF, SETVBUF_SIZ) != 0)
  702.       fprintf(Ctrl.fpMsg, "warning, unable to set up buffering for output file\n");
  703. #endif
  704.  
  705.    nMatches = BMG_SearchReplace(&Ctrl, ReplaceBuf, nItemsReplace);
  706.  
  707.    fclose(Ctrl.fpIn);
  708.    fclose(Ctrl.fpOut);
  709.  
  710.    fCriticalPart = 1;    /* ignore interrupts here */
  711.  
  712.    if (nMatches == -1)   /* error in writing file */
  713.    {
  714.       fprintf(Ctrl.fpMsg, "gsar: error in writing file '%s' - cleaning up\n", pOutFile);
  715.       if (remove(pOutFile) != 0)
  716.          Abort("error, unable to remove output file '%s'", pOutFile);
  717.       exit(EXIT_FAILURE);  /* exit & tell operating system that we've failed */
  718.    }
  719.  
  720.    if (nMatches == 0)
  721.    {
  722.       if (remove(pOutFile) != 0)
  723.          Abort("error, unable to remove output file '%s'", pOutFile);
  724.    }
  725.    else
  726.       fprintf(Ctrl.fpMsg, "%s: %ld occurrence%s changed\n",
  727.                Ctrl.pInputFile, nMatches, (nMatches > 1) ? "s" : "");
  728.  
  729.    /* Make sure the Ctrl-C handler does not delete a completly
  730.     * processed output file
  731.     */
  732.    pOutFile = NULL;
  733.  
  734.    fCriticalPart = 0;   /* enable interrupts */
  735. }
  736.  
  737.  
  738. /* Performs a BMG search and replace on one or multiple files. The files
  739.  * to process are found in pFileList. Files that cannot be opened
  740.  * are ignored.
  741.  */
  742. void SearchReplace(void)
  743. {
  744.    long  nMatches;
  745. #ifdef __UNIX__
  746.    struct stat stat_buf;
  747.    /*
  748.      - some compilers mess this up...
  749.      extern int chown(char *, uid_t, gid_t);
  750.      extern int chmod(char *, mode_t);
  751.    */
  752. #endif
  753.  
  754.    Ctrl.fpMsg = stdout;    /* redirect messages */
  755.  
  756.    while (*pFileList != NULL)
  757.    {
  758.       Ctrl.pInputFile = *pFileList++;
  759.  
  760.       if (!fCheckFile(Ctrl.pInputFile))
  761.          continue;
  762.  
  763.       if ((Ctrl.fpIn = fopen(Ctrl.pInputFile, "rb")) == NULL)
  764.       {
  765.          fprintf(Ctrl.fpMsg, "gsar: unable to open input file '%s'\n", Ctrl.pInputFile);
  766.          continue;
  767.       }
  768.  
  769.       /* generate a temporary file name with prefix 'gsr_'
  770.          */
  771.       pOutFile = ExtractPathFromFSpec(Ctrl.pInputFile);
  772.       if ((pOutFile = TmpName(pOutFile, "gsr_")) == NULL)
  773.          Abort("error, unable to create a temporary file name");
  774.  
  775.       if ((Ctrl.fpOut = fopen(pOutFile, "wb")) == NULL)
  776.          Abort("error, unable to open output file '%s' ", pOutFile);
  777.  
  778. #ifdef __UNIX__
  779.       /* In Unix, we try to preserve the mode and id's of the file : */
  780.       if (stat(Ctrl.pInputFile, &stat_buf) != -1)
  781.       {
  782.          chown(pOutFile, stat_buf.st_uid, stat_buf.st_gid);
  783.          chmod(pOutFile, stat_buf.st_mode);
  784.       }
  785.       /* We just ignore errors here ... (hpv) */
  786. #endif
  787.  
  788. #if defined(MSDOS) && (!defined(__ZTC__)) || (defined(__ZTC__) && !defined(__SMALL__))
  789.       /* Don't use setvbuf if we're compiling under Zortech small model
  790.          */
  791.       if (setvbuf(Ctrl.fpIn, NULL, _IOFBF, SETVBUF_SIZ) != 0)
  792.          fprintf(Ctrl.fpMsg, "warning, unable to set up buffering for input file\n");
  793.       if (setvbuf(Ctrl.fpOut, NULL, _IOFBF, SETVBUF_SIZ) != 0)
  794.          fprintf(Ctrl.fpMsg, "warning, unable to set up buffering for output file\n");
  795. #endif
  796.  
  797.       nMatches = BMG_SearchReplace(&Ctrl, ReplaceBuf, nItemsReplace);
  798.  
  799.       fclose(Ctrl.fpIn);
  800.       fclose(Ctrl.fpOut);
  801.  
  802.       fCriticalPart = 1;      /* ignore interrupts here */
  803.  
  804.       if (nMatches == -1)   /* error in writing file */
  805.       {
  806.          fprintf(Ctrl.fpMsg, "gsar: error in writing file '%s' - cleaning up\n", pOutFile);
  807.          if (remove(pOutFile) != 0)
  808.             Abort("error, unable to remove output file '%s'", pOutFile);
  809.          exit(EXIT_FAILURE);
  810.       }
  811.  
  812.       if (nMatches == 0)
  813.       {
  814.          if (remove(pOutFile) != 0)
  815.             Abort("error, unable to remove output file '%s'", pOutFile);
  816.       }
  817.       else
  818.       {
  819.          /* if we can't remove the input file delete the tmp output file
  820.             */
  821.          if (remove(Ctrl.pInputFile) != 0)
  822.          {
  823.             fprintf(Ctrl.fpMsg, "gsar: error, unable to remove input file '%s' before rename (read-only ?)", Ctrl.pInputFile);
  824.             if (remove(pOutFile) != 0)
  825.                Abort("error, unable to remove output file '%s'", pOutFile);
  826.             exit(EXIT_FAILURE);
  827.          }
  828.  
  829.          if (rename(pOutFile, Ctrl.pInputFile) != 0)
  830.             Abort("error, unable to rename file '%s' to '%s'", pOutFile, Ctrl.pInputFile);
  831.  
  832.          fprintf(Ctrl.fpMsg, "%s: %ld occurrence%s changed\n",
  833.                   Ctrl.pInputFile, nMatches, (nMatches > 1) ? "s" : "");
  834.       }
  835.       fCriticalPart = 0;
  836.    }
  837. }
  838.  
  839. int main(int argc, char *argv[])
  840. {
  841.    int i;            /* number of files on command line */
  842.  
  843. #if defined(__ZTC__)
  844.    /* Automatic response file expansion */
  845.    response_expand(&argc, &argv);
  846. #endif
  847.  
  848.    Ctrl.fFileSpec = 1;
  849.    Ctrl.fpMsg = stdout;
  850.  
  851.    if (signal(SIGINT, CtrlBreak) == SIG_ERR)
  852.       Abort("internal error, unable to set SIGINT");
  853.  
  854.    /* parse command line arguments & set variables
  855.     * i is number of files found
  856.     */
  857.    i = GetArgs(argc, argv);
  858.  
  859.    /* perform different health & sanity checks
  860.     */
  861.  
  862.    if (nItemsSearch == 0)
  863.       Abort("command error, no search string specified");
  864.  
  865.    /* display search and replace buffers if any
  866.     */
  867.    if (fBuffers)
  868.    {
  869.       fputc('\n', Ctrl.fpMsg);
  870.  
  871.       fprintf(Ctrl.fpMsg, "Search buffer  (ASCII) : ");
  872.       DumpBuffer(SearchBuf, nItemsSearch, 0);
  873.       fprintf(Ctrl.fpMsg, "Length = %3d   (Hex)   : ", nItemsSearch);
  874.       DumpBuffer(SearchBuf, nItemsSearch, 1);
  875.       fprintf(Ctrl.fpMsg, "\n");
  876.  
  877.       if (fSearchReplace)
  878.       {
  879.          if (nItemsReplace > 0)
  880.          {
  881.             fprintf(Ctrl.fpMsg, "Replace buffer (ASCII) : ");
  882.             DumpBuffer(ReplaceBuf, nItemsReplace, 0);
  883.             fprintf(Ctrl.fpMsg, "Length = %3d   (Hex)   : ", nItemsReplace);
  884.             DumpBuffer(ReplaceBuf, nItemsReplace, 1);
  885.          }
  886.          else
  887.             fprintf(Ctrl.fpMsg, "Note: Replace buffer is empty, search string will be removed from file!\n");
  888.       }
  889.  
  890.       return EXIT_SUCCESS;
  891.    }
  892.  
  893.    if (i == 0 && !fFilter)
  894.       Abort("command error, no input file name specified");
  895.  
  896.    if (i != 2 && fForce && !fOverWrite)
  897.       Abort("command error, two file names are required to use the 'f' option");
  898.  
  899.    /* set up the search pattern once and for all
  900.     */
  901.    BMG_Setup(SearchBuf, (int) nItemsSearch, fFolded);
  902.  
  903.    if (fFilter)
  904.    {
  905.       if (fOverWrite || fForce)
  906.          Abort("command error, the 'o' or 'f' option is meaningless in 'filter' mode");
  907.  
  908.       StreamSearchReplace();
  909.       return EXIT_SUCCESS;
  910.    }
  911.  
  912.    /* just perform a file search
  913.     */
  914.    if (!fSearchReplace)
  915.    {
  916.       if (fOverWrite || fForce)
  917.          Abort("command error, the 'o' or 'f' option is meaningless in 'search' mode");
  918.  
  919.       FileSearch();
  920.       return EXIT_SUCCESS;
  921.    }
  922.  
  923.    /* Do a search and replace with specific output file
  924.     */
  925.    if (i == 2 && !fOverWrite && fSearchReplace)
  926.    {
  927.       OneSearchReplace();
  928.       return EXIT_SUCCESS;
  929.    }
  930.  
  931.    if (!fOverWrite && fSearchReplace)
  932.       Abort("command error, multiple search & replace requires the 'o' option");
  933.  
  934.    if (fOverWrite && fSearchReplace)
  935.    {
  936.       if (fForce)
  937.          Abort("command error, the 'f' option is meaningless in multiple search and replace");
  938.       SearchReplace();
  939.    }
  940.    return EXIT_SUCCESS;
  941. }
  942.  
  943.