Backport test implementation to verify readdir behavior

Resolves: RHEL-56542
This commit is contained in:
Frédéric Bérat 2025-01-10 11:19:08 +01:00
parent 08cb9d7284
commit d2c49567f7
10 changed files with 1540 additions and 1 deletions

56
glibc-RHEL-56542-1.patch Normal file
View File

@ -0,0 +1,56 @@
commit 0e12ca024119ec6c6d2ac852a65046002efa0e80
Author: Steve Grubb <sgrubb@redhat.com>
Date: Fri Mar 11 15:29:06 2022 -0500
associate a deallocation for opendir
This patch associates closedir as a deallocation for opendir and fdopendir.
This required moving the closedir declaration above the other 2 functions.
Reviewed-by: Paul Eggert <eggert@cs.ucla.edu>
Reviewed-by: Siddhesh Poyarekar <siddhesh@sourceware.org>
diff --git a/dirent/dirent.h b/dirent/dirent.h
index 1d1fab7e55cdad4d..84834e8db9dea874 100644
--- a/dirent/dirent.h
+++ b/dirent/dirent.h
@@ -126,28 +126,30 @@ enum
The actual structure is opaque to users. */
typedef struct __dirstream DIR;
+/* Close the directory stream DIRP.
+ Return 0 if successful, -1 if not.
+
+ This function is a possible cancellation point and therefore not
+ marked with __THROW. */
+extern int closedir (DIR *__dirp) __nonnull ((1));
+
/* Open a directory stream on NAME.
Return a DIR stream on the directory, or NULL if it could not be opened.
This function is a possible cancellation point and therefore not
marked with __THROW. */
-extern DIR *opendir (const char *__name) __nonnull ((1));
+extern DIR *opendir (const char *__name) __nonnull ((1))
+ __attribute_malloc__ __attr_dealloc (closedir, 1);
#ifdef __USE_XOPEN2K8
/* Same as opendir, but open the stream on the file descriptor FD.
This function is a possible cancellation point and therefore not
marked with __THROW. */
-extern DIR *fdopendir (int __fd);
+extern DIR *fdopendir (int __fd)
+ __attribute_malloc__ __attr_dealloc (closedir, 1);
#endif
-/* Close the directory stream DIRP.
- Return 0 if successful, -1 if not.
-
- This function is a possible cancellation point and therefore not
- marked with __THROW. */
-extern int closedir (DIR *__dirp) __nonnull ((1));
-
/* Read a directory entry from DIRP. Return a pointer to a `struct
dirent' describing the entry, or NULL for EOF or error. The
storage returned may be overwritten by a later readdir call on the

236
glibc-RHEL-56542-2.patch Normal file
View File

@ -0,0 +1,236 @@
commit 766b73768b290b303f5b56268c6c0d588d5a9267
Author: Florian Weimer <fweimer@redhat.com>
Date: Mon Sep 19 08:10:41 2022 +0200
Linux: Do not skip d_ino == 0 entries in readdir, readdir64 (bug 12165)
POSIX does not say this value is special. For example, old XFS file
systems may still use inode number zero.
Also update the comment regarding ENOENT. Linux may return ENOENT
for some file systems.
diff --git a/sysdeps/unix/sysv/linux/readdir.c b/sysdeps/unix/sysv/linux/readdir.c
index b4801351645d1236..ef95611f09e761a4 100644
--- a/sysdeps/unix/sysv/linux/readdir.c
+++ b/sysdeps/unix/sysv/linux/readdir.c
@@ -28,48 +28,33 @@ __readdir_unlocked (DIR *dirp)
struct dirent *dp;
int saved_errno = errno;
- do
+ if (dirp->offset >= dirp->size)
{
- size_t reclen;
+ /* We've emptied out our buffer. Refill it. */
- if (dirp->offset >= dirp->size)
+ size_t maxread = dirp->allocation;
+ ssize_t bytes;
+
+ bytes = __getdents (dirp->fd, dirp->data, maxread);
+ if (bytes <= 0)
{
- /* We've emptied out our buffer. Refill it. */
-
- size_t maxread = dirp->allocation;
- ssize_t bytes;
-
- bytes = __getdents (dirp->fd, dirp->data, maxread);
- if (bytes <= 0)
- {
- /* On some systems getdents fails with ENOENT when the
- open directory has been rmdir'd already. POSIX.1
- requires that we treat this condition like normal EOF. */
- if (bytes < 0 && errno == ENOENT)
- bytes = 0;
-
- /* Don't modifiy errno when reaching EOF. */
- if (bytes == 0)
- __set_errno (saved_errno);
- dp = NULL;
- break;
- }
- dirp->size = (size_t) bytes;
-
- /* Reset the offset into the buffer. */
- dirp->offset = 0;
+ /* Linux may fail with ENOENT on some file systems if the
+ directory inode is marked as dead (deleted). POSIX
+ treats this as a regular end-of-directory condition, so
+ do not set errno in that case, to indicate success. */
+ if (bytes == 0 || errno == ENOENT)
+ __set_errno (saved_errno);
+ return NULL;
}
+ dirp->size = (size_t) bytes;
- dp = (struct dirent *) &dirp->data[dirp->offset];
-
- reclen = dp->d_reclen;
-
- dirp->offset += reclen;
-
- dirp->filepos = dp->d_off;
+ /* Reset the offset into the buffer. */
+ dirp->offset = 0;
+ }
- /* Skip deleted files. */
- } while (dp->d_ino == 0);
+ dp = (struct dirent *) &dirp->data[dirp->offset];
+ dirp->offset += dp->d_reclen;
+ dirp->filepos = dp->d_off;
return dp;
}
diff --git a/sysdeps/unix/sysv/linux/readdir64.c b/sysdeps/unix/sysv/linux/readdir64.c
index 52b11eb9d91bb5fd..8a60504649aa54a2 100644
--- a/sysdeps/unix/sysv/linux/readdir64.c
+++ b/sysdeps/unix/sysv/linux/readdir64.c
@@ -37,48 +37,36 @@ __readdir64 (DIR *dirp)
__libc_lock_lock (dirp->lock);
#endif
- do
+ if (dirp->offset >= dirp->size)
{
- size_t reclen;
+ /* We've emptied out our buffer. Refill it. */
- if (dirp->offset >= dirp->size)
+ size_t maxread = dirp->allocation;
+ ssize_t bytes;
+
+ bytes = __getdents64 (dirp->fd, dirp->data, maxread);
+ if (bytes <= 0)
{
- /* We've emptied out our buffer. Refill it. */
-
- size_t maxread = dirp->allocation;
- ssize_t bytes;
-
- bytes = __getdents64 (dirp->fd, dirp->data, maxread);
- if (bytes <= 0)
- {
- /* On some systems getdents fails with ENOENT when the
- open directory has been rmdir'd already. POSIX.1
- requires that we treat this condition like normal EOF. */
- if (bytes < 0 && errno == ENOENT)
- bytes = 0;
-
- /* Don't modifiy errno when reaching EOF. */
- if (bytes == 0)
- __set_errno (saved_errno);
- dp = NULL;
- break;
- }
- dirp->size = (size_t) bytes;
-
- /* Reset the offset into the buffer. */
- dirp->offset = 0;
+ /* Linux may fail with ENOENT on some file systems if the
+ directory inode is marked as dead (deleted). POSIX
+ treats this as a regular end-of-directory condition, so
+ do not set errno in that case, to indicate success. */
+ if (bytes == 0 || errno == ENOENT)
+ __set_errno (saved_errno);
+#if IS_IN (libc)
+ __libc_lock_unlock (dirp->lock);
+#endif
+ return NULL;
}
+ dirp->size = (size_t) bytes;
- dp = (struct dirent64 *) &dirp->data[dirp->offset];
-
- reclen = dp->d_reclen;
-
- dirp->offset += reclen;
-
- dirp->filepos = dp->d_off;
+ /* Reset the offset into the buffer. */
+ dirp->offset = 0;
+ }
- /* Skip deleted files. */
- } while (dp->d_ino == 0);
+ dp = (struct dirent64 *) &dirp->data[dirp->offset];
+ dirp->offset += dp->d_reclen;
+ dirp->filepos = dp->d_off;
#if IS_IN (libc)
__libc_lock_unlock (dirp->lock);
@@ -115,48 +103,36 @@ __old_readdir64 (DIR *dirp)
__libc_lock_lock (dirp->lock);
#endif
- do
+ if (dirp->offset >= dirp->size)
{
- size_t reclen;
+ /* We've emptied out our buffer. Refill it. */
- if (dirp->offset >= dirp->size)
+ size_t maxread = dirp->allocation;
+ ssize_t bytes;
+
+ bytes = __old_getdents64 (dirp->fd, dirp->data, maxread);
+ if (bytes <= 0)
{
- /* We've emptied out our buffer. Refill it. */
-
- size_t maxread = dirp->allocation;
- ssize_t bytes;
-
- bytes = __old_getdents64 (dirp->fd, dirp->data, maxread);
- if (bytes <= 0)
- {
- /* On some systems getdents fails with ENOENT when the
- open directory has been rmdir'd already. POSIX.1
- requires that we treat this condition like normal EOF. */
- if (bytes < 0 && errno == ENOENT)
- bytes = 0;
-
- /* Don't modifiy errno when reaching EOF. */
- if (bytes == 0)
- __set_errno (saved_errno);
- dp = NULL;
- break;
- }
- dirp->size = (size_t) bytes;
-
- /* Reset the offset into the buffer. */
- dirp->offset = 0;
+ /* Linux may fail with ENOENT on some file systems if the
+ directory inode is marked as dead (deleted). POSIX
+ treats this as a regular end-of-directory condition, so
+ do not set errno in that case, to indicate success. */
+ if (bytes == 0 || errno == ENOENT)
+ __set_errno (saved_errno);
+#if IS_IN (libc)
+ __libc_lock_unlock (dirp->lock);
+#endif
+ return NULL;
}
+ dirp->size = (size_t) bytes;
- dp = (struct __old_dirent64 *) &dirp->data[dirp->offset];
-
- reclen = dp->d_reclen;
-
- dirp->offset += reclen;
-
- dirp->filepos = dp->d_off;
+ /* Reset the offset into the buffer. */
+ dirp->offset = 0;
+ }
- /* Skip deleted files. */
- } while (dp->d_ino == 0);
+ dp = (struct __old_dirent64 *) &dirp->data[dirp->offset];
+ dirp->offset += dp->d_reclen;
+ dirp->filepos = dp->d_off;
#if IS_IN (libc)
__libc_lock_unlock (dirp->lock);

