# define __pathconf pathconf
# define __rawmemchr rawmemchr
# define __readlink readlink
+# define __stat stat
# ifndef MAXSYMLINKS
# ifdef SYMLOOP_MAX
# define MAXSYMLINKS SYMLOOP_MAX
#if !FUNC_REALPATH_WORKS || defined _LIBC
+/* 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
+suffix_requires_dir_check (char const *end)
+{
+ /* 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;
+}
+
+/* Return true if DIR is a directory, false (setting errno) otherwise.
+ DIREND points to the NUL byte at the end of the DIR string.
+ Store garbage into DIREND[0] and DIREND[1]. */
+
+static bool
+dir_check (char *dir, char *dirend)
+{
+ /* Append "/"; otherwise EOVERFLOW would be ambiguous. */
+ strcpy (dirend, "/");
+
+ struct stat st;
+ return __stat (dir, &st) == 0 || errno == EOVERFLOW;
+}
+
static idx_t
get_path_max (void)
{
return path_max < 0 ? 1024 : path_max <= IDX_MAX ? path_max : IDX_MAX;
}
-/* 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)
+/* 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 *dest;
char const *start;
}
struct scratch_buffer extra_buffer, link_buffer;
- struct scratch_buffer rname_buffer;
- struct scratch_buffer *rname_buf = &rname_buffer;
scratch_buffer_init (&extra_buffer);
scratch_buffer_init (&link_buffer);
scratch_buffer_init (rname_buf);
name ends in '/'. */
idx_t startlen = end - start;
- if (startlen == 1 && start[0] == '.')
+ if (startlen == 0)
+ break;
+ else if (startlen == 1 && start[0] == '.')
/* nothing */;
else if (startlen == 2 && start[0] == '.' && start[1] == '.')
{
if (!ISSLASH (dest[-1]))
*dest++ = '/';
- while (rname + rname_buf->length - dest <= startlen)
+ enum { dir_check_room = sizeof "/" };
+ while (rname + rname_buf->length - dest < startlen + dir_check_room)
{
idx_t dest_offset = dest - rname;
if (!scratch_buffer_grow_preserve (rname_buf))
dest = __mempcpy (dest, start, startlen);
*dest = '\0';
- /* If STARTLEN == 0, RNAME ends in '/'; use stat rather than
- readlink, because readlink might fail with EINVAL without
- checking whether RNAME sans '/' is valid. */
- struct stat st;
- char *buf = NULL;
+ char *buf;
ssize_t n;
- if (startlen != 0)
+ while (true)
{
- while (true)
- {
- 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 (n < 0)
- buf = NULL;
+ 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 (buf)
+ if (0 <= n)
{
if (++num_links > __eloop_threshold ())
{
dest++;
}
}
- else if (! (startlen == 0
- ? stat (rname, &st) == 0 || errno == EOVERFLOW
+ else if (! (suffix_requires_dir_check (end)
+ ? dir_check (rname, dest)
: errno == EINVAL))
goto error;
}
char *result = realloc (rname, rname_size);
return result != NULL ? result : 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 */
# define SLASHES "/"
#endif
+/* 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
+suffix_requires_dir_check (char const *end)
+{
+ /* 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;
+}
+
+/* Return true if DIR is a directory, false (setting errno) otherwise.
+ DIREND points to the NUL byte at the end of the DIR string.
+ Store garbage into DIREND[0] and DIREND[1]. */
+
+static bool
+dir_check (char *dir, char *dirend)
+{
+ /* Append "/"; otherwise EOVERFLOW would be ambiguous. */
+ strcpy (dirend, "/");
+
+ struct stat st;
+ return stat (dir, &st) == 0 || errno == EOVERFLOW;
+}
+
#if !((HAVE_CANONICALIZE_FILE_NAME && FUNC_REALPATH_WORKS) \
|| GNULIB_CANONICALIZE_LGPL)
/* Return the canonical absolute name of file NAME. A canonical name
# if defined GCC_LINT || defined lint
__attribute__ ((__noinline__))
# elif __OPTIMIZE__ && !__NO_INLINE__
-# warning "GCC might issue a bogus -Wreturn-local-addr warning here."
-# warning "See <https://gcc.gnu.org/bugzilla/show_bug.cgi?id=93644>."
+# define GCC_BOGUS_WRETURN_LOCAL_ADDR
# endif
#endif
static char *
name ends in '/'. */
idx_t startlen = end - start;
- if (startlen == 1 && start[0] == '.')
+ if (startlen == 0)
+ break;
+ else if (startlen == 1 && start[0] == '.')
/* nothing */;
else if (startlen == 2 && start[0] == '.' && start[1] == '.')
{
if (!ISSLASH (dest[-1]))
*dest++ = '/';
- while (rname + rname_buf->length - dest <= startlen)
+ enum { dir_check_room = sizeof "/" };
+ while (rname + rname_buf->length - dest < startlen + dir_check_room)
{
idx_t dest_offset = dest - rname;
if (!scratch_buffer_grow_preserve (rname_buf))
dest = mempcpy (dest, start, startlen);
*dest = '\0';
- /* If STARTLEN == 0, RNAME ends in '/'; use stat rather than
- readlink, because readlink might fail with EINVAL without
- checking whether RNAME sans '/' is valid. */
char discard;
- struct stat st;
- char *buf = NULL;
- ssize_t n;
- if (!logical && startlen != 0)
+ char *buf;
+ ssize_t n = -1;
+ if (!logical)
{
while (true)
{
if (!scratch_buffer_grow (&link_buffer))
xalloc_die ();
}
- if (n < 0)
- buf = NULL;
}
- if (buf)
+ if (0 <= n)
{
/* A physical traversal and RNAME is a symbolic link. */
Get the device and inode of the parent directory, as
pre-2017 POSIX says this info is not reliable for
symlinks. */
+ struct stat st;
dest[- startlen] = '\0';
if (stat (*rname ? rname : ".", &st) != 0)
goto error;
}
}
else if (! (can_exist == CAN_MISSING
- || (startlen == 0
- ? stat (rname, &st) == 0 || errno == EOVERFLOW
+ || (suffix_requires_dir_check (end)
+ ? dir_check (rname, dest)
: ((logical && 0 <= readlink (rname, &discard, 1))
|| errno == EINVAL))
|| (can_exist == CAN_ALL_BUT_LAST
char *
canonicalize_filename_mode (const char *name, canonicalize_mode_t can_mode)
{
- /* If GCC -Wreturn-local-addr warns about this buffer, the warning
- is bogus; see canonicalize_filename_mode_stk. */
+ #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 canonicalize_filename_mode_stk (name, can_mode, &rname_buffer);
}