diff options
author | Brad Smith <brad@cvs.openbsd.org> | 2005-03-29 19:34:15 +0000 |
---|---|---|
committer | Brad Smith <brad@cvs.openbsd.org> | 2005-03-29 19:34:15 +0000 |
commit | adf95fe87462b9313a1237dcb6a4814626da51a9 (patch) | |
tree | 1dbf547862ec390deb3ee88f6104d656d17ebcf8 /lib | |
parent | 714246ebb18cf669ce9f18b957235b160d31a547 (diff) |
Make realpath() thread-safe. New implementation does not use chdir(2) at all.
From: FreeBSD by Constantin S. Svintsoff <kostik (at) iclub.nsu.ru>
ok otto@ millert@
Diffstat (limited to 'lib')
-rw-r--r-- | lib/libc/stdlib/realpath.3 | 20 | ||||
-rw-r--r-- | lib/libc/stdlib/realpath.c | 247 |
2 files changed, 143 insertions, 124 deletions
diff --git a/lib/libc/stdlib/realpath.3 b/lib/libc/stdlib/realpath.3 index b8093b51e15..c178f0a9d34 100644 --- a/lib/libc/stdlib/realpath.3 +++ b/lib/libc/stdlib/realpath.3 @@ -28,7 +28,7 @@ .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF .\" SUCH DAMAGE. .\" -.\" $OpenBSD: realpath.3,v 1.10 2003/06/02 20:18:38 millert Exp $ +.\" $OpenBSD: realpath.3,v 1.11 2005/03/29 19:34:14 brad Exp $ .\" .Dd February 16, 1994 .Dt REALPATH 3 @@ -40,7 +40,7 @@ .Fd #include <sys/param.h> .Fd #include <stdlib.h> .Ft "char *" -.Fn realpath "const char *pathname" "char resolvedname[MAXPATHLEN]" +.Fn realpath "const char *pathname" "char resolved[PATH_MAX]" .Sh DESCRIPTION The .Fn realpath @@ -53,13 +53,13 @@ and in .Fa pathname , and copies the resulting absolute pathname into the memory referenced by -.Fa resolvedname . +.Fa resolved . The -.Fa resolvedname +.Fa resolved argument .Em must refer to a buffer capable of storing at least -.Dv MAXPATHLEN +.Dv PATH_MAX characters. .Pp The @@ -76,14 +76,14 @@ is called. The .Fn realpath function returns -.Fa resolvedname +.Fa resolved on success. If an error occurs, .Fn realpath returns .Dv NULL , and -.Fa resolvedname +.Fa resolved contains the pathname which caused the problem. .Sh ERRORS The function @@ -91,11 +91,7 @@ The function may fail and set the external variable .Va errno for any of the errors specified for the library functions -.Xr chdir 2 , -.Xr close 2 , -.Xr fchdir 2 , .Xr lstat 2 , -.Xr open 2 , .Xr readlink 2 , and .Xr getcwd 3 . @@ -115,6 +111,6 @@ The version always returns absolute pathnames, whereas the Solaris implementation will, under certain circumstances, return a relative -.Fa resolvedname +.Fa resolved when given a relative .Fa pathname . diff --git a/lib/libc/stdlib/realpath.c b/lib/libc/stdlib/realpath.c index 37b4ad61597..62e06b00be8 100644 --- a/lib/libc/stdlib/realpath.c +++ b/lib/libc/stdlib/realpath.c @@ -1,9 +1,5 @@ /* - * Copyright (c) 1994 - * The Regents of the University of California. All rights reserved. - * - * This code is derived from software contributed to Berkeley by - * Jan-Simon Pendry. + * Copyright (c) 2003 Constantin S. Svintsoff <kostik@iclub.nsu.ru> * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions @@ -13,14 +9,14 @@ * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. - * 3. Neither the name of the University nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. + * 3. The names of the authors may not be used to endorse or promote + * products derived from this software without specific prior written + * permission. * - * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) @@ -30,146 +26,173 @@ * SUCH DAMAGE. */ +#if 0 +#include <sys/cdefs.h> +__FBSDID("$FreeBSD: /usr/local/www/cvsroot/FreeBSD/src/lib/libc/stdlib/realpath.c,v 1.9.2.1 2003/05/22 17:11:44 fjoe Exp $"); +#endif + #if defined(LIBC_SCCS) && !defined(lint) -static char *rcsid = "$OpenBSD: realpath.c,v 1.11 2004/11/30 15:12:59 millert Exp $"; +static char *rcsid = "$OpenBSD: realpath.c,v 1.12 2005/03/29 19:34:14 brad Exp $"; #endif /* LIBC_SCCS and not lint */ #include <sys/param.h> #include <sys/stat.h> #include <errno.h> -#include <fcntl.h> #include <stdlib.h> #include <string.h> #include <unistd.h> /* - * char *realpath(const char *path, char resolved_path[MAXPATHLEN]); + * char *realpath(const char *path, char resolved[PATH_MAX]); * * Find the real name of path, by removing all ".", ".." and symlink * components. Returns (resolved) on success, or (NULL) on failure, * in which case the path which caused trouble is left in (resolved). */ char * -realpath(path, resolved) - const char *path; - char *resolved; +realpath(const char *path, char resolved[PATH_MAX]) { struct stat sb; - int fd, n, needslash, serrno; - char *p, *q, wbuf[MAXPATHLEN]; - int symlinks = 0; + char *p, *q, *s; + size_t left_len, resolved_len; + unsigned symlinks; + int serrno, slen; + char left[PATH_MAX], next_token[PATH_MAX], symlink[PATH_MAX]; - /* Save the starting point. */ - if ((fd = open(".", O_RDONLY)) < 0) { - resolved[0] = '.'; + serrno = errno; + symlinks = 0; + if (path[0] == '/') { + resolved[0] = '/'; resolved[1] = '\0'; - return (NULL); - } - - /* Convert "." -> "" to optimize away a needless lstat() and chdir() */ - if (path[0] == '.' && path[1] == '\0') - path = ""; - - /* - * Find the dirname and basename from the path to be resolved. - * Change directory to the dirname component. - * lstat the basename part. - * if it is a symlink, read in the value and loop. - * if it is a directory, then change to that directory. - * get the current directory name and append the basename. - */ - if (strlcpy(resolved, path, MAXPATHLEN) >= MAXPATHLEN) { - serrno = ENAMETOOLONG; - goto err2; - } -loop: - q = strrchr(resolved, '/'); - if (q != NULL) { - p = q + 1; - if (q == resolved) - q = "/"; - else { - do { - --q; - } while (q > resolved && *q == '/'); - q[1] = '\0'; - q = resolved; - } - if (chdir(q) < 0) - goto err1; - } else - p = resolved; - - /* Deal with the last component. */ - if (*p != '\0' && lstat(p, &sb) == 0) { - if (S_ISLNK(sb.st_mode)) { - if (++symlinks > MAXSYMLINKS) { - errno = ELOOP; - goto err1; - } - if ((n = readlink(p, resolved, MAXPATHLEN-1)) < 0) - goto err1; - resolved[n] = '\0'; - goto loop; - } - if (S_ISDIR(sb.st_mode)) { - if (chdir(p) < 0) - goto err1; - p = ""; + if (path[1] == '\0') + return (resolved); + resolved_len = 1; + left_len = strlcpy(left, path + 1, sizeof(left)); + } else { + if (getcwd(resolved, PATH_MAX) == NULL) { + strlcpy(resolved, ".", PATH_MAX); + return (NULL); } + resolved_len = strlen(resolved); + left_len = strlcpy(left, path, sizeof(left)); } - - /* - * Save the last component name and get the full pathname of - * the current directory. - */ - if (strlcpy(wbuf, p, sizeof(wbuf)) >= sizeof(wbuf)) { + if (left_len >= sizeof(left) || resolved_len >= PATH_MAX) { errno = ENAMETOOLONG; - goto err1; + return (NULL); } - if (getcwd(resolved, MAXPATHLEN) == NULL) - goto err1; /* - * Join the two strings together, ensuring that the right thing - * happens if the last component is empty, or the dirname is root. + * Iterate over path components in `left'. */ - if (resolved[0] == '/' && resolved[1] == '\0') - needslash = 0; - else - needslash = 1; - - if (*wbuf) { - if (strlen(resolved) + strlen(wbuf) + needslash >= MAXPATHLEN) { + while (left_len != 0) { + /* + * Extract the next path component and adjust `left' + * and its length. + */ + p = strchr(left, '/'); + s = p ? p : left + left_len; + if (s - left >= sizeof(next_token)) { errno = ENAMETOOLONG; - goto err1; + return (NULL); } - if (needslash) { - if (strlcat(resolved, "/", MAXPATHLEN) >= MAXPATHLEN) { + memcpy(next_token, left, s - left); + next_token[s - left] = '\0'; + left_len -= s - left; + if (p != NULL) + memmove(left, s + 1, left_len + 1); + if (resolved[resolved_len - 1] != '/') { + if (resolved_len + 1 >= PATH_MAX) { errno = ENAMETOOLONG; - goto err1; + return (NULL); } + resolved[resolved_len++] = '/'; + resolved[resolved_len] = '\0'; } - if (strlcat(resolved, wbuf, MAXPATHLEN) >= MAXPATHLEN) { + if (next_token[0] == '\0') + continue; + else if (strcmp(next_token, ".") == 0) + continue; + else if (strcmp(next_token, "..") == 0) { + /* + * Strip the last path component except when we have + * single "/" + */ + if (resolved_len > 1) { + resolved[resolved_len - 1] = '\0'; + q = strrchr(resolved, '/') + 1; + *q = '\0'; + resolved_len = q - resolved; + } + continue; + } + + /* + * Append the next path component and lstat() it. If + * lstat() fails we still can return successfully if + * there are no more path components left. + */ + resolved_len = strlcat(resolved, next_token, PATH_MAX); + if (resolved_len >= PATH_MAX) { errno = ENAMETOOLONG; - goto err1; + return (NULL); } - } + if (lstat(resolved, &sb) != 0) { + if (errno == ENOENT && p == NULL) { + errno = serrno; + return (resolved); + } + return (NULL); + } + if (S_ISLNK(sb.st_mode)) { + if (symlinks++ > MAXSYMLINKS) { + errno = ELOOP; + return (NULL); + } + slen = readlink(resolved, symlink, sizeof(symlink) - 1); + if (slen < 0) + return (NULL); + symlink[slen] = '\0'; + if (symlink[0] == '/') { + resolved[1] = 0; + resolved_len = 1; + } else if (resolved_len > 1) { + /* Strip the last path component. */ + resolved[resolved_len - 1] = '\0'; + q = strrchr(resolved, '/') + 1; + *q = '\0'; + resolved_len = q - resolved; + } - /* Go back to where we came from. */ - if (fchdir(fd) < 0) { - serrno = errno; - goto err2; + /* + * If there are any path components left, then + * append them to symlink. The result is placed + * in `left'. + */ + if (p != NULL) { + if (symlink[slen - 1] != '/') { + if (slen + 1 >= sizeof(symlink)) { + errno = ENAMETOOLONG; + return (NULL); + } + symlink[slen] = '/'; + symlink[slen + 1] = 0; + } + left_len = strlcat(symlink, left, sizeof(left)); + if (left_len >= sizeof(left)) { + errno = ENAMETOOLONG; + return (NULL); + } + } + left_len = strlcpy(left, symlink, sizeof(left)); + } } - /* It's okay if the close fails, what's an fd more or less? */ - (void)close(fd); + /* + * Remove trailing slash except when the resolved pathname + * is a single "/". + */ + if (resolved_len > 1 && resolved[resolved_len - 1] == '/') + resolved[resolved_len - 1] = '\0'; return (resolved); - -err1: serrno = errno; - (void)fchdir(fd); -err2: (void)close(fd); - errno = serrno; - return (NULL); } |