1/* Install modified versions of certain ANSI-incompatible system header
2   files which are fixed to work correctly with ANSI C and placed in a
3   directory that GCC will search.
4
5   Copyright (C) 1997, 1998, 1999, 2000 Free Software Foundation, Inc.
6
7This file is part of GCC.
8
9GCC is free software; you can redistribute it and/or modify
10it under the terms of the GNU General Public License as published by
11the Free Software Foundation; either version 2, or (at your option)
12any later version.
13
14GCC is distributed in the hope that it will be useful,
15but WITHOUT ANY WARRANTY; without even the implied warranty of
16MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17GNU General Public License for more details.
18
19You should have received a copy of the GNU General Public License
20along with GCC; see the file COPYING.  If not, write to
21the Free Software Foundation, 59 Temple Place - Suite 330,
22Boston, MA 02111-1307, USA.  */
23
24#include "fixlib.h"
25
26#if defined( HAVE_MMAP_FILE )
27#include <sys/mman.h>
28#define  BAD_ADDR ((void*)-1)
29#endif
30
31#if ! defined( SIGCHLD ) && defined( SIGCLD )
32#  define SIGCHLD SIGCLD
33#endif
34#ifndef SEPARATE_FIX_PROC
35#include "server.h"
36#endif
37
38
39/*  The contents of this string are not very important.  It is mostly
40    just used as part of the "I am alive and working" test.  */
41
42static const char program_id[] = "fixincl version 1.1";
43
44/*  This format will be used at the start of every generated file */
45
46static const char z_std_preamble[] =
47"/*  DO NOT EDIT THIS FILE.\n\n\
48    It has been auto-edited by fixincludes from:\n\n\
49\t\"%s/%s\"\n\n\
50    This had to be done to correct non-standard usages in the\n\
51    original, manufacturer supplied header file.  */\n\n";
52
53/*  Working environment strings.  Essentially, invocation 'options'.  */
54
55#define _ENV_(v,m,n,t)   tCC* v = NULL;
56ENV_TABLE
57#undef _ENV_
58
59int find_base_len = 0;
60
61typedef enum {
62  VERB_SILENT = 0,
63  VERB_FIXES,
64  VERB_APPLIES,
65  VERB_PROGRESS,
66  VERB_TESTS,
67  VERB_EVERYTHING
68} te_verbose;
69
70te_verbose  verbose_level = VERB_PROGRESS;
71int have_tty = 0;
72
73#define VLEVEL(l)  ((unsigned int) verbose_level >= (unsigned int) l)
74#define NOT_SILENT VLEVEL(VERB_FIXES)
75
76pid_t process_chain_head = (pid_t) -1;
77
78char*  pz_curr_file;  /*  name of the current file under test/fix  */
79char*  pz_curr_data;  /*  original contents of that file  */
80char*  pz_temp_file;  /*  for DOS, a place to stash the temporary
81                          fixed data between system(3) calls  */
82t_bool curr_data_mapped;
83int    data_map_fd;
84size_t data_map_size;
85size_t ttl_data_size = 0;
86
87#ifdef DO_STATS
88int process_ct = 0;
89int apply_ct = 0;
90int fixed_ct = 0;
91int altered_ct = 0;
92#endif /* DO_STATS */
93
94const char incl_quote_pat[] = "^[ \t]*#[ \t]*include[ \t]*\"[^/]";
95tSCC z_fork_err[] = "Error %d (%s) starting filter process for %s\n";
96regex_t incl_quote_re;
97
98static void do_version (void) ATTRIBUTE_NORETURN;
99char *load_file (const char *);
100void run_compiles (void);
101void initialize (int argc, char** argv);
102void process (void);
103
104/*  External Source Code */
105
106#include "fixincl.x"
107
108/* * * * * * * * * * * * * * * * * * *
109 *
110 *  MAIN ROUTINE
111 */
112extern int main (int, char **);
113int
114main (int argc, char** argv)
115{
116  char *file_name_buf;
117
118  initialize ( argc, argv );
119
120  have_tty = isatty (fileno (stderr));
121
122  /* Before anything else, ensure we can allocate our file name buffer. */
123  file_name_buf = load_file_data (stdin);
124
125  /*  Because of the way server shells work, you have to keep stdin, out
126      and err open so that the proper input file does not get closed
127      by accident  */
128
129  freopen ("/dev/null", "r", stdin);
130
131  if (file_name_buf == (char *) NULL)
132    {
133      fputs ("No file names listed for fixing\n", stderr);
134      exit (EXIT_FAILURE);
135    }
136
137  for (;;)
138    {
139      char* pz_end;
140
141      /*  skip to start of name, past any "./" prefixes */
142
143      while (ISSPACE (*file_name_buf))  file_name_buf++;
144      while ((file_name_buf[0] == '.') && (file_name_buf[1] == '/'))
145        file_name_buf += 2;
146
147      /*  Check for end of list  */
148
149      if (*file_name_buf == NUL)
150        break;
151
152      /*  Set global file name pointer and find end of name */
153
154      pz_curr_file = file_name_buf;
155      pz_end = strchr( pz_curr_file, '\n' );
156      if (pz_end == (char*)NULL)
157        pz_end = file_name_buf = pz_curr_file + strlen (pz_curr_file);
158      else
159        file_name_buf = pz_end + 1;
160
161      while ((pz_end > pz_curr_file) && ISSPACE( pz_end[-1]))  pz_end--;
162
163      /*  IF no name is found (blank line) or comment marker, skip line  */
164
165      if ((pz_curr_file == pz_end) || (*pz_curr_file == '#'))
166        continue;
167      *pz_end = NUL;
168
169      process ();
170    } /*  for (;;) */
171
172#ifdef DO_STATS
173  if (VLEVEL( VERB_PROGRESS )) {
174    tSCC zFmt[] =
175      "\
176Processed %5d files containing %d bytes    \n\
177Applying  %5d fixes to %d files\n\
178Altering  %5d of them\n";
179
180    fprintf (stderr, zFmt, process_ct, ttl_data_size, apply_ct,
181             fixed_ct, altered_ct);
182          }
183#endif /* DO_STATS */
184
185# ifdef SEPARATE_FIX_PROC
186  unlink( pz_temp_file );
187#endif
188  exit (EXIT_SUCCESS);
189    }
190
191
192static void
193do_version (void)
194        {
195  static const char zFmt[] = "echo '%s'";
196  char zBuf[ 1024 ];
197
198  /* The 'version' option is really used to test that:
199     1.  The program loads correctly (no missing libraries)
200     2.  that we can compile all the regular expressions.
201     3.  we can correctly run our server shell process
202  */
203  run_compiles ();
204  sprintf (zBuf, zFmt, program_id);
205#ifndef SEPARATE_FIX_PROC
206  puts (zBuf + 5);
207  exit (strcmp (run_shell (zBuf), program_id));
208#else
209  exit (system (zBuf));
210#endif
211}
212
213/* * * * * * * * * * * * */
214
215void
216initialize ( int argc, char** argv )
217{
218  static const char var_not_found[] =
219#ifndef __STDC__
220    "fixincl ERROR:  %s environment variable not defined\n"
221#else
222    "fixincl ERROR:  %s environment variable not defined\n"
223    "each of these must be defined:\n"
224# define _ENV_(vv,mm,nn,tt) "\t" nn "  - " tt "\n"
225  ENV_TABLE
226# undef _ENV_
227#endif
228    ;
229
230  xmalloc_set_program_name (argv[0]);
231
232  switch (argc)
233  {
234    case 1:
235      break;
236
237    case 2:
238      if (strcmp (argv[1], "-v") == 0)
239        do_version ();
240      if (freopen (argv[1], "r", stdin) == (FILE*)NULL)
241  {
242          fprintf (stderr, "Error %d (%s) reopening %s as stdin\n",
243                   errno, xstrerror (errno), argv[1] );
244        exit (EXIT_FAILURE);
245      }
246      break;
247
248    default:
249      fputs ("fixincl ERROR:  too many command line arguments\n", stderr);
250        exit (EXIT_FAILURE);
251      }
252
253#ifdef SIGCHLD
254  /* We *MUST* set SIGCHLD to SIG_DFL so that the wait4() call will
255     receive the signal.  A different setting is inheritable */
256  signal (SIGCHLD, SIG_DFL);
257#endif
258
259#define _ENV_(v,m,n,t)   { tSCC var[] = n;  \
260  v = getenv (var); if (m && (v == NULL)) { \
261  fprintf (stderr, var_not_found, var);     \
262  exit (EXIT_FAILURE); } }
263
264ENV_TABLE
265
266#undef _ENV_
267
268  if (ISDIGIT ( *pz_verbose ))
269    verbose_level = (te_verbose)atoi( pz_verbose );
270  else
271    switch (*pz_verbose) {
272    case 's':
273    case 'S':
274      verbose_level = VERB_SILENT;     break;
275
276    case 'f':
277    case 'F':
278      verbose_level = VERB_FIXES;      break;
279
280    case 'a':
281    case 'A':
282      verbose_level = VERB_APPLIES;    break;
283
284    default:
285    case 'p':
286    case 'P':
287      verbose_level = VERB_PROGRESS;   break;
288
289    case 't':
290    case 'T':
291      verbose_level = VERB_TESTS;      break;
292
293    case 'e':
294    case 'E':
295      verbose_level = VERB_EVERYTHING; break;
296    }
297  if (verbose_level >= VERB_EVERYTHING) {
298    verbose_level = VERB_EVERYTHING;
299    fputs ("fixinc verbosity:  EVERYTHING\n", stderr);
300  }
301  while ((pz_find_base[0] == '.') && (pz_find_base[1] == '/'))
302    pz_find_base += 2;
303  if ((pz_find_base[0] != '.') || (pz_find_base[1] != NUL))
304    find_base_len = strlen( pz_find_base );
305
306  /*  Compile all the regular expressions now.
307      That way, it is done only once for the whole run.
308      */
309  run_compiles ();
310
311# ifdef SEPARATE_FIX_PROC
312  /* NULL as the first argument to `tempnam' causes it to DTRT
313     wrt the temporary directory where the file will be created.  */
314  pz_temp_file = tempnam( NULL, "fxinc" );
315# endif
316
317  signal (SIGQUIT, SIG_IGN);
318#ifdef SIGIOT
319  signal (SIGIOT,  SIG_IGN);
320#endif
321#ifdef SIGPIPE
322  signal (SIGPIPE, SIG_IGN);
323#endif
324  signal (SIGALRM, SIG_IGN);
325  signal (SIGTERM, SIG_IGN);
326}
327
328/* * * * * * * * * * * * *
329
330   load_file loads all the contents of a file into malloc-ed memory.
331   Its argument is the name of the file to read in; the returned
332   result is the NUL terminated contents of the file.  The file
333   is presumed to be an ASCII text file containing no NULs.  */
334char *
335load_file ( const char* fname )
336  {
337    struct stat stbf;
338  char* res;
339
340  if (stat (fname, &stbf) != 0)
341      {
342      if (NOT_SILENT)
343        fprintf (stderr, "error %d (%s) stat-ing %s\n",
344                 errno, xstrerror (errno), fname );
345        return (char *) NULL;
346      }
347  if (stbf.st_size == 0)
348    return (char *) NULL;
349
350  /*  Make the data map size one larger than the file size for documentation
351      purposes.  Truth is that there will be a following NUL character if
352      the file size is not a multiple of the page size.  If it is a multiple,
353      then this adjustment sometimes fails anyway.  */
354  data_map_size = stbf.st_size+1;
355  data_map_fd   = open (fname, O_RDONLY);
356  ttl_data_size += data_map_size-1;
357
358  if (data_map_fd < 0)
359    {
360      if (NOT_SILENT)
361        fprintf (stderr, "error %d (%s) opening %s for read\n",
362                 errno, xstrerror (errno), fname);
363        return (char *) NULL;
364      }
365
366#ifdef HAVE_MMAP_FILE
367  curr_data_mapped = BOOL_TRUE;
368
369  /*  IF the file size is a multiple of the page size,
370      THEN sometimes you will seg fault trying to access a trailing byte */
371  if ((stbf.st_size & (getpagesize()-1)) == 0)
372    res = (char*)BAD_ADDR;
373  else
374    res = (char*)mmap ((void*)NULL, data_map_size, PROT_READ,
375                       MAP_PRIVATE, data_map_fd, 0);
376  if (res == (char*)BAD_ADDR)
377#endif
378              {
379      FILE* fp = fdopen (data_map_fd, "r");
380      curr_data_mapped = BOOL_FALSE;
381      res = load_file_data (fp);
382    fclose (fp);
383  }
384
385  return res;
386    }
387
388static int
389machine_matches( tFixDesc* p_fixd )
390    {
391# ifndef SEPARATE_FIX_PROC
392          tSCC case_fmt[] = "case %s in\n";     /*  9 bytes, plus string */
393          tSCC esac_fmt[] =
394               " )\n    echo %s ;;\n* ) echo %s ;;\nesac";/*  4 bytes */
395          tSCC skip[] = "skip";                 /*  4 bytes */
396          tSCC run[] = "run";                   /*  3 bytes */
397          /* total bytes to add to machine sum:    49 - see fixincl.tpl */
398
399          const char **papz_machs = p_fixd->papz_machs;
400          char *pz;
401          const char *pz_sep = "";
402          tCC *pz_if_true;
403          tCC *pz_if_false;
404          char cmd_buf[ MACH_LIST_SIZE_LIMIT ]; /* size lim from fixincl.tpl */
405
406          /* Start the case statement */
407
408          sprintf (cmd_buf, case_fmt, pz_machine);
409          pz = cmd_buf + strlen (cmd_buf);
410
411          /*  Determine if a match means to apply the fix or not apply it */
412
413          if (p_fixd->fd_flags & FD_MACH_IFNOT)
414            {
415              pz_if_true  = skip;
416              pz_if_false = run;
417            }
418          else
419            {
420              pz_if_true  = run;
421              pz_if_false = skip;
422            }
423
424          /*  Emit all the machine names.  If there are more than one,
425              then we will insert " | \\\n" between the names  */
426
427          for (;;)
428            {
429              const char* pz_mach = *(papz_machs++);
430
431              if (pz_mach == (const char*) NULL)
432                break;
433              sprintf (pz, "%s  %s", pz_sep, pz_mach);
434              pz += strlen (pz);
435              pz_sep = " | \\\n";
436            }
437
438          /* Now emit the match and not-match actions and the esac */
439
440          sprintf (pz, esac_fmt, pz_if_true, pz_if_false);
441
442          /*  Run the script.
443              The result will start either with 's' or 'r'.  */
444
445          {
446            int skip;
447            pz = run_shell (cmd_buf);
448            skip = (*pz == 's');
449            free ( (void*)pz );
450            if (skip)
451              {
452                p_fixd->fd_flags |= FD_SKIP_TEST;
453		return BOOL_FALSE;
454              }
455           }
456
457  return BOOL_TRUE;
458# else /* is SEPARATE_FIX_PROC */
459  const char **papz_machs = p_fixd->papz_machs;
460  int invert = (p_fixd->fd_flags & FD_MACH_IFNOT) != 0;
461  for (;;)
462    {
463      const char* pz_mach = *(papz_machs++);
464
465      if (pz_mach == (const char*) NULL)
466        break;
467      if (strstr (pz_mach, "dos") != NULL && !invert)
468	return BOOL_TRUE;
469        }
470
471  p_fixd->fd_flags |= FD_SKIP_TEST;
472  return BOOL_FALSE;
473# endif
474}
475
476/* * * * * * * * * * * * *
477
478   run_compiles   run all the regexp compiles for all the fixes once.
479   */
480void
481run_compiles (void)
482{
483  tFixDesc *p_fixd = fixDescList;
484  int fix_ct = FIX_COUNT;
485  regex_t *p_re = xcalloc (REGEX_COUNT, sizeof (regex_t));
486
487  /*  Make sure compile_re does not stumble across invalid data */
488
489  memset (&incl_quote_re, '\0', sizeof (regex_t));
490
491  compile_re (incl_quote_pat, &incl_quote_re, 1,
492              "quoted include", "run_compiles");
493
494  /*  Allow machine name tests to be ignored (testing, mainly) */
495
496  if (pz_machine && ((*pz_machine == '\0') || (*pz_machine == '*')))
497    pz_machine = (char*)NULL;
498
499  /* FOR every fixup, ...  */
500  do
501    {
502      tTestDesc *p_test = p_fixd->p_test_desc;
503      int test_ct = p_fixd->test_ct;
504
505      /*  IF the machine type pointer is not NULL (we are not in test mode)
506             AND this test is for or not done on particular machines
507          THEN ...   */
508
509      if (  (pz_machine != NULL)
510         && (p_fixd->papz_machs != (const char**) NULL)
511         && ! machine_matches (p_fixd) )
512        continue;
513
514      /* FOR every test for the fixup, ...  */
515
516      while (--test_ct >= 0)
517        {
518          switch (p_test->type)
519            {
520            case TT_EGREP:
521            case TT_NEGREP:
522              p_test->p_test_regex = p_re++;
523              compile_re (p_test->pz_test_text, p_test->p_test_regex, 0,
524                          "select test", p_fixd->fix_name);
525            default: break;
526            }
527          p_test++;
528        }
529    }
530  while (p_fixd++, --fix_ct > 0);
531}
532
533
534/* * * * * * * * * * * * *
535
536   create_file  Create the output modified file.
537   Input:    the name of the file to create
538   Returns:  a file pointer to the new, open file  */
539
540#if defined(S_IRUSR) && defined(S_IWUSR) && \
541    defined(S_IRGRP) && defined(S_IROTH)
542
543#define S_IRALL	(S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)
544#else
545#   define S_IRALL 0644
546#endif
547
548#if defined(S_IRWXU) && defined(S_IRGRP) && defined(S_IXGRP) && \
549    defined(S_IROTH) && defined(S_IXOTH)
550
551#   define S_DIRALL (S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH)
552#else
553#   define S_DIRALL 0755
554#endif
555
556
557static FILE *
558create_file (void)
559{
560  int fd;
561  FILE *pf;
562  char fname[MAXPATHLEN];
563
564  sprintf (fname, "%s/%s", pz_dest_dir, pz_curr_file + find_base_len);
565
566  fd = open (fname, O_WRONLY | O_CREAT | O_TRUNC, S_IRALL);
567
568  /*  We may need to create the directories needed... */
569  if ((fd < 0) && (errno == ENOENT))
570    {
571      char *pz_dir = strchr (fname + 1, '/');
572      struct stat stbf;
573
574      while (pz_dir != (char *) NULL)
575        {
576          *pz_dir = NUL;
577          if (stat (fname, &stbf) < 0)
578            {
579              mkdir (fname, S_IFDIR | S_DIRALL);
580            }
581
582          *pz_dir = '/';
583          pz_dir = strchr (pz_dir + 1, '/');
584        }
585
586      /*  Now, lets try the open again... */
587      fd = open (fname, O_WRONLY | O_CREAT | O_TRUNC, S_IRALL);
588    }
589  if (fd < 0)
590    {
591      fprintf (stderr, "Error %d (%s) creating %s\n",
592               errno, xstrerror (errno), fname);
593      exit (EXIT_FAILURE);
594    }
595  if (NOT_SILENT)
596    fprintf (stderr, "Fixed:  %s\n", pz_curr_file);
597  pf = fdopen (fd, "w");
598
599  /*
600   *  IF pz_machine is NULL, then we are in some sort of test mode.
601   *  Do not insert the current directory name.  Use a constant string.
602   */
603  fprintf (pf, z_std_preamble,
604           (pz_machine == NULL)
605           ? "fixinc/tests/inc"
606           : pz_input_dir,
607           pz_curr_file);
608
609  return pf;
610}
611
612
613/* * * * * * * * * * * * *
614
615  test_test   make sure a shell-style test expression passes.
616  Input:  a pointer to the descriptor of the test to run and
617          the name of the file that we might want to fix
618  Result: APPLY_FIX or SKIP_FIX, depending on the result of the
619          shell script we run.  */
620#ifndef SEPARATE_FIX_PROC
621static int
622test_test (tTestDesc* p_test, char* pz_test_file)
623{
624  tSCC cmd_fmt[] =
625"file=%s\n\
626if ( test %s ) > /dev/null 2>&1\n\
627then echo TRUE\n\
628else echo FALSE\n\
629fi";
630
631  char *pz_res;
632  int res;
633
634  static char cmd_buf[4096];
635
636  sprintf (cmd_buf, cmd_fmt, pz_test_file, p_test->pz_test_text);
637  pz_res = run_shell (cmd_buf);
638
639  switch (*pz_res) {
640  case 'T':
641    res = APPLY_FIX;
642    break;
643
644  case 'F':
645    res = SKIP_FIX;
646    break;
647
648  default:
649    fprintf (stderr, "Script yielded bogus result of `%s':\n%s\n\n",
650             pz_res, cmd_buf );
651  }
652
653  free ((void *) pz_res);
654  return res;
655}
656#else
657/*
658 *  IF we are in MS-DOS land, then whatever shell-type test is required
659 *  will, by definition, fail
660 */
661#define test_test(t,tf)  SKIP_FIX
662#endif
663
664/* * * * * * * * * * * * *
665
666  egrep_test   make sure an egrep expression is found in the file text.
667  Input:  a pointer to the descriptor of the test to run and
668          the pointer to the contents of the file under suspicion
669  Result: APPLY_FIX if the pattern is found, SKIP_FIX otherwise
670
671  The caller may choose to reverse meaning if the sense of the test
672  is inverted.  */
673
674static int
675egrep_test (char* pz_data, tTestDesc* p_test)
676{
677#ifdef DEBUG
678  if (p_test->p_test_regex == 0)
679    fprintf (stderr, "fixincl ERROR RE not compiled:  `%s'\n",
680             p_test->pz_test_text);
681#endif
682  if (xregexec (p_test->p_test_regex, pz_data, 0, 0, 0) == 0)
683    return APPLY_FIX;
684  return SKIP_FIX;
685}
686
687
688/* * * * * * * * * * * * *
689
690  quoted_file_exists  Make sure that a file exists before we emit
691  the file name.  If we emit the name, our invoking shell will try
692  to copy a non-existing file into the destination directory.  */
693
694static int
695quoted_file_exists (const char* pz_src_path,
696                    const char* pz_file_path,
697                    const char* pz_file)
698{
699  char z[ MAXPATHLEN ];
700  char* pz;
701  sprintf (z, "%s/%s/", pz_src_path, pz_file_path);
702  pz = z + strlen ( z );
703
704  for (;;) {
705    char ch = *pz_file++;
706    if (! ISGRAPH( ch ))
707      return 0;
708    if (ch == '"')
709      break;
710    *pz++ = ch;
711  }
712  *pz = '\0';
713  {
714    struct stat s;
715    if (stat (z, &s) != 0)
716      return 0;
717    return S_ISREG( s.st_mode );
718  }
719}
720
721
722/* * * * * * * * * * * * *
723 *
724   extract_quoted_files
725
726   The syntax, `#include "file.h"' specifies that the compiler is to
727   search the local directory of the current file before the include
728   list.  Consequently, if we have modified a header and stored it in
729   another directory, any files that are included by that modified
730   file in that fashion must also be copied into this new directory.
731   This routine finds those flavors of #include and for each one found
732   emits a triple of:
733
734    1.  source directory of the original file
735    2.  the relative path file name of the #includ-ed file
736    3.  the full destination path for this file
737
738   Input:  the text of the file, the file name and a pointer to the
739           match list where the match information was stored.
740   Result: internally nothing.  The results are written to stdout
741           for interpretation by the invoking shell  */
742
743
744static void
745extract_quoted_files (char* pz_data,
746                      const char* pz_fixed_file,
747                      regmatch_t* p_re_match)
748{
749  char *pz_dir_end = strrchr (pz_fixed_file, '/');
750  char *pz_incl_quot = pz_data;
751
752  if (VLEVEL( VERB_APPLIES ))
753    fprintf (stderr, "Quoted includes in %s\n", pz_fixed_file);
754
755  /*  Set "pz_fixed_file" to point to the containing subdirectory of the source
756      If there is none, then it is in our current directory, ".".   */
757
758  if (pz_dir_end == (char *) NULL)
759    pz_fixed_file = ".";
760  else
761    *pz_dir_end = '\0';
762
763  for (;;)
764    {
765      pz_incl_quot += p_re_match->rm_so;
766
767      /*  Skip forward to the included file name */
768      while (*pz_incl_quot != '"')
769        pz_incl_quot++;
770
771      if (quoted_file_exists (pz_src_dir, pz_fixed_file, pz_incl_quot))
772        {
773          /* Print the source directory and the subdirectory
774             of the file in question.  */
775          printf ("%s  %s/", pz_src_dir, pz_fixed_file);
776          pz_dir_end = pz_incl_quot;
777
778          /* Append to the directory the relative path of the desired file */
779          while (*pz_incl_quot != '"')
780            putc (*pz_incl_quot++, stdout);
781
782          /* Now print the destination directory appended with the
783             relative path of the desired file */
784          printf ("  %s/%s/", pz_dest_dir, pz_fixed_file);
785          while (*pz_dir_end != '"')
786            putc (*pz_dir_end++, stdout);
787
788          /* End of entry */
789          putc ('\n', stdout);
790        }
791
792      /* Find the next entry */
793      if (xregexec (&incl_quote_re, pz_incl_quot, 1, p_re_match, 0) != 0)
794        break;
795    }
796}
797
798
799/* * * * * * * * * * * * *
800
801    Somebody wrote a *_fix subroutine that we must call.
802    */
803#ifndef SEPARATE_FIX_PROC
804static int
805internal_fix (int read_fd, tFixDesc* p_fixd)
806{
807  int fd[2];
808
809  if (pipe( fd ) != 0)
810    {
811      fprintf (stderr, "Error %d on pipe(2) call\n", errno );
812      exit (EXIT_FAILURE);
813    }
814
815  for (;;)
816    {
817      pid_t childid = fork();
818
819      switch (childid)
820        {
821        case -1:
822          break;
823
824        case 0:
825          close (fd[0]);
826          goto do_child_task;
827
828        default:
829          /*
830           *  Parent process
831           */
832          close (read_fd);
833          close (fd[1]);
834          return fd[0];
835        }
836
837      /*
838       *  Parent in error
839       */
840      fprintf (stderr, z_fork_err, errno, xstrerror (errno),
841               p_fixd->fix_name);
842      {
843        static int failCt = 0;
844        if ((errno != EAGAIN) || (++failCt > 10))
845          exit (EXIT_FAILURE);
846        sleep (1);
847      }
848    } do_child_task:;
849
850  /*
851   *  Close our current stdin and stdout
852   */
853  close (STDIN_FILENO);
854  close (STDOUT_FILENO);
855  UNLOAD_DATA();
856
857  /*
858   *  Make the fd passed in the stdin, and the write end of
859   *  the new pipe become the stdout.
860   */
861  fcntl (fd[1], F_DUPFD, STDOUT_FILENO);
862  fcntl (read_fd, F_DUPFD, STDIN_FILENO);
863
864  apply_fix (p_fixd, pz_curr_file);
865  exit (0);
866}
867#endif /* !SEPARATE_FIX_PROC */
868
869
870#ifdef SEPARATE_FIX_PROC
871static void
872fix_with_system (tFixDesc* p_fixd,
873                 tCC* pz_fix_file,
874                 tCC* pz_file_source,
875                 tCC* pz_temp_file)
876{
877  char*  pz_cmd;
878  char*  pz_scan;
879  size_t argsize;
880
881  if (p_fixd->fd_flags & FD_SUBROUTINE)
882    {
883      tSCC z_applyfix_prog[] = "/fixinc/applyfix";
884
885      argsize = 32
886              + strlen( pz_orig_dir )
887              + sizeof( z_applyfix_prog )
888              + strlen( pz_fix_file )
889              + strlen( pz_file_source )
890              + strlen( pz_temp_file );
891
892      pz_cmd = xmalloc (argsize);
893
894      strcpy( pz_cmd, pz_orig_dir );
895      pz_scan = pz_cmd + strlen( pz_orig_dir );
896      strcpy( pz_scan, z_applyfix_prog );
897      pz_scan += sizeof( z_applyfix_prog ) - 1;
898      *(pz_scan++) = ' ';
899
900      /*
901       *  Now add the fix number and file names that may be needed
902       */
903      sprintf (pz_scan, "%ld \'%s\' \'%s\' \'%s\'", p_fixd - fixDescList,
904	       pz_fix_file, pz_file_source, pz_temp_file);
905    }
906  else /* NOT an "internal" fix: */
907    {
908      size_t parg_size;
909#ifdef __MSDOS__
910      /* Don't use the "src > dstX; rm -f dst; mv -f dstX dst" trick:
911         dst is a temporary file anyway, so we know there's no other
912         file by that name; and DOS's system(3) doesn't mind to
913         clobber existing file in redirection.  Besides, with DOS 8+3
914         limited file namespace, we can easily lose if dst already has
915         an extension that is 3 or more characters long.
916
917         I do not think the 8+3 issue is relevant because all the files
918         we operate on are named "*.h", making 8+2 adequate.  Anyway,
919         the following bizarre use of 'cat' only works on DOS boxes.
920         It causes the file to be dropped into a temporary file for
921         'cat' to read (pipes do not work on DOS).  */
922      tSCC   z_cmd_fmt[] = " \'%s\' | cat > \'%s\'";
923#else
924      /* Don't use positional formatting arguments because some lame-o
925         implementations cannot cope  :-(.  */
926      tSCC   z_cmd_fmt[] = " %s > %sX ; rm -f %s; mv -f %sX %s";
927#endif
928      tCC**  ppArgs = p_fixd->patch_args;
929
930      argsize = sizeof( z_cmd_fmt ) + strlen( pz_temp_file )
931              + strlen( pz_file_source );
932      parg_size = argsize;
933
934
935      /*
936       *  Compute the size of the command line.  Add lotsa extra space
937       *  because some of the args to sed use lotsa single quotes.
938       *  (This requires three extra bytes per quote.  Here we allow
939       *  for up to 8 single quotes for each argument, including the
940       *  command name "sed" itself.  Nobody will *ever* need more. :)
941       */
942      for (;;)
943        {
944          tCC* p_arg = *(ppArgs++);
945          if (p_arg == NULL)
946            break;
947          argsize += 24 + strlen( p_arg );
948        }
949
950      /* Estimated buffer size we will need.  */
951      pz_scan = pz_cmd = xmalloc (argsize);
952      /* How much of it do we allot to the program name and its
953         arguments.  */
954      parg_size = argsize - parg_size;
955
956      ppArgs = p_fixd->patch_args;
957
958      /*
959       *  Copy the program name, unquoted
960       */
961      {
962        tCC*   pArg = *(ppArgs++);
963        for (;;)
964          {
965            char ch = *(pArg++);
966            if (ch == NUL)
967        break;
968            *(pz_scan++) = ch;
969    }
970}
971
972      /*
973       *  Copy the program arguments, quoted
974       */
975      for (;;)
976        {
977          tCC*   pArg = *(ppArgs++);
978	  char*  pz_scan_save;
979          if (pArg == NULL)
980            break;
981          *(pz_scan++) = ' ';
982          pz_scan = make_raw_shell_str( pz_scan_save = pz_scan, pArg,
983					parg_size - (pz_scan - pz_cmd) );
984	  /*
985	   *  Make sure we don't overflow the buffer due to sloppy
986	   *  size estimation.
987	   */
988	  while (pz_scan == (char*)NULL)
989	    {
990	      size_t already_filled = pz_scan_save - pz_cmd;
991	      pz_cmd = xrealloc (pz_cmd, argsize += 100);
992	      pz_scan_save = pz_scan = pz_cmd + already_filled;
993	      parg_size += 100;
994	      pz_scan = make_raw_shell_str( pz_scan, pArg,
995					    parg_size - (pz_scan - pz_cmd) );
996	    }
997        }
998
999      /*
1000       *  add the file machinations.
1001       */
1002#ifdef __MSDOS__
1003      sprintf (pz_scan, z_cmd_fmt, pz_file_source, pz_temp_file );
1004#else
1005      sprintf (pz_scan, z_cmd_fmt, pz_file_source, pz_temp_file,
1006               pz_temp_file, pz_temp_file, pz_temp_file);
1007#endif
1008    }
1009  system( pz_cmd );
1010  free( (void*)pz_cmd );
1011}
1012
1013/* * * * * * * * * * * * *
1014
1015    This loop should only cycle for 1/2 of one loop.
1016    "chain_open" starts a process that uses "read_fd" as
1017    its stdin and returns the new fd this process will use
1018    for stdout.  */
1019
1020#else /* is *NOT* SEPARATE_FIX_PROC */
1021static int
1022start_fixer (int read_fd, tFixDesc* p_fixd, char* pz_fix_file)
1023{
1024  tCC* pz_cmd_save;
1025  char* pz_cmd;
1026
1027  if ((p_fixd->fd_flags & FD_SUBROUTINE) != 0)
1028    return internal_fix (read_fd, p_fixd);
1029
1030  if ((p_fixd->fd_flags & FD_SHELL_SCRIPT) == 0)
1031    pz_cmd = (char*)NULL;
1032  else
1033    {
1034      tSCC z_cmd_fmt[] = "file='%s'\n%s";
1035      pz_cmd = xmalloc (strlen (p_fixd->patch_args[2])
1036			+ sizeof (z_cmd_fmt) + strlen (pz_fix_file));
1037      sprintf (pz_cmd, z_cmd_fmt, pz_fix_file, p_fixd->patch_args[2]);
1038      pz_cmd_save = p_fixd->patch_args[2];
1039      p_fixd->patch_args[2] = pz_cmd;
1040    }
1041
1042  /*  Start a fix process, handing off the  previous read fd for its
1043      stdin and getting a new fd that reads from the fix process' stdout.
1044      We normally will not loop, but we will up to 10 times if we keep
1045      getting "EAGAIN" errors.
1046
1047      */
1048  for (;;)
1049    {
1050      static int failCt = 0;
1051      int fd;
1052
1053      fd = chain_open (read_fd,
1054                       (tCC **) p_fixd->patch_args,
1055                       (process_chain_head == -1)
1056                       ? &process_chain_head : (pid_t *) NULL);
1057
1058      if (fd != -1)
1059        {
1060          read_fd = fd;
1061          break;
1062        }
1063
1064      fprintf (stderr, z_fork_err, errno, xstrerror (errno),
1065               p_fixd->fix_name);
1066
1067      if ((errno != EAGAIN) || (++failCt > 10))
1068        exit (EXIT_FAILURE);
1069      sleep (1);
1070    }
1071
1072  /*  IF we allocated a shell script command,
1073      THEN free it and restore the command format to the fix description */
1074  if (pz_cmd != (char*)NULL)
1075    {
1076      free ((void*)pz_cmd);
1077      p_fixd->patch_args[2] = pz_cmd_save;
1078    }
1079
1080  return read_fd;
1081}
1082#endif
1083
1084
1085/* * * * * * * * * * * * *
1086
1087   Process the potential fixes for a particular include file.
1088   Input:  the original text of the file and the file's name
1089   Result: none.  A new file may or may not be created.  */
1090
1091static t_bool
1092fix_applies (tFixDesc* p_fixd)
1093    {
1094  const char *pz_fname = pz_curr_file;
1095  const char *pz_scan = p_fixd->file_list;
1096      int test_ct;
1097  tTestDesc *p_test;
1098
1099# ifdef SEPARATE_FIX_PROC
1100  /*
1101   *  There is only one fix that uses a shell script as of this writing.
1102   *  I hope to nuke it anyway, it does not apply to DOS and it would
1103   *  be painful to implement.  Therefore, no "shell" fixes for DOS.
1104   */
1105  if (p_fixd->fd_flags & (FD_SHELL_SCRIPT | FD_SKIP_TEST))
1106    return BOOL_FALSE;
1107# else
1108      if (p_fixd->fd_flags & FD_SKIP_TEST)
1109    return BOOL_FALSE;
1110# endif
1111
1112      /*  IF there is a file name restriction,
1113          THEN ensure the current file name matches one in the pattern  */
1114
1115  if (pz_scan != (char *) NULL)
1116        {
1117          size_t name_len;
1118
1119          while ((pz_fname[0] == '.') && (pz_fname[1] == '/'))
1120            pz_fname += 2;
1121          name_len = strlen (pz_fname);
1122
1123          for (;;)
1124            {
1125              pz_scan = strstr (pz_scan + 1, pz_fname);
1126              /*  IF we can't match the string at all,
1127                  THEN bail  */
1128              if (pz_scan == (char *) NULL)
1129            return BOOL_FALSE;
1130
1131              /*  IF the match is surrounded by the '|' markers,
1132                  THEN we found a full match -- time to run the tests  */
1133
1134              if ((pz_scan[-1] == '|') && (pz_scan[name_len] == '|'))
1135                break;
1136            }
1137        }
1138
1139      /*  FOR each test, see if it fails.
1140          IF it does fail, then we go on to the next test */
1141
1142      for (p_test = p_fixd->p_test_desc, test_ct = p_fixd->test_ct;
1143           test_ct-- > 0;
1144           p_test++)
1145        {
1146          switch (p_test->type)
1147            {
1148            case TT_TEST:
1149          if (test_test (p_test, pz_curr_file) != APPLY_FIX) {
1150#ifdef DEBUG
1151            if (VLEVEL( VERB_EVERYTHING ))
1152              fprintf (stderr, z_failed, "TEST", p_fixd->fix_name,
1153                       pz_fname, p_fixd->test_ct - test_ct);
1154#endif
1155            return BOOL_FALSE;
1156                }
1157              break;
1158
1159            case TT_EGREP:
1160          if (egrep_test (pz_curr_data, p_test) != APPLY_FIX) {
1161#ifdef DEBUG
1162            if (VLEVEL( VERB_EVERYTHING ))
1163              fprintf (stderr, z_failed, "EGREP", p_fixd->fix_name,
1164                       pz_fname, p_fixd->test_ct - test_ct);
1165#endif
1166            return BOOL_FALSE;
1167                }
1168              break;
1169
1170            case TT_NEGREP:
1171          if (egrep_test (pz_curr_data, p_test) == APPLY_FIX) {
1172#ifdef DEBUG
1173            if (VLEVEL( VERB_EVERYTHING ))
1174              fprintf (stderr, z_failed, "NEGREP", p_fixd->fix_name,
1175                       pz_fname, p_fixd->test_ct - test_ct);
1176#endif
1177            /*  Negated sense  */
1178            return BOOL_FALSE;
1179          }
1180          break;
1181
1182        case TT_FUNCTION:
1183          if (run_test (p_test->pz_test_text, pz_curr_file, pz_curr_data)
1184              != APPLY_FIX) {
1185#ifdef DEBUG
1186            if (VLEVEL( VERB_EVERYTHING ))
1187              fprintf (stderr, z_failed, "FTEST", p_fixd->fix_name,
1188                       pz_fname, p_fixd->test_ct - test_ct);
1189#endif
1190            return BOOL_FALSE;
1191                }
1192              break;
1193            }
1194        }
1195
1196  return BOOL_TRUE;
1197}
1198
1199
1200/* * * * * * * * * * * * *
1201
1202   Write out a replacement file  */
1203
1204static void
1205write_replacement (tFixDesc* p_fixd)
1206            {
1207   const char* pz_text = p_fixd->patch_args[0];
1208
1209   if ((pz_text == (char*)NULL) || (*pz_text == NUL))
1210     return;
1211
1212   {
1213     FILE* out_fp = create_file ();
1214     fputs (pz_text, out_fp);
1215     fclose (out_fp);
1216   }
1217    }
1218
1219
1220/* * * * * * * * * * * * *
1221
1222    We have work to do.  Read back in the output
1223      of the filtering chain.  Compare each byte as we read it with
1224      the contents of the original file.  As soon as we find any
1225      difference, we will create the output file, write out all
1226      the matched text and then copy any remaining data from the
1227      output of the filter chain.
1228      */
1229static void
1230test_for_changes (int read_fd)
1231  {
1232    FILE *in_fp = fdopen (read_fd, "r");
1233    FILE *out_fp = (FILE *) NULL;
1234  unsigned char *pz_cmp = (unsigned char*)pz_curr_data;
1235
1236#ifdef DO_STATS
1237  fixed_ct++;
1238#endif
1239    for (;;)
1240      {
1241        int ch;
1242
1243        ch = getc (in_fp);
1244        if (ch == EOF)
1245          break;
1246      ch &= 0xFF; /* all bytes are 8 bits */
1247
1248        /*  IF we are emitting the output
1249            THEN emit this character, too.
1250            */
1251        if (out_fp != (FILE *) NULL)
1252          putc (ch, out_fp);
1253
1254        /*  ELSE if this character does not match the original,
1255            THEN now is the time to start the output.
1256            */
1257        else if (ch != *pz_cmp)
1258          {
1259          out_fp = create_file ();
1260
1261#ifdef DO_STATS
1262          altered_ct++;
1263#endif
1264          /*  IF there are matched data, write the matched part now. */
1265          if ((char*)pz_cmp != pz_curr_data)
1266            fwrite (pz_curr_data, (size_t)((char*)pz_cmp - pz_curr_data),
1267					1, out_fp);
1268
1269            /*  Emit the current unmatching character */
1270            putc (ch, out_fp);
1271          }
1272        else
1273          /*  ELSE the character matches.  Advance the compare ptr */
1274          pz_cmp++;
1275      }
1276
1277    /*  IF we created the output file, ... */
1278    if (out_fp != (FILE *) NULL)
1279      {
1280        regmatch_t match;
1281
1282        /* Close the file and see if we have to worry about
1283	   `#include "file.h"' constructs.  */
1284        fclose (out_fp);
1285      if (xregexec (&incl_quote_re, pz_curr_data, 1, &match, 0) == 0)
1286        extract_quoted_files (pz_curr_data, pz_curr_file, &match);
1287      }
1288
1289    fclose (in_fp);
1290  close (read_fd);  /* probably redundant, but I'm paranoid */
1291}
1292
1293
1294/* * * * * * * * * * * * *
1295
1296   Process the potential fixes for a particular include file.
1297   Input:  the original text of the file and the file's name
1298   Result: none.  A new file may or may not be created.  */
1299
1300void
1301process (void)
1302{
1303  tFixDesc *p_fixd = fixDescList;
1304  int todo_ct = FIX_COUNT;
1305  int read_fd = -1;
1306# ifndef SEPARATE_FIX_PROC
1307  int num_children = 0;
1308# else /* is SEPARATE_FIX_PROC */
1309  char* pz_file_source = pz_curr_file;
1310# endif
1311
1312  if (access (pz_curr_file, R_OK) != 0)
1313    {
1314      int erno = errno;
1315      fprintf (stderr, "Cannot access %s from %s\n\terror %d (%s)\n",
1316               pz_curr_file, getcwd ((char *) NULL, MAXPATHLEN),
1317               erno, xstrerror (erno));
1318      return;
1319    }
1320
1321  pz_curr_data = load_file (pz_curr_file);
1322  if (pz_curr_data == (char *) NULL)
1323    return;
1324
1325#ifdef DO_STATS
1326  process_ct++;
1327#endif
1328  if (VLEVEL( VERB_PROGRESS ) && have_tty)
1329    fprintf (stderr, "%6lu %-50s   \r",
1330	     (unsigned long) data_map_size, pz_curr_file);
1331
1332# ifndef SEPARATE_FIX_PROC
1333  process_chain_head = NOPROCESS;
1334
1335  /* For every fix in our fix list, ...  */
1336  for (; todo_ct > 0; p_fixd++, todo_ct--)
1337    {
1338      if (! fix_applies (p_fixd))
1339        continue;
1340
1341      if (VLEVEL( VERB_APPLIES ))
1342        fprintf (stderr, "Applying %-24s to %s\n",
1343                 p_fixd->fix_name, pz_curr_file);
1344
1345      if (p_fixd->fd_flags & FD_REPLACEMENT)
1346        {
1347          write_replacement (p_fixd);
1348          UNLOAD_DATA();
1349          return;
1350        }
1351
1352      /*  IF we do not have a read pointer,
1353          THEN this is the first fix for the current file.
1354          Open the source file.  That will be used as stdin for
1355          the first fix.  Any subsequent fixes will use the
1356          stdout descriptor of the previous fix for its stdin.  */
1357
1358      if (read_fd == -1)
1359        {
1360          read_fd = open (pz_curr_file, O_RDONLY);
1361          if (read_fd < 0)
1362            {
1363              fprintf (stderr, "Error %d (%s) opening %s\n", errno,
1364                       xstrerror (errno), pz_curr_file);
1365              exit (EXIT_FAILURE);
1366            }
1367
1368          /*  Ensure we do not get duplicate output */
1369
1370          fflush (stdout);
1371        }
1372
1373      read_fd = start_fixer (read_fd, p_fixd, pz_curr_file);
1374      num_children++;
1375    }
1376
1377  /*  IF we have a read-back file descriptor,
1378      THEN check for changes and write output if changed.   */
1379
1380  if (read_fd >= 0)
1381    {
1382      test_for_changes (read_fd);
1383#ifdef DO_STATS
1384      apply_ct += num_children;
1385#endif
1386  /* Wait for child processes created by chain_open()
1387         to avoid leaving zombies.  */
1388      do  {
1389    wait ((int *) NULL);
1390      } while (--num_children > 0);
1391    }
1392
1393# else /* is SEPARATE_FIX_PROC */
1394
1395  for (; todo_ct > 0; p_fixd++, todo_ct--)
1396    {
1397      if (! fix_applies (p_fixd))
1398        continue;
1399
1400      if (VLEVEL( VERB_APPLIES ))
1401        fprintf (stderr, "Applying %-24s to %s\n",
1402                 p_fixd->fix_name, pz_curr_file);
1403
1404      if (p_fixd->fd_flags & FD_REPLACEMENT)
1405        {
1406          write_replacement (p_fixd);
1407          UNLOAD_DATA();
1408          return;
1409        }
1410      fix_with_system (p_fixd, pz_curr_file, pz_file_source, pz_temp_file);
1411      pz_file_source = pz_temp_file;
1412    }
1413
1414  read_fd = open (pz_temp_file, O_RDONLY);
1415  if (read_fd < 0)
1416    {
1417      if (errno != ENOENT)
1418        fprintf (stderr, "error %d (%s) opening output (%s) for read\n",
1419                 errno, xstrerror (errno), pz_temp_file);
1420    }
1421  else
1422    {
1423      test_for_changes (read_fd);
1424      /* Unlinking a file while it is still open is a Bad Idea on
1425         DOS/Windows.  */
1426      close (read_fd);
1427      unlink (pz_temp_file);
1428    }
1429
1430# endif
1431  UNLOAD_DATA();
1432}
1433