According to the Single Unix Specification[0], the realpath stdlib function should return NULL (to indicate an error) when the first argument to realpath is either NULL or an empty string and set errno to EINVAL/ENOENT respectively. For the empty string case, FreeBSD is currently populating resolved with the current working directory and returning the pointer to that. For the NULL case, I see no check whether path is NULL or not. Instead, the first use of it is dereferencing the pointer if (path[0] == '/' [0] - http://www.opengroup.org/onlinepubs/009695399/functions/realpath.html
On Mon, Nov 17, 2008 at 02:04:59AM +0000, James Vega wrote: > According to the Single Unix Specification[0], the realpath stdlib function should return NULL (to indicate an error) when the first argument to realpath is either NULL or an empty string and set errno to EINVAL/ENOENT respectively. > > For the empty string case, FreeBSD is currently populating resolved with the current working directory and returning the pointer to that. > > For the NULL case, I see no check whether path is NULL or not. Instead, the first use of it is dereferencing the pointer > > if (path[0] == '/' > > [0] - http://www.opengroup.org/onlinepubs/009695399/functions/realpath.html The situation is even worse. realpath(3) breaks those SUS rules ("shall fail if") too: [ENOENT] A component of file_name does not name an existing file [ENOTDIR] A component of the path prefix is not a directory i.e. realpath(3) does not detect fictional file name (last component) with ENOENT and does not detect intermediate non-directories components with ENOTDIR. See following examples with realpath(1): # realpath /non_existent /non_existent (should be "No such file or directory") # realpath /bin/non_existent/cp realpath: /bin/non_existent/cp: No such file or directory (should be: "Not a directory", as early as non_existent checked) -- http://ache.pp.ru/
We ran into this recently. This is a real problem in portable code, since it's not possible to assume that realpath() will notify the caller if a file doesn't exist, even though it should according to the specification. Arjan -- Using Opera's revolutionary e-mail client: http://www.opera.com/mail/
Responsible Changed From-To: freebsd-bugs->kib Please note that the NULL issue has already been fixed by r206893 and PR 121897 was closed. The issue Andrey Chernov described in his followup to this PR however remains to be fixed. Kib, could you take a look at this?
Quick look makes me think that the following change is enough. diff --git a/lib/libc/stdlib/realpath.c b/lib/libc/stdlib/realpath.c index 2c9562e..f038759 100644 --- a/lib/libc/stdlib/realpath.c +++ b/lib/libc/stdlib/realpath.c @@ -163,10 +163,8 @@ realpath(const char * __restrict path, char * __restrict resolved) return (NULL); } if (lstat(resolved, &sb) != 0) { - if (errno == ENOENT && p == NULL) { - errno = serrno; - return (resolved); - } + if (errno != ENOENT || p != NULL) + errno = ENOTDIR; if (m) free(resolved); return (NULL);
Author: kib Date: Fri Jun 1 14:40:16 2012 New Revision: 236400 URL: http://svn.freebsd.org/changeset/base/236400 Log: MFC r235266: According to SUSv4, realpath(3) must fail if [ENOENT] A component of file_name does not name an existing file or file_name points to an empty string. [ENOTDIR] A component of the path prefix is not a directory, or the file_name argument contains at least one non- <slash> character and ends with one or more trailing <slash> characters and the last pathname component names an existing file that is neither a directory nor a symbolic link to a directory. Add checks for the listed conditions, and set errno accordingly. Update the realpath(3) manpage to mention SUS behaviour. Remove the requirement to include sys/param.h before stdlib.h. PR: 128933 Modified: stable/9/lib/libc/stdlib/realpath.3 stable/9/lib/libc/stdlib/realpath.c Directory Properties: stable/9/lib/libc/ (props changed) Modified: stable/9/lib/libc/stdlib/realpath.3 ============================================================================== --- stable/9/lib/libc/stdlib/realpath.3 Fri Jun 1 14:29:59 2012 (r236399) +++ stable/9/lib/libc/stdlib/realpath.3 Fri Jun 1 14:40:16 2012 (r236400) @@ -31,7 +31,7 @@ .\" @(#)realpath.3 8.2 (Berkeley) 2/16/94 .\" $FreeBSD$ .\" -.Dd April 19, 2010 +.Dd May 11, 2012 .Dt REALPATH 3 .Os .Sh NAME @@ -40,7 +40,6 @@ .Sh LIBRARY .Lb libc .Sh SYNOPSIS -.In sys/param.h .In stdlib.h .Ft "char *" .Fn realpath "const char *pathname" "char *resolved_path" @@ -72,11 +71,12 @@ The function will resolve both absolute and relative paths and return the absolute pathname corresponding to .Fa pathname . -All but the last component of +All components of .Fa pathname must exist when .Fn realpath -is called. +is called, and all but the last component must name either directories or +symlinks pointing to the directories. .Sh "RETURN VALUES" The .Fn realpath Modified: stable/9/lib/libc/stdlib/realpath.c ============================================================================== --- stable/9/lib/libc/stdlib/realpath.c Fri Jun 1 14:29:59 2012 (r236399) +++ stable/9/lib/libc/stdlib/realpath.c Fri Jun 1 14:40:16 2012 (r236400) @@ -132,8 +132,29 @@ realpath(const char * __restrict path, c resolved[resolved_len++] = '/'; resolved[resolved_len] = '\0'; } - if (next_token[0] == '\0') + if (next_token[0] == '\0') { + /* + * Handle consequential slashes. The path + * before slash shall point to a directory. + * + * Only the trailing slashes are not covered + * by other checks in the loop, but we verify + * the prefix for any (rare) "//" or "/\0" + * occurence to not implement lookahead. + */ + if (lstat(resolved, &sb) != 0) { + if (m) + free(resolved); + return (NULL); + } + if (!S_ISDIR(sb.st_mode)) { + if (m) + free(resolved); + errno = ENOTDIR; + return (NULL); + } continue; + } else if (strcmp(next_token, ".") == 0) continue; else if (strcmp(next_token, "..") == 0) { @@ -151,9 +172,7 @@ realpath(const char * __restrict path, c } /* - * Append the next path component and lstat() it. If - * lstat() fails we still can return successfully if - * there are no more path components left. + * Append the next path component and lstat() it. */ resolved_len = strlcat(resolved, next_token, PATH_MAX); if (resolved_len >= PATH_MAX) { @@ -163,10 +182,8 @@ realpath(const char * __restrict path, c return (NULL); } if (lstat(resolved, &sb) != 0) { - if (errno == ENOENT && p == NULL) { - errno = serrno; - return (resolved); - } + if (errno != ENOENT || p != NULL) + errno = ENOTDIR; if (m) free(resolved); return (NULL); _______________________________________________ svn-src-all@freebsd.org mailing list http://lists.freebsd.org/mailman/listinfo/svn-src-all To unsubscribe, send any mail to "svn-src-all-unsubscribe@freebsd.org"
State Changed From-To: open->closed Committed to HEAD and RELENG_9.
The fix applied for this bug appears to have mis-interpreted SUS, in a manner incompatible with glibc and Solaris implementations, and was detected as a failure in the gnulib testsuite, as mentioned in this thread[1]. [1]https://lists.gnu.org/archive/html/bug-gnulib/2012-09/msg00079.html As already mentioned, SUS requires realpath to fail with ENOENT if a component does not exist, and with ENOTDIR if a component is not a directory. But the SUS (POSIX) wording is intended that a file can be detected only as not being a directory if it already exists as some other file type, since ENOENT was already reserved for the case of not existing. Therefore, I disagree with your argument of: > # realpath /bin/non_existent/cp > realpath: /bin/non_existent/cp: No such file or directory > (should be: "Not a directory", as early as non_existent checked) and instead, argue that it should be: # realpath /bin/non_existent/cp realpath: /bin/non_existent/cp: No such file or directory (correct - since /bin/non_existent does not exist, ENOENT trumps ENOTDIR) # realpath /bin/cp/cp realpath: /bin/cp/cp: Not a directory (since /bin/cp exists, but is not a directory, /bin/cp/cp cannot be checked) -- Eric Blake eblake@redhat.com +1-919-301-3266 Libvirt virtualization library http://libvirt.org