OSDN Git Service

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