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