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
.\" 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 .

+ 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
* 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);
}

Loading…
Cancel
Save