OSDN Git Service

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