diff options
-rw-r--r-- | stdlib/Makefile | 3 | ||||
-rw-r--r-- | stdlib/canonicalize.c | 244 | ||||
-rw-r--r-- | stdlib/test-canon.c | 181 |
3 files changed, 294 insertions, 134 deletions
diff --git a/stdlib/Makefile b/stdlib/Makefile index 46d7aa13a5..aa072082cc 100644 --- a/stdlib/Makefile +++ b/stdlib/Makefile @@ -45,7 +45,8 @@ routines := \ rpmatch strfmon distribute := exit.h grouping.h -tests := tst-strtol tst-strtod testmb testrand testsort testdiv +tests := tst-strtol tst-strtod testmb testrand testsort testdiv \ + test-canon # Several mpn functions from GNU MP are used by the strtod function. diff --git a/stdlib/canonicalize.c b/stdlib/canonicalize.c index 8008a28cc2..a65b7f187c 100644 --- a/stdlib/canonicalize.c +++ b/stdlib/canonicalize.c @@ -17,174 +17,152 @@ License along with the GNU C Library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ +#include <assert.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <limits.h> +#include <sys/param.h> #include <sys/stat.h> #include <errno.h> -/* Return the canonical absolute name of file NAME. The last file name - component need not exist, and may be a symlink to a nonexistent file. - 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. */ +/* 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 char * canonicalize (const char *name, char *resolved) { - struct stat st; - const char *p; + char *rpath, *dest, *extra_buf = NULL; + const char *start, *end, *rpath_limit; long int path_max; - char *result, *dir, *end; - size_t namelen; + int num_links = 0; - if (! resolved) - path_max = 0; - else - { #ifdef PATH_MAX - path_max = PATH_MAX; + path_max = PATH_MAX; #else - path_max = pathconf (name, _PC_PATH_MAX); - if (path_max <= 0) - path_max = 1024; + path_max = pathconf (name, _PC_PATH_MAX); + if (path_max <= 0) + path_max = 1024; #endif - } - p = strrchr (name, '/'); - if (!p) - { - dir = (char *) "."; - p = name; - } - else - { - if (p++ == name) - dir = (char *) "/"; - else - { - dir = __alloca (p - name); - memcpy (dir, name, p - name - 1); - dir[p - name] = '\0'; - } - } + rpath = resolved; + rpath_limit = rpath + path_max; + if (!resolved) + rpath = malloc (path_max); - result = __canonicalize_directory_name_internal (dir, resolved, path_max); - if (!result) - return NULL; + strcpy (rpath, "/"); + if (name[0] != '/' && !getcwd (rpath, path_max)) + goto error; + dest = rpath + strlen (rpath); - /* Reconstruct the file name in the canonicalized directory. */ - namelen = strlen (name); - end = strchr (result, '\0'); - if (resolved) + for (start = end = name; *start; start = end) { - /* Make sure the name is not too long. */ - if (end - result + namelen > path_max) + struct stat st; + int n; + + /* skip sequence of multiple path-separators: */ + while (*start == '/') ++start; + + /* find end of path component: */ + for (end = start; *end && *end != '/'; ++end); + + if (end - start == 0) + break; + else if (strncmp (start, ".", end - start) == 0) + /* nothing */; + else if (strncmp (start, "..", end - start) == 0) { + /* back up to previous component, ignore if at root already: */ + if (dest > rpath + 1) + while ((--dest)[-1] != '/'); + } else { - errno = ENAMETOOLONG; - return NULL; - } - } - else - { - /* The name is dynamically allocated. Extend it. */ - char *new = realloc (result, end - result + namelen + 1); - if (! new) - { - free (result); - return NULL; - } - end = new + (end - result); - result = new; - } - memcpy (end, name, namelen + 1); + size_t new_size; - while (__lstat (result, &st) == 0 && S_ISLNK (st.st_mode)) - { - /* The file is a symlink. Read its contents. */ - ssize_t n; - read_link_contents: - n = readlink (result, end, - resolved ? result + path_max - end : namelen + 1); - if (n < 0) - /* Error reading the link contents. */ - return NULL; - - if (end[0] == '/') - { - /* Absolute symlink. */ - if (resolved ? (end + n < result + path_max) : (n < namelen + 1)) - { - /* It fit in our buffer, so we have the whole thing. */ - memcpy (result, end, n); - result[n] = '\0'; - } - else if (resolved) + if (dest[-1] != '/') + *dest++ = '/'; + + if (dest + (end - start) >= rpath_limit) { - /* It didn't fit in the remainder of the buffer. Either it - fits in the entire buffer, or it doesn't. Copy back the - unresolved name onto the canonical directory and try once - more. */ - memcpy (end, name, namelen + 1); - n = readlink (result, result, path_max); - if (n < 0) - return NULL; - if (n == path_max) + if (resolved) { errno = ENAMETOOLONG; - return NULL; + goto error; } - result[n] = '\0'; + new_size = rpath_limit - rpath; + if (end - start + 1 > path_max) + new_size += end - start + 1; + else + new_size += path_max; + rpath = realloc (rpath, new_size); + rpath_limit = rpath + new_size; + if (!rpath) + return NULL; } - else - /* Try again with a bigger buffer. */ - goto extend_buffer; - /* Check the resolved name for being a symlink too. */ - continue; - } + memcpy (dest, start, end - start); + dest += end - start; + *dest = '\0'; + + if (__lstat (rpath, &st) < 0) + goto error; - if (resolved) - { - if (end + n == result + path_max) + if (S_ISLNK (st.st_mode)) { - /* The link contents we read fill the buffer completely. - There may be even more to read, and there is certainly no - space for the null terminator. */ - errno = ENAMETOOLONG; - return NULL; - } - } - else if (n == namelen + 1) - extend_buffer: - { - /* The name buffer is dynamically allocated. Extend it. */ - char *new; + char * buf = __alloca(path_max); - /* Copy back the unresolved name onto the canonical directory. */ - memcpy (end, name, namelen + 1); + if (++num_links > MAXSYMLINKS) + { + errno = ELOOP; + goto error; + } - /* Make more space for readlink. */ - namelen *= 2; - new = realloc (result, end - result + namelen + 1); - if (! new) - { - free (result); - return NULL; - } - end = new + (end - result); - result = new; + n = readlink (rpath, buf, path_max); + if (n < 0) + goto error; + buf[n] = '\0'; - goto read_link_contents; - } + if (!extra_buf) + extra_buf = __alloca (path_max); - /* Terminate the string; readlink does not. */ - end[n] = '\0'; - } + if (n + strlen (end) >= path_max) + { + errno = ENAMETOOLONG; + goto error; + } - return result; + /* careful here, end may be a pointer into extra_buf... */ + strcat (buf, end); + strcpy (extra_buf, buf); + name = end = extra_buf; + + 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 + num_links = 0; + } + } + if (dest > rpath + 1 && dest[-1] == '/') + --dest; + *dest = '\0'; + return rpath; + +error: + if (!resolved) + free (rpath); + return NULL; } weak_alias (canonicalize, realpath) diff --git a/stdlib/test-canon.c b/stdlib/test-canon.c new file mode 100644 index 0000000000..6f0171d200 --- /dev/null +++ b/stdlib/test-canon.c @@ -0,0 +1,181 @@ +/* Test program for returning the canonical absolute name of a given file. +Copyright (C) 1996 Free Software Foundation, Inc. +This file is part of the GNU C Library. +Contributed by David Mosberger <davidm@azstarnet.com>. + +The GNU C Library is free software; you can redistribute it and/or +modify it under the terms of the GNU Library General Public License as +published by the Free Software Foundation; either version 2 of the +License, or (at your option) any later version. + +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 +Library General Public License for more details. + +You should have received a copy of the GNU Library General Public +License along with the GNU C Library; see the file COPYING.LIB. If +not, write to the Free Software Foundation, Inc., 675 Mass Ave, +Cambridge, MA 02139, USA. */ + +/* This file must be run from within a directory called "stdlib". */ + +#include <errno.h> +#include <fcntl.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <sys/param.h> + +static char cwd[1024]; +static size_t cwd_len; + +struct { + const char * name; + const char * value; +} symlinks[] = { + {"SYMLINK_LOOP", "SYMLINK_LOOP"}, + {"SYMLINK_1", "."}, + {"SYMLINK_2", "//////./../../etc"}, + {"SYMLINK_3", "SYMLINK_1"}, + {"SYMLINK_4", "SYMLINK_2"}, + {"SYMLINK_5", "doesNotExist"}, +}; + +struct { + const char * in, * out, * resolved; + int errno; +} tests[] = { + /* 0 */ + {"/", "/"}, + {"/////////////////////////////////", "/"}, + {"/.././.././.././..///", "/"}, + {"/etc", "/etc"}, + {"/etc/../etc", "/etc"}, + /* 5 */ + {"/doesNotExist/../etc", 0, "/doesNotExist", ENOENT}, + {"./././././././././.", "."}, + {"/etc/.//doesNotExist", 0, "/etc/doesNotExist", ENOENT}, + {"./doesExist", "./doesExist"}, + {"./doesExist/", "./doesExist"}, + /* 10 */ + {"./doesExist/../doesExist", "./doesExist"}, + {"foobar", 0, "./foobar", ENOENT}, + {".", "."}, + {"./foobar", 0, "./foobar", ENOENT}, + {"SYMLINK_LOOP", 0, "./SYMLINK_LOOP", ELOOP}, + /* 15 */ + {"./SYMLINK_LOOP", 0, "./SYMLINK_LOOP", ELOOP}, + {"SYMLINK_1", "."}, + {"SYMLINK_1/foobar", 0, "./foobar", ENOENT}, + {"SYMLINK_2", "/etc"}, + {"SYMLINK_3", "."}, + /* 20 */ + {"SYMLINK_4", "/etc"}, + {"../stdlib/SYMLINK_1", "."}, + {"../stdlib/SYMLINK_2", "/etc"}, + {"../stdlib/SYMLINK_3", "."}, + {"../stdlib/SYMLINK_4", "/etc"}, + /* 25 */ + {"./SYMLINK_5", 0, "./doesNotExist", ENOENT}, + {"SYMLINK_5", 0, "./doesNotExist", ENOENT}, + {"SYMLINK_5/foobar", 0, "./doesNotExist", ENOENT}, + {"doesExist/../../stdlib/doesExist", "./doesExist"}, + {"doesExist/.././../stdlib/.", "."} +}; + + +int +check_path (const char * result, const char * expected) +{ + int good; + + if (!result) + return (expected == NULL); + + if (!expected) + return 0; + + if (expected[0] == '.' && (expected[1] == '/' || expected[1] == '\0')) + good = (strncmp (result, cwd, cwd_len) == 0 + && strcmp (result + cwd_len, expected + 1) == 0); + else + good = (strcmp (expected, result) == 0); + + return good; +} + + +void +main (int argc, char ** argv) +{ + char * result; + int fd, i, errors = 0; + char buf[PATH_MAX]; + + getcwd (cwd, sizeof(buf)); + cwd_len = strlen (cwd); + + for (i = 0; i < sizeof (symlinks) / sizeof (symlinks[0]); ++i) + symlink (symlinks[i].value, symlinks[i].name); + + fd = open("doesExist", O_CREAT | O_EXCL, 0777); + + for (i = 0; i < sizeof (tests) / sizeof (tests[0]); ++i) + { + buf[0] = '\0'; + result = realpath (tests[i].in, buf); + + if (!check_path (result, tests[i].out)) + { + printf ("%s: flunked test %d (expected `%s', got `%s')\n", + argv[0], i, tests[i].out ? tests[i].out : "NULL", + result ? result : "NULL"); + ++errors; + continue; + } + + if (!check_path (buf, tests[i].out ? tests[i].out : tests[i].resolved)) + { + printf ("%s: flunked test %d (expected resolved `%s', got `%s')\n", + argv[0], i, tests[i].out ? tests[i].out : tests[i].resolved, + buf); + ++errors; + continue; + } + + if (!tests[i].out && errno != tests[i].errno) + { + printf ("%s: flunked test %d (expected errno %d, got %d)\n", + argv[0], i, tests[i].errno, errno); + ++errors; + continue; + } + } + + getcwd (buf, sizeof(buf)); + if (strcmp (buf, cwd)) + { + printf ("%s: current working directory changed from %s to %s\n", + argv[0], cwd, buf); + ++errors; + } + + if (fd >= 0) + unlink("doesExist"); + + for (i = 0; i < sizeof (symlinks) / sizeof (symlinks[0]); ++i) + unlink (symlinks[i].name); + + if (errors == 0) + { + puts ("No errors."); + exit (EXIT_SUCCESS); + } + else + { + printf ("%d errors.\n", errors); + exit (EXIT_FAILURE); + } +} |