modules: support 8.3 truncated filenames.
[m4/ericb.git] / m4 / path.c
blob44ed620c4294fbd96814467760bdcf19f472aa76
1 /* GNU m4 -- A simple macro processor
2 Copyright (C) 1989-1993, 1998, 2004, 2006-2010, 2013-2014 Free
3 Software Foundation, Inc.
5 This file is part of GNU M4.
7 GNU M4 is free software: you can redistribute it and/or modify
8 it under the terms of the GNU General Public License as published by
9 the Free Software Foundation, either version 3 of the License, or
10 (at your option) any later version.
12 GNU M4 is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 GNU General Public License for more details.
17 You should have received a copy of the GNU General Public License
18 along with this program. If not, see <http://www.gnu.org/licenses/>.
21 /* Handling of path search of included files via the builtins "include"
22 and "sinclude". */
24 #include <config.h>
26 #include <stdio.h>
27 #include <string.h>
28 #include <sys/stat.h>
29 #include <unistd.h>
31 #include "m4private.h"
33 #include "configmake.h"
34 #include "dirname.h"
35 #include "filenamecat.h"
37 #if OS2 /* Any others? */
38 # define TRUNCATE_FILENAME 1
39 #endif
41 /* Define this to see runtime debug info. Implied by DEBUG. */
42 /*#define DEBUG_INCL */
44 static const char *FILE_SUFFIXES[] = {
45 "",
46 ".m4f",
47 ".m4",
48 ".so",
49 ".dll",
50 NULL
53 static const char *NO_SUFFIXES[] = { "", NULL };
55 static void search_path_add (m4__search_path_info *, const char *, bool);
56 static void search_path_env_init (m4__search_path_info *, char *, bool);
57 static void include_env_init (m4 *context);
59 #ifdef DEBUG_INCL
60 static void include_dump (m4 *context);
61 #endif
65 * General functions for search paths
68 static void
69 search_path_add (m4__search_path_info *info, const char *dir, bool prepend)
71 m4__search_path *path = (m4__search_path *) xmalloc (sizeof *path);
73 path->len = strlen (dir);
74 path->dir = xstrdup (dir);
76 if (path->len > info->max_length) /* remember len of longest directory */
77 info->max_length = path->len;
79 if (prepend)
81 path->next = info->list;
82 info->list = path;
83 if (info->list_end == NULL)
84 info->list_end = path;
86 else
88 path->next = NULL;
90 if (info->list_end == NULL)
91 info->list = path;
92 else
93 info->list_end->next = path;
94 info->list_end = path;
98 static void
99 search_path_env_init (m4__search_path_info *info, char *path, bool isabs)
101 char *path_end;
103 if (info == NULL || path == NULL)
104 return;
108 path_end = strchr (path, PATH_SEPARATOR);
109 if (path_end)
110 *path_end = '\0';
111 if (!isabs || *path == '/')
112 search_path_add (info, path, false);
113 path = path_end + 1;
115 while (path_end);
118 static void
119 include_env_init (m4 *context)
121 char *m4path;
123 if (m4_get_posixly_correct_opt (context))
124 return;
126 m4path = getenv ("M4PATH");
127 if (m4path)
128 m4path = xstrdup (m4path);
129 search_path_env_init (m4__get_search_path (context), m4path, false);
130 free (m4path);
134 #if TRUNCATE_FILENAME
135 /* Destructively modify PATH to contain no more than 8 non-`.'
136 characters, optionally followed by a `.' and a filenname extension
137 of 3 characters or fewer. */
138 static char *
139 path_truncate (char *path)
141 char *p, *beg = path; /* following final '/' */
142 for (p = path; *p != '\0'; ++p)
144 if (ISSLASH (*p))
145 beg = 1+ p;
148 char *end = strchr (beg, '.'); /* first period */
149 char *ext = strrchr (beg, '.'); /* last period */
151 size_t len = (size_t) (end - beg); /* length of filename element */
152 if (len > 8)
153 end = beg + 8;
155 if (ext == NULL)
157 *end = '\0';
159 else if (ext != end)
161 stpncpy (end, ext, 4);
164 return path;
166 #else
167 # define path_truncate(path) (path)
168 #endif
172 /* Functions for normal input path search */
174 void
175 m4_add_include_directory (m4 *context, const char *dir, bool prepend)
177 if (m4_get_posixly_correct_opt (context))
178 return;
180 search_path_add (m4__get_search_path (context), dir, prepend);
182 #ifdef DEBUG_INCL
183 xfprintf (stderr, "add_include_directory (%s) %s;\n", dir,
184 prepend ? "prepend" : "append");
185 #endif
189 /* Search for FILENAME according to -B options, `.', -I options, then
190 M4PATH environment. If successful, return the open file, and if
191 RESULT is not NULL, set *RESULT to a malloc'd string that
192 represents the file found with respect to the current working
193 directory. Otherwise, return NULL, and errno reflects the failure
194 from searching `.' (regardless of what else was searched). */
195 char *
196 m4_path_search (m4 *context, const char *filename, const char **suffixes)
198 m4__search_path *incl;
199 char *filepath; /* buffer for constructed name */
200 size_t max_suffix_len = 0;
201 int i, e = 0;
203 /* Reject empty file. */
204 if (*filename == '\0')
206 errno = ENOENT;
207 return NULL;
210 /* Use no suffixes by default. */
211 if (suffixes == NULL)
212 suffixes = NO_SUFFIXES;
214 /* Find the longest suffix, so that we will always allocate enough
215 memory for a filename with suffix. */
216 for (i = 0; suffixes && suffixes[i]; ++i)
218 size_t len = strlen (suffixes[i]);
219 if (len > max_suffix_len)
220 max_suffix_len = len;
223 /* If file is absolute, or if we are not searching a path, a single
224 lookup will do the trick. */
225 if (IS_ABSOLUTE_FILE_NAME (filename))
227 size_t mem = strlen (filename);
229 /* Try appending each of the suffixes we were given. */
230 filepath = path_truncate (strncpy (xmalloc (mem + max_suffix_len +1), filename, mem));
231 for (i = 0; suffixes && suffixes[i]; ++i)
233 strcpy (filepath + mem, suffixes[i]);
234 if (access (filepath, R_OK) == 0)
235 return filepath;
237 /* If search fails, we'll use the error we got from the first
238 access (usually with no suffix). */
239 if (i == 0)
240 e = errno;
242 free (filepath);
244 /* No such file. */
245 errno = e;
246 return NULL;
249 for (incl = m4__get_search_path (context)->list;
250 incl != NULL; incl = incl->next)
252 char *pathname = file_name_concat (incl->dir, filename, NULL);
253 size_t mem = strlen (pathname);
255 #ifdef DEBUG_INCL
256 xfprintf (stderr, "path_search (%s) -- trying %s\n", filename, pathname);
257 #endif
259 if (access (pathname, R_OK) == 0)
261 m4_debug_message (context, M4_DEBUG_TRACE_PATH,
262 _("path search for %s found %s"),
263 quotearg_style (locale_quoting_style, filename),
264 quotearg_n_style (1, locale_quoting_style, pathname));
265 return pathname;
267 else if (!incl->len)
268 /* Capture errno only when searching `.'. */
269 e = errno;
271 filepath = path_truncate (strncpy (xmalloc (mem + max_suffix_len +1), pathname, mem));
272 free (pathname);
274 for (i = 0; suffixes && suffixes[i]; ++i)
276 strcpy (filepath + mem, suffixes[i]);
277 if (access (filepath, R_OK) == 0)
278 return filepath;
280 free (filepath);
283 errno = e;
284 return NULL;
288 /* Attempt to open FILE; if it opens, verify that it is not a
289 directory, and ensure it does not leak across execs. */
290 FILE *
291 m4_fopen (m4 *context, const char *file, const char *mode)
293 FILE *fp = NULL;
295 if (file)
297 struct stat st;
298 int fd;
300 fp = fopen (file, mode);
301 fd = fileno (fp);
303 if (fstat (fd, &st) == 0 && S_ISDIR (st.st_mode))
305 fclose (fp);
306 errno = EISDIR;
307 return NULL;
309 if (set_cloexec_flag (fileno (fp), true) != 0)
310 m4_error (context, 0, errno, NULL,
311 _("cannot protect input file across forks"));
313 return fp;
317 /* Generic load function. Push the input file or load the module named
318 FILENAME, if it can be found in the search path. Complain
319 about inaccesible files iff SILENT is false. */
320 bool
321 m4_load_filename (m4 *context, const m4_call_info *caller,
322 const char *filename, m4_obstack *obs, bool silent)
324 char *filepath = NULL;
325 char *suffix = NULL;
326 bool new_input = false;
328 if (m4_get_posixly_correct_opt (context))
330 if (access (filename, R_OK) == 0)
331 filepath = xstrdup (filename);
333 else
334 filepath = m4_path_search (context, filename, FILE_SUFFIXES);
336 if (filepath)
337 suffix = strrchr (filepath, '.');
339 if (!m4_get_posixly_correct_opt (context)
340 && suffix
341 && STREQ (suffix, ".so"))
343 m4_module_load (context, filename, obs);
345 else
347 FILE *fp = NULL;
349 if (filepath)
350 fp = m4_fopen (context, filepath, "r");
352 if (fp == NULL)
354 if (!silent)
355 m4_error (context, 0, errno, caller, _("cannot open file '%s'"),
356 filename);
357 free (filepath);
358 return false;
361 m4_push_file (context, fp, filepath, true);
362 new_input = true;
364 free (filepath);
366 return new_input;
370 void
371 m4__include_init (m4 *context)
373 include_env_init (context);
376 m4__search_path_info *info = m4__get_search_path (context);
378 /* If M4PATH was not set, then search just the current directory by
379 default. */
380 assert (info);
381 if (info->list_end == NULL)
382 search_path_add (info, "", false);
384 /* Non-core modules installation directory. */
385 search_path_add (info, PKGLIBDIR, false);
388 #ifdef DEBUG_INCL
389 fputs ("initial include search path...\n", stderr);
390 include_dump (context);
391 #endif
396 #ifdef DEBUG_INCL
398 static void
399 include_dump (m4 *context)
401 m4__search_path *incl;
403 fputs ("include_dump:\n", stderr);
404 for (incl = m4__get_search_path (context)->list;
405 incl != NULL; incl = incl->next)
406 xfprintf (stderr, "\t'%s'\n", incl->dir);
409 #endif /* DEBUG_INCL */