| /* GNU m4 -- A simple macro processor |
| |
| Copyright (C) 1989-1993, 2004, 2006-2014, 2016-2017, 2020-2025 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 <https://www.gnu.org/licenses/>. |
| */ |
| |
| /* Handling of path search of included files via the builtins "include" |
| and "sinclude". */ |
| |
| #include "m4.h" |
| |
| struct includes |
| { |
| struct includes *next; /* next directory to search */ |
| const char *dir; /* directory */ |
| int len; |
| }; |
| |
| typedef struct includes includes; |
| |
| static includes *dir_list; /* the list of path directories */ |
| static includes *dir_list_end; /* the end of same */ |
| static int dir_max_length; /* length of longest directory name */ |
| |
| struct dependency |
| { |
| struct dependency *next; /* next in list of dependencies */ |
| int ref_from; /* bit mask: places file referenced from */ |
| char *path; /* pathname of this dependency */ |
| }; |
| |
| typedef struct dependency dependency; |
| |
| static dependency *dependency_list; /* the list of dependencies */ |
| static dependency *dependency_list_end; /* the end of same */ |
| |
| |
| void |
| include_init (void) |
| { |
| dir_list = NULL; |
| dir_list_end = NULL; |
| dir_max_length = 0; |
| } |
| |
| void |
| include_env_init (void) |
| { |
| char *path; |
| char *env_path; |
| |
| if (no_gnu_extensions) |
| return; |
| |
| env_path = getenv ("M4PATH"); |
| if (env_path == NULL) |
| return; |
| |
| env_path = xstrdup (env_path); |
| path = env_path; |
| |
| for (;;) |
| { |
| char *path_end = strchr (path, ':'); |
| if (path_end) |
| *path_end = '\0'; |
| add_include_directory (path); |
| if (!path_end) |
| break; |
| path = path_end + 1; |
| } |
| free (env_path); |
| } |
| |
| void |
| add_include_directory (const char *dir) |
| { |
| includes *incl; |
| |
| if (no_gnu_extensions) |
| return; |
| |
| if (*dir == '\0') |
| dir = "."; |
| |
| incl = (includes *) xmalloc (sizeof (struct includes)); |
| incl->next = NULL; |
| incl->len = strlen (dir); |
| incl->dir = xstrdup (dir); |
| |
| if (incl->len > dir_max_length) /* remember len of longest directory */ |
| dir_max_length = incl->len; |
| |
| if (dir_list_end == NULL) |
| dir_list = incl; |
| else |
| dir_list_end->next = incl; |
| dir_list_end = incl; |
| |
| #ifdef DEBUG_INCL |
| xfprintf (stderr, "add_include_directory (%s);\n", dir); |
| #endif |
| } |
| |
| /* Attempt to open FILE; if it opens, verify that it is not a |
| directory, and ensure it does not leak across execs. Use binary |
| mode instead of text if BINARY is set. */ |
| static FILE * |
| m4_fopen (const char *file, bool binary) |
| { |
| FILE *fp = fopen (file, binary ? "rbe" : "re"); |
| if (fp) |
| { |
| struct stat st; |
| int fd = fileno (fp); |
| if (fstat (fd, &st) == 0 && S_ISDIR (st.st_mode)) |
| { |
| fclose (fp); |
| errno = EISDIR; |
| return NULL; |
| } |
| } |
| return fp; |
| } |
| |
| /* Search for FILE, first in `.', then according to -I options. If |
| successful, return the open file (in BINARY mode if requested), 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. */ |
| |
| FILE * |
| m4_path_search (const char *file, bool binary, char **result) |
| { |
| FILE *fp; |
| includes *incl; |
| char *name; /* buffer for constructed name */ |
| int e; |
| |
| if (result) |
| *result = NULL; |
| |
| /* Reject empty file. */ |
| if (!*file) |
| { |
| errno = ENOENT; |
| return NULL; |
| } |
| |
| /* Look in current working directory first. */ |
| fp = m4_fopen (file, binary); |
| if (fp != NULL) |
| { |
| if (result) |
| *result = xstrdup (file); |
| return fp; |
| } |
| |
| /* If file not found, and filename absolute, fail. */ |
| if (IS_ABSOLUTE_FILE_NAME (file) || no_gnu_extensions) |
| return NULL; |
| e = errno; |
| |
| for (incl = dir_list; incl != NULL; incl = incl->next) |
| { |
| name = file_name_concat (incl->dir, file, NULL); |
| |
| #ifdef DEBUG_INCL |
| xfprintf (stderr, "m4_path_search (%s) -- trying %s\n", file, name); |
| #endif |
| |
| fp = m4_fopen (name, binary); |
| if (fp != NULL) |
| { |
| if (debug_level & DEBUG_TRACE_PATH) |
| debug_message ("path search for %s found %s", |
| quotearg_style (locale_quoting_style, file), |
| quotearg_n_style (1, locale_quoting_style, name)); |
| if (set_cloexec_flag (fileno (fp), true) != 0) |
| m4_warn (errno, NULL, |
| _("cannot protect input file across forks")); |
| if (result) |
| *result = name; |
| else |
| free (name); |
| return fp; |
| } |
| free (name); |
| } |
| errno = e; |
| return fp; |
| } |
| |
| /* Track that PATH was opened from the given REF_FROM. */ |
| void |
| record_dependency (const char *path, int ref_from) |
| { |
| dependency *dp; |
| for (dp = dependency_list; dp != NULL; dp = dp->next) |
| if (STREQ (path, dp->path)) |
| { |
| /* Remember all the places this file has been referenced from. */ |
| dp->ref_from |= ref_from; |
| return; |
| } |
| |
| dp = xmalloc (sizeof (dependency)); |
| dp->next = NULL; |
| dp->ref_from = ref_from; |
| dp->path = xstrdup (path); |
| |
| if (dependency_list_end == NULL) |
| dependency_list = dp; |
| else |
| dependency_list_end->next = dp; |
| dependency_list_end = dp; |
| } |
| |
| /* Output the Makefile fragment at PATH tracking all dependencies seen, |
| using TARGET as the rule to regenerate this run. PHONY controls |
| whether additional '.PHONY' rules are also output. */ |
| void |
| generate_make_dependencies (const char *path, const char *target, int phony) |
| { |
| FILE *fp; |
| size_t col, len, maxcol; |
| dependency *dp; |
| |
| fp = fopen (path, "w"); |
| if (fp == NULL) |
| { |
| m4_error (0, errno, NULL, _("unable to open %s"), |
| quotearg_style_mem (locale_quoting_style, |
| path, strlen (path))); |
| return; |
| } |
| |
| fputs (_("# Automatically generated by GNU m4.\n"), fp); |
| |
| /* Generate the main dependency rule. */ |
| maxcol = 78; |
| fprintf (fp, "%s:", target); |
| col = strlen (target) + 1; |
| for (dp = dependency_list; dp != NULL; dp = dp->next) |
| { |
| len = 1 + strlen (dp->path); |
| if (col + len + 2 > maxcol) /* +2 for trailing space/backslash */ |
| { |
| fputs (" \\\n ", fp); |
| col = 1; |
| } |
| fprintf (fp, " %s", dp->path); |
| col += len; |
| } |
| fputc ('\n', fp); |
| |
| /* Generate phony targets for user-specified subset of dependencies. */ |
| if (phony != 0) |
| for (dp = dependency_list; dp != NULL; dp = dp->next) |
| if ((dp->ref_from & phony) != 0) |
| fprintf (fp, "\n%s:\n", dp->path); |
| |
| fclose (fp); |
| } |
| |
| #ifdef DEBUG_INCL |
| |
| static void MAYBE_UNUSED |
| include_dump (void) |
| { |
| includes *incl; |
| |
| xfprintf (stderr, "include_dump:\n"); |
| for (incl = dir_list; incl != NULL; incl = incl->next) |
| xfprintf (stderr, "\t%s\n", incl->dir); |
| } |
| |
| #endif /* DEBUG_INCL */ |