blob: d43ef5984045ec3b3da068a5524f4645a230bcb1 [file] [log] [blame]
/* 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 */