| /* 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 GCC will search. | |
| Copyright (C) 1997, 1998, 1999, 2000, 2004, 2009, 2012 | |
| Free Software Foundation, Inc. | |
| This file is part of GCC. | |
| GCC 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 3, or (at your option) | |
| any later version. | |
| GCC 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 GCC; see the file COPYING3. If not see | |
| <http://www.gnu.org/licenses/>. */ | |
| #include "fixlib.h" | |
| #include <fnmatch.h> | |
| #include <sys/stat.h> | |
| #ifndef SEPARATE_FIX_PROC | |
| #include <sys/wait.h> | |
| #endif | |
| #if defined( HAVE_MMAP_FILE ) | |
| #include <sys/mman.h> | |
| #define BAD_ADDR ((void*)-1) | |
| #endif | |
| #ifndef SEPARATE_FIX_PROC | |
| #include "server.h" | |
| #endif | |
| /* The contents of this string are not very important. It is mostly | |
| just used as part of the "I am alive and working" test. */ | |
| static const char program_id[] = "fixincl version 1.1"; | |
| /* This format will be used at the start of every generated file */ | |
| static const char z_std_preamble[] = | |
| "/* DO NOT EDIT THIS FILE.\n\n\ | |
| It has been auto-edited by fixincludes from:\n\n\ | |
| \t\"%s/%s\"\n\n\ | |
| This had to be done to correct non-standard usages in the\n\ | |
| original, manufacturer supplied header file. */\n\n"; | |
| int find_base_len = 0; | |
| int have_tty = 0; | |
| pid_t process_chain_head = (pid_t) -1; | |
| char* pz_curr_file; /* name of the current file under test/fix */ | |
| char* pz_curr_data; /* original contents of that file */ | |
| char* pz_temp_file; /* for DOS, a place to stash the temporary | |
| fixed data between system(3) calls */ | |
| t_bool curr_data_mapped; | |
| int data_map_fd; | |
| size_t data_map_size; | |
| size_t ttl_data_size = 0; | |
| #ifdef DO_STATS | |
| int process_ct = 0; | |
| int apply_ct = 0; | |
| int fixed_ct = 0; | |
| int altered_ct = 0; | |
| #endif /* DO_STATS */ | |
| const char incl_quote_pat[] = "^[ \t]*#[ \t]*include[ \t]*\"[^/]"; | |
| regex_t incl_quote_re; | |
| #ifndef SEPARATE_FIX_PROC | |
| tSCC z_fork_err[] = "Error %d (%s) starting filter process for %s\n"; | |
| #endif | |
| static void do_version (void) ATTRIBUTE_NORETURN; | |
| char *load_file (const char *); | |
| void run_compiles (void); | |
| void initialize (int argc, char** argv); | |
| void process (void); | |
| /* External Source Code */ | |
| #include "fixincl.x" | |
| /* * * * * * * * * * * * * * * * * * * | |
| * | |
| * MAIN ROUTINE | |
| */ | |
| extern int main (int, char **); | |
| int | |
| main (int argc, char** argv) | |
| { | |
| char *file_name_buf; | |
| initialize ( argc, argv ); | |
| have_tty = isatty (fileno (stderr)); | |
| /* Before anything else, ensure we can allocate our file name buffer. */ | |
| file_name_buf = load_file_data (stdin); | |
| /* Because of the way server shells work, you have to keep stdin, out | |
| and err open so that the proper input file does not get closed | |
| by accident */ | |
| freopen ("/dev/null", "r", stdin); | |
| if (file_name_buf == (char *) NULL) | |
| { | |
| fputs ("No file names listed for fixing\n", stderr); | |
| exit (EXIT_FAILURE); | |
| } | |
| for (;;) | |
| { | |
| char* pz_end; | |
| /* skip to start of name, past any "./" prefixes */ | |
| while (ISSPACE (*file_name_buf)) file_name_buf++; | |
| while ((file_name_buf[0] == '.') && (file_name_buf[1] == '/')) | |
| file_name_buf += 2; | |
| /* Check for end of list */ | |
| if (*file_name_buf == NUL) | |
| break; | |
| /* Set global file name pointer and find end of name */ | |
| pz_curr_file = file_name_buf; | |
| pz_end = strchr( pz_curr_file, '\n' ); | |
| if (pz_end == (char*)NULL) | |
| pz_end = file_name_buf = pz_curr_file + strlen (pz_curr_file); | |
| else | |
| file_name_buf = pz_end + 1; | |
| while ((pz_end > pz_curr_file) && ISSPACE( pz_end[-1])) pz_end--; | |
| /* IF no name is found (blank line) or comment marker, skip line */ | |
| if ((pz_curr_file == pz_end) || (*pz_curr_file == '#')) | |
| continue; | |
| *pz_end = NUL; | |
| process (); | |
| } /* for (;;) */ | |
| #ifdef DO_STATS | |
| if (VLEVEL( VERB_PROGRESS )) { | |
| tSCC zFmt[] = | |
| "\ | |
| Processed %5d files containing %d bytes \n\ | |
| Applying %5d fixes to %d files\n\ | |
| Altering %5d of them\n"; | |
| fprintf (stderr, zFmt, process_ct, ttl_data_size, apply_ct, | |
| fixed_ct, altered_ct); | |
| } | |
| #endif /* DO_STATS */ | |
| # ifdef SEPARATE_FIX_PROC | |
| unlink( pz_temp_file ); | |
| # endif | |
| exit (EXIT_SUCCESS); | |
| } | |
| static void | |
| do_version (void) | |
| { | |
| static const char zFmt[] = "echo '%s'"; | |
| char zBuf[ 1024 ]; | |
| /* The 'version' option is really used to test that: | |
| 1. The program loads correctly (no missing libraries) | |
| 2. that we can compile all the regular expressions. | |
| 3. we can correctly run our server shell process | |
| */ | |
| run_compiles (); | |
| sprintf (zBuf, zFmt, program_id); | |
| #ifndef SEPARATE_FIX_PROC | |
| puts (zBuf + 5); | |
| exit (strcmp (run_shell (zBuf), program_id)); | |
| #else | |
| exit (system_with_shell (zBuf)); | |
| #endif | |
| } | |
| /* * * * * * * * * * * * */ | |
| void | |
| initialize ( int argc, char** argv ) | |
| { | |
| xmalloc_set_program_name (argv[0]); | |
| switch (argc) | |
| { | |
| case 1: | |
| break; | |
| case 2: | |
| if (strcmp (argv[1], "-v") == 0) | |
| do_version (); | |
| if (freopen (argv[1], "r", stdin) == (FILE*)NULL) | |
| { | |
| fprintf (stderr, "Error %d (%s) reopening %s as stdin\n", | |
| errno, xstrerror (errno), argv[1] ); | |
| exit (EXIT_FAILURE); | |
| } | |
| break; | |
| default: | |
| fputs ("fixincl ERROR: too many command line arguments\n", stderr); | |
| exit (EXIT_FAILURE); | |
| } | |
| #ifdef SIGCHLD | |
| /* We *MUST* set SIGCHLD to SIG_DFL so that the wait4() call will | |
| receive the signal. A different setting is inheritable */ | |
| signal (SIGCHLD, SIG_DFL); | |
| #endif | |
| initialize_opts (); | |
| if (ISDIGIT ( *pz_verbose )) | |
| verbose_level = (te_verbose)atoi( pz_verbose ); | |
| else | |
| switch (*pz_verbose) { | |
| case 's': | |
| case 'S': | |
| verbose_level = VERB_SILENT; break; | |
| case 'f': | |
| case 'F': | |
| verbose_level = VERB_FIXES; break; | |
| case 'a': | |
| case 'A': | |
| verbose_level = VERB_APPLIES; break; | |
| default: | |
| case 'p': | |
| case 'P': | |
| verbose_level = VERB_PROGRESS; break; | |
| case 't': | |
| case 'T': | |
| verbose_level = VERB_TESTS; break; | |
| case 'e': | |
| case 'E': | |
| verbose_level = VERB_EVERYTHING; break; | |
| } | |
| if (verbose_level >= VERB_EVERYTHING) { | |
| verbose_level = VERB_EVERYTHING; | |
| fputs ("fixinc verbosity: EVERYTHING\n", stderr); | |
| } | |
| while ((pz_find_base[0] == '.') && (pz_find_base[1] == '/')) | |
| pz_find_base += 2; | |
| if ((pz_find_base[0] != '.') || (pz_find_base[1] != NUL)) | |
| 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 (); | |
| # ifdef SEPARATE_FIX_PROC | |
| /* NULL as the first argument to `tempnam' causes it to DTRT | |
| wrt the temporary directory where the file will be created. */ | |
| pz_temp_file = tempnam( NULL, "fxinc" ); | |
| #if defined(__MINGW32__) | |
| fix_path_separators (pz_temp_file); | |
| #endif | |
| # endif | |
| signal (SIGQUIT, SIG_IGN); | |
| signal (SIGIOT, SIG_IGN); | |
| signal (SIGPIPE, SIG_IGN); | |
| signal (SIGALRM, SIG_IGN); | |
| signal (SIGTERM, SIG_IGN); | |
| } | |
| /* * * * * * * * * * * * * | |
| 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 ( const char* fname ) | |
| { | |
| struct stat stbf; | |
| char* res; | |
| if (stat (fname, &stbf) != 0) | |
| { | |
| if (NOT_SILENT) | |
| fprintf (stderr, "error %d (%s) stat-ing %s\n", | |
| errno, xstrerror (errno), fname ); | |
| return (char *) NULL; | |
| } | |
| if (stbf.st_size == 0) | |
| return (char*)NULL; | |
| /* Make the data map size one larger than the file size for documentation | |
| purposes. Truth is that there will be a following NUL character if | |
| the file size is not a multiple of the page size. If it is a multiple, | |
| then this adjustment sometimes fails anyway. */ | |
| data_map_size = stbf.st_size+1; | |
| data_map_fd = open (fname, O_RDONLY); | |
| ttl_data_size += data_map_size-1; | |
| if (data_map_fd < 0) | |
| { | |
| if (NOT_SILENT) | |
| fprintf (stderr, "error %d (%s) opening %s for read\n", | |
| errno, xstrerror (errno), fname); | |
| return (char*)NULL; | |
| } | |
| #ifdef HAVE_MMAP_FILE | |
| curr_data_mapped = BOOL_TRUE; | |
| /* IF the file size is a multiple of the page size, | |
| THEN sometimes you will seg fault trying to access a trailing byte */ | |
| if ((stbf.st_size & (getpagesize()-1)) == 0) | |
| res = (char*)BAD_ADDR; | |
| else | |
| res = (char*)mmap ((void*)NULL, data_map_size, PROT_READ, | |
| MAP_PRIVATE, data_map_fd, 0); | |
| if (res == (char*)BAD_ADDR) | |
| #endif | |
| { | |
| FILE* fp = fdopen (data_map_fd, "r"); | |
| curr_data_mapped = BOOL_FALSE; | |
| res = load_file_data (fp); | |
| fclose (fp); | |
| } | |
| return res; | |
| } | |
| static int | |
| machine_matches( tFixDesc* p_fixd ) | |
| { | |
| char const ** papz_machs = p_fixd->papz_machs; | |
| int have_match = BOOL_FALSE; | |
| for (;;) | |
| { | |
| char const * pz_mpat = *(papz_machs++); | |
| if (pz_mpat == NULL) | |
| break; | |
| if (fnmatch(pz_mpat, pz_machine, 0) == 0) | |
| { | |
| have_match = BOOL_TRUE; | |
| break; | |
| } | |
| } | |
| /* Check for sense inversion then set the "skip test" flag, if needed */ | |
| if (p_fixd->fd_flags & FD_MACH_IFNOT) | |
| have_match = ! have_match; | |
| if (! have_match) | |
| p_fixd->fd_flags |= FD_SKIP_TEST; | |
| return have_match; | |
| } | |
| /* * * * * * * * * * * * * | |
| * | |
| * run_compiles run all the regexp compiles for all the fixes once. | |
| */ | |
| void | |
| run_compiles (void) | |
| { | |
| tFixDesc *p_fixd = fixDescList; | |
| int fix_ct = FIX_COUNT; | |
| regex_t *p_re = XCNEWVEC (regex_t, REGEX_COUNT); | |
| /* Make sure compile_re does not stumble across invalid data */ | |
| memset (&incl_quote_re, '\0', sizeof (regex_t)); | |
| compile_re (incl_quote_pat, &incl_quote_re, 1, | |
| "quoted include", "run_compiles"); | |
| /* Allow machine name tests to be ignored (testing, mainly) */ | |
| if (pz_machine && ((*pz_machine == '\0') || (*pz_machine == '*'))) | |
| pz_machine = (char*)NULL; | |
| /* FOR every fixup, ... */ | |
| do | |
| { | |
| tTestDesc *p_test; | |
| int test_ct; | |
| if (fixinc_mode && (p_fixd->fd_flags & FD_REPLACEMENT)) | |
| { | |
| p_fixd->fd_flags |= FD_SKIP_TEST; | |
| continue; | |
| } | |
| 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) | |
| && ! machine_matches (p_fixd) ) | |
| continue; | |
| /* FOR every test for the fixup, ... */ | |
| while (--test_ct >= 0) | |
| { | |
| switch (p_test->type) | |
| { | |
| case TT_EGREP: | |
| case TT_NEGREP: | |
| p_test->p_test_regex = p_re++; | |
| compile_re (p_test->pz_test_text, p_test->p_test_regex, 0, | |
| "select test", p_fixd->fix_name); | |
| default: break; | |
| } | |
| 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 */ | |
| #if defined(S_IRUSR) && defined(S_IWUSR) && \ | |
| defined(S_IRGRP) && defined(S_IROTH) | |
| # define S_IRALL (S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH) | |
| #else | |
| # define S_IRALL 0644 | |
| #endif | |
| #if defined(S_IRWXU) && defined(S_IRGRP) && defined(S_IXGRP) && \ | |
| defined(S_IROTH) && defined(S_IXOTH) | |
| # define S_DIRALL (S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH) | |
| #else | |
| # define S_DIRALL 0755 | |
| #endif | |
| static FILE * | |
| create_file (void) | |
| { | |
| int fd; | |
| FILE *pf; | |
| char fname[MAXPATHLEN]; | |
| sprintf (fname, "%s/%s", pz_dest_dir, pz_curr_file + 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) | |
| { | |
| #ifdef _WIN32 | |
| mkdir (fname); | |
| #else | |
| mkdir (fname, S_IFDIR | S_DIRALL); | |
| #endif | |
| } | |
| *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, xstrerror (errno), fname); | |
| exit (EXIT_FAILURE); | |
| } | |
| if (NOT_SILENT) | |
| fprintf (stderr, "Fixed: %s\n", pz_curr_file); | |
| pf = fdopen (fd, "w"); | |
| /* | |
| * IF pz_machine is NULL, then we are in some sort of test mode. | |
| * Do not insert the current directory name. Use a constant string. | |
| */ | |
| fprintf (pf, z_std_preamble, | |
| (pz_machine == NULL) | |
| ? "fixinc/tests/inc" | |
| : pz_input_dir, | |
| pz_curr_file); | |
| 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: APPLY_FIX or SKIP_FIX, depending on the result of the | |
| shell script we run. */ | |
| #ifndef SEPARATE_FIX_PROC | |
| static int | |
| test_test (tTestDesc* p_test, char* pz_test_file) | |
| { | |
| tSCC cmd_fmt[] = | |
| "file=%s\n\ | |
| if ( test %s ) > /dev/null 2>&1\n\ | |
| then echo TRUE\n\ | |
| else echo FALSE\n\ | |
| fi"; | |
| char *pz_res; | |
| int res; | |
| static char cmd_buf[4096]; | |
| sprintf (cmd_buf, cmd_fmt, pz_test_file, p_test->pz_test_text); | |
| pz_res = run_shell (cmd_buf); | |
| switch (*pz_res) { | |
| case 'T': | |
| res = APPLY_FIX; | |
| break; | |
| case 'F': | |
| res = SKIP_FIX; | |
| break; | |
| default: | |
| fprintf (stderr, "Script yielded bogus result of `%s':\n%s\n\n", | |
| pz_res, cmd_buf ); | |
| res = SKIP_FIX; | |
| } | |
| free ((void *) pz_res); | |
| return res; | |
| } | |
| #elif defined(__MINGW32__) || defined(__DJGPP__) | |
| static int | |
| test_test (tTestDesc* p_test, char* pz_test_file) | |
| { | |
| tSCC cmd_fmt[] = | |
| #if defined(__DJGPP__) | |
| "file=%s; test %s >/dev/null 2>/dev/null"; | |
| #else | |
| "file=%s; test %s > /dev/null 2>&1"; | |
| #endif | |
| int res; | |
| char *cmd_buf = XNEWVEC (char, strlen(cmd_fmt) + strlen(pz_test_file) + strlen(p_test->pz_test_text)); | |
| sprintf (cmd_buf, cmd_fmt, pz_test_file, p_test->pz_test_text); | |
| res = system_with_shell (cmd_buf); | |
| free (cmd_buf); | |
| return res ? SKIP_FIX : APPLY_FIX; | |
| } | |
| #else | |
| /* | |
| * IF we are in MS-DOS land, then whatever shell-type test is required | |
| * will, by definition, fail | |
| */ | |
| #define test_test(t,tf) SKIP_FIX | |
| #endif | |
| /* * * * * * * * * * * * * | |
| 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: APPLY_FIX if the pattern is found, SKIP_FIX otherwise | |
| The caller may choose to reverse meaning if the sense of the test | |
| is inverted. */ | |
| static int | |
| egrep_test (char* pz_data, tTestDesc* p_test) | |
| { | |
| #ifdef DEBUG | |
| if (p_test->p_test_regex == 0) | |
| fprintf (stderr, "fixincl ERROR RE not compiled: `%s'\n", | |
| p_test->pz_test_text); | |
| #endif | |
| if (xregexec (p_test->p_test_regex, pz_data, 0, 0, 0) == 0) | |
| return APPLY_FIX; | |
| return SKIP_FIX; | |
| } | |
| /* * * * * * * * * * * * * | |
| cksum_test check the sum of the candidate file | |
| Input: the original file contents and the file name | |
| Result: APPLY_FIX if the check sum matches, SKIP_FIX otherwise | |
| The caller may choose to reverse meaning if the sense of the test | |
| is inverted. */ | |
| static int | |
| cksum_test (char * pz_data, tTestDesc * p_test, char * fname) | |
| { | |
| unsigned int cksum; | |
| /* | |
| * Testing is off in normal operation mode. | |
| * So, in testing mode, APPLY_FIX is always returned. | |
| */ | |
| if (fixinc_mode != TESTING_OFF) | |
| return APPLY_FIX; | |
| { | |
| char * fnm = strrchr(fname, '/'); | |
| if (fnm != NULL) | |
| fname = fnm + 1; | |
| errno = 0; | |
| cksum = (unsigned int)strtoul(p_test->pz_test_text, &fnm, 10); | |
| if (errno != 0) | |
| return SKIP_FIX; | |
| if (! ISSPACE(*fnm++)) | |
| return SKIP_FIX; | |
| while (ISSPACE(*fnm)) fnm++; | |
| if (! ISDIGIT(*fnm++)) | |
| return SKIP_FIX; | |
| while (ISDIGIT(*fnm)) fnm++; | |
| if (! ISSPACE(*fnm++)) | |
| return SKIP_FIX; | |
| while (ISSPACE(*fnm)) fnm++; | |
| if (strcmp(fnm, fname) != 0) | |
| return SKIP_FIX; | |
| } | |
| { | |
| unsigned int sum = 0; | |
| while (*pz_data != NUL) { | |
| sum = (sum >> 1) + ((sum & 1) << 15) + (unsigned)(*pz_data++); | |
| sum &= 0xFFFF; | |
| } | |
| return (sum == cksum) ? APPLY_FIX : SKIP_FIX; | |
| } | |
| } | |
| /* * * * * * * * * * * * * | |
| quoted_file_exists Make sure that a file exists before we emit | |
| the file name. If we emit the name, our invoking shell will try | |
| to copy a non-existing file into the destination directory. */ | |
| static int | |
| quoted_file_exists (const char* pz_src_path, | |
| const char* pz_file_path, | |
| const char* pz_file) | |
| { | |
| char z[ MAXPATHLEN ]; | |
| char* pz; | |
| sprintf (z, "%s/%s/", pz_src_path, pz_file_path); | |
| pz = z + strlen ( z ); | |
| for (;;) { | |
| char ch = *pz_file++; | |
| if (! ISGRAPH( ch )) | |
| return 0; | |
| if (ch == '"') | |
| break; | |
| *pz++ = ch; | |
| } | |
| *pz = '\0'; | |
| { | |
| struct stat s; | |
| if (stat (z, &s) != 0) | |
| return 0; | |
| return S_ISREG( s.st_mode ); | |
| } | |
| } | |
| /* * * * * * * * * * * * * | |
| * | |
| 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 #include-d 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 */ | |
| static void | |
| extract_quoted_files (char* pz_data, | |
| char* pz_file, | |
| regmatch_t* p_re_match) | |
| { | |
| char *pz_dir_end = strrchr (pz_file, '/'); | |
| char *pz_incl_quot = pz_data; | |
| if (VLEVEL( VERB_APPLIES )) | |
| fprintf (stderr, "Quoted includes in %s\n", pz_file); | |
| /* Set "pz_fixed_file" to point to the containing subdirectory of the source | |
| If there is none, then it is in our current directory, ".". */ | |
| const char *pz_fixed_file = pz_file; | |
| if (pz_dir_end == (char *) NULL) | |
| pz_fixed_file = "."; | |
| else | |
| *pz_dir_end = '\0'; | |
| for (;;) | |
| { | |
| pz_incl_quot += p_re_match->rm_so; | |
| /* Skip forward to the included file name */ | |
| while (*pz_incl_quot != '"') | |
| pz_incl_quot++; | |
| if (quoted_file_exists (pz_src_dir, pz_fixed_file, pz_incl_quot)) | |
| { | |
| /* Print the source directory and the subdirectory | |
| of the file in question. */ | |
| printf ("%s %s/", pz_src_dir, pz_fixed_file); | |
| 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_fixed_file); | |
| while (*pz_dir_end != '"') | |
| putc (*pz_dir_end++, stdout); | |
| /* End of entry */ | |
| putc ('\n', stdout); | |
| } | |
| /* Find the next entry */ | |
| if (xregexec (&incl_quote_re, pz_incl_quot, 1, p_re_match, 0) != 0) | |
| break; | |
| } | |
| } | |
| /* * * * * * * * * * * * * | |
| Somebody wrote a *_fix subroutine that we must call. | |
| */ | |
| #ifndef SEPARATE_FIX_PROC | |
| static int | |
| internal_fix (int read_fd, tFixDesc* p_fixd) | |
| { | |
| int fd[2]; | |
| if (pipe( fd ) != 0) | |
| { | |
| fprintf (stderr, "Error %d on pipe(2) call\n", errno ); | |
| exit (EXIT_FAILURE); | |
| } | |
| for (;;) | |
| { | |
| pid_t childid = fork(); | |
| switch (childid) | |
| { | |
| case -1: | |
| break; | |
| case 0: | |
| close (fd[0]); | |
| goto do_child_task; | |
| default: | |
| /* | |
| * Parent process | |
| */ | |
| close (read_fd); | |
| close (fd[1]); | |
| return fd[0]; | |
| } | |
| /* | |
| * Parent in error | |
| */ | |
| fprintf (stderr, z_fork_err, errno, xstrerror (errno), | |
| p_fixd->fix_name); | |
| { | |
| static int failCt = 0; | |
| if ((errno != EAGAIN) || (++failCt > 10)) | |
| exit (EXIT_FAILURE); | |
| sleep (1); | |
| } | |
| } do_child_task:; | |
| /* | |
| * Close our current stdin and stdout | |
| */ | |
| close (STDIN_FILENO); | |
| close (STDOUT_FILENO); | |
| UNLOAD_DATA(); | |
| /* | |
| * Make the fd passed in the stdin, and the write end of | |
| * the new pipe become the stdout. | |
| */ | |
| dup2 (fd[1], STDOUT_FILENO); | |
| dup2 (read_fd, STDIN_FILENO); | |
| apply_fix (p_fixd, pz_curr_file); | |
| exit (0); | |
| } | |
| #endif /* !SEPARATE_FIX_PROC */ | |
| #ifdef SEPARATE_FIX_PROC | |
| static void | |
| fix_with_system (tFixDesc* p_fixd, | |
| tCC* pz_fix_file, | |
| tCC* pz_file_source, | |
| tCC* pz_temp_file) | |
| { | |
| char* pz_cmd; | |
| char* pz_scan; | |
| size_t argsize; | |
| if (p_fixd->fd_flags & FD_SUBROUTINE) | |
| { | |
| static const char z_applyfix_prog[] = | |
| "/../fixincludes/applyfix" EXE_EXT; | |
| struct stat buf; | |
| argsize = 32 | |
| + strlen (pz_orig_dir) | |
| + sizeof (z_applyfix_prog) | |
| + strlen (pz_fix_file) | |
| + strlen (pz_file_source) | |
| + strlen (pz_temp_file); | |
| /* Allocate something sure to be big enough for our purposes */ | |
| pz_cmd = XNEWVEC (char, argsize); | |
| strcpy (pz_cmd, pz_orig_dir); | |
| pz_scan = pz_cmd + strlen (pz_orig_dir); | |
| strcpy (pz_scan, z_applyfix_prog); | |
| /* IF we can't find the "applyfix" executable file at the first guess, | |
| try one level higher up */ | |
| if (stat (pz_cmd, &buf) == -1) | |
| { | |
| strcpy (pz_scan, "/.."); | |
| strcpy (pz_scan+3, z_applyfix_prog); | |
| } | |
| pz_scan += strlen (pz_scan); | |
| /* | |
| * Now add the fix number and file names that may be needed | |
| */ | |
| sprintf (pz_scan, " %ld '%s' '%s' '%s'", (long) (p_fixd - fixDescList), | |
| pz_fix_file, pz_file_source, pz_temp_file); | |
| } | |
| else /* NOT an "internal" fix: */ | |
| { | |
| size_t parg_size; | |
| #if defined(__MSDOS__) && !defined(__DJGPP__) | |
| /* Don't use the "src > dstX; rm -f dst; mv -f dstX dst" trick: | |
| dst is a temporary file anyway, so we know there's no other | |
| file by that name; and DOS's system(3) doesn't mind to | |
| clobber existing file in redirection. Besides, with DOS 8+3 | |
| limited file namespace, we can easily lose if dst already has | |
| an extension that is 3 or more characters long. | |
| I do not think the 8+3 issue is relevant because all the files | |
| we operate on are named "*.h", making 8+2 adequate. Anyway, | |
| the following bizarre use of 'cat' only works on DOS boxes. | |
| It causes the file to be dropped into a temporary file for | |
| 'cat' to read (pipes do not work on DOS). */ | |
| tSCC z_cmd_fmt[] = " '%s' | cat > '%s'"; | |
| #else | |
| /* Don't use positional formatting arguments because some lame-o | |
| implementations cannot cope :-(. */ | |
| tSCC z_cmd_fmt[] = " %s > %sX ; rm -f %s; mv -f %sX %s"; | |
| #endif | |
| tSCC z_subshell_start[] = "( "; | |
| tSCC z_subshell_end[] = " ) < "; | |
| tCC** ppArgs = p_fixd->patch_args; | |
| argsize = sizeof( z_cmd_fmt ) + strlen( pz_temp_file ) | |
| + strlen( pz_file_source ); | |
| parg_size = argsize; | |
| if (p_fixd->fd_flags & FD_SHELL_SCRIPT) | |
| { | |
| argsize += strlen( z_subshell_start ) + strlen ( z_subshell_end ); | |
| } | |
| /* | |
| * Compute the size of the command line. Add lotsa extra space | |
| * because some of the args to sed use lotsa single quotes. | |
| * (This requires three extra bytes per quote. Here we allow | |
| * for up to 8 single quotes for each argument, including the | |
| * command name "sed" itself. Nobody will *ever* need more. :) | |
| */ | |
| for (;;) | |
| { | |
| tCC* p_arg = *(ppArgs++); | |
| if (p_arg == NULL) | |
| break; | |
| argsize += 24 + strlen( p_arg ); | |
| } | |
| /* Estimated buffer size we will need. */ | |
| pz_scan = pz_cmd = XNEWVEC (char, argsize); | |
| /* How much of it do we allot to the program name and its | |
| arguments. */ | |
| parg_size = argsize - parg_size; | |
| ppArgs = p_fixd->patch_args; | |
| /* | |
| * If it's shell script, enclose it in parentheses and skip "sh -c". | |
| */ | |
| if (p_fixd->fd_flags & FD_SHELL_SCRIPT) | |
| { | |
| strcpy (pz_scan, z_subshell_start); | |
| pz_scan += strlen (z_subshell_start); | |
| ppArgs += 2; | |
| } | |
| /* | |
| * Copy the program name, unquoted | |
| */ | |
| { | |
| tCC* pArg = *(ppArgs++); | |
| for (;;) | |
| { | |
| char ch = *(pArg++); | |
| if (ch == NUL) | |
| break; | |
| *(pz_scan++) = ch; | |
| } | |
| } | |
| /* | |
| * Copy the program arguments, quoted | |
| */ | |
| for (;;) | |
| { | |
| tCC* pArg = *(ppArgs++); | |
| char* pz_scan_save; | |
| if (pArg == NULL) | |
| break; | |
| *(pz_scan++) = ' '; | |
| pz_scan = make_raw_shell_str( pz_scan_save = pz_scan, pArg, | |
| parg_size - (pz_scan - pz_cmd) ); | |
| /* | |
| * Make sure we don't overflow the buffer due to sloppy | |
| * size estimation. | |
| */ | |
| while (pz_scan == (char*)NULL) | |
| { | |
| size_t already_filled = pz_scan_save - pz_cmd; | |
| pz_cmd = xrealloc (pz_cmd, argsize += 100); | |
| pz_scan_save = pz_scan = pz_cmd + already_filled; | |
| parg_size += 100; | |
| pz_scan = make_raw_shell_str( pz_scan, pArg, | |
| parg_size - (pz_scan - pz_cmd) ); | |
| } | |
| } | |
| /* | |
| * Close parenthesis if it's shell script. | |
| */ | |
| if (p_fixd->fd_flags & FD_SHELL_SCRIPT) | |
| { | |
| strcpy (pz_scan, z_subshell_end); | |
| pz_scan += strlen (z_subshell_end); | |
| } | |
| /* | |
| * add the file machinations. | |
| */ | |
| #if defined(__MSDOS__) && !defined(__DJGPP__) | |
| sprintf (pz_scan, z_cmd_fmt, pz_file_source, pz_temp_file ); | |
| #else | |
| sprintf (pz_scan, z_cmd_fmt, pz_file_source, pz_temp_file, | |
| pz_temp_file, pz_temp_file, pz_temp_file); | |
| #endif | |
| } | |
| system_with_shell (pz_cmd); | |
| free (pz_cmd); | |
| } | |
| /* * * * * * * * * * * * * | |
| This loop should only cycle for 1/2 of one loop. | |
| "chain_open" starts a process that uses "read_fd" as | |
| its stdin and returns the new fd this process will use | |
| for stdout. */ | |
| #else /* is *NOT* SEPARATE_FIX_PROC */ | |
| static int | |
| start_fixer (int read_fd, tFixDesc* p_fixd, char* pz_fix_file) | |
| { | |
| tCC* pz_cmd_save; | |
| char* pz_cmd; | |
| if ((p_fixd->fd_flags & FD_SUBROUTINE) != 0) | |
| return internal_fix (read_fd, p_fixd); | |
| if ((p_fixd->fd_flags & FD_SHELL_SCRIPT) == 0) | |
| { | |
| pz_cmd = NULL; | |
| pz_cmd_save = NULL; | |
| } | |
| else | |
| { | |
| tSCC z_cmd_fmt[] = "file='%s'\n%s"; | |
| pz_cmd = XNEWVEC (char, strlen (p_fixd->patch_args[2]) | |
| + sizeof (z_cmd_fmt) + strlen (pz_fix_file)); | |
| sprintf (pz_cmd, z_cmd_fmt, pz_fix_file, p_fixd->patch_args[2]); | |
| pz_cmd_save = p_fixd->patch_args[2]; | |
| p_fixd->patch_args[2] = pz_cmd; | |
| } | |
| /* Start a fix process, handing off the previous read fd for its | |
| stdin and getting a new fd that reads from the fix process' stdout. | |
| We normally will not loop, but we will up to 10 times if we keep | |
| getting "EAGAIN" errors. | |
| */ | |
| for (;;) | |
| { | |
| static int failCt = 0; | |
| int fd; | |
| fd = chain_open (read_fd, | |
| (tCC **) p_fixd->patch_args, | |
| (process_chain_head == -1) | |
| ? &process_chain_head : (pid_t *) NULL); | |
| if (fd != -1) | |
| { | |
| read_fd = fd; | |
| break; | |
| } | |
| fprintf (stderr, z_fork_err, errno, xstrerror (errno), | |
| p_fixd->fix_name); | |
| if ((errno != EAGAIN) || (++failCt > 10)) | |
| exit (EXIT_FAILURE); | |
| sleep (1); | |
| } | |
| /* IF we allocated a shell script command, | |
| THEN free it and restore the command format to the fix description */ | |
| if (pz_cmd != (char*)NULL) | |
| { | |
| free ((void*)pz_cmd); | |
| p_fixd->patch_args[2] = pz_cmd_save; | |
| } | |
| return read_fd; | |
| } | |
| #endif | |
| #ifdef DEBUG | |
| # define NOTE_SKIP(_ttyp) do { \ | |
| if (VLEVEL( VERB_EVERYTHING )) \ | |
| fprintf (stderr, z_failed, _ttyp, p_fixd->fix_name, \ | |
| pz_fname, p_fixd->test_ct - test_ct); \ | |
| } while (0) | |
| #else | |
| # define NOTE_SKIP(_ttyp) | |
| #endif | |
| /* * * * * * * * * * * * * | |
| * | |
| * 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. | |
| */ | |
| static t_bool | |
| fix_applies (tFixDesc* p_fixd) | |
| { | |
| const char *pz_fname = pz_curr_file; | |
| const char *pz_scan = p_fixd->file_list; | |
| int test_ct; | |
| tTestDesc *p_test; | |
| t_bool saw_sum_test = BOOL_FALSE; | |
| t_bool one_sum_passed = BOOL_FALSE; | |
| #if defined(__MSDOS__) && !defined(__DJGPP__) | |
| /* | |
| * There is only one fix that uses a shell script as of this writing. | |
| * I hope to nuke it anyway, it does not apply to DOS and it would | |
| * be painful to implement. Therefore, no "shell" fixes for DOS. | |
| */ | |
| if (p_fixd->fd_flags & (FD_SHELL_SCRIPT | FD_SKIP_TEST)) | |
| return BOOL_FALSE; | |
| #else | |
| if (p_fixd->fd_flags & FD_SKIP_TEST) | |
| return BOOL_FALSE; | |
| #endif | |
| /* IF there is a file name restriction, | |
| THEN ensure the current file name matches one in the pattern */ | |
| if (pz_scan != (char *) NULL) | |
| { | |
| while ((pz_fname[0] == '.') && (pz_fname[1] == '/')) | |
| pz_fname += 2; | |
| for (;;) | |
| { | |
| if (fnmatch (pz_scan, pz_fname, 0) == 0) | |
| break; | |
| pz_scan += strlen (pz_scan) + 1; | |
| if (*pz_scan == NUL) | |
| return BOOL_FALSE; | |
| } | |
| } | |
| /* FOR each test, see if it fails. | |
| "sum" fails only if all "sum" tests fail. | |
| 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++) | |
| { | |
| switch (p_test->type) | |
| { | |
| case TT_TEST: | |
| if (test_test (p_test, pz_curr_file) != APPLY_FIX) { | |
| NOTE_SKIP("TEST"); | |
| return BOOL_FALSE; | |
| } | |
| break; | |
| case TT_EGREP: | |
| if (egrep_test (pz_curr_data, p_test) != APPLY_FIX) { | |
| NOTE_SKIP("EGREP"); | |
| return BOOL_FALSE; | |
| } | |
| break; | |
| case TT_NEGREP: | |
| if (egrep_test (pz_curr_data, p_test) == APPLY_FIX) { | |
| NOTE_SKIP("NEGREP"); | |
| /* Negated sense */ | |
| return BOOL_FALSE; | |
| } | |
| break; | |
| case TT_CKSUM: | |
| if (one_sum_passed) | |
| break; /* No need to check any more */ | |
| saw_sum_test = BOOL_TRUE; | |
| if (cksum_test (pz_curr_data, p_test, pz_curr_file) != APPLY_FIX) { | |
| NOTE_SKIP("CKSUM"); | |
| } else { | |
| one_sum_passed = BOOL_TRUE; | |
| } | |
| break; | |
| case TT_FUNCTION: | |
| if (run_test (p_test->pz_test_text, pz_curr_file, pz_curr_data) | |
| != APPLY_FIX) { | |
| NOTE_SKIP("FTEST"); | |
| return BOOL_FALSE; | |
| } | |
| break; | |
| } | |
| } | |
| if (saw_sum_test) | |
| return one_sum_passed; | |
| return BOOL_TRUE; | |
| } | |
| /* * * * * * * * * * * * * | |
| Write out a replacement file */ | |
| static void | |
| write_replacement (tFixDesc* p_fixd) | |
| { | |
| const char* pz_text = p_fixd->patch_args[0]; | |
| if ((pz_text == (char*)NULL) || (*pz_text == NUL)) | |
| return; | |
| { | |
| FILE* out_fp = create_file (); | |
| size_t sz = strlen (pz_text); | |
| fwrite (pz_text, sz, 1, out_fp); | |
| if (pz_text[ sz-1 ] != '\n') | |
| fputc ('\n', out_fp); | |
| fclose (out_fp); | |
| } | |
| } | |
| /* * * * * * * * * * * * * | |
| 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. | |
| */ | |
| static void | |
| test_for_changes (int read_fd) | |
| { | |
| FILE *in_fp = fdopen (read_fd, "r"); | |
| FILE *out_fp = (FILE *) NULL; | |
| unsigned char *pz_cmp = (unsigned char*)pz_curr_data; | |
| #ifdef DO_STATS | |
| fixed_ct++; | |
| #endif | |
| for (;;) | |
| { | |
| int ch; | |
| ch = getc (in_fp); | |
| if (ch == EOF) | |
| break; | |
| ch &= 0xFF; /* all bytes are 8 bits */ | |
| /* 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 (); | |
| #ifdef DO_STATS | |
| altered_ct++; | |
| #endif | |
| /* IF there are matched data, write the matched part now. */ | |
| if ((char*)pz_cmp != pz_curr_data) | |
| fwrite (pz_curr_data, (size_t)((char*)pz_cmp - pz_curr_data), | |
| 1, out_fp); | |
| /* 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 (xregexec (&incl_quote_re, pz_curr_data, 1, &match, 0) == 0) | |
| extract_quoted_files (pz_curr_data, pz_curr_file, &match); | |
| } | |
| fclose (in_fp); | |
| close (read_fd); /* probably redundant, but I'm paranoid */ | |
| } | |
| /* * * * * * * * * * * * * | |
| 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 (void) | |
| { | |
| tFixDesc *p_fixd = fixDescList; | |
| int todo_ct = FIX_COUNT; | |
| int read_fd = -1; | |
| # ifndef SEPARATE_FIX_PROC | |
| int num_children = 0; | |
| # else /* is SEPARATE_FIX_PROC */ | |
| char* pz_file_source = pz_curr_file; | |
| # endif | |
| if (access (pz_curr_file, R_OK) != 0) | |
| { | |
| /* It may happens if for e. g. the distro ships some broken symlinks | |
| in /usr/include. */ | |
| /* "INPUT" is exported in fixinc.sh, which is the pwd where fixincl | |
| runs. It's used instead of getcwd to avoid allocating a buffer | |
| with unknown length. */ | |
| const char *cwd = getenv ("INPUT"); | |
| if (!cwd) | |
| cwd = "the working directory"; | |
| fprintf (stderr, "Cannot access %s from %s: %s\n", pz_curr_file, cwd, | |
| xstrerror (errno)); | |
| return; | |
| } | |
| pz_curr_data = load_file (pz_curr_file); | |
| if (pz_curr_data == (char *) NULL) | |
| return; | |
| #ifdef DO_STATS | |
| process_ct++; | |
| #endif | |
| if (VLEVEL( VERB_PROGRESS ) && have_tty) | |
| fprintf (stderr, "%6lu %-50s \r", | |
| (unsigned long) data_map_size, pz_curr_file); | |
| # ifndef SEPARATE_FIX_PROC | |
| process_chain_head = NOPROCESS; | |
| /* For every fix in our fix list, ... */ | |
| for (; todo_ct > 0; p_fixd++, todo_ct--) | |
| { | |
| if (! fix_applies (p_fixd)) | |
| continue; | |
| if (VLEVEL( VERB_APPLIES )) | |
| fprintf (stderr, "Applying %-24s to %s\n", | |
| p_fixd->fix_name, pz_curr_file); | |
| if (p_fixd->fd_flags & FD_REPLACEMENT) | |
| { | |
| write_replacement (p_fixd); | |
| UNLOAD_DATA(); | |
| return; | |
| } | |
| /* 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 for its stdin. */ | |
| if (read_fd == -1) | |
| { | |
| read_fd = open (pz_curr_file, O_RDONLY); | |
| if (read_fd < 0) | |
| { | |
| fprintf (stderr, "Error %d (%s) opening %s\n", errno, | |
| xstrerror (errno), pz_curr_file); | |
| exit (EXIT_FAILURE); | |
| } | |
| /* Ensure we do not get duplicate output */ | |
| fflush (stdout); | |
| } | |
| read_fd = start_fixer (read_fd, p_fixd, pz_curr_file); | |
| num_children++; | |
| } | |
| /* IF we have a read-back file descriptor, | |
| THEN check for changes and write output if changed. */ | |
| if (read_fd >= 0) | |
| { | |
| test_for_changes (read_fd); | |
| #ifdef DO_STATS | |
| apply_ct += num_children; | |
| #endif | |
| /* Wait for child processes created by chain_open() | |
| to avoid leaving zombies. */ | |
| do { | |
| wait ((int *) NULL); | |
| } while (--num_children > 0); | |
| } | |
| # else /* is SEPARATE_FIX_PROC */ | |
| for (; todo_ct > 0; p_fixd++, todo_ct--) | |
| { | |
| if (! fix_applies (p_fixd)) | |
| continue; | |
| if (VLEVEL( VERB_APPLIES )) | |
| fprintf (stderr, "Applying %-24s to %s\n", | |
| p_fixd->fix_name, pz_curr_file); | |
| if (p_fixd->fd_flags & FD_REPLACEMENT) | |
| { | |
| write_replacement (p_fixd); | |
| UNLOAD_DATA(); | |
| return; | |
| } | |
| fix_with_system (p_fixd, pz_curr_file, pz_file_source, pz_temp_file); | |
| pz_file_source = pz_temp_file; | |
| } | |
| read_fd = open (pz_temp_file, O_RDONLY); | |
| if (read_fd < 0) | |
| { | |
| if (errno != ENOENT) | |
| fprintf (stderr, "error %d (%s) opening output (%s) for read\n", | |
| errno, xstrerror (errno), pz_temp_file); | |
| } | |
| else | |
| { | |
| test_for_changes (read_fd); | |
| /* Unlinking a file while it is still open is a Bad Idea on | |
| DOS/Windows. */ | |
| close (read_fd); | |
| unlink (pz_temp_file); | |
| } | |
| # endif | |
| UNLOAD_DATA(); | |
| } |