[PATCH 1/5] stdlib: Sync canonicalize with gnulib [BZ #10635] [BZ #26592] [BZ #26241]
Adhemerval Zanella
adhemerval.zanella@linaro.org
Thu Dec 24 15:16:57 GMT 2020
It sync with gnulib version d9c121346 with the following difference
require fix a glibc build:
--- ../../gnulib/gnulib-lib/lib/canonicalize-lgpl.c
+++ stdlib/canonicalize.c
@@ -46,7 +46,7 @@
# define FILE_SYSTEM_PREFIX_LEN(name) 0
# define IS_ABSOLUTE_FILE_NAME(name) ISSLASH(*(name))
# define ISSLASH(c) ((c) == '/')
-# define freea(p) ((void) (p))
+# define FUNC_REALPATH_WORKS 1
#else
# define __canonicalize_file_name canonicalize_file_name
# define __realpath realpath
@@ -270,7 +270,7 @@
buf[n] = '\0';
char *extra_buf = extra_buffer.data;
- idx_t end_idx;
+ idx_t end_idx = 0;
if (end_in_extra_buffer)
end_idx = end - extra_buf;
idx_t len = strlen (end);
It shows the following testcase failures:
FAIL: stdlib/test-canon
azanella@birita:~/Projects/glibc/build/x86_64-linux-gnu$ cat stdlib/test-canon.out
stdlib/test-canon: flunked test 30 (expected resolved `./doesExist/someFile', got `[builddir]/stdlib/doesExist/someFile/')
stdlib/test-canon: flunked test 31 (expected `NULL', got `[builddir]/stdlib/doesExist')
2 errors.
(the [builddir] the build directory I used)
This regression will be fixed in the last commit of this set.
The sync also fixes BZ#26592 and BZ#26241.
---
stdlib/canonicalize.c | 456 +++++++++++++++++++++++++++---------------
1 file changed, 297 insertions(+), 159 deletions(-)
diff --git a/stdlib/canonicalize.c b/stdlib/canonicalize.c
index 3fcb399a5d..66685f4526 100644
--- a/stdlib/canonicalize.c
+++ b/stdlib/canonicalize.c
@@ -16,43 +16,126 @@
License along with the GNU C Library; if not, see
<https://www.gnu.org/licenses/>. */
-#include <assert.h>
+#ifndef _LIBC
+/* Don't use __attribute__ __nonnull__ in this compilation unit. Otherwise gcc
+ optimizes away the name == NULL test below. */
+# define _GL_ARG_NONNULL(params)
+
+# define _GL_USE_STDLIB_ALLOC 1
+# include <libc-config.h>
+#endif
+
+/* Specification. */
#include <stdlib.h>
-#include <string.h>
-#include <unistd.h>
-#include <limits.h>
-#include <sys/stat.h>
+
#include <errno.h>
+#include <limits.h>
+#include <stdbool.h>
#include <stddef.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include <scratch_buffer.h>
+
+#ifdef _LIBC
+# include <eloop-threshold.h>
+# include <shlib-compat.h>
+typedef ptrdiff_t idx_t;
+# define IDX_MAX PTRDIFF_MAX
+# define FILE_SYSTEM_PREFIX_LEN(name) 0
+# define IS_ABSOLUTE_FILE_NAME(name) ISSLASH(*(name))
+# define ISSLASH(c) ((c) == '/')
+# define FUNC_REALPATH_WORKS 1
+#else
+# define __canonicalize_file_name canonicalize_file_name
+# define __realpath realpath
+# include "idx.h"
+# include "pathmax.h"
+# include "filename.h"
+# 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.
+ */
+# undef getcwd
+# endif
+# if defined VMS && !defined getcwd
+ /* We want the directory in Unix syntax, not in VMS syntax.
+ The gnulib override of 'getcwd' takes 2 arguments; the original VMS
+ 'getcwd' takes 3 arguments. */
+# define __getcwd(buf, max) getcwd (buf, max, 0)
+# else
+# define __getcwd getcwd
+# endif
+# else
+# define __getcwd(buf, max) getwd (buf)
+# endif
+# define __mempcpy mempcpy
+# define __pathconf pathconf
+# define __rawmemchr rawmemchr
+# define __readlink readlink
+# ifndef MAXSYMLINKS
+# ifdef SYMLOOP_MAX
+# define MAXSYMLINKS SYMLOOP_MAX
+# else
+# define MAXSYMLINKS 20
+# endif
+# endif
+# define __eloop_threshold() MAXSYMLINKS
+#endif
+
+#ifndef DOUBLE_SLASH_IS_DISTINCT_ROOT
+# define DOUBLE_SLASH_IS_DISTINCT_ROOT 0
+#endif
+
+#if !FUNC_REALPATH_WORKS || defined _LIBC
-#include <eloop-threshold.h>
-#include <shlib-compat.h>
+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;
+}
/* 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
+ 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'
+ 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
+ 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)
{
- 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;
if (name == NULL)
{
/* As per Single Unix Specification V2 we must return an error if
- either parameter is a null pointer. We extend this to allow
- the RESOLVED parameter to be NULL in case the we are expected to
- allocate the room for the return value. */
+ either parameter is a null pointer. We extend this to allow
+ the RESOLVED parameter to be NULL in case the we are expected to
+ allocate the room for the return value. */
__set_errno (EINVAL);
return NULL;
}
@@ -60,166 +143,221 @@ __realpath (const char *name, char *resolved)
if (name[0] == '\0')
{
/* As per Single Unix Specification V2 we must return an error if
- the name argument points to an empty string. */
+ the name argument points to an empty string. */
__set_errno (ENOENT);
return NULL;
}
-#ifdef PATH_MAX
- path_max = PATH_MAX;
-#else
- path_max = __pathconf (name, _PC_PATH_MAX);
- if (path_max <= 0)
- path_max = 1024;
-#endif
-
- if (resolved == NULL)
- {
- rpath = malloc (path_max);
- if (rpath == NULL)
- return NULL;
- }
- else
- rpath = resolved;
- rpath_limit = rpath + path_max;
-
- if (name[0] != '/')
+ 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);
+ 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. */
+ idx_t prefix_len = FILE_SYSTEM_PREFIX_LEN (name);
+
+ if (!IS_ABSOLUTE_FILE_NAME (name))
{
- if (!__getcwd (rpath, path_max))
- {
- rpath[0] = '\0';
- goto error;
- }
- dest = __rawmemchr (rpath, '\0');
+ while (!__getcwd (rname, rname_buf->length))
+ {
+ if (errno != ERANGE)
+ {
+ dest = rname;
+ goto error;
+ }
+ if (!scratch_buffer_grow (rname_buf))
+ goto error_nomem;
+ rname = rname_buf->data;
+ }
+ dest = __rawmemchr (rname, '\0');
+ start = name;
+ prefix_len = FILE_SYSTEM_PREFIX_LEN (rname);
}
else
{
- rpath[0] = '/';
- dest = rpath + 1;
+ dest = __mempcpy (rname, name, prefix_len);
+ *dest++ = '/';
+ if (DOUBLE_SLASH_IS_DISTINCT_ROOT)
+ {
+ if (prefix_len == 0 /* implies ISSLASH (name[0]) */
+ && ISSLASH (name[1]) && !ISSLASH (name[2]))
+ *dest++ = '/';
+ *dest = '\0';
+ }
+ start = name + prefix_len;
}
- for (start = end = name; *start; start = end)
+ for ( ; *start; start = end)
{
- struct stat64 st;
- int n;
-
- /* Skip sequence of multiple path-separators. */
- while (*start == '/')
- ++start;
-
- /* Find end of path component. */
- for (end = start; *end && *end != '/'; ++end)
- /* Nothing. */;
-
- if (end - start == 0)
- break;
- else if (end - start == 1 && start[0] == '.')
- /* nothing */;
- else if (end - start == 2 && start[0] == '.' && start[1] == '.')
- {
- /* Back up to previous component, ignore if at root already. */
- if (dest > rpath + 1)
- while ((--dest)[-1] != '/');
- }
+ /* Skip sequence of multiple file name separators. */
+ while (ISSLASH (*start))
+ ++start;
+
+ /* Find end of component. */
+ for (end = start; *end && !ISSLASH (*end); ++end)
+ /* Nothing. */;
+
+ /* Length of this file name component; it can be zero if a file
+ name ends in '/'. */
+ idx_t startlen = end - start;
+
+ if (startlen == 1 && start[0] == '.')
+ /* nothing */;
+ else if (startlen == 2 && start[0] == '.' && start[1] == '.')
+ {
+ /* Back up to previous component, ignore if at root already. */
+ if (dest > rname + prefix_len + 1)
+ for (--dest; dest > rname && !ISSLASH (dest[-1]); --dest)
+ continue;
+ if (DOUBLE_SLASH_IS_DISTINCT_ROOT
+ && dest == rname + 1 && !prefix_len
+ && ISSLASH (*dest) && !ISSLASH (dest[1]))
+ dest++;
+ }
else
- {
- size_t new_size;
-
- if (dest[-1] != '/')
- *dest++ = '/';
-
- if (dest + (end - start) >= rpath_limit)
- {
- ptrdiff_t dest_offset = dest - rpath;
- char *new_rpath;
-
- if (resolved)
- {
- __set_errno (ENAMETOOLONG);
- if (dest > rpath + 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)
- goto error;
- rpath = new_rpath;
- rpath_limit = rpath + new_size;
-
- dest = rpath + dest_offset;
- }
-
- dest = __mempcpy (dest, start, end - start);
- *dest = '\0';
-
- if (__lstat64 (rpath, &st) < 0)
- goto error;
-
- if (S_ISLNK (st.st_mode))
- {
- char *buf = __alloca (path_max);
- size_t len;
-
- if (++num_links > __eloop_threshold ())
- {
- __set_errno (ELOOP);
- goto error;
- }
-
- n = __readlink (rpath, buf, path_max - 1);
- if (n < 0)
- goto error;
- buf[n] = '\0';
-
- if (!extra_buf)
- extra_buf = __alloca (path_max);
-
- len = strlen (end);
- if (path_max - n <= len)
- {
- __set_errno (ENAMETOOLONG);
- goto error;
- }
-
- /* Careful here, end may be a pointer into extra_buf... */
- memmove (&extra_buf[n], end, len + 1);
- name = end = memcpy (extra_buf, buf, n);
-
- if (buf[0] == '/')
- dest = rpath + 1; /* It's an absolute symlink */
- else
- /* Back up to previous component, ignore if at root already: */
- if (dest > rpath + 1)
- while ((--dest)[-1] != '/');
- }
- else if (!S_ISDIR (st.st_mode) && *end != '\0')
- {
- __set_errno (ENOTDIR);
- goto error;
- }
- }
+ {
+ if (!ISSLASH (dest[-1]))
+ *dest++ = '/';
+
+ while (rname + rname_buf->length - dest <= startlen)
+ {
+ idx_t dest_offset = dest - rname;
+ if (!scratch_buffer_grow_preserve (rname_buf))
+ goto error_nomem;
+ rname = rname_buf->data;
+ dest = rname + dest_offset;
+ }
+
+ 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;
+ ssize_t n;
+ if (startlen != 0)
+ {
+ 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;
+ }
+ if (buf)
+ {
+ if (++num_links > __eloop_threshold ())
+ {
+ __set_errno (ELOOP);
+ goto error;
+ }
+
+ buf[n] = '\0';
+
+ char *extra_buf = extra_buffer.data;
+ idx_t end_idx = 0;
+ if (end_in_extra_buffer)
+ end_idx = end - extra_buf;
+ idx_t len = strlen (end);
+ while (extra_buffer.length <= len + n)
+ {
+ 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))
+ {
+ idx_t pfxlen = FILE_SYSTEM_PREFIX_LEN (buf);
+
+ dest = __mempcpy (rname, buf, pfxlen);
+ *dest++ = '/'; /* It's an absolute symlink */
+ if (DOUBLE_SLASH_IS_DISTINCT_ROOT)
+ {
+ if (ISSLASH (buf[1]) && !ISSLASH (buf[2]) && !pfxlen)
+ *dest++ = '/';
+ *dest = '\0';
+ }
+ /* Install the new prefix to be in effect hereafter. */
+ prefix_len = pfxlen;
+ }
+ else
+ {
+ /* Back up to previous component, ignore if at root
+ already: */
+ if (dest > rname + prefix_len + 1)
+ for (--dest; dest > rname && !ISSLASH (dest[-1]); --dest)
+ continue;
+ if (DOUBLE_SLASH_IS_DISTINCT_ROOT && dest == rname + 1
+ && ISSLASH (*dest) && !ISSLASH (dest[1]) && !prefix_len)
+ dest++;
+ }
+ }
+ else if (! (startlen == 0
+ ? stat (rname, &st) == 0 || errno == EOVERFLOW
+ : errno == EINVAL))
+ goto error;
+ }
}
- if (dest > rpath + 1 && dest[-1] == '/')
+ if (dest > rname + prefix_len + 1 && ISSLASH (dest[-1]))
--dest;
- *dest = '\0';
-
- assert (resolved == NULL || resolved == rpath);
- return rpath;
+ if (DOUBLE_SLASH_IS_DISTINCT_ROOT && dest == rname + 1 && !prefix_len
+ && ISSLASH (*dest) && !ISSLASH (dest[1]))
+ dest++;
+ failed = false;
error:
- assert (resolved == NULL || resolved == rpath);
- if (resolved == NULL)
- free (rpath);
- return NULL;
+ *dest++ = '\0';
+ if (resolved != NULL && dest - rname <= get_path_max ())
+ rname = strcpy (resolved, rname);
+
+error_nomem:
+ scratch_buffer_free (&extra_buffer);
+ scratch_buffer_free (&link_buffer);
+ if (failed || rname == resolved)
+ scratch_buffer_free (rname_buf);
+
+ if (failed)
+ return NULL;
+
+ if (rname == resolved)
+ return rname;
+ idx_t rname_size = dest - rname;
+ if (rname == rname_on_stack)
+ {
+ rname = malloc (rname_size);
+ if (rname == NULL)
+ return NULL;
+ return memcpy (rname, rname_on_stack, rname_size);
+ }
+ char *result = realloc (rname, rname_size);
+ return result != NULL ? result : rname;
}
libc_hidden_def (__realpath)
versioned_symbol (libc, __realpath, realpath, GLIBC_2_3);
+#endif /* !FUNC_REALPATH_WORKS || defined _LIBC */
#if SHLIB_COMPAT(libc, GLIBC_2_0, GLIBC_2_3)
--
2.25.1
More information about the Libc-alpha
mailing list