Listing NFS directories from Linux-emulated binaries causes directory entries with zero-length filenames to show up. These entries do not show when listing from native binaries. Ex: ~$ /compat/linux/bin/ls -aQ /share "" "" "" "" "" "" "" "." "other files" "" "" "" "" "" "" "" ".." "etc" This is new behaviour in FreeBSD 15.0-RELEASE not present in 14.3-RELEASE. This is present on NFSv3 and NFSv4. NFS hosts tried: FreeBSD 14.3 and 15.0.
I think the root cause is that FreeBSD's getdirentries(2) can fill the d_off field with 0 when operating in the NFS client, and the linux_getdents64(2) on top doesn't understand that.
Problem seems to be that getdents64(2) returns 26 instead of 12 entries. openat(AT_FDCWD, "/mnt/tmp", O_RDONLY|O_NONBLOCK|O_CLOEXEC|O_DIRECTORY) = 3 fstat(3, {st_mode=S_IFDIR|0755, st_size=4096, ...}) = 0 getdents64(3, 0x104a550 /* 26 entries */, 32768) = 768 The correct number of entries for my specific directory is 12.
For more fun try: /compat/linux/bin/find /mnt/tmp Side notes: - bug present in 16-current - bug also applies when the server is Linux
(In reply to Jerry Williams from comment #0) > > not present in 14.3-RELEASE. For me the problem appears in 14.2-STABLE.
Hmmmm... openat(AT_FDCWD, "/mnt/tmp", O_RDONLY|O_NONBLOCK|O_CLOEXEC|O_DIRECTORY) = 3 fstat(3, {st_mode=S_IFDIR|0755, st_size=4096, ...}) = 0 getdents64(3, 0x104a550 /* 26 entries */, 32768) = 768 So that is exactly 32768 for the return value (the total number of bytes filled in with the structures of variable length). That is probably not a coincidence.
(In reply to Martin Cracauer from comment #5) The NFS client always returns a full block as requested by the syscall. The block is filled out with entries of 512bytes with d_fileno == 0. On FreeBSD (and 4BSD for that matter), entries with d_fileno/d_ino == 0 are ignored. I suspect that Linux does not ignore these entries, but it is just a hunch. Note that getdents(2) is not a POSIX standard, so it is not surprising that it is not Linux compatible. If there is some other way Linux marks directory entries to be ignored, then the syscall emulator could translate them. Otherwise, I do not see a way to fix this. I do not know what the Linux syscall is expected to return. (You cannot just throw away ignored entries. Doing that will break the offset of the next block.) I won't bore you with the details, but on FreeBSD the NFS blocks are cached in the buffer cache and creating a short one as the last block results in breakage of the caching. (I patched it that way once, but someone spotted that the cache hit rate dropped significantly.)
(In reply to Martin Cracauer from comment #5) Disregard that comment, didn't look in the right place.
(In reply to Rick Macklem from comment #6) Why can't I throw out to-be-ignored entries? Isn't it that they can only appear at the end and hence the stream position doesn't matter anymore?
(In reply to Martin Cracauer from comment #8) They can exist in the middle. The tradition (from UFS literally forever) is to expand the d_reclen field to assume free'd entries. However, this does not work for the first entry in a block. For the first entry in a block, mark it "invalid" by setting d_fileno (used to be called d_ino) to 0 (0 was never a valid inode#). NFS, the second type of file system for 4BSD, followed the same mechanism. As I mentioned, I did turn off expanding the last block to a full blocksize (8K at the moment) and committed the patch. bde@ noted that this broke directory block caching, with the cache miss rate increasing dramatically. I cannot remember why, but I reverted the patch that took it out. There is no reason why the Linux glue cannot take them out, since it appears to be broken now, anyhow. I can also tell you that the NFS readdir client code can easily be broken. (Disabling readahead exposes a race, to give you one example. Maintaining the correct directory offset cookies is not easy. As such, I have no urge to try and change it, to make it somehow "Linux compatible".) I also wonder what happens when Linux apps use the POSIX opendir(), readdir() function calls instead of the non-standard syscall. --> Someone could test this. Just write a little program on Linux that uses opendir(), readdir() and then try the binary on FreeBSD and see if it breaks? Note that I do not know if these entries with d_fileno == 0 are causing the breakage. It is just a hunch. I am not familiar with what the semantics of their getdents() syscall is, nor do I use the Linuxolator.
(In reply to Rick Macklem from comment #9) Using opendir() still exhibits the behaviour. I didn't know if uploading a binary was appropriate here, so I posted it here https://codeberg.org/jwillia3/292282 test.c: #include <dirent.h> #include <stdio.h> int main(int argc, char **argv) { DIR *dirp; struct dirent *ent; int n; n = 0; dirp = opendir(argv[1]); while ((ent = readdir(dirp))) { printf("%-8d %s\n", (int) ent->d_fileno, ent->d_name); n++; } closedir(dirp); printf("%d entries\n", n); } transcript: ~$ doas -s # pkg install linux-rl9-devtools Updating FreeBSD-ports repository catalogue... FreeBSD-ports repository is up to date. Updating FreeBSD-ports-kmods repository catalogue... FreeBSD-ports-kmods repository is up to date. All repositories are up to date. The following 1 package(s) will be affected (of 0 checked): New packages to be INSTALLED: linux-rl9-devtools: 9.6_1 [FreeBSD-ports] Number of packages to be installed: 1 The process will require 280 MiB more space. 86 MiB to be downloaded. Proceed with this action? [Y/n]: [1/1] Fetching linux-rl9-devtools-9.6_1~5a97eb46b1.pkg: 100% 86 MiB 45.0MB/s 00:02 Checking integrity... done (0 conflicting) [1/1] Installing linux-rl9-devtools-9.6_1... [1/1] Extracting linux-rl9-devtools-9.6_1: 100% # ^D ~$ /compat/linux/bin/bash bash-5.1$ cc --version cc (GCC) 11.5.0 20240719 (Red Hat 11.5.0-5) Copyright (C) 2021 Free Software Foundation, Inc. This is free software; see the source for copying conditions. There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. bash-5.1$ cc $HOME/test.c -o linuxtest bash-5.1$ file linuxtest linuxtest: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=94f5823b0f59ead9e040aa3282d54e2e76c7bb6f, for GNU/Linux 3.2.0, not stripped bash-5.1$ ./linuxtest /mnt/tmp 2964769 . 2 .. 2964770 emptyfile 2964771 test.c 2964772 test 2964773 a.out 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 21 entries
Created attachment 267048 [details] Modify linux_getdents() to skip invalid dir entries You can try this patch. Note that it is COMPLETELY UNTESTED!! I do not have any Linux system to create a Linux binary with handy. I also do not maintain the Linuxolater, so I do not know if fix is appropriate, even if it works?
(In reply to Rick Macklem from comment #11) That does seem to work. Thanks!
A commit in branch main references this bug: URL: https://cgit.FreeBSD.org/src/commit/?id=110f2567cb51f1eeddbd5d9937000ad64f6dc746 commit 110f2567cb51f1eeddbd5d9937000ad64f6dc746 Author: Rick Macklem <rmacklem@FreeBSD.org> AuthorDate: 2026-02-07 22:12:55 +0000 Commit: Rick Macklem <rmacklem@FreeBSD.org> CommitDate: 2026-02-07 22:12:55 +0000 linux_file.c: Fix handling of NFS getdents() emulation Bugzilla PR#292282 reports a problem, where a Linux binary running in the Linuxulator gets bogus entries in a readdir()/getdents() reply when the directory is an NFS mount. This appears to be caused by the NFS client including entries with d_fileno == 0, which are always ignored by BSD, but are not ignored by Linux. This patch filters out the "d_fileno == 0" entries and the reporter of the bugzilla PR notes that it fixes the problem for him. It could be argued that the NFS client should filter out the "d_fileno == 0" entries, but the NFS client readdir code is "fragile" and any change to it runs a significant risk of causing regression type problems. As such, since the LInuxulator is already broken for this case, it seems safer to filter them out there. PR: 292282 Tested by: Jerry Williams <jwillia3@proton.me> Reviewed by: markj MFC after: 2 weeks Differential Revision: https://reviews.freebsd.org/D54679 sys/compat/linux/linux_file.c | 178 ++++++++++++++++++++++++++---------------- 1 file changed, 110 insertions(+), 68 deletions(-)
The patch has been committed to main and will be MFC'd.
A commit in branch stable/15 references this bug: URL: https://cgit.FreeBSD.org/src/commit/?id=86a6407d028d9a58c93c7d1ac54e737d78b1aaaf commit 86a6407d028d9a58c93c7d1ac54e737d78b1aaaf Author: Rick Macklem <rmacklem@FreeBSD.org> AuthorDate: 2026-02-07 22:12:55 +0000 Commit: Rick Macklem <rmacklem@FreeBSD.org> CommitDate: 2026-02-21 17:03:38 +0000 linux_file.c: Fix handling of NFS getdents() emulation Bugzilla PR#292282 reports a problem, where a Linux binary running in the Linuxulator gets bogus entries in a readdir()/getdents() reply when the directory is an NFS mount. This appears to be caused by the NFS client including entries with d_fileno == 0, which are always ignored by BSD, but are not ignored by Linux. This patch filters out the "d_fileno == 0" entries and the reporter of the bugzilla PR notes that it fixes the problem for him. It could be argued that the NFS client should filter out the "d_fileno == 0" entries, but the NFS client readdir code is "fragile" and any change to it runs a significant risk of causing regression type problems. As such, since the LInuxulator is already broken for this case, it seems safer to filter them out there. PR: 292282 (cherry picked from commit 110f2567cb51f1eeddbd5d9937000ad64f6dc746) sys/compat/linux/linux_file.c | 178 ++++++++++++++++++++++++++---------------- 1 file changed, 110 insertions(+), 68 deletions(-)
Patch has been committed and MFC'd.