| Message ID | 20251219140600.212527-1-ghaderer@wyplay.com |
|---|---|
| State | New |
| Headers | show |
| Series | [pseudo] unix: realpath fails if the resolved path doesn't exist. | expand |
I'm going to investigate this a bit more, but I think generally speaking this may be right. Looking at the POSIX documentation for realpath, we need to set errno as well. I've just got to figure out what the errno needs to be. It looks like there are two different errno settings, one is based on the dirname, the other the basename. But I need to figure out what real glibc does in this case so we can mimic. I plan to work on this today and provide more feedback as I get it. --Mark On 12/19/25 8:06 AM, Gauthier HADERER via lists.yoctoproject.org wrote: > The pseudo implementation of `realpath()' may return a path which doesn't > exist. This is not POSIX compliant and causes troubles with uutils (coreutils > in Rust). > > For example, the tail commands tries to determine the file path of its standard > input file descriptor by calling `realpath("/dev/fd/0")'. When the input is a > pipe, the GNU C library returns NULL but pseudo returns > `/proc/<pid>/fd/pipe:[xxxxxx]'. As it got a path, the tail command tries to > open it and fails. > > Upstream-Status: Pending > --- > ports/unix/guts/realpath.c | 8 ++++++++ > 1 file changed, 8 insertions(+) > > diff --git a/ports/unix/guts/realpath.c b/ports/unix/guts/realpath.c > index 8d8118b..ee7d409 100644 > --- a/ports/unix/guts/realpath.c > +++ b/ports/unix/guts/realpath.c > @@ -14,6 +14,14 @@ > errno = ENAMETOOLONG; > return NULL; > } > + > + /* We must fail if the target path doesn't exist. */ > + struct stat buf; > + > + if (base_lstat(rname, &buf) == -1) { > + return NULL; > + } > + > len = strlen(rname); > char *ep = rname + len - 1; > while (ep > rname && *ep == '/') {
On 12/22/25 9:37 AM, Mark Hatle wrote: > I'm going to investigate this a bit more, but I think generally speaking this > may be right. > > Looking at the POSIX documentation for realpath, we need to set errno as well. > I've just got to figure out what the errno needs to be. It looks like there are > two different errno settings, one is based on the dirname, the other the > basename. But I need to figure out what real glibc does in this case so we can > mimic. > > I plan to work on this today and provide more feedback as I get it. > > --Mark > > On 12/19/25 8:06 AM, Gauthier HADERER via lists.yoctoproject.org wrote: >> The pseudo implementation of `realpath()' may return a path which doesn't >> exist. This is not POSIX compliant and causes troubles with uutils (coreutils >> in Rust). >> >> For example, the tail commands tries to determine the file path of its standard >> input file descriptor by calling `realpath("/dev/fd/0")'. When the input is a >> pipe, the GNU C library returns NULL but pseudo returns >> `/proc/<pid>/fd/pipe:[xxxxxx]'. As it got a path, the tail command tries to >> open it and fails. (without pseudo) echo | realpath /dev/fd/0 results in /proc/1399637/fd/pipe:[1176962683] So the result of the pipe isn't actually wrong according to what I'm seeing. What I am finding is realpath needs to: Resolve <dirname>, if it doesn't exist it's NULL and an errno. TRY to resolve the <basename>, if this doesn't exist, then we just return it as it is, otherwise it's resolved (if a symlink converted to the target) otherwise just returned 'as-is'. So I think something else is causing an issue here. The result from pseudo may still be incorrect, but it's not as simple as just checking if the file exists or not. Not existing is allowed, as long as the <dirname> exists, if the file is a symlink that the target of the symlink itself exists, otherwise it's returned "as-is". --Mark >> Upstream-Status: Pending >> --- >> ports/unix/guts/realpath.c | 8 ++++++++ >> 1 file changed, 8 insertions(+) >> >> diff --git a/ports/unix/guts/realpath.c b/ports/unix/guts/realpath.c >> index 8d8118b..ee7d409 100644 >> --- a/ports/unix/guts/realpath.c >> +++ b/ports/unix/guts/realpath.c >> @@ -14,6 +14,14 @@ >> errno = ENAMETOOLONG; >> return NULL; >> } >> + >> + /* We must fail if the target path doesn't exist. */ >> + struct stat buf; >> + >> + if (base_lstat(rname, &buf) == -1) { >> + return NULL; >> + } >> + >> len = strlen(rname); >> char *ep = rname + len - 1; >> while (ep > rname && *ep == '/') { > > > -=-=-=-=-=-=-=-=-=-=-=- > Links: You receive all messages sent to this group. > View/Reply Online (#2845): https://lists.yoctoproject.org/g/yocto-patches/message/2845 > Mute This Topic: https://lists.yoctoproject.org/mt/116859482/3616948 > Group Owner: yocto-patches+owner@lists.yoctoproject.org > Unsubscribe: https://lists.yoctoproject.org/g/yocto-patches/leave/13201099/3616948/947757854/xyzzy [mark.hatle@kernel.crashing.org] > -=-=-=-=-=-=-=-=-=-=-=- > >
I created a different implementation that I THINK will resolve this particular issue. My concern below is that all of the test cases that I ran in Ubuntu 24.04 LTS showed that this changes realpath behavior. That may include errno in some cases, or just no longer returning a result when the realpath on 24.04 _DOES_ return the full /proc/<pid>/fd/pipe:[xxxx] path. Instead the patch that I just sent to the list for review modifies the psseudo_fix_path code by attempting to detect that the resolution has moved into the '/proc' filesystem and then stops doing further symlink resolution. This appears to resolve the issue by converting /dev/fd/0 to /proc/self/fd/0 and then passing that into open or similar, which does work since we never try to directly access the pipe:[....] filename. If you can test this/provide feedback that would be helpful! Thank you! --Mark On 12/19/25 8:06 AM, Gauthier HADERER via lists.yoctoproject.org wrote: > The pseudo implementation of `realpath()' may return a path which doesn't > exist. This is not POSIX compliant and causes troubles with uutils (coreutils > in Rust). > > For example, the tail commands tries to determine the file path of its standard > input file descriptor by calling `realpath("/dev/fd/0")'. When the input is a > pipe, the GNU C library returns NULL but pseudo returns > `/proc/<pid>/fd/pipe:[xxxxxx]'. As it got a path, the tail command tries to > open it and fails. > > Upstream-Status: Pending > --- > ports/unix/guts/realpath.c | 8 ++++++++ > 1 file changed, 8 insertions(+) > > diff --git a/ports/unix/guts/realpath.c b/ports/unix/guts/realpath.c > index 8d8118b..ee7d409 100644 > --- a/ports/unix/guts/realpath.c > +++ b/ports/unix/guts/realpath.c > @@ -14,6 +14,14 @@ > errno = ENAMETOOLONG; > return NULL; > } > + > + /* We must fail if the target path doesn't exist. */ > + struct stat buf; > + > + if (base_lstat(rname, &buf) == -1) { > + return NULL; > + } > + > len = strlen(rname); > char *ep = rname + len - 1; > while (ep > rname && *ep == '/') {
The `realpath()` libc function and the realpath command have a different behavior. The `realpath()` libc function will fail if the target file doesn't exist. By default, the realpath command will happily return a path if the final component is missing. To get the same behavior as the libc function, you must pass the --canonicalize-existing option. Or you can use this simple program. > > #include <limits.h> > #include <stdlib.h> > #include <stdio.h> > int main(int argc, char **argv) { > if (argc != 2) > return 2; > char *rpath = realpath(argv[1], NULL); > if (!rpath) { > perror("realpath"); > return 1; > } > printf("%s\n", rpath); > free(rpath); > return 0; > } >
Here's an updated version of my patch with a fix regarding the type of the stat structure.
Thank you for the patch. I've merged this in a temporary branch 'fray/master', along with a test case and Richard's openat2 syscall work. d3c58c2 test-syscall: Add a syscall test ad9f32c ports/linux/pseudo_wrappers: Avoid openat2 usage via syscall 3225656 unix: realpath fails if the resolved path doesn't exist. 4e4f97a test-realpath: Verify the realpath behavior matches glibc 786d146 pseudo_util.c: Skip realpath like expansion for /proc 0922771 test: Cleanup test output --Mark On 1/8/26 2:36 AM, Gauthier HADERER via lists.yoctoproject.org wrote: > Here's an updated version of my patch with a fix regarding the type of the stat > structure. > _._,_._,_ > -------------------------------------------------------------------------------- > Links: > > You receive all messages sent to this group. > > View/Reply Online (#2913) > <https://lists.yoctoproject.org/g/yocto-patches/message/2913> | Reply to Group > <mailto:yocto-patches@lists.yoctoproject.org?subject=Re:%20Re%3A%20%5Byocto-patches%5D%20%5Bpseudo%5D%20%5BPATCH%5D%20unix%3A%20realpath%20fails%20if%20the%20resolved%20path%20doesn%27t%20exist.> | Reply to Sender <mailto:ghaderer@wyplay.com?subject=Private:%20Re:%20Re%3A%20%5Byocto-patches%5D%20%5Bpseudo%5D%20%5BPATCH%5D%20unix%3A%20realpath%20fails%20if%20the%20resolved%20path%20doesn%27t%20exist.> | Mute This Topic <https://lists.yoctoproject.org/mt/116859482/3616948> | New Topic <https://lists.yoctoproject.org/g/yocto-patches/post> > Your Subscription > <https://lists.yoctoproject.org/g/yocto-patches/editsub/3616948> | Contact Group > Owner <mailto:yocto-patches+owner@lists.yoctoproject.org> | Unsubscribe > <https://lists.yoctoproject.org/g/yocto-patches/leave/13201099/3616948/947757854/xyzzy> [mark.hatle@kernel.crashing.org] > > _._,_._,_
diff --git a/ports/unix/guts/realpath.c b/ports/unix/guts/realpath.c index 8d8118b..ee7d409 100644 --- a/ports/unix/guts/realpath.c +++ b/ports/unix/guts/realpath.c @@ -14,6 +14,14 @@ errno = ENAMETOOLONG; return NULL; } + + /* We must fail if the target path doesn't exist. */ + struct stat buf; + + if (base_lstat(rname, &buf) == -1) { + return NULL; + } + len = strlen(rname); char *ep = rname + len - 1; while (ep > rname && *ep == '/') {
The pseudo implementation of `realpath()' may return a path which doesn't exist. This is not POSIX compliant and causes troubles with uutils (coreutils in Rust). For example, the tail commands tries to determine the file path of its standard input file descriptor by calling `realpath("/dev/fd/0")'. When the input is a pipe, the GNU C library returns NULL but pseudo returns `/proc/<pid>/fd/pipe:[xxxxxx]'. As it got a path, the tail command tries to open it and fails. Upstream-Status: Pending --- ports/unix/guts/realpath.c | 8 ++++++++ 1 file changed, 8 insertions(+)