| |
| /* Install modified versions of certain ANSI-incompatible system header |
| files which are fixed to work correctly with ANSI C and placed in a |
| directory that GNU C will search. |
| |
| Copyright (C) 1997, 1998, 1999 Free Software Foundation, Inc. |
| |
| This file is part of GNU CC. |
| |
| GNU CC is free software; you can redistribute it and/or modify |
| it under the terms of the GNU General Public License as published by |
| the Free Software Foundation; either version 2, or (at your option) |
| any later version. |
| |
| GNU CC is distributed in the hope that it will be useful, |
| but WITHOUT ANY WARRANTY; without even the implied warranty of |
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| GNU General Public License for more details. |
| |
| You should have received a copy of the GNU General Public License |
| along with GNU CC; see the file COPYING. If not, write to |
| the Free Software Foundation, 59 Temple Place - Suite 330, |
| Boston, MA 02111-1307, USA. */ |
| |
| #ifdef FIXINC_BROKEN |
| /* The fixincl program is known to not run properly on this particular |
| system. Instead of producing a probably broken executable, we force |
| a compilation error and let the mkfixinc.sh script install the |
| inclhack.sh shell script instead. */ |
| # include "The fixincl program does not work properly on this system!" |
| #endif |
| |
| #include "auto-host.h" |
| |
| #include <sys/types.h> |
| #include <sys/param.h> |
| #include <sys/stat.h> |
| #ifdef HAVE_SYS_WAIT_H |
| #include <sys/wait.h> |
| #endif |
| #include <signal.h> |
| #include <stdio.h> |
| #ifdef HAVE_UNISTD_H |
| #include <unistd.h> |
| #endif |
| #include <stdlib.h> |
| #include <errno.h> |
| #include <string.h> |
| #ifdef HAVE_FCNTL_H |
| #include <fcntl.h> |
| #endif |
| #include <ctype.h> |
| |
| #include "regex.h" |
| #include "server.h" |
| |
| static const char program_id[] = "fixincl version 1.0"; |
| |
| #define MINIMUM_MAXIMUM_LINES 128 |
| |
| /* If this particular system's header files define the macro `MAXPATHLEN', |
| we happily take advantage of it; otherwise we use a value which ought |
| to be large enough. */ |
| #ifndef MAXPATHLEN |
| # define MAXPATHLEN 4096 |
| #endif |
| #define NAME_TABLE_SIZE (MINIMUM_MAXIMUM_LINES * MAXPATHLEN) |
| |
| char *file_name_buf; |
| |
| #define tSCC static const char |
| #define tCC const char |
| #define tSC static char |
| |
| typedef int t_success; |
| |
| #define FAILURE (-1) |
| #define SUCCESS 0 |
| #define PROBLEM 1 |
| |
| #define SUCCEEDED(p) ((p) == SUCCESS) |
| #define SUCCESSFUL(p) SUCCEEDED (p) |
| #define FAILED(p) ((p) < SUCCESS) |
| #define HADGLITCH(p) ((p) > SUCCESS) |
| |
| #define NUL '\0' |
| |
| /* Test Descriptor |
| |
| Each fix may have associated tests that determine |
| whether the fix needs to be applied or not. |
| Each test has a type (from the te_test_type enumeration); |
| associated test text; and, if the test is TT_EGREP or |
| the negated form TT_NEGREP, a pointer to the compiled |
| version of the text string. |
| |
| */ |
| typedef enum |
| { |
| TT_TEST, TT_EGREP, TT_NEGREP |
| } te_test_type; |
| |
| typedef struct test_desc tTestDesc; |
| |
| struct test_desc |
| { |
| te_test_type type; |
| const char *pz_test_text; |
| regex_t *p_test_regex; |
| }; |
| |
| typedef struct patch_desc tPatchDesc; |
| |
| /* Fix Descriptor |
| |
| Everything you ever wanted to know about how to apply |
| a particular fix (which files, how to qualify them, |
| how to actually make the fix, etc...) |
| |
| */ |
| #define FD_MACH_ONLY 0x0000 |
| #define FD_MACH_IFNOT 0x0001 |
| #define FD_SKIP_TEST 0x8000 |
| |
| typedef struct fix_desc tFixDesc; |
| struct fix_desc |
| { |
| const char* fix_name; /* Name of the fix */ |
| const char* file_list; /* List of files it applies to */ |
| const char** papz_machs; /* List of machine/os-es it applies to */ |
| regex_t* unused; |
| int test_ct; |
| int fd_flags; |
| tTestDesc* p_test_desc; |
| const char** patch_args; |
| }; |
| |
| /* Working environment strings. Essentially, invocation 'options'. */ |
| char *pz_dest_dir = NULL; |
| char *pz_src_dir = NULL; |
| char *pz_machine = NULL; |
| char *pz_find_base = NULL; |
| int find_base_len = 0; |
| |
| pid_t process_chain_head = (pid_t) -1; |
| |
| const char incl_quote_pat[] = "^[ \t]*#[ \t]*include[ \t]*\"[^/]"; |
| regex_t incl_quote_re; |
| |
| char *load_file (const char *pzFile); |
| void process (char *data, const char *file); |
| void run_compiles (void); |
| |
| #include "fixincl.x" |
| |
| /* * * * * * * * * * * * * * * * * * * |
| * |
| * MAIN ROUTINE |
| */ |
| int |
| main (argc, argv) |
| int argc; |
| char **argv; |
| { |
| static const char gnu_lib_mark[] = |
| "This file is part of the GNU C Library"; |
| static const char var_not_found[] = |
| "fixincl ERROR: %s environment variable not defined\n"; |
| |
| #ifndef NO_BOGOSITY_LIMITS |
| # define BOGUS_LIMIT MINIMUM_MAXIMUM_LINES |
| size_t loop_ct; |
| #endif |
| |
| char *apz_names[BOGUS_LIMIT]; |
| size_t file_name_ct; |
| |
| /* Before anything else, ensure we can allocate our file name buffer. */ |
| file_name_buf = (char *) malloc (NAME_TABLE_SIZE); |
| if (file_name_buf == (char *) NULL) |
| { |
| fprintf (stderr, "fixincl cannot allocate 0x%08X bytes\n", |
| NAME_TABLE_SIZE); |
| exit (EXIT_FAILURE); |
| } |
| |
| switch (argc) |
| { |
| case 1: |
| break; |
| |
| case 2: |
| if (strcmp (argv[1], "-v") == 0) |
| { |
| static const char zFmt[] = "echo '%s'"; |
| |
| /* The 'version' option is really used to test that: |
| 1. The program loads correctly (no missing libraries) |
| 2. we can correctly run our server shell process |
| 3. that we can compile all the regular expressions. |
| */ |
| run_compiles (); |
| sprintf (file_name_buf, zFmt, program_id); |
| fputs (file_name_buf + 5, stdout); |
| exit (strcmp (run_shell (file_name_buf), program_id)); |
| } |
| freopen (argv[1], "r", stdin); |
| break; |
| |
| default: |
| fputs ("fixincl ERROR: too many command line arguments\n", stderr); |
| exit (EXIT_FAILURE); |
| } |
| |
| { |
| static const char var[] = "TARGET_MACHINE"; |
| pz_machine = getenv (var); |
| if (pz_machine == (char *) NULL) |
| { |
| fprintf (stderr, var_not_found, var); |
| exit (EXIT_FAILURE); |
| } |
| } |
| |
| { |
| static const char var[] = "DESTDIR"; |
| pz_dest_dir = getenv (var); |
| if (pz_dest_dir == (char *) NULL) |
| { |
| fprintf (stderr, var_not_found, var); |
| exit (EXIT_FAILURE); |
| } |
| } |
| |
| { |
| static const char var[] = "SRCDIR"; |
| pz_src_dir = getenv (var); |
| if (pz_src_dir == (char *) NULL) |
| { |
| fprintf (stderr, var_not_found, var); |
| exit (EXIT_FAILURE); |
| } |
| } |
| |
| { |
| static const char var[] = "FIND_BASE"; |
| pz_find_base = getenv (var); |
| if (pz_find_base == (char *) NULL) |
| { |
| fprintf (stderr, var_not_found, var); |
| exit (EXIT_FAILURE); |
| } |
| find_base_len = strlen( pz_find_base ); |
| } |
| |
| /* Compile all the regular expressions now. |
| That way, it is done only once for the whole run. |
| */ |
| run_compiles (); |
| |
| signal (SIGQUIT, SIG_IGN); |
| signal (SIGIOT, SIG_IGN); |
| signal (SIGPIPE, SIG_IGN); |
| signal (SIGALRM, SIG_IGN); |
| signal (SIGTERM, SIG_IGN); |
| |
| #ifndef NO_BOGOSITY_LIMITS |
| /* Some systems only allow so many calls to fork(2). |
| This is inadequate for this program. Consequently, |
| we must let a grandfather process spawn children |
| that then spawn all the processes that do the real work. |
| */ |
| for (;;) |
| { |
| char *pz_buf; |
| pid_t child; |
| |
| /* Only the parent process can read from stdin without confusing |
| the world. (How does the child tell the parent to skip |
| forward? Pipes and files behave differently.) */ |
| file_name_ct = 0; |
| pz_buf = file_name_buf; |
| while ( (file_name_ct < BOGUS_LIMIT) |
| && (pz_buf < (file_name_buf + NAME_TABLE_SIZE - MAXPATHLEN))) |
| { |
| if (fgets (pz_buf, MAXPATHLEN, stdin) == (char *) NULL) |
| break; |
| while (isspace (*pz_buf)) |
| pz_buf++; |
| if ((*pz_buf == '\0') || (*pz_buf == '#')) |
| continue; |
| apz_names[file_name_ct++] = pz_buf; |
| pz_buf += strlen (pz_buf); |
| while (isspace (pz_buf[-1])) |
| pz_buf--; |
| *pz_buf++ = '\0'; |
| } |
| |
| /* IF we did not get any files this time thru |
| THEN we must be done. */ |
| if (file_name_ct == 0) |
| return EXIT_SUCCESS; |
| |
| child = fork (); |
| if (child == NULLPROCESS) |
| break; |
| |
| if (child == NOPROCESS) |
| { |
| fprintf (stderr, "Error %d (%s) forking in main\n", |
| errno, strerror (errno)); |
| exit (EXIT_FAILURE); |
| } |
| #ifndef DEBUG |
| { |
| int status; |
| (void)wait (&status); |
| } |
| #else |
| fprintf (stderr, "Waiting for %d to complete %d files\n", |
| child, file_name_ct); |
| |
| { |
| int status; |
| pid_t dead_kid = wait (&status); |
| |
| if (dead_kid != child) |
| fprintf (stderr, "fixincl woke up from a strange child %d (not %d)\n", |
| dead_kid, child); |
| else |
| fprintf (stderr, "child finished %d files %s\n", file_name_ct, |
| status ? strerror (status & 0xFF) : "ok"); |
| } |
| #endif |
| } |
| #else |
| #error "NON-BOGUS LIMITS NOT SUPPORTED?!?!" |
| #endif |
| |
| signal (SIGCLD, SIG_IGN); |
| |
| #ifdef DEBUG |
| fprintf (stderr, "Child start -- processing %d files\n", |
| file_name_ct); |
| #endif |
| |
| /* For every file specified in stdandard in |
| (except as throttled for bogus reasons)... |
| */ |
| for (loop_ct = 0; loop_ct < file_name_ct; loop_ct++) |
| { |
| char *pz_data; |
| char *pz_file_name = apz_names[loop_ct]; |
| |
| if (access (pz_file_name, R_OK) != 0) |
| { |
| int erno = errno; |
| fprintf (stderr, "Cannot access %s from %s\n\terror %d (%s)\n", |
| pz_file_name, getcwd ((char *) NULL, MAXPATHLEN), |
| erno, strerror (erno)); |
| } |
| else if (pz_data = load_file (pz_file_name), (pz_data != (char *) NULL)) |
| { |
| if (strstr (pz_data, gnu_lib_mark) == (char *) NULL) |
| process (pz_data, pz_file_name); |
| free ((void *) pz_data); |
| } |
| } |
| |
| return EXIT_SUCCESS; |
| } |
| |
| |
| /* * * * * * * * * * * * * |
| |
| load_file loads all the contents of a file into malloc-ed memory. |
| Its argument is the name of the file to read in; the returned |
| result is the NUL terminated contents of the file. The file |
| is presumed to be an ASCII text file containing no NULs. */ |
| char * |
| load_file (pz_file_name) |
| const char *pz_file_name; |
| { |
| char *pz_data; |
| size_t file_size; |
| |
| { |
| struct stat stbf; |
| |
| if (stat (pz_file_name, &stbf) != 0) |
| { |
| fprintf (stderr, "error %d (%s) stat-ing %s\n", |
| errno, strerror (errno), pz_file_name); |
| return (char *) NULL; |
| } |
| file_size = stbf.st_size; |
| } |
| if (file_size == 0) |
| return (char *) NULL; |
| |
| pz_data = (char *) malloc ((file_size + 16) & ~0x00F); |
| if (pz_data == (char *) NULL) |
| { |
| fprintf (stderr, "error: could not malloc %d bytes\n", |
| file_size); |
| exit (EXIT_FAILURE); |
| } |
| |
| { |
| FILE *fp = fopen (pz_file_name, "r"); |
| size_t size_left = file_size; |
| char *read_ptr = pz_data; |
| |
| if (fp == (FILE *) NULL) |
| { |
| fprintf (stderr, "error %d (%s) opening %s\n", errno, |
| strerror (errno), pz_file_name); |
| free ((void *) pz_data); |
| return (char *) NULL; |
| } |
| |
| do |
| { |
| size_t sizeRead = fread ((void *) read_ptr, 1, size_left, fp); |
| |
| if (sizeRead == 0) |
| { |
| if (feof (fp)) |
| break; |
| |
| if (ferror (fp)) |
| { |
| int err = errno; |
| if (err != EISDIR) |
| fprintf (stderr, "error %d (%s) reading %s\n", err, |
| strerror (err), pz_file_name); |
| free ((void *) pz_data); |
| fclose (fp); |
| return (char *) NULL; |
| } |
| } |
| |
| read_ptr += sizeRead; |
| size_left -= sizeRead; |
| } |
| while (size_left != 0); |
| |
| *read_ptr = '\0'; |
| fclose (fp); |
| } |
| return pz_data; |
| } |
| |
| |
| /* * * * * * * * * * * * * |
| |
| run_compiles run all the regexp compiles for all the fixes once. |
| */ |
| void |
| run_compiles () |
| { |
| tSCC z_bad_comp[] = "fixincl ERROR: cannot compile %s regex for %s\n" |
| "\texpr = `%s'\n" "\terror %s\n"; |
| tFixDesc *p_fixd = fixDescList; |
| int fix_ct = FIX_COUNT; |
| tTestDesc *p_test; |
| int test_ct; |
| int re_ct = REGEX_COUNT; |
| const char *pz_err; |
| regex_t *p_re = (regex_t *) malloc (REGEX_COUNT * sizeof (regex_t)); |
| |
| if (p_re == (regex_t *) NULL) |
| { |
| fprintf (stderr, "fixincl ERROR: cannot allocate %d bytes for regex\n", |
| REGEX_COUNT * sizeof (regex_t)); |
| exit (EXIT_FAILURE); |
| } |
| |
| /* The patterns we search for are all egrep patterns. |
| In the shell version of this program, we invoke egrep |
| with the supplied pattern. Here, we will run |
| re_compile_pattern, but it must be using the same rules. */ |
| |
| re_set_syntax (RE_SYNTAX_EGREP); |
| pz_err = re_compile_pattern (incl_quote_pat, sizeof (incl_quote_pat)-1, |
| &incl_quote_re); |
| if (pz_err != (char *) NULL) |
| { |
| fprintf (stderr, z_bad_comp, "quoted include", "run_compiles", |
| incl_quote_pat, pz_err); |
| exit (EXIT_FAILURE); |
| } |
| |
| /* FOR every fixup, ... */ |
| do |
| { |
| p_test = p_fixd->p_test_desc; |
| test_ct = p_fixd->test_ct; |
| |
| /* IF the machine type pointer is not NULL (we are not in test mode) |
| AND this test is for or not done on particular machines |
| THEN ... */ |
| |
| if ( (pz_machine != NULL) |
| && (p_fixd->papz_machs != (const char**) NULL) ) |
| { |
| const char **papz_machs = p_fixd->papz_machs; |
| char *pz = file_name_buf; |
| char *pz_sep = ""; |
| tCC *pz_if_true; |
| tCC *pz_if_false; |
| tSCC skip[] = "skip"; |
| tSCC run[] = "run"; |
| |
| /* Construct a shell script that looks like this: |
| |
| case our-cpu-platform-os in |
| tests-cpu-platform-os-pattern ) |
| echo run ;; |
| * ) |
| echo skip ;; |
| esac |
| |
| where 'run' and 'skip' may be reversed, depending on |
| the sense of the test. */ |
| |
| sprintf (pz, "case %s in\n", pz_machine); |
| pz += strlen (pz); |
| |
| if (p_fixd->fd_flags & FD_MACH_IFNOT) |
| { |
| pz_if_true = skip; |
| pz_if_false = run; |
| } |
| else |
| { |
| pz_if_true = run; |
| pz_if_false = skip; |
| } |
| |
| /* FOR any additional machine names to test for, |
| insert the " | \\\n" glue and the machine pattern. */ |
| |
| for (;;) |
| { |
| const char* pz_mach = *(papz_machs++); |
| |
| if (pz_mach == (const char*) NULL) |
| break; |
| sprintf (pz, "%s %s", pz_sep, pz_mach); |
| pz += strlen (pz); |
| pz_sep = " | \\\n"; |
| } |
| sprintf (pz, " )\n echo %s ;;\n * )\n echo %s ;;\nesac", |
| pz_if_true, pz_if_false); |
| |
| /* Run the script. |
| The result will start either with 's' or 'r'. */ |
| |
| pz = run_shell (file_name_buf); |
| if (*pz == 's') |
| { |
| p_fixd->fd_flags |= FD_SKIP_TEST; |
| continue; |
| } |
| } |
| |
| /* FOR every test for the fixup, ... */ |
| |
| while (--test_ct >= 0) |
| { |
| switch (p_test->type) |
| { |
| case TT_EGREP: |
| case TT_NEGREP: |
| /* You might consider putting the following under #ifdef. |
| The number of re's used is computed by autogen. |
| So, it is static and known at compile time. */ |
| |
| if (--re_ct < 0) |
| { |
| fputs ("out of RE's\n", stderr); |
| exit (EXIT_FAILURE); |
| } |
| |
| p_test->p_test_regex = p_re++; |
| pz_err = re_compile_pattern (p_test->pz_test_text, |
| strlen (p_test->pz_test_text), |
| p_test->p_test_regex); |
| if (pz_err != (char *) NULL) |
| { |
| fprintf (stderr, z_bad_comp, "select test", p_fixd->fix_name, |
| p_test->pz_test_text, pz_err); |
| exit (EXIT_FAILURE); |
| } |
| } |
| p_test++; |
| } |
| } |
| while (p_fixd++, --fix_ct > 0); |
| } |
| |
| |
| /* * * * * * * * * * * * * |
| |
| create_file Create the output modified file. |
| Input: the name of the file to create |
| Returns: a file pointer to the new, open file */ |
| |
| #define S_IRALL (S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH) |
| |
| FILE * |
| create_file (pz_file_name) |
| const char *pz_file_name; |
| { |
| int fd; |
| FILE *pf; |
| char fname[MAXPATHLEN]; |
| |
| #ifdef DEBUG |
| if (strncmp( pz_file_name, pz_find_base, find_base_len ) != 0) |
| { |
| fprintf (stderr, "Error: input file %s does not match %s/*\n", |
| pz_file_name, pz_find_base ); |
| exit (1); |
| } |
| #endif |
| |
| sprintf (fname, "%s/%s", pz_dest_dir, pz_file_name + find_base_len); |
| |
| fd = open (fname, O_WRONLY | O_CREAT | O_TRUNC, S_IRALL); |
| |
| /* We may need to create the directories needed... */ |
| if ((fd < 0) && (errno == ENOENT)) |
| { |
| char *pz_dir = strchr (fname + 1, '/'); |
| struct stat stbf; |
| |
| while (pz_dir != (char *) NULL) |
| { |
| *pz_dir = NUL; |
| if (stat (fname, &stbf) < 0) |
| { |
| mkdir (fname, S_IFDIR | S_IRWXU | S_IRGRP | S_IXGRP |
| | S_IROTH | S_IXOTH); |
| } |
| |
| *pz_dir = '/'; |
| pz_dir = strchr (pz_dir + 1, '/'); |
| } |
| |
| /* Now, lets try the open again... */ |
| fd = open (fname, O_WRONLY | O_CREAT | O_TRUNC, S_IRALL); |
| } |
| if (fd < 0) |
| { |
| fprintf (stderr, "Error %d (%s) creating %s\n", |
| errno, strerror (errno), fname); |
| exit (EXIT_FAILURE); |
| } |
| fprintf (stderr, "Fixed: %s\n", pz_file_name); |
| pf = fdopen (fd, "w"); |
| |
| #ifdef LATER |
| { |
| static const char hdr[] = |
| "/* DO NOT EDIT THIS FILE.\n\n" |
| " It has been auto-edited by fixincludes from /usr/include/%s\n" |
| " This had to be done to correct non-standard usages in the\n" |
| " original, manufacturer supplied header file. */\n\n"; |
| |
| fprintf (pf, hdr, pz_file_name); |
| } |
| #endif |
| return pf; |
| } |
| |
| |
| /* * * * * * * * * * * * * |
| |
| test_test make sure a shell-style test expression passes. |
| Input: a pointer to the descriptor of the test to run and |
| the name of the file that we might want to fix |
| Result: SUCCESS or FAILURE, depending on the result of the |
| shell script we run. */ |
| |
| t_success |
| test_test (p_test, pz_file_name) |
| tTestDesc *p_test; |
| char* pz_file_name; |
| { |
| char *pz_res; |
| t_success res = FAILURE; |
| |
| static char cmd_buf[4096]; |
| tSCC cmd_fmt[] = |
| "file=%s\n" |
| "if ( test %s ) > /dev/null 2>&1\n" |
| "then echo TRUE\n" |
| "else echo FALSE\n" |
| "fi"; |
| |
| sprintf (cmd_buf, cmd_fmt, pz_file_name, p_test->pz_test_text); |
| pz_res = run_shell (cmd_buf); |
| if (*pz_res == 'T') |
| res = SUCCESS; |
| free ((void *) pz_res); |
| return res; |
| } |
| |
| |
| /* * * * * * * * * * * * * |
| |
| egrep_test make sure an egrep expression is found in the file text. |
| Input: a pointer to the descriptor of the test to run and |
| the pointer to the contents of the file under suspicion |
| Result: SUCCESS if the pattern is found, FAILURE otherwise |
| |
| The caller may choose 'FAILURE' as 'SUCCESS' if the sense of the test |
| is inverted. */ |
| |
| t_success |
| egrep_test (pz_data, p_test) |
| char *pz_data; |
| tTestDesc *p_test; |
| { |
| regmatch_t match; |
| |
| #ifndef NO_BOGOSITY |
| if (p_test->p_test_regex == 0) |
| fprintf (stderr, "fixincl ERROR RE not compiled: `%s'\n", |
| p_test->pz_test_text); |
| #endif |
| if (regexec (p_test->p_test_regex, pz_data, 1, &match, 0) == 0) |
| return SUCCESS; |
| return FAILURE; |
| } |
| |
| |
| /* * * * * * * * * * * * * |
| * |
| extract_quoted_files |
| |
| The syntax, `#include "file.h"' specifies that the compiler is to |
| search the local directory of the current file before the include |
| list. Consequently, if we have modified a header and stored it in |
| another directory, any files that are included by that modified |
| file in that fashion must also be copied into this new directory. |
| This routine finds those flavors of #include and for each one found |
| emits a triple of: |
| |
| 1. source directory of the original file |
| 2. the relative path file name of the #includ-ed file |
| 3. the full destination path for this file |
| |
| Input: the text of the file, the file name and a pointer to the |
| match list where the match information was stored. |
| Result: internally nothing. The results are written to stdout |
| for interpretation by the invoking shell */ |
| |
| void |
| extract_quoted_files (pz_data, pz_file_name, p_re_match) |
| char *pz_data; |
| const char *pz_file_name; |
| regmatch_t *p_re_match; |
| { |
| char *pz_dir_end = strrchr (pz_file_name, '/'); |
| char *pz_incl_quot = pz_data; |
| |
| fprintf (stderr, "Quoted includes in %s\n", pz_file_name); |
| |
| /* Set "pz_file_name" to point to the containing subdirectory of the source |
| If there is none, then it is in our current direcory, ".". */ |
| |
| if (pz_dir_end == (char *) NULL) |
| pz_file_name = "."; |
| else |
| *pz_dir_end = '\0'; |
| |
| for (;;) |
| { |
| pz_incl_quot += p_re_match->rm_so; |
| |
| /* Skip forward to the included file name */ |
| while (isspace (*pz_incl_quot)) |
| pz_incl_quot++; |
| while (isspace (*++pz_incl_quot)) |
| ; |
| pz_incl_quot += sizeof ("include") - 1; |
| while (*pz_incl_quot++ != '"') |
| ; |
| |
| /* Print the source directory and the subdirectory of the file |
| in question. */ |
| printf ("%s %s/", pz_src_dir, pz_file_name); |
| pz_dir_end = pz_incl_quot; |
| |
| /* Append to the directory the relative path of the desired file */ |
| while (*pz_incl_quot != '"') |
| putc (*pz_incl_quot++, stdout); |
| |
| /* Now print the destination directory appended with the |
| relative path of the desired file */ |
| printf (" %s/%s/", pz_dest_dir, pz_file_name); |
| while (*pz_dir_end != '"') |
| putc (*pz_dir_end++, stdout); |
| |
| /* End of entry */ |
| putc ('\n', stdout); |
| |
| /* Find the next entry */ |
| if (regexec (&incl_quote_re, pz_incl_quot, 1, p_re_match, 0) != 0) |
| break; |
| } |
| } |
| |
| |
| /* * * * * * * * * * * * * |
| |
| Process the potential fixes for a particular include file. |
| Input: the original text of the file and the file's name |
| Result: none. A new file may or may not be created. */ |
| |
| void |
| process (pz_data, pz_file_name) |
| char *pz_data; |
| const char *pz_file_name; |
| { |
| static char env_current_file[1024] = { "file=" }; |
| tFixDesc *p_fixd = fixDescList; |
| int todo_ct = FIX_COUNT; |
| t_fd_pair fdp = { -1, -1 }; |
| |
| /* IF this is the first time through, |
| THEN put the 'file' environment variable into the environment. |
| This is used by some of the subject shell scripts and tests. */ |
| |
| if (env_current_file[5] == NUL) |
| putenv (env_current_file); |
| |
| /* |
| Ghastly as it is, this actually updates the value of the variable: |
| |
| putenv(3C) C Library Functions putenv(3C) |
| |
| DESCRIPTION |
| putenv() makes the value of the environment variable name |
| equal to value by altering an existing variable or creating |
| a new one. In either case, the string pointed to by string |
| becomes part of the environment, so altering the string will |
| change the environment. string points to a string of the |
| form ``name=value.'' The space used by string is no longer |
| used once a new string-defining name is passed to putenv(). |
| */ |
| strcpy (env_current_file + 5, pz_file_name); |
| process_chain_head = NOPROCESS; |
| |
| /* For every fix in our fix list, ... */ |
| for (; todo_ct > 0; p_fixd++, todo_ct--) |
| { |
| tTestDesc *p_test; |
| int test_ct; |
| |
| if (p_fixd->fd_flags & FD_SKIP_TEST) |
| continue; |
| |
| /* IF there is a file name restriction, |
| THEN ensure the current file name matches one in the pattern */ |
| |
| if (p_fixd->file_list != (char *) NULL) |
| { |
| const char *pz_fname = pz_file_name; |
| const char *pz_scan = p_fixd->file_list; |
| size_t name_len; |
| |
| while ((pz_fname[0] == '.') && (pz_fname[1] == '/')) |
| pz_fname += 2; |
| name_len = strlen (pz_fname); |
| |
| for (;;) |
| { |
| pz_scan = strstr (pz_scan + 1, pz_fname); |
| /* IF we can't match the string at all, |
| THEN bail */ |
| if (pz_scan == (char *) NULL) |
| goto next_fix; |
| |
| /* IF the match is surrounded by the '|' markers, |
| THEN we found a full match -- time to run the tests */ |
| |
| if ((pz_scan[-1] == '|') && (pz_scan[name_len] == '|')) |
| break; |
| } |
| } |
| |
| /* FOR each test, see if it fails. |
| IF it does fail, then we go on to the next test */ |
| |
| for (p_test = p_fixd->p_test_desc, test_ct = p_fixd->test_ct; |
| test_ct-- > 0; |
| p_test++) |
| { |
| #ifdef DEBUG |
| static const char z_test_fail[] = |
| "%16s test %2d failed for %s\n"; |
| #endif |
| switch (p_test->type) |
| { |
| case TT_TEST: |
| if (!SUCCESSFUL (test_test (p_test, pz_file_name))) |
| { |
| #ifdef DEBUG |
| fprintf (stderr, z_test_fail, p_fixd->fix_name, |
| p_fixd->test_ct - test_ct, pz_file_name); |
| #endif |
| goto next_fix; |
| } |
| break; |
| |
| case TT_EGREP: |
| if (!SUCCESSFUL (egrep_test (pz_data, p_test))) |
| { |
| #ifdef DEBUG |
| fprintf (stderr, z_test_fail, p_fixd->fix_name, |
| p_fixd->test_ct - test_ct, pz_file_name); |
| #endif |
| goto next_fix; |
| } |
| break; |
| |
| case TT_NEGREP: |
| if (SUCCESSFUL (egrep_test (pz_data, p_test))) |
| { |
| #ifdef DEBUG |
| fprintf (stderr, z_test_fail, p_fixd->fix_name, |
| p_fixd->test_ct - test_ct, pz_file_name); |
| #endif |
| goto next_fix; |
| } |
| break; |
| } |
| } |
| |
| fprintf (stderr, "Applying %-24s to %s\n", |
| p_fixd->fix_name, pz_file_name); |
| |
| /* IF we do not have a read pointer, |
| THEN this is the first fix for the current file. |
| Open the source file. That will be used as stdin for |
| the first fix. Any subsequent fixes will use the |
| stdout descriptor of the previous fix as its stdin. */ |
| |
| if (fdp.read_fd == -1) |
| { |
| fdp.read_fd = open (pz_file_name, O_RDONLY); |
| if (fdp.read_fd < 0) |
| { |
| fprintf (stderr, "Error %d (%s) opening %s\n", errno, |
| strerror (errno), pz_file_name); |
| exit (EXIT_FAILURE); |
| } |
| } |
| |
| /* This loop should only cycle for 1/2 of one loop. |
| "chain_open" starts a process that uses "fdp.read_fd" as |
| its stdin and returns the new fd this process will use |
| for stdout. */ |
| |
| for (;;) |
| { |
| static int failCt = 0; |
| int fd = chain_open (fdp.read_fd, |
| (t_pchar *) p_fixd->patch_args, |
| (process_chain_head == -1) |
| ? &process_chain_head : (pid_t *) NULL); |
| |
| if (fd != -1) |
| { |
| fdp.read_fd = fd; |
| break; |
| } |
| |
| fprintf (stderr, "Error %d (%s) starting filter process " |
| "for %s\n", errno, strerror (errno), |
| p_fixd->fix_name); |
| |
| if ((errno != EAGAIN) || (++failCt > 10)) |
| exit (EXIT_FAILURE); |
| sleep (1); |
| } |
| |
| next_fix: |
| ; |
| } |
| |
| /* IF after all the tests we did not start any patch programs, |
| THEN quit now. */ |
| |
| if (fdp.read_fd < 0) |
| return; |
| |
| /* OK. We have work to do. Read back in the output |
| of the filtering chain. Compare each byte as we read it with |
| the contents of the original file. As soon as we find any |
| difference, we will create the output file, write out all |
| the matched text and then copy any remaining data from the |
| output of the filter chain. |
| */ |
| { |
| FILE *in_fp = fdopen (fdp.read_fd, "r"); |
| FILE *out_fp = (FILE *) NULL; |
| char *pz_cmp = pz_data; |
| |
| for (;;) |
| { |
| int ch; |
| |
| ch = getc (in_fp); |
| if (ch == EOF) |
| break; |
| |
| /* IF we are emitting the output |
| THEN emit this character, too. |
| */ |
| if (out_fp != (FILE *) NULL) |
| putc (ch, out_fp); |
| |
| /* ELSE if this character does not match the original, |
| THEN now is the time to start the output. |
| */ |
| else if (ch != *pz_cmp) |
| { |
| out_fp = create_file (pz_file_name); |
| |
| /* IF there are matched data, write it all now. */ |
| if (pz_cmp != pz_data) |
| { |
| char c = *pz_cmp; |
| |
| *pz_cmp = NUL; |
| fputs (pz_data, out_fp); |
| *pz_cmp = c; |
| } |
| |
| /* Emit the current unmatching character */ |
| putc (ch, out_fp); |
| } |
| else |
| /* ELSE the character matches. Advance the compare ptr */ |
| pz_cmp++; |
| } |
| |
| /* IF we created the output file, ... */ |
| if (out_fp != (FILE *) NULL) |
| { |
| regmatch_t match; |
| |
| /* Close the file and see if we have to worry about |
| `#include "file.h"' constructs. */ |
| fclose (out_fp); |
| if (regexec (&incl_quote_re, pz_data, 1, &match, 0) == 0) |
| extract_quoted_files (pz_data, pz_file_name, &match); |
| } |
| fclose (in_fp); |
| } |
| close (fdp.read_fd); /* probably redundant, but I'm paranoid */ |
| } |