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