diff options
Diffstat (limited to 'stdlib/canonicalize.c')
-rw-r--r-- | stdlib/canonicalize.c | 244 |
1 files changed, 111 insertions, 133 deletions
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) |