summaryrefslogtreecommitdiff
path: root/gnulib/import/canonicalize-lgpl.c
diff options
context:
space:
mode:
Diffstat (limited to 'gnulib/import/canonicalize-lgpl.c')
-rw-r--r--gnulib/import/canonicalize-lgpl.c495
1 files changed, 267 insertions, 228 deletions
diff --git a/gnulib/import/canonicalize-lgpl.c b/gnulib/import/canonicalize-lgpl.c
index 9f990988393..4bb4c0c2d96 100644
--- a/gnulib/import/canonicalize-lgpl.c
+++ b/gnulib/import/canonicalize-lgpl.c
@@ -1,19 +1,20 @@
/* Return the canonical absolute name of a given file.
- Copyright (C) 1996-2020 Free Software Foundation, Inc.
+ Copyright (C) 1996-2021 Free Software Foundation, Inc.
This file is part of the GNU C Library.
- This program 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.
+ The GNU C Library 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.
- This program is distributed in the hope that it will be useful,
+ The GNU C Library 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.
+ 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/>. */
+ You should have received a copy of the GNU General Public
+ License along with the GNU C Library; if not, see
+ <https://www.gnu.org/licenses/>. */
#ifndef _LIBC
/* Don't use __attribute__ __nonnull__ in this compilation unit. Otherwise gcc
@@ -21,38 +22,38 @@
# define _GL_ARG_NONNULL(params)
# define _GL_USE_STDLIB_ALLOC 1
-# include <config.h>
+# include <libc-config.h>
#endif
-#if !HAVE_CANONICALIZE_FILE_NAME || !FUNC_REALPATH_WORKS || defined _LIBC
-
/* Specification. */
#include <stdlib.h>
-#include <alloca.h>
-#include <string.h>
-#include <unistd.h>
+#include <errno.h>
+#include <fcntl.h>
#include <limits.h>
-#if HAVE_SYS_PARAM_H || defined _LIBC
-# include <sys/param.h>
-#endif
+#include <stdbool.h>
+#include <string.h>
#include <sys/stat.h>
-#include <errno.h>
-#include <stddef.h>
+#include <unistd.h>
+
+#include <eloop-threshold.h>
+#include <filename.h>
+#include <idx.h>
+#include <intprops.h>
+#include <scratch_buffer.h>
#ifdef _LIBC
# include <shlib-compat.h>
+# define GCC_LINT 1
+# define _GL_ATTRIBUTE_PURE __attribute__ ((__pure__))
#else
-# define SHLIB_COMPAT(lib, introduced, obsoleted) 0
-# define versioned_symbol(lib, local, symbol, version) extern int dummy
-# define compat_symbol(lib, local, symbol, version)
-# define weak_alias(local, symbol)
# define __canonicalize_file_name canonicalize_file_name
# define __realpath realpath
# include "pathmax.h"
-# include "malloca.h"
-# include "filename.h"
-# if HAVE_GETCWD
+# define __faccessat faccessat
+# if defined _WIN32 && !defined __CYGWIN__
+# define __getcwd _getcwd
+# elif HAVE_GETCWD
# if IN_RELOCWRAPPER
/* When building the relocatable program wrapper, use the system's getcwd
function, not the gnulib override, otherwise we would get a link error.
@@ -70,57 +71,137 @@
# else
# define __getcwd(buf, max) getwd (buf)
# endif
+# define __mempcpy mempcpy
+# define __pathconf pathconf
+# define __rawmemchr rawmemchr
# define __readlink readlink
-# define __set_errno(e) errno = (e)
-# ifndef MAXSYMLINKS
-# ifdef SYMLOOP_MAX
-# define MAXSYMLINKS SYMLOOP_MAX
-# else
-# define MAXSYMLINKS 20
-# endif
-# endif
+# define __stat stat
#endif
-#ifndef DOUBLE_SLASH_IS_DISTINCT_ROOT
-# define DOUBLE_SLASH_IS_DISTINCT_ROOT 0
+/* Suppress bogus GCC -Wmaybe-uninitialized warnings. */
+#if defined GCC_LINT || defined lint
+# define IF_LINT(Code) Code
+#else
+# define IF_LINT(Code) /* empty */
#endif
-/* Define this independently so that stdint.h is not a prerequisite. */
-#ifndef SIZE_MAX
-# define SIZE_MAX ((size_t) -1)
+#ifndef DOUBLE_SLASH_IS_DISTINCT_ROOT
+# define DOUBLE_SLASH_IS_DISTINCT_ROOT false
#endif
-#if !FUNC_REALPATH_WORKS || defined _LIBC
+#if defined _LIBC || !FUNC_REALPATH_WORKS
+
+/* Return true if FILE's existence can be shown, false (setting errno)
+ otherwise. Follow symbolic links. */
+static bool
+file_accessible (char const *file)
+{
+# if defined _LIBC || HAVE_FACCESSAT
+ return __faccessat (AT_FDCWD, file, F_OK, AT_EACCESS) == 0;
+# else
+ struct stat st;
+ return __stat (file, &st) == 0 || errno == EOVERFLOW;
+# endif
+}
-static void
-alloc_failed (void)
+/* True if concatenating END as a suffix to a file name means that the
+ code needs to check that the file name is that of a searchable
+ directory, since the canonicalize_filename_mode_stk code won't
+ check this later anyway when it checks an ordinary file name
+ component within END. END must either be empty, or start with a
+ slash. */
+
+static bool _GL_ATTRIBUTE_PURE
+suffix_requires_dir_check (char const *end)
{
-#if defined _WIN32 && ! defined __CYGWIN__
- /* Avoid errno problem without using the malloc or realloc modules; see:
- https://lists.gnu.org/r/bug-gnulib/2016-08/msg00025.html */
- errno = ENOMEM;
+ /* If END does not start with a slash, the suffix is OK. */
+ while (ISSLASH (*end))
+ {
+ /* Two or more slashes act like a single slash. */
+ do
+ end++;
+ while (ISSLASH (*end));
+
+ switch (*end++)
+ {
+ default: return false; /* An ordinary file name component is OK. */
+ case '\0': return true; /* Trailing "/" is trouble. */
+ case '.': break; /* Possibly "." or "..". */
+ }
+ /* Trailing "/.", or "/.." even if not trailing, is trouble. */
+ if (!*end || (*end == '.' && (!end[1] || ISSLASH (end[1]))))
+ return true;
+ }
+
+ return false;
+}
+
+/* Append this to a file name to test whether it is a searchable directory.
+ On POSIX platforms "/" suffices, but "/./" is sometimes needed on
+ macOS 10.13 <https://bugs.gnu.org/30350>, and should also work on
+ platforms like AIX 7.2 that need at least "/.". */
+
+#if defined _LIBC || defined LSTAT_FOLLOWS_SLASHED_SYMLINK
+static char const dir_suffix[] = "/";
+#else
+static char const dir_suffix[] = "/./";
#endif
+
+/* Return true if DIR is a searchable dir, false (setting errno) otherwise.
+ DIREND points to the NUL byte at the end of the DIR string.
+ Store garbage into DIREND[0 .. strlen (dir_suffix)]. */
+
+static bool
+dir_check (char *dir, char *dirend)
+{
+ strcpy (dirend, dir_suffix);
+ return file_accessible (dir);
}
-/* Return the canonical absolute name of file NAME. A canonical name
- does not contain any ".", ".." components nor any repeated path
- separators ('/') or symlinks. All path components must exist. If
- RESOLVED is null, the result is malloc'd; otherwise, if the
- canonical name is PATH_MAX chars or more, returns null with 'errno'
- set to ENAMETOOLONG; if the name fits in fewer than PATH_MAX chars,
- returns the name in RESOLVED. If the name cannot be resolved and
- RESOLVED is non-NULL, it contains the path of the first component
- that cannot be resolved. If the path can be resolved, RESOLVED
- holds the same value as the value returned. */
+static idx_t
+get_path_max (void)
+{
+# ifdef PATH_MAX
+ long int path_max = PATH_MAX;
+# else
+ /* The caller invoked realpath with a null RESOLVED, even though
+ PATH_MAX is not defined as a constant. The glibc manual says
+ programs should not do this, and POSIX says the behavior is undefined.
+ Historically, glibc here used the result of pathconf, or 1024 if that
+ failed; stay consistent with this (dubious) historical practice. */
+ int err = errno;
+ long int path_max = __pathconf ("/", _PC_PATH_MAX);
+ __set_errno (err);
+# endif
+ return path_max < 0 ? 1024 : path_max <= IDX_MAX ? path_max : IDX_MAX;
+}
-char *
-__realpath (const char *name, char *resolved)
+/* Act like __realpath (see below), with an additional argument
+ rname_buf that can be used as temporary storage.
+
+ If GCC_LINT is defined, do not inline this function with GCC 10.1
+ and later, to avoid creating a pointer to the stack that GCC
+ -Wreturn-local-addr incorrectly complains about. See:
+ https://gcc.gnu.org/bugzilla/show_bug.cgi?id=93644
+ Although the noinline attribute can hurt performance a bit, no better way
+ to pacify GCC is known; even an explicit #pragma does not pacify GCC.
+ When the GCC bug is fixed this workaround should be limited to the
+ broken GCC versions. */
+#if __GNUC_PREREQ (10, 1)
+# if defined GCC_LINT || defined lint
+__attribute__ ((__noinline__))
+# elif __OPTIMIZE__ && !__NO_INLINE__
+# define GCC_BOGUS_WRETURN_LOCAL_ADDR
+# endif
+#endif
+static char *
+realpath_stk (const char *name, char *resolved,
+ struct scratch_buffer *rname_buf)
{
- char *rpath, *dest, *extra_buf = NULL;
- const char *start, *end, *rpath_limit;
- long int path_max;
+ char *dest;
+ char const *start;
+ char const *end;
int num_links = 0;
- size_t prefix_len;
if (name == NULL)
{
@@ -140,205 +221,148 @@ __realpath (const char *name, char *resolved)
return NULL;
}
-#ifdef PATH_MAX
- path_max = PATH_MAX;
-#else
- path_max = pathconf (name, _PC_PATH_MAX);
- if (path_max <= 0)
- path_max = 8192;
-#endif
-
- if (resolved == NULL)
- {
- rpath = malloc (path_max);
- if (rpath == NULL)
- {
- alloc_failed ();
- return NULL;
- }
- }
- else
- rpath = resolved;
- rpath_limit = rpath + path_max;
+ struct scratch_buffer extra_buffer, link_buffer;
+ scratch_buffer_init (&extra_buffer);
+ scratch_buffer_init (&link_buffer);
+ scratch_buffer_init (rname_buf);
+ char *rname_on_stack = rname_buf->data;
+ char *rname = rname_on_stack;
+ bool end_in_extra_buffer = false;
+ bool failed = true;
/* This is always zero for Posix hosts, but can be 2 for MS-Windows
and MS-DOS X:/foo/bar file names. */
- prefix_len = FILE_SYSTEM_PREFIX_LEN (name);
+ idx_t prefix_len = FILE_SYSTEM_PREFIX_LEN (name);
if (!IS_ABSOLUTE_FILE_NAME (name))
{
- if (!__getcwd (rpath, path_max))
+ while (!__getcwd (rname, rname_buf->length))
{
- rpath[0] = '\0';
- goto error;
+ if (errno != ERANGE)
+ {
+ dest = rname;
+ goto error;
+ }
+ if (!scratch_buffer_grow (rname_buf))
+ goto error_nomem;
+ rname = rname_buf->data;
}
- dest = strchr (rpath, '\0');
+ dest = __rawmemchr (rname, '\0');
start = name;
- prefix_len = FILE_SYSTEM_PREFIX_LEN (rpath);
+ prefix_len = FILE_SYSTEM_PREFIX_LEN (rname);
}
else
{
- dest = rpath;
- if (prefix_len)
- {
- memcpy (rpath, name, prefix_len);
- dest += prefix_len;
- }
+ dest = __mempcpy (rname, name, prefix_len);
*dest++ = '/';
if (DOUBLE_SLASH_IS_DISTINCT_ROOT)
{
- if (ISSLASH (name[1]) && !ISSLASH (name[2]) && !prefix_len)
+ if (prefix_len == 0 /* implies ISSLASH (name[0]) */
+ && ISSLASH (name[1]) && !ISSLASH (name[2]))
*dest++ = '/';
*dest = '\0';
}
start = name + prefix_len;
}
- for (end = start; *start; start = end)
+ for ( ; *start; start = end)
{
-#ifdef _LIBC
- struct stat64 st;
-#else
- struct stat st;
-#endif
-
- /* Skip sequence of multiple path-separators. */
+ /* Skip sequence of multiple file name separators. */
while (ISSLASH (*start))
++start;
- /* Find end of path component. */
+ /* Find end of component. */
for (end = start; *end && !ISSLASH (*end); ++end)
/* Nothing. */;
- if (end - start == 0)
+ /* Length of this file name component; it can be zero if a file
+ name ends in '/'. */
+ idx_t startlen = end - start;
+
+ if (startlen == 0)
break;
- else if (end - start == 1 && start[0] == '.')
+ else if (startlen == 1 && start[0] == '.')
/* nothing */;
- else if (end - start == 2 && start[0] == '.' && start[1] == '.')
+ else if (startlen == 2 && start[0] == '.' && start[1] == '.')
{
/* Back up to previous component, ignore if at root already. */
- if (dest > rpath + prefix_len + 1)
- for (--dest; dest > rpath && !ISSLASH (dest[-1]); --dest)
+ if (dest > rname + prefix_len + 1)
+ for (--dest; dest > rname && !ISSLASH (dest[-1]); --dest)
continue;
if (DOUBLE_SLASH_IS_DISTINCT_ROOT
- && dest == rpath + 1 && !prefix_len
+ && dest == rname + 1 && !prefix_len
&& ISSLASH (*dest) && !ISSLASH (dest[1]))
dest++;
}
else
{
- size_t new_size;
-
if (!ISSLASH (dest[-1]))
*dest++ = '/';
- if (dest + (end - start) >= rpath_limit)
+ while (rname + rname_buf->length - dest
+ < startlen + sizeof dir_suffix)
{
- ptrdiff_t dest_offset = dest - rpath;
- char *new_rpath;
-
- if (resolved)
- {
- __set_errno (ENAMETOOLONG);
- if (dest > rpath + prefix_len + 1)
- dest--;
- *dest = '\0';
- goto error;
- }
- new_size = rpath_limit - rpath;
- if (end - start + 1 > path_max)
- new_size += end - start + 1;
- else
- new_size += path_max;
- new_rpath = (char *) realloc (rpath, new_size);
- if (new_rpath == NULL)
- {
- alloc_failed ();
- goto error;
- }
- rpath = new_rpath;
- rpath_limit = rpath + new_size;
-
- dest = rpath + dest_offset;
+ idx_t dest_offset = dest - rname;
+ if (!scratch_buffer_grow_preserve (rname_buf))
+ goto error_nomem;
+ rname = rname_buf->data;
+ dest = rname + dest_offset;
}
-#ifdef _LIBC
- dest = __mempcpy (dest, start, end - start);
-#else
- memcpy (dest, start, end - start);
- dest += end - start;
-#endif
+ dest = __mempcpy (dest, start, startlen);
*dest = '\0';
- /* FIXME: if lstat fails with errno == EOVERFLOW,
- the entry exists. */
-#ifdef _LIBC
- if (__lxstat64 (_STAT_VER, rpath, &st) < 0)
-#else
- if (lstat (rpath, &st) < 0)
-#endif
- goto error;
-
- if (S_ISLNK (st.st_mode))
+ char *buf;
+ ssize_t n;
+ while (true)
{
- char *buf;
- size_t len;
- ssize_t n;
-
- if (++num_links > MAXSYMLINKS)
+ buf = link_buffer.data;
+ idx_t bufsize = link_buffer.length;
+ n = __readlink (rname, buf, bufsize - 1);
+ if (n < bufsize - 1)
+ break;
+ if (!scratch_buffer_grow (&link_buffer))
+ goto error_nomem;
+ }
+ if (0 <= n)
+ {
+ if (++num_links > __eloop_threshold ())
{
__set_errno (ELOOP);
goto error;
}
- buf = malloca (path_max);
- if (!buf)
- {
- __set_errno (ENOMEM);
- goto error;
- }
-
- n = __readlink (rpath, buf, path_max - 1);
- if (n < 0)
- {
- int saved_errno = errno;
- freea (buf);
- __set_errno (saved_errno);
- goto error;
- }
buf[n] = '\0';
- if (!extra_buf)
+ char *extra_buf = extra_buffer.data;
+ idx_t end_idx IF_LINT (= 0);
+ if (end_in_extra_buffer)
+ end_idx = end - extra_buf;
+ size_t len = strlen (end);
+ if (INT_ADD_OVERFLOW (len, n))
{
- extra_buf = malloca (path_max);
- if (!extra_buf)
- {
- freea (buf);
- __set_errno (ENOMEM);
- goto error;
- }
+ __set_errno (ENOMEM);
+ goto error_nomem;
}
-
- len = strlen (end);
- /* Check that n + len + 1 doesn't overflow and is <= path_max. */
- if (n >= SIZE_MAX - len || n + len >= path_max)
+ while (extra_buffer.length <= len + n)
{
- freea (buf);
- __set_errno (ENAMETOOLONG);
- goto error;
+ if (!scratch_buffer_grow_preserve (&extra_buffer))
+ goto error_nomem;
+ extra_buf = extra_buffer.data;
}
+ if (end_in_extra_buffer)
+ end = extra_buf + end_idx;
/* Careful here, end may be a pointer into extra_buf... */
memmove (&extra_buf[n], end, len + 1);
name = end = memcpy (extra_buf, buf, n);
+ end_in_extra_buffer = true;
if (IS_ABSOLUTE_FILE_NAME (buf))
{
- size_t pfxlen = FILE_SYSTEM_PREFIX_LEN (buf);
+ idx_t pfxlen = FILE_SYSTEM_PREFIX_LEN (buf);
- if (pfxlen)
- memcpy (rpath, buf, pfxlen);
- dest = rpath + pfxlen;
+ dest = __mempcpy (rname, buf, pfxlen);
*dest++ = '/'; /* It's an absolute symlink */
if (DOUBLE_SLASH_IS_DISTINCT_ROOT)
{
@@ -353,44 +377,67 @@ __realpath (const char *name, char *resolved)
{
/* Back up to previous component, ignore if at root
already: */
- if (dest > rpath + prefix_len + 1)
- for (--dest; dest > rpath && !ISSLASH (dest[-1]); --dest)
+ if (dest > rname + prefix_len + 1)
+ for (--dest; dest > rname && !ISSLASH (dest[-1]); --dest)
continue;
- if (DOUBLE_SLASH_IS_DISTINCT_ROOT && dest == rpath + 1
+ if (DOUBLE_SLASH_IS_DISTINCT_ROOT && dest == rname + 1
&& ISSLASH (*dest) && !ISSLASH (dest[1]) && !prefix_len)
dest++;
}
}
- else if (!S_ISDIR (st.st_mode) && *end != '\0')
- {
- __set_errno (ENOTDIR);
- goto error;
- }
+ else if (! (suffix_requires_dir_check (end)
+ ? dir_check (rname, dest)
+ : errno == EINVAL))
+ goto error;
}
}
- if (dest > rpath + prefix_len + 1 && ISSLASH (dest[-1]))
+ if (dest > rname + prefix_len + 1 && ISSLASH (dest[-1]))
--dest;
- if (DOUBLE_SLASH_IS_DISTINCT_ROOT && dest == rpath + 1 && !prefix_len
+ if (DOUBLE_SLASH_IS_DISTINCT_ROOT && dest == rname + 1 && !prefix_len
&& ISSLASH (*dest) && !ISSLASH (dest[1]))
dest++;
- *dest = '\0';
+ failed = false;
- if (extra_buf)
- freea (extra_buf);
+error:
+ *dest++ = '\0';
+ if (resolved != NULL && dest - rname <= get_path_max ())
+ rname = strcpy (resolved, rname);
- return rpath;
+error_nomem:
+ scratch_buffer_free (&extra_buffer);
+ scratch_buffer_free (&link_buffer);
-error:
- {
- int saved_errno = errno;
- if (extra_buf)
- freea (extra_buf);
- if (resolved == NULL)
- free (rpath);
- __set_errno (saved_errno);
- }
- return NULL;
+ if (failed || rname == resolved)
+ {
+ scratch_buffer_free (rname_buf);
+ return failed ? NULL : resolved;
+ }
+
+ return scratch_buffer_dupfree (rname_buf, dest - rname);
}
+
+/* Return the canonical absolute name of file NAME. A canonical name
+ does not contain any ".", ".." components nor any repeated file name
+ separators ('/') or symlinks. All file name components must exist. If
+ RESOLVED is null, the result is malloc'd; otherwise, if the
+ canonical name is PATH_MAX chars or more, returns null with 'errno'
+ set to ENAMETOOLONG; if the name fits in fewer than PATH_MAX chars,
+ returns the name in RESOLVED. If the name cannot be resolved and
+ RESOLVED is non-NULL, it contains the name of the first component
+ that cannot be resolved. If the name can be resolved, RESOLVED
+ holds the same value as the value returned. */
+
+char *
+__realpath (const char *name, char *resolved)
+{
+ #ifdef GCC_BOGUS_WRETURN_LOCAL_ADDR
+ #warning "GCC might issue a bogus -Wreturn-local-addr warning here."
+ #warning "See <https://gcc.gnu.org/bugzilla/show_bug.cgi?id=93644>."
+ #endif
+ struct scratch_buffer rname_buffer;
+ return realpath_stk (name, resolved, &rname_buffer);
+}
+libc_hidden_def (__realpath)
versioned_symbol (libc, __realpath, realpath, GLIBC_2_3);
#endif /* !FUNC_REALPATH_WORKS || defined _LIBC */
@@ -418,11 +465,3 @@ __canonicalize_file_name (const char *name)
return __realpath (name, NULL);
}
weak_alias (__canonicalize_file_name, canonicalize_file_name)
-
-#else
-
-/* This declaration is solely to ensure that after preprocessing
- this file is never empty. */
-typedef int dummy;
-
-#endif