/* GNU m4 -- A simple macro processor Copyright (C) 1989-1993, 1998, 2004, 2006-2010, 2013-2014 Free Software Foundation, Inc. This file is part of GNU M4. GNU M4 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 of the License, or (at your option) any later version. GNU M4 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 this program. If not, see . */ /* Handling of path search of included files via the builtins "include" and "sinclude". */ #include #include #include #include #include #include "m4private.h" #include "configmake.h" #include "dirname.h" #include "filenamecat.h" #if OS2 /* Any others? */ # define TRUNCATE_FILENAME 1 #endif /* Define this to see runtime debug info. Implied by DEBUG. */ /*#define DEBUG_INCL */ static const char *FILE_SUFFIXES[] = { "", ".m4f", ".m4", LT_MODULE_EXT, NULL }; static const char *NO_SUFFIXES[] = { "", NULL }; static void search_path_add (m4__search_path_info *, const char *, bool); static void search_path_env_init (m4__search_path_info *, char *, bool); static void include_env_init (m4 *context); #ifdef DEBUG_INCL static void include_dump (m4 *context); #endif /* * General functions for search paths */ static void search_path_add (m4__search_path_info *info, const char *dir, bool prepend) { m4__search_path *path = (m4__search_path *) xmalloc (sizeof *path); path->len = strlen (dir); path->dir = xstrdup (dir); if (path->len > info->max_length) /* remember len of longest directory */ info->max_length = path->len; if (prepend) { path->next = info->list; info->list = path; if (info->list_end == NULL) info->list_end = path; } else { path->next = NULL; if (info->list_end == NULL) info->list = path; else info->list_end->next = path; info->list_end = path; } } static void search_path_env_init (m4__search_path_info *info, char *path, bool isabs) { char *path_end; if (info == NULL || path == NULL) return; do { path_end = strchr (path, PATH_SEPARATOR); if (path_end) *path_end = '\0'; if (!isabs || *path == '/') search_path_add (info, path, false); path = path_end + 1; } while (path_end); } static void include_env_init (m4 *context) { char *m4path; if (m4_get_posixly_correct_opt (context)) return; m4path = getenv ("M4PATH"); if (m4path) m4path = xstrdup (m4path); search_path_env_init (m4__get_search_path (context), m4path, false); free (m4path); } #if TRUNCATE_FILENAME /* Destructively modify PATH to contain no more than 8 non-`.' characters, optionally followed by a `.' and a filenname extension of 3 characters or fewer. */ static char * path_truncate (char *path) { char *p, *beg = path; /* following final '/' */ for (p = path; *p != '\0'; ++p) { if (ISSLASH (*p)) beg = 1+ p; } char *end = strchr (beg, '.'); /* first period */ char *ext = strrchr (beg, '.'); /* last period */ size_t len = (size_t) (end - beg); /* length of filename element */ if (len > 8) end = beg + 8; if (ext == NULL) { *end = '\0'; } else if (ext != end) { stpncpy (end, ext, 4); } return path; } #else # define path_truncate(path) (path) #endif /* Functions for normal input path search */ void m4_add_include_directory (m4 *context, const char *dir, bool prepend) { if (m4_get_posixly_correct_opt (context)) return; search_path_add (m4__get_search_path (context), dir, prepend); #ifdef DEBUG_INCL xfprintf (stderr, "add_include_directory (%s) %s;\n", dir, prepend ? "prepend" : "append"); #endif } /* Search for FILENAME according to -B options, `.', -I options, then M4PATH environment. If successful, return the open file, and if RESULT is not NULL, set *RESULT to a malloc'd string that represents the file found with respect to the current working directory. Otherwise, return NULL, and errno reflects the failure from searching `.' (regardless of what else was searched). */ char * m4_path_search (m4 *context, const char *filename, const char **suffixes) { m4__search_path *incl; char *filepath; /* buffer for constructed name */ size_t max_suffix_len = 0; int i, e = 0; /* Reject empty file. */ if (*filename == '\0') { errno = ENOENT; return NULL; } /* Use no suffixes by default. */ if (suffixes == NULL) suffixes = NO_SUFFIXES; /* Find the longest suffix, so that we will always allocate enough memory for a filename with suffix. */ for (i = 0; suffixes && suffixes[i]; ++i) { size_t len = strlen (suffixes[i]); if (len > max_suffix_len) max_suffix_len = len; } /* If file is absolute, or if we are not searching a path, a single lookup will do the trick. */ if (IS_ABSOLUTE_FILE_NAME (filename)) { size_t mem = strlen (filename); /* Try appending each of the suffixes we were given. */ filepath = path_truncate (strncpy (xmalloc (mem + max_suffix_len +1), filename, mem)); for (i = 0; suffixes && suffixes[i]; ++i) { strcpy (filepath + mem, suffixes[i]); if (access (filepath, R_OK) == 0) return filepath; /* If search fails, we'll use the error we got from the first access (usually with no suffix). */ if (i == 0) e = errno; } free (filepath); /* No such file. */ errno = e; return NULL; } for (incl = m4__get_search_path (context)->list; incl != NULL; incl = incl->next) { char *pathname = file_name_concat (incl->dir, filename, NULL); size_t mem = strlen (pathname); #ifdef DEBUG_INCL xfprintf (stderr, "path_search (%s) -- trying %s\n", filename, pathname); #endif if (access (pathname, R_OK) == 0) { m4_debug_message (context, M4_DEBUG_TRACE_PATH, _("path search for %s found %s"), quotearg_style (locale_quoting_style, filename), quotearg_n_style (1, locale_quoting_style, pathname)); return pathname; } else if (!incl->len) /* Capture errno only when searching `.'. */ e = errno; filepath = path_truncate (strncpy (xmalloc (mem + max_suffix_len +1), pathname, mem)); free (pathname); for (i = 0; suffixes && suffixes[i]; ++i) { strcpy (filepath + mem, suffixes[i]); if (access (filepath, R_OK) == 0) return filepath; } free (filepath); } errno = e; return NULL; } /* Attempt to open FILE; if it opens, verify that it is not a directory, and ensure it does not leak across execs. */ FILE * m4_fopen (m4 *context, const char *file, const char *mode) { FILE *fp = NULL; if (file) { struct stat st; int fd; fp = fopen (file, mode); fd = fileno (fp); if (fstat (fd, &st) == 0 && S_ISDIR (st.st_mode)) { fclose (fp); errno = EISDIR; return NULL; } if (set_cloexec_flag (fileno (fp), true) != 0) m4_error (context, 0, errno, NULL, _("cannot protect input file across forks")); } return fp; } /* Generic load function. Push the input file or load the module named FILENAME, if it can be found in the search path. Complain about inaccesible files iff SILENT is false. */ bool m4_load_filename (m4 *context, const m4_call_info *caller, const char *filename, m4_obstack *obs, bool silent) { char *filepath = NULL; char *suffix = NULL; bool new_input = false; if (m4_get_posixly_correct_opt (context)) { if (access (filename, R_OK) == 0) filepath = xstrdup (filename); } else filepath = m4_path_search (context, filename, FILE_SUFFIXES); if (filepath) suffix = strrchr (filepath, '.'); if (!m4_get_posixly_correct_opt (context) && suffix && STREQ (suffix, LT_MODULE_EXT)) { m4_module_load (context, filename, obs); } else { FILE *fp = NULL; if (filepath) fp = m4_fopen (context, filepath, "r"); if (fp == NULL) { if (!silent) m4_error (context, 0, errno, caller, _("cannot open file '%s'"), filename); free (filepath); return false; } m4_push_file (context, fp, filepath, true); new_input = true; } free (filepath); return new_input; } void m4__include_init (m4 *context) { include_env_init (context); { m4__search_path_info *info = m4__get_search_path (context); /* If M4PATH was not set, then search just the current directory by default. */ assert (info); if (info->list_end == NULL) search_path_add (info, "", false); /* Non-core modules installation directory. */ search_path_add (info, PKGLIBDIR, false); } #ifdef DEBUG_INCL fputs ("initial include search path...\n", stderr); include_dump (context); #endif } #ifdef DEBUG_INCL static void include_dump (m4 *context) { m4__search_path *incl; fputs ("include_dump:\n", stderr); for (incl = m4__get_search_path (context)->list; incl != NULL; incl = incl->next) xfprintf (stderr, "\t'%s'\n", incl->dir); } #endif /* DEBUG_INCL */