realpath: Report correct path on failure
If lstat() fails with EACCES or ENOTDIR, the path we need to return in the caller-provided buffer is that of the parent directory (which is either unreadable or not a directory; the latter can only happen in the case of a race) rather than that of the child we attempted to stat. Sponsored by: Klara, Inc. Reviewed by: markj Differential Revision: https://reviews.freebsd.org/D53025
This commit is contained in:
@@ -49,7 +49,7 @@ realpath1(const char *path, char *resolved)
|
||||
{
|
||||
struct stat sb;
|
||||
char *p, *q;
|
||||
size_t left_len, resolved_len, next_token_len;
|
||||
size_t left_len, prev_len, resolved_len, next_token_len;
|
||||
unsigned symlinks;
|
||||
ssize_t slen;
|
||||
char left[PATH_MAX], next_token[PATH_MAX], symlink[PATH_MAX];
|
||||
@@ -98,6 +98,7 @@ realpath1(const char *path, char *resolved)
|
||||
left_len = 0;
|
||||
}
|
||||
|
||||
prev_len = resolved_len;
|
||||
if (resolved[resolved_len - 1] != '/') {
|
||||
if (resolved_len + 1 >= PATH_MAX) {
|
||||
errno = ENAMETOOLONG;
|
||||
@@ -133,8 +134,17 @@ realpath1(const char *path, char *resolved)
|
||||
errno = ENAMETOOLONG;
|
||||
return (NULL);
|
||||
}
|
||||
if (lstat(resolved, &sb) != 0)
|
||||
if (lstat(resolved, &sb) != 0) {
|
||||
/*
|
||||
* EACCES means the parent directory is not
|
||||
* readable, while ENOTDIR means the parent
|
||||
* directory is not a directory. Rewind the path
|
||||
* to correctly indicate where the error lies.
|
||||
*/
|
||||
if (errno == EACCES || errno == ENOTDIR)
|
||||
resolved[prev_len] = '\0';
|
||||
return (NULL);
|
||||
}
|
||||
if (S_ISLNK(sb.st_mode)) {
|
||||
if (symlinks++ > MAXSYMLINKS) {
|
||||
errno = ELOOP;
|
||||
|
||||
@@ -158,15 +158,8 @@ ATF_TC_BODY(realpath_partial, tc)
|
||||
ATF_REQUIRE_EQ(0, chmod("foo", 000));
|
||||
ATF_REQUIRE_ERRNO(EACCES, realpath("foo/bar/baz", resb) == NULL);
|
||||
len = strnlen(resb, sizeof(resb));
|
||||
ATF_REQUIRE(len > 8 && len < sizeof(resb));
|
||||
/*
|
||||
* This is arguably wrong. The problem is not with bar, but with
|
||||
* foo. However, since foo exists and is a directory and the only
|
||||
* reliable way to detect whether a directory is readable is to
|
||||
* attempt to read it, we do not detect the problem until we try
|
||||
* to access bar.
|
||||
*/
|
||||
ATF_REQUIRE_STREQ("/foo/bar", resb + len - 8);
|
||||
ATF_REQUIRE(len > 4 && len < sizeof(resb));
|
||||
ATF_REQUIRE_STREQ("/foo", resb + len - 4);
|
||||
|
||||
/* scenario 6: not a directory */
|
||||
ATF_REQUIRE_EQ(0, close(creat("bar", 0644)));
|
||||
|
||||
Reference in New Issue
Block a user