Browse Source

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@
OPENBSD_3_8
brad 20 years ago
parent
commit
3d190e977a
2 changed files with 143 additions and 124 deletions
  1. +8
    -12
      src/lib/libc/stdlib/realpath.3
  2. +135
    -112
      src/lib/libc/stdlib/realpath.c

+ 8
- 12
src/lib/libc/stdlib/realpath.3 View File

@ -28,7 +28,7 @@
.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
.\" SUCH DAMAGE. .\" 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 .Dd February 16, 1994
.Dt REALPATH 3 .Dt REALPATH 3
@ -40,7 +40,7 @@
.Fd #include <sys/param.h> .Fd #include <sys/param.h>
.Fd #include <stdlib.h> .Fd #include <stdlib.h>
.Ft "char *" .Ft "char *"
.Fn realpath "const char *pathname" "char resolvedname[MAXPATHLEN]"
.Fn realpath "const char *pathname" "char resolved[PATH_MAX]"
.Sh DESCRIPTION .Sh DESCRIPTION
The The
.Fn realpath .Fn realpath
@ -53,13 +53,13 @@ and
in in
.Fa pathname , .Fa pathname ,
and copies the resulting absolute pathname into the memory referenced by and copies the resulting absolute pathname into the memory referenced by
.Fa resolvedname .
.Fa resolved .
The The
.Fa resolvedname
.Fa resolved
argument argument
.Em must .Em must
refer to a buffer capable of storing at least refer to a buffer capable of storing at least
.Dv MAXPATHLEN
.Dv PATH_MAX
characters. characters.
.Pp .Pp
The The
@ -76,14 +76,14 @@ is called.
The The
.Fn realpath .Fn realpath
function returns function returns
.Fa resolvedname
.Fa resolved
on success. on success.
If an error occurs, If an error occurs,
.Fn realpath .Fn realpath
returns returns
.Dv NULL , .Dv NULL ,
and and
.Fa resolvedname
.Fa resolved
contains the pathname which caused the problem. contains the pathname which caused the problem.
.Sh ERRORS .Sh ERRORS
The function The function
@ -91,11 +91,7 @@ The function
may fail and set the external variable may fail and set the external variable
.Va errno .Va errno
for any of the errors specified for the library functions for any of the errors specified for the library functions
.Xr chdir 2 ,
.Xr close 2 ,
.Xr fchdir 2 ,
.Xr lstat 2 , .Xr lstat 2 ,
.Xr open 2 ,
.Xr readlink 2 , .Xr readlink 2 ,
and and
.Xr getcwd 3 . .Xr getcwd 3 .
@ -115,6 +111,6 @@ The
version always returns absolute pathnames, version always returns absolute pathnames,
whereas the Solaris implementation will, whereas the Solaris implementation will,
under certain circumstances, return a relative under certain circumstances, return a relative
.Fa resolvedname
.Fa resolved
when given a relative when given a relative
.Fa pathname . .Fa pathname .

+ 135
- 112
src/lib/libc/stdlib/realpath.c View File

@ -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 * Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions * modification, are permitted provided that the following conditions
@ -13,14 +9,14 @@
* 2. Redistributions in binary form must reproduce the above copyright * 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the * notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution. * 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 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * 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 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
@ -30,146 +26,173 @@
* SUCH DAMAGE. * 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) #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 */ #endif /* LIBC_SCCS and not lint */
#include <sys/param.h> #include <sys/param.h>
#include <sys/stat.h> #include <sys/stat.h>
#include <errno.h> #include <errno.h>
#include <fcntl.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include <unistd.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 * Find the real name of path, by removing all ".", ".." and symlink
* components. Returns (resolved) on success, or (NULL) on failure, * components. Returns (resolved) on success, or (NULL) on failure,
* in which case the path which caused trouble is left in (resolved). * in which case the path which caused trouble is left in (resolved).
*/ */
char * char *
realpath(path, resolved)
const char *path;
char *resolved;
realpath(const char *path, char resolved[PATH_MAX])
{ {
struct stat sb; 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'; 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; 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; 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; 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; 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); return (resolved);
err1: serrno = errno;
(void)fchdir(fd);
err2: (void)close(fd);
errno = serrno;
return (NULL);
} }

Loading…
Cancel
Save