104
glibc-RHEL-56542-3.patch Normal file
View File

@ -0,0 +1,104 @@
commit 4e16d89866e660426438238a47c2345bdc47dd97
Author: Adhemerval Zanella <adhemerval.zanella@linaro.org>
Date: Thu Aug 10 08:56:00 2023 -0300
linux: Make fdopendir fail with O_PATH (BZ 30373)
It is not strictly required by the POSIX, since O_PATH is a Linux
extension, but it is QoI to fail early instead of at readdir. Also
the check is free, since fdopendir already checks if the file
descriptor is opened for read.
Checked on x86_64-linux-gnu.
Conflicts:
sysdeps/unix/sysv/linux/Makefile (new test added)
diff --git a/sysdeps/unix/sysv/linux/Makefile b/sysdeps/unix/sysv/linux/Makefile
index 617f7718b2a5779d..74656e56038844aa 100644
--- a/sysdeps/unix/sysv/linux/Makefile
+++ b/sysdeps/unix/sysv/linux/Makefile
@@ -125,6 +125,7 @@ tests += tst-clone tst-clone2 tst-clone3 tst-fanotify tst-personality \
tst-prctl \
tst-scm_rights \
tst-getauxval \
+ tst-fdopendir-o_path \
# tests
# Test for the symbol version of fcntl that was replaced in glibc 2.28.
diff --git a/sysdeps/unix/sysv/linux/fdopendir.c b/sysdeps/unix/sysv/linux/fdopendir.c
index 32ec10e206305e3c..d06eeb3cafa4966c 100644
--- a/sysdeps/unix/sysv/linux/fdopendir.c
+++ b/sysdeps/unix/sysv/linux/fdopendir.c
@@ -37,10 +37,16 @@ __fdopendir (int fd)
return NULL;
}
- /* Make sure the descriptor allows for reading. */
int flags = __fcntl64_nocancel (fd, F_GETFL);
if (__glibc_unlikely (flags == -1))
return NULL;
+ /* Fail early for descriptors opened with O_PATH. */
+ if (__glibc_unlikely (flags & O_PATH))
+ {
+ __set_errno (EBADF);
+ return NULL;
+ }
+ /* Make sure the descriptor allows for reading. */
if (__glibc_unlikely ((flags & O_ACCMODE) == O_WRONLY))
{
__set_errno (EINVAL);
diff --git a/sysdeps/unix/sysv/linux/tst-fdopendir-o_path.c b/sysdeps/unix/sysv/linux/tst-fdopendir-o_path.c
new file mode 100644
index 0000000000000000..2531cf8ddb92ff45
--- /dev/null
+++ b/sysdeps/unix/sysv/linux/tst-fdopendir-o_path.c
@@ -0,0 +1,48 @@
+/* Check if fdopendir fails with file descriptor opened with O_PATH (BZ 30737)
+ Copyright (C) 2023 Free Software Foundation, Inc.
+ This file is part of the GNU C Library.
+
+ The GNU C Library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ The GNU C Library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with the GNU C Library; if not, see
+ <https://www.gnu.org/licenses/>. */
+
+#include <errno.h>
+#include <fcntl.h>
+#include <dirent.h>
+#include <support/check.h>
+#include <support/temp_file.h>
+#include <support/xunistd.h>
+
+static int
+do_test (void)
+{
+ char *dirname = support_create_temp_directory ("tst-fdopendir-o_path");
+
+ {
+ int fd = xopen (dirname, O_RDONLY | O_DIRECTORY, 0600);
+ DIR *dir = fdopendir (fd);
+ TEST_VERIFY_EXIT (dir != NULL);
+ closedir (dir);
+ }
+
+ {
+ int fd = xopen (dirname, O_RDONLY | O_PATH | O_DIRECTORY, 0600);
+ TEST_VERIFY (fdopendir (fd) == NULL);
+ TEST_COMPARE (errno, EBADF);
+ xclose (fd);
+ }
+
+ return 0;
+}
+
+#include <support/test-driver.c>

80
glibc-RHEL-56542-4.patch Normal file
View File

@ -0,0 +1,80 @@
commit a4a12af5abe22d63fbebf0a219d8d13eff6db20c
Author: Carlos O'Donell <carlos@redhat.com>
Date: Thu Jun 8 07:30:33 2023 -0400
dirent: Reformat Makefile.
Reflow and sort Makefile.
Code generation changes present due to link order changes.
No regressions on x86_64 and i686.
Conflicts:
dirent/Makefile (resorting)
diff --git a/dirent/Makefile b/dirent/Makefile
index 5bad3c112209a2ce..450bcd5d8981f40b 100644
--- a/dirent/Makefile
+++ b/dirent/Makefile
@@ -22,17 +22,49 @@ subdir := dirent
include ../Makeconfig
-headers := dirent.h bits/dirent.h bits/dirent_ext.h
-routines := opendir closedir readdir readdir_r rewinddir \
- seekdir telldir scandir alphasort versionsort \
- getdents getdents64 dirfd readdir64 readdir64_r scandir64 \
- alphasort64 versionsort64 fdopendir \
- scandirat scandirat64 \
- scandir-cancel scandir-tail scandir64-tail
-
-tests := list tst-seekdir opendir-tst1 bug-readdir1 tst-fdopendir \
- tst-fdopendir2 tst-scandir tst-scandir64 \
- tst-rewinddir \
+headers := \
+ bits/dirent.h \
+ bits/dirent_ext.h \
+ dirent.h \
+ # headers
+routines := \
+ alphasort \
+ alphasort64 \
+ closedir \
+ dirfd \
+ fdopendir \
+ getdents \
+ getdents64 \
+ opendir \
+ readdir \
+ readdir64 \
+ readdir64_r \
+ readdir_r \
+ rewinddir \
+ scandir \
+ scandir-cancel \
+ scandir-tail \
+ scandir64 \
+ scandir64-tail \
+ scandirat \
+ scandirat64 \
+ seekdir \
+ telldir \
+ versionsort \
+ versionsort64 \
+ # routines
+
+tests := \
+ bug-readdir1 \
+ list \
+ opendir-tst1 \
+ tst-fdopendir \
+ tst-fdopendir2 \
+ tst-rewinddir \
+ tst-scandir \
+ tst-scandir64 \
+ tst-seekdir \
+ # tests
CFLAGS-scandir.c += $(uses-callbacks)
CFLAGS-scandir64.c += $(uses-callbacks)

48
glibc-RHEL-56542-5.patch Normal file
View File

@ -0,0 +1,48 @@
commit 61f2c2e1d1287a791c22d86c943b44bcf66bb8ad
Author: Florian Weimer <fweimer@redhat.com>
Date: Fri Aug 30 21:52:23 2024 +0200
Linux: readdir_r needs to report getdents failures (bug 32124)
Upon error, return the errno value set by the __getdents call
in __readdir_unlocked. Previously, kernel-reported errors
were ignored.
Reviewed-by: DJ Delorie <dj@redhat.com>
diff --git a/sysdeps/unix/sysv/linux/readdir_r.c b/sysdeps/unix/sysv/linux/readdir_r.c
index 4792d730eb2c1fa1..2a2491e5e3786746 100644
--- a/sysdeps/unix/sysv/linux/readdir_r.c
+++ b/sysdeps/unix/sysv/linux/readdir_r.c
@@ -25,14 +25,22 @@ __readdir_r (DIR *dirp, struct dirent *entry, struct dirent **result)
{
struct dirent *dp;
size_t reclen;
+ int saved_errno = errno;
__libc_lock_lock (dirp->lock);
while (1)
{
+ /* If errno is changed from 0, the NULL return value indicates
+ an actual error. It overrides a pending ENAMETOOLONG error. */
+ __set_errno (0);
dp = __readdir_unlocked (dirp);
if (dp == NULL)
- break;
+ {
+ if (errno != 0)
+ dirp->errcode = errno;
+ break;
+ }
reclen = dp->d_reclen;
if (reclen <= offsetof (struct dirent, d_name) + NAME_MAX + 1)
@@ -61,6 +69,7 @@ __readdir_r (DIR *dirp, struct dirent *entry, struct dirent **result)
__libc_lock_unlock (dirp->lock);
+ __set_errno (saved_errno);
return dp != NULL ? 0 : dirp->errcode;
}

136
glibc-RHEL-56542-6.patch Normal file
View File

@ -0,0 +1,136 @@
commit 4c09aa31b1aeea1329674109eb02d4ba506b0ad2
Author: Florian Weimer <fweimer@redhat.com>
Date: Sat Sep 21 19:32:34 2024 +0200
dirent: Add tst-closedir-leaks
It verfies that closedir deallocates memory and closes
file descriptors.
Reviewed-by: DJ Delorie <dj@redhat.com>
diff --git a/dirent/Makefile b/dirent/Makefile
index 450bcd5d8981f40b..a0404b82b81ed9e8 100644
--- a/dirent/Makefile
+++ b/dirent/Makefile
@@ -58,6 +58,7 @@ tests := \
bug-readdir1 \
list \
opendir-tst1 \
+ tst-closedir-leaks \
tst-fdopendir \
tst-fdopendir2 \
tst-rewinddir \
@@ -66,6 +67,18 @@ tests := \
tst-seekdir \
# tests
+ifeq ($(run-built-tests),yes)
+ifneq ($(PERL),no)
+generated += \
+ $(objpfx)tst-closedir-leaks-mem.out \
+ # generated
+
+tests-special += \
+ $(objpfx)tst-closedir-leaks-mem.out \
+ # tests-special
+endif # $(PERL) ! no
+endif # $(run-built-tests) == yes
+
CFLAGS-scandir.c += $(uses-callbacks)
CFLAGS-scandir64.c += $(uses-callbacks)
CFLAGS-scandir-tail.c += $(uses-callbacks)
@@ -74,3 +87,10 @@ CFLAGS-scandir64-tail.c += $(uses-callbacks)
include ../Rules
opendir-tst1-ARGS = --test-dir=${common-objpfx}dirent
+
+tst-closedir-leaks-ENV += MALLOC_TRACE=$(objpfx)tst-closedir-leaks.mtrace \
+ LD_PRELOAD=$(common-objpfx)/malloc/libc_malloc_debug.so
+
+$(objpfx)tst-closedir-leaks-mem.out: $(objpfx)tst-closedir-leaks.out
+ $(common-objpfx)malloc/mtrace $(objpfx)tst-closedir-leaks.mtrace > $@; \
+ $(evaluate-test)
diff --git a/dirent/tst-closedir-leaks.c b/dirent/tst-closedir-leaks.c
new file mode 100644
index 0000000000000000..d9de119b637ea623
--- /dev/null
+++ b/dirent/tst-closedir-leaks.c
@@ -0,0 +1,77 @@
+/* Test for resource leaks in closedir.
+ Copyright (C) 2024 Free Software Foundation, Inc.
+ This file is part of the GNU C Library.
+
+ The GNU C Library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ The GNU C Library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with the GNU C Library; if not, see
+ <https://www.gnu.org/licenses/>. */
+
+#include <fcntl.h>
+#include <limits.h>
+#include <mcheck.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <support/check.h>
+#include <support/descriptors.h>
+#include <support/readdir.h>
+#include <support/xdirent.h>
+#include <support/xunistd.h>
+
+static void
+one_test (enum support_readdir_op op, unsigned int read_limit,
+ bool use_fdopendir)
+{
+ struct support_descriptors *fds = support_descriptors_list ();
+ struct support_dirent e = { 0, };
+
+ DIR *stream;
+ if (use_fdopendir)
+ {
+ int fd = xopen (".", O_RDONLY | O_DIRECTORY, 0);
+ stream = xfdopendir (fd);
+ /* The descriptor fd will be closed by closedir below. */
+ }
+ else
+ stream = xopendir (".");
+ for (unsigned int i = 0; i < read_limit; ++i)
+ if (!support_readdir (stream, op, &e))
+ break;
+ TEST_COMPARE (closedir (stream), 0);
+
+ free (e.d_name);
+ support_descriptors_check (fds);
+ support_descriptors_free (fds);
+}
+
+static int
+do_test (void)
+{
+ mtrace ();
+
+ for (int use_fdopendir = 0; use_fdopendir < 2; ++use_fdopendir)
+ {
+ /* No reads, operation does not matter. */
+ one_test (SUPPORT_READDIR, 0, use_fdopendir);
+
+ for (enum support_readdir_op op = 0; op <= support_readdir_op_last();
+ ++op)
+ {
+ one_test (op, 1, use_fdopendir);
+ one_test (op, UINT_MAX, use_fdopendir); /* Unlimited reads. */
+ }
+ }
+
+ return 0;
+}
+
+#include <support/test-driver.c>

389
glibc-RHEL-56542-7.patch Normal file
View File

@ -0,0 +1,389 @@
commit e92718552e1d17b8eccbffb88bf5bbb2235c4596
Author: Florian Weimer <fweimer@redhat.com>
Date: Sat Sep 21 19:32:34 2024 +0200
Linux: Use readdir64_r for compat __old_readdir64_r (bug 32128)
It is not necessary to do the conversion at the getdents64
layer for readdir64_r. Doing it piecewise for readdir64
is slightly simpler and allows deleting __old_getdents64.
This fixes bug 32128 because readdir64_r handles the length
check correctly.
Reviewed-by: DJ Delorie <dj@redhat.com>
diff --git a/sysdeps/unix/sysv/linux/getdents64.c b/sysdeps/unix/sysv/linux/getdents64.c
index 6323e003b30ae0e6..6f8e9147fbfb79bc 100644
--- a/sysdeps/unix/sysv/linux/getdents64.c
+++ b/sysdeps/unix/sysv/linux/getdents64.c
@@ -33,100 +33,3 @@ __getdents64 (int fd, void *buf, size_t nbytes)
}
libc_hidden_def (__getdents64)
weak_alias (__getdents64, getdents64)
-
-#if _DIRENT_MATCHES_DIRENT64
-strong_alias (__getdents64, __getdents)
-#else
-# include <shlib-compat.h>
-
-# if SHLIB_COMPAT(libc, GLIBC_2_1, GLIBC_2_2)
-# include <olddirent.h>
-# include <unistd.h>
-
-static ssize_t
-handle_overflow (int fd, __off64_t offset, ssize_t count)
-{
- /* If this is the first entry in the buffer, we can report the
- error. */
- if (offset == 0)
- {
- __set_errno (EOVERFLOW);
- return -1;
- }
-
- /* Otherwise, seek to the overflowing entry, so that the next call
- will report the error, and return the data read so far. */
- if (__lseek64 (fd, offset, SEEK_SET) != 0)
- return -1;
- return count;
-}
-
-ssize_t
-__old_getdents64 (int fd, char *buf, size_t nbytes)
-{
- /* We do not move the individual directory entries. This is only
- possible if the target type (struct __old_dirent64) is smaller
- than the source type. */
- _Static_assert (offsetof (struct __old_dirent64, d_name)
- <= offsetof (struct dirent64, d_name),
- "__old_dirent64 is larger than dirent64");
- _Static_assert (__alignof__ (struct __old_dirent64)
- <= __alignof__ (struct dirent64),
- "alignment of __old_dirent64 is larger than dirent64");
-
- ssize_t retval = INLINE_SYSCALL_CALL (getdents64, fd, buf, nbytes);
- if (retval > 0)
- {
- /* This is the marker for the first entry. Offset 0 is reserved
- for the first entry (see rewinddir). Here, we use it as a
- marker for the first entry in the buffer. We never actually
- seek to offset 0 because handle_overflow reports the error
- directly, so it does not matter that the offset is incorrect
- if entries have been read from the descriptor before (so that
- the descriptor is not actually at offset 0). */
- __off64_t previous_offset = 0;
-
- char *p = buf;
- char *end = buf + retval;
- while (p < end)
- {
- struct dirent64 *source = (struct dirent64 *) p;
-
- /* Copy out the fixed-size data. */
- __ino_t ino = source->d_ino;
- __off64_t offset = source->d_off;
- unsigned int reclen = source->d_reclen;
- unsigned char type = source->d_type;
-
- /* Check for ino_t overflow. */
- if (__glibc_unlikely (ino != source->d_ino))
- return handle_overflow (fd, previous_offset, p - buf);
-
- /* Convert to the target layout. Use a separate struct and
- memcpy to side-step aliasing issues. */
- struct __old_dirent64 result;
- result.d_ino = ino;
- result.d_off = offset;
- result.d_reclen = reclen;
- result.d_type = type;
-
- /* Write the fixed-sized part of the result to the
- buffer. */
- size_t result_name_offset = offsetof (struct __old_dirent64, d_name);
- memcpy (p, &result, result_name_offset);
-
- /* Adjust the position of the name if necessary. Copy
- everything until the end of the record, including the
- terminating NUL byte. */
- if (result_name_offset != offsetof (struct dirent64, d_name))
- memmove (p + result_name_offset, source->d_name,
- reclen - offsetof (struct dirent64, d_name));
-
- p += reclen;
- previous_offset = offset;
- }
- }
- return retval;
-}
-# endif /* SHLIB_COMPAT(libc, GLIBC_2_1, GLIBC_2_2) */
-#endif /* _DIRENT_MATCHES_DIRENT64 */
diff --git a/sysdeps/unix/sysv/linux/olddirent.h b/sysdeps/unix/sysv/linux/olddirent.h
index 3e672d47f5a5e421..d1a486e855e744c3 100644
--- a/sysdeps/unix/sysv/linux/olddirent.h
+++ b/sysdeps/unix/sysv/linux/olddirent.h
@@ -34,8 +34,6 @@ extern struct __old_dirent64 *__old_readdir64 (DIR *__dirp);
libc_hidden_proto (__old_readdir64);
extern int __old_readdir64_r (DIR *__dirp, struct __old_dirent64 *__entry,
struct __old_dirent64 **__result);
-extern __ssize_t __old_getdents64 (int __fd, char *__buf, size_t __nbytes)
- attribute_hidden;
int __old_scandir64 (const char * __dir,
struct __old_dirent64 *** __namelist,
int (*__selector) (const struct __old_dirent64 *),
diff --git a/sysdeps/unix/sysv/linux/readdir64.c b/sysdeps/unix/sysv/linux/readdir64.c
index 8a60504649aa54a2..c3450b083b6b64cf 100644
--- a/sysdeps/unix/sysv/linux/readdir64.c
+++ b/sysdeps/unix/sysv/linux/readdir64.c
@@ -26,17 +26,13 @@
#undef __readdir
#undef readdir
-/* Read a directory entry from DIRP. */
-struct dirent64 *
-__readdir64 (DIR *dirp)
+/* Read a directory entry from DIRP. No locking. */
+static struct dirent64 *
+__readdir64_unlocked (DIR *dirp)
{
struct dirent64 *dp;
int saved_errno = errno;
-#if IS_IN (libc)
- __libc_lock_lock (dirp->lock);
-#endif
-
if (dirp->offset >= dirp->size)
{
/* We've emptied out our buffer. Refill it. */
@@ -53,9 +49,6 @@ __readdir64 (DIR *dirp)
do not set errno in that case, to indicate success. */
if (bytes == 0 || errno == ENOENT)
__set_errno (saved_errno);
-#if IS_IN (libc)
- __libc_lock_unlock (dirp->lock);
-#endif
return NULL;
}
dirp->size = (size_t) bytes;
@@ -68,10 +61,16 @@ __readdir64 (DIR *dirp)
dirp->offset += dp->d_reclen;
dirp->filepos = dp->d_off;
-#if IS_IN (libc)
- __libc_lock_unlock (dirp->lock);
-#endif
+ return dp;
+}
+/* Read a directory entry from DIRP. */
+struct dirent64 *
+__readdir64 (DIR *dirp)
+{
+ __libc_lock_lock (dirp->lock);
+ struct dirent64 *dp = __readdir64_unlocked (dirp);
+ __libc_lock_unlock (dirp->lock);
return dp;
}
libc_hidden_def (__readdir64)
@@ -99,45 +98,54 @@ __old_readdir64 (DIR *dirp)
struct __old_dirent64 *dp;
int saved_errno = errno;
-#if IS_IN (libc)
__libc_lock_lock (dirp->lock);
-#endif
- if (dirp->offset >= dirp->size)
+ while (1)
{
- /* We've emptied out our buffer. Refill it. */
+ errno = 0;
+ struct dirent64 *newdp = __readdir64_unlocked (dirp);
+ if (newdp == NULL)
+ {
+ if (errno == 0 && dirp->errcode != 0)
+ __set_errno (dirp->errcode);
+ else if (errno == 0)
+ __set_errno (saved_errno);
+ dp = NULL;
+ break;
+ }
- size_t maxread = dirp->allocation;
- ssize_t bytes;
+ /* Convert to the target layout. Use a separate struct and
+ memcpy to side-step aliasing issues. */
+ struct __old_dirent64 result;
+ result.d_ino = newdp->d_ino;
+ result.d_off = newdp->d_off;
+ result.d_reclen = newdp->d_reclen;
+ result.d_type = newdp->d_type;
- bytes = __old_getdents64 (dirp->fd, dirp->data, maxread);
- if (bytes <= 0)
+ /* Check for ino_t overflow. */
+ if (__glibc_unlikely (result.d_ino != newdp->d_ino))
{
- /* Linux may fail with ENOENT on some file systems if the
- directory inode is marked as dead (deleted). POSIX
- treats this as a regular end-of-directory condition, so
- do not set errno in that case, to indicate success. */
- if (bytes == 0 || errno == ENOENT)
- __set_errno (saved_errno);
-#if IS_IN (libc)
- __libc_lock_unlock (dirp->lock);
-#endif
- return NULL;
+ dirp->errcode = ENAMETOOLONG;
+ continue;
}
- dirp->size = (size_t) bytes;
- /* Reset the offset into the buffer. */
- dirp->offset = 0;
- }
+ /* Overwrite the fixed-sized part. */
+ dp = (struct __old_dirent64 *) newdp;
+ memcpy (dp, &result, offsetof (struct __old_dirent64, d_name));
- dp = (struct __old_dirent64 *) &dirp->data[dirp->offset];
- dirp->offset += dp->d_reclen;
- dirp->filepos = dp->d_off;
+ /* Move the name. */
+ _Static_assert (offsetof (struct __old_dirent64, d_name)
+ <= offsetof (struct dirent64, d_name),
+ "old struct must be smaller");
+ if (offsetof (struct __old_dirent64, d_name)
+ != offsetof (struct dirent64, d_name))
+ memmove (dp->d_name, newdp->d_name, strlen (newdp->d_name) + 1);
-#if IS_IN (libc)
- __libc_lock_unlock (dirp->lock);
-#endif
+ __set_errno (saved_errno);
+ break;
+ }
+ __libc_lock_unlock (dirp->lock);
return dp;
}
libc_hidden_def (__old_readdir64)
diff --git a/sysdeps/unix/sysv/linux/readdir64_r.c b/sysdeps/unix/sysv/linux/readdir64_r.c
index 073a6453d1bbbd61..0c60987343b1f37f 100644
--- a/sysdeps/unix/sysv/linux/readdir64_r.c
+++ b/sysdeps/unix/sysv/linux/readdir64_r.c
@@ -135,91 +135,37 @@ attribute_compat_text_section
__old_readdir64_r (DIR *dirp, struct __old_dirent64 *entry,
struct __old_dirent64 **result)
{
- struct __old_dirent64 *dp;
- size_t reclen;
- const int saved_errno = errno;
- int ret;
-
- __libc_lock_lock (dirp->lock);
-
- do
+ while (1)
{
- if (dirp->offset >= dirp->size)
- {
- /* We've emptied out our buffer. Refill it. */
-
- size_t maxread = dirp->allocation;
- ssize_t bytes;
-
- maxread = dirp->allocation;
-
- bytes = __old_getdents64 (dirp->fd, dirp->data, maxread);
- if (bytes <= 0)
- {
- /* On some systems getdents fails with ENOENT when the
- open directory has been rmdir'd already. POSIX.1
- requires that we treat this condition like normal EOF. */
- if (bytes < 0 && errno == ENOENT)
- {
- bytes = 0;
- __set_errno (saved_errno);
- }
- if (bytes < 0)
- dirp->errcode = errno;
+ struct dirent64 new_entry;
+ struct dirent64 *newp;
+ int ret = __readdir64_r (dirp, &new_entry, &newp);
- dp = NULL;
- break;
- }
- dirp->size = (size_t) bytes;
-
- /* Reset the offset into the buffer. */
- dirp->offset = 0;
+ if (ret != 0)
+ return ret;
+ else if (newp == NULL)
+ {
+ *result = NULL;
+ return 0;
}
-
- dp = (struct __old_dirent64 *) &dirp->data[dirp->offset];
-
- reclen = dp->d_reclen;
-
- dirp->offset += reclen;
-
- dirp->filepos = dp->d_off;
-
- if (reclen > offsetof (struct __old_dirent64, d_name) + NAME_MAX + 1)
+ else
{
- /* The record is very long. It could still fit into the
- caller-supplied buffer if we can skip padding at the
- end. */
- size_t namelen = _D_EXACT_NAMLEN (dp);
- if (namelen <= NAME_MAX)
- reclen = offsetof (struct __old_dirent64, d_name) + namelen + 1;
- else
+ entry->d_ino = newp->d_ino;
+ if (entry->d_ino != newp->d_ino)
{
- /* The name is too long. Ignore this file. */
- dirp->errcode = ENAMETOOLONG;
- dp->d_ino = 0;
+ dirp->errcode = EOVERFLOW;
continue;
}
+ size_t namelen = strlen (newp->d_name);
+ entry->d_off = newp->d_off;
+ entry->d_reclen = (offsetof (struct __old_dirent64, d_name)
+ + namelen + 1);
+ entry->d_type = newp->d_type;
+ memcpy (entry->d_name, newp->d_name, namelen + 1);
+ *result = entry;
+ return 0;
}
-
- /* Skip deleted and ignored files. */
- }
- while (dp->d_ino == 0);
-
- if (dp != NULL)
- {
- *result = memcpy (entry, dp, reclen);
- entry->d_reclen = reclen;
- ret = 0;
}
- else
- {
- *result = NULL;
- ret = dirp->errcode;
- }
-
- __libc_lock_unlock (dirp->lock);
-
- return ret;
}
compat_symbol (libc, __old_readdir64_r, readdir64_r, GLIBC_2_1);

264
glibc-RHEL-56542-8.patch Normal file
View File

@ -0,0 +1,264 @@
commit 4ec355af454695556db1212d1c9ca9c3789cddf4
Author: Florian Weimer <fweimer@redhat.com>
Date: Sat Sep 21 19:32:34 2024 +0200
dirent: Add tst-readdir-long
It tests long names and ENAMETOOLONG handling, specifically
for readdir_r. This is a regression test for bug 14699,
bug 32124, and bug 32128.
Reviewed-by: DJ Delorie <dj@redhat.com>
Conflicts:
dirent/Makefile (fixup context)
diff --git a/dirent/Makefile b/dirent/Makefile
index a0404b82b81ed9e8..d12be2f0384b184f 100644
--- a/dirent/Makefile
+++ b/dirent/Makefile
@@ -61,6 +61,7 @@ tests := \
tst-closedir-leaks \
tst-fdopendir \
tst-fdopendir2 \
+ tst-readdir-long \
tst-rewinddir \
tst-scandir \
tst-scandir64 \
diff --git a/dirent/tst-readdir-long.c b/dirent/tst-readdir-long.c
new file mode 100644
index 0000000000000000..409318fa52fc664f
--- /dev/null
+++ b/dirent/tst-readdir-long.c
@@ -0,0 +1,231 @@
+/* Test readdir (+variants) behavior with file names of varying length.
+ Copyright (C) 2024 Free Software Foundation, Inc.
+ This file is part of the GNU C Library.
+
+ The GNU C Library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ The GNU C Library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with the GNU C Library; if not, see
+ <https://www.gnu.org/licenses/>. */
+
+#include <dirent.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <support/check.h>
+#include <support/fuse.h>
+#include <support/support.h>
+#include <support/xdirent.h>
+#include <support/readdir.h>
+
+/* If positive, at this length an EMSGSIZE error is injected. */
+static _Atomic int inject_error_at_length;
+
+/* Return a file name, LENGTH bytes long. */
+static char *
+name_of_length (size_t length)
+{
+ char *result = xmalloc (length + 1);
+ unsigned int prefix = snprintf (result, length + 1, "%zu-", length);
+ for (size_t i = prefix; i < length; ++i)
+ result[i] = 'A' + ((length + i) % 26);
+ result[length] = '\0';
+ return result;
+}
+
+/* Add the directory entry at OFFSET to the stream D. */
+static uint64_t
+add_directory_entry (struct support_fuse_dirstream *d, uint64_t offset)
+{
+ unsigned int length = offset + 1;
+ if (length > 1000)
+ /* Longer than what is possible to produce with 256
+ UTF-8-encoded Unicode code points. */
+ return 0;
+
+ char *to_free = NULL;
+ const char *name;
+ uint64_t ino = 1000 + length; /* Arbitrary value, distinct from 1. */
+ uint32_t type = DT_REG;
+ if (offset <= 1)
+ {
+ type = DT_DIR;
+ name = ".." + !offset; /* "." or "..". */
+ ino = 1;
+ }
+ else if (length == 1000)
+ name = "short";
+ else
+ {
+ to_free = name_of_length (length);
+ name = to_free;
+ }
+
+ ++offset;
+ bool added = support_fuse_dirstream_add (d, ino, offset, type, name);
+ free (to_free);
+ if (added)
+ return offset;
+ else
+ return 0;
+}
+
+/* Set to true if getdents64 should produce only one entry. */
+static _Atomic bool one_entry_per_getdents64;
+
+static void
+fuse_thread (struct support_fuse *f, void *closure)
+{
+ struct fuse_in_header *inh;
+ while ((inh = support_fuse_next (f)) != NULL)
+ {
+ if (support_fuse_handle_mountpoint (f)
+ || (inh->nodeid == 1 && support_fuse_handle_directory (f)))
+ continue;
+ switch (inh->opcode)
+ {
+ case FUSE_READDIR:
+ if (inh->nodeid == 1)
+ {
+ uint64_t offset = support_fuse_cast (READ, inh)->offset;
+ if (inject_error_at_length == offset + 1)
+ support_fuse_reply_error (f, EMSGSIZE);
+ else
+ {
+ struct support_fuse_dirstream *d
+ = support_fuse_prepare_readdir (f);
+ while (true)
+ {
+ offset = add_directory_entry (d, offset);
+ if (offset == 0 || one_entry_per_getdents64
+ /* Error will be reported at next READDIR. */
+ || offset + 1 == inject_error_at_length)
+ break;
+ }
+ support_fuse_reply_prepared (f);
+ }
+ }
+ else
+ support_fuse_reply_error (f, EIO);
+ break;
+ default:
+ FAIL ("unexpected event %s", support_fuse_opcode (inh->opcode));
+ support_fuse_reply_error (f, EIO);
+ }
+ }
+}
+
+/* Run the tests for the specified readdir variant OP. */
+static void
+run_readdir_tests (struct support_fuse *f, enum support_readdir_op op)
+{
+ printf ("info: testing %s (inject_error=%d unbuffered=%d)\n",
+ support_readdir_function (op), inject_error_at_length,
+ (int) one_entry_per_getdents64);
+
+ bool testing_r = support_readdir_r_variant (op);
+
+ DIR *dir = xopendir (support_fuse_mountpoint (f));
+ struct support_dirent e = { 0, };
+ TEST_VERIFY (support_readdir (dir, op, &e));
+ TEST_COMPARE (e.d_ino, 1);
+ TEST_COMPARE_STRING (e.d_name, ".");
+
+ TEST_VERIFY (support_readdir (dir, op, &e));
+ TEST_COMPARE (e.d_ino, 1);
+ TEST_COMPARE_STRING (e.d_name, "..");
+
+ for (unsigned int i = 3; i < 1000; ++i)
+ {
+ if (i == inject_error_at_length)
+ /* Error expected below. */
+ break;
+
+ if (i >= sizeof ((struct dirent) { 0, }.d_name) && testing_r)
+ /* This is a readir_r test. The longer names are not
+ available because they do not fit into struct dirent. */
+ break;
+
+ char *expected_name = name_of_length (i);
+ TEST_COMPARE (strlen (expected_name), i);
+ TEST_VERIFY (support_readdir (dir, op, &e));
+ TEST_COMPARE (e.d_ino, 1000 + i);
+ TEST_COMPARE_STRING (e.d_name, expected_name);
+ free (expected_name);
+ }
+
+ if (inject_error_at_length == 0)
+ {
+ /* Check that the ENAMETOOLONG error does not prevent reading a
+ later short name. */
+ TEST_VERIFY (support_readdir (dir, op, &e));
+ TEST_COMPARE (e.d_ino, 2000);
+ TEST_COMPARE_STRING (e.d_name, "short");
+
+ if (testing_r)
+ /* An earlier name was too long. */
+ support_readdir_expect_error (dir, op, ENAMETOOLONG);
+ else
+ /* Entire directory read without error. */
+ TEST_VERIFY (!support_readdir (dir, op, &e));
+ }
+ else
+ support_readdir_expect_error (dir, op, EMSGSIZE);
+
+ free (e.d_name);
+ xclosedir (dir);
+}
+
+/* Run all readdir variants for both fully-buffered an unbuffered
+ (one-at-a-time) directory streams. */
+static void
+run_fully_buffered_and_singleton_buffers (struct support_fuse *f)
+{
+ for (int do_one_entry = 0; do_one_entry < 2; ++do_one_entry)
+ {
+ one_entry_per_getdents64 = do_one_entry;
+ for (enum support_readdir_op op = 0; op <= support_readdir_op_last();
+ ++op)
+ run_readdir_tests (f, op);
+ }
+}
+
+static int
+do_test (void)
+{
+ /* Smoke test for name_of_length. */
+ {
+ char *name = name_of_length (5);
+ TEST_COMPARE_STRING (name, "5-HIJ");
+ free (name);
+
+ name = name_of_length (6);
+ TEST_COMPARE_STRING (name, "6-IJKL");
+ free (name);
+ }
+
+ support_fuse_init ();
+ struct support_fuse *f = support_fuse_mount (fuse_thread, NULL);
+
+ run_fully_buffered_and_singleton_buffers (f);
+
+ inject_error_at_length = 100;
+ run_fully_buffered_and_singleton_buffers (f);
+
+ inject_error_at_length = 300;
+ run_fully_buffered_and_singleton_buffers (f);
+
+ support_fuse_unmount (f);
+ return 0;
+}
+
+#include <support/test-driver.c>

214
glibc-RHEL-56542-9.patch Normal file
View File

@ -0,0 +1,214 @@
commit 6f3f6c506cdaf981a4374f1f12863b98ac7fea1a
Author: Florian Weimer <fweimer@redhat.com>
Date: Sat Sep 21 19:32:34 2024 +0200
Linux: readdir64_r should not skip d_ino == 0 entries (bug 32126)
This is the same bug as bug 12165, but for readdir_r. The
regression test covers both bug 12165 and bug 32126.
Reviewed-by: DJ Delorie <dj@redhat.com>
diff --git a/dirent/Makefile b/dirent/Makefile
index d12be2f0384b184f..4cecd169b42c540b 100644
--- a/dirent/Makefile
+++ b/dirent/Makefile
@@ -62,6 +62,7 @@ tests := \
tst-fdopendir \
tst-fdopendir2 \
tst-readdir-long \
+ tst-readdir-zero-inode \
tst-rewinddir \
tst-scandir \
tst-scandir64 \
diff --git a/dirent/tst-readdir-zero-inode.c b/dirent/tst-readdir-zero-inode.c
new file mode 100644
index 0000000000000000..af9fb946abe6c483
--- /dev/null
+++ b/dirent/tst-readdir-zero-inode.c
@@ -0,0 +1,134 @@
+/* Test that readdir does not skip entries with d_ino == 0 (bug 12165).
+ Copyright (C) 2024 Free Software Foundation, Inc.
+ This file is part of the GNU C Library.
+
+ The GNU C Library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ The GNU C Library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with the GNU C Library; if not, see
+ <https://www.gnu.org/licenses/>. */
+
+#include <stdlib.h>
+#include <support/check.h>
+#include <support/fuse.h>
+#include <support/readdir.h>
+#include <support/xdirent.h>
+
+/* Add the directory entry at OFFSET to the stream D. */
+static uint64_t
+add_directory_entry (struct support_fuse_dirstream *d, uint64_t offset)
+{
+ bool added = false;
+ ++offset;
+ switch (offset - 1)
+ {
+ case 0:
+ added = support_fuse_dirstream_add (d, 1, offset, DT_DIR, ".");
+ break;
+ case 1:
+ added = support_fuse_dirstream_add (d, 1, offset, DT_DIR, "..");
+ break;
+ case 2:
+ added = support_fuse_dirstream_add (d, 2, offset, DT_REG, "before");
+ break;
+ case 3:
+ added = support_fuse_dirstream_add (d, 0, offset, DT_REG, "zero");
+ break;
+ case 4:
+ added = support_fuse_dirstream_add (d, 3, offset, DT_REG, "after");
+ break;
+ }
+ if (added)
+ return offset;
+ else
+ return 0;
+}
+
+/* Set to true if getdents64 should produce only one entry. */
+static bool one_entry_per_getdents64;
+
+static void
+fuse_thread (struct support_fuse *f, void *closure)
+{
+ struct fuse_in_header *inh;
+ while ((inh = support_fuse_next (f)) != NULL)
+ {
+ if (support_fuse_handle_mountpoint (f)
+ || (inh->nodeid == 1 && support_fuse_handle_directory (f)))
+ continue;
+ switch (inh->opcode)
+ {
+ case FUSE_READDIR:
+ if (inh->nodeid == 1)
+ {
+ uint64_t offset = support_fuse_cast (READ, inh)->offset;
+ struct support_fuse_dirstream *d
+ = support_fuse_prepare_readdir (f);
+ while (true)
+ {
+ offset = add_directory_entry (d, offset);
+ if (offset == 0 || one_entry_per_getdents64)
+ break;
+ }
+ support_fuse_reply_prepared (f);
+ }
+ else
+ support_fuse_reply_error (f, EIO);
+ break;
+ default:
+ FAIL ("unexpected event %s", support_fuse_opcode (inh->opcode));
+ support_fuse_reply_error (f, EIO);
+ }
+ }
+}
+
+static int
+do_test (void)
+{
+ support_fuse_init ();
+
+ for (enum support_readdir_op op = 0; op <= support_readdir_op_last (); ++op)
+ {
+ struct support_fuse *f = support_fuse_mount (fuse_thread, NULL);
+ DIR *dir = xopendir (support_fuse_mountpoint (f));
+ struct support_dirent e = { 0, };
+
+ TEST_VERIFY (support_readdir (dir, op, &e));
+ TEST_COMPARE_STRING (e.d_name, ".");
+ TEST_COMPARE (e.d_ino, 1);
+
+ TEST_VERIFY (support_readdir (dir, op, &e));
+ TEST_COMPARE_STRING (e.d_name, "..");
+ TEST_COMPARE (e.d_ino, 1);
+
+ TEST_VERIFY (support_readdir (dir, op, &e));
+ TEST_COMPARE_STRING (e.d_name, "before");
+ TEST_COMPARE (e.d_ino, 2);
+
+ TEST_VERIFY (support_readdir (dir, op, &e));
+ TEST_COMPARE_STRING (e.d_name, "zero");
+ TEST_COMPARE (e.d_ino, 0);
+
+ TEST_VERIFY (support_readdir (dir, op, &e));
+ TEST_COMPARE_STRING (e.d_name, "after");
+ TEST_COMPARE (e.d_ino, 3);
+
+ TEST_VERIFY (!support_readdir (dir, op, &e));
+
+ free (e.d_name);
+ xclosedir (dir);
+ support_fuse_unmount (f);
+ }
+
+ return 0;
+}
+
+#include <support/test-driver.c>
diff --git a/sysdeps/unix/sysv/linux/readdir64_r.c b/sysdeps/unix/sysv/linux/readdir64_r.c
index 0c60987343b1f37f..2e188364ff5aa143 100644
--- a/sysdeps/unix/sysv/linux/readdir64_r.c
+++ b/sysdeps/unix/sysv/linux/readdir64_r.c
@@ -37,7 +37,7 @@ __readdir64_r (DIR *dirp, struct dirent64 *entry, struct dirent64 **result)
__libc_lock_lock (dirp->lock);
- do
+ while (1)
{
if (dirp->offset >= dirp->size)
{
@@ -79,26 +79,21 @@ __readdir64_r (DIR *dirp, struct dirent64 *entry, struct dirent64 **result)
dirp->filepos = dp->d_off;
- if (reclen > offsetof (struct dirent64, d_name) + NAME_MAX + 1)
+ if (reclen <= offsetof (struct dirent64, d_name) + NAME_MAX + 1)
+ break;
+
+ /* The record is very long. It could still fit into the
+ caller-supplied buffer if we can skip padding at the end. */
+ size_t namelen = _D_EXACT_NAMLEN (dp);
+ if (namelen <= NAME_MAX)
{
- /* The record is very long. It could still fit into the
- caller-supplied buffer if we can skip padding at the
- end. */
- size_t namelen = _D_EXACT_NAMLEN (dp);
- if (namelen <= NAME_MAX)
- reclen = offsetof (struct dirent64, d_name) + namelen + 1;
- else
- {
- /* The name is too long. Ignore this file. */
- dirp->errcode = ENAMETOOLONG;
- dp->d_ino = 0;
- continue;
- }
+ reclen = offsetof (struct dirent64, d_name) + namelen + 1;
+ break;
}
- /* Skip deleted and ignored files. */
+ /* The name is too long. Ignore this file. */
+ dirp->errcode = ENAMETOOLONG;
}
- while (dp->d_ino == 0);
if (dp != NULL)
{

View File

@ -157,7 +157,7 @@ end \
Summary: The GNU libc libraries
Name: glibc
Version: %{glibcversion}
Release: 149%{?dist}
Release: 150%{?dist}
# In general, GPLv2+ is used by programs, LGPLv2+ is used for
# libraries.
@ -1053,6 +1053,15 @@ Patch745: glibc-RHEL-68850-2.patch
Patch746: glibc-RHEL-61568.patch
Patch747: glibc-RHEL-58979.patch
Patch748: glibc-RHEL-65354.patch
Patch749: glibc-RHEL-56542-1.patch
Patch750: glibc-RHEL-56542-2.patch
Patch751: glibc-RHEL-56542-3.patch
Patch752: glibc-RHEL-56542-4.patch
Patch753: glibc-RHEL-56542-5.patch
Patch754: glibc-RHEL-56542-6.patch
Patch755: glibc-RHEL-56542-7.patch
Patch756: glibc-RHEL-56542-8.patch
Patch757: glibc-RHEL-56542-9.patch
##############################################################################
# Continued list of core "glibc" package information:
@ -3046,6 +3055,9 @@ update_gconv_modules_cache ()
%endif
%changelog
* Fri Jan 10 2025 Frédéric Bérat <fberat@redhat.com> - 2.34-150
- Backport test implementation to verify readdir behavior (RHEL-56542)
* Wed Jan 08 2025 Frédéric Bérat <fberat@redhat.com> - 2.34-149
- Backport: fix the glibc manual to handle spaces for @deftypefun
references. (RHEL-65356)