Fix readdir_r error reporting and readdir64_r entry handling, extend dirent testing. (RHEL-111120)

Resolves: RHEL-111120
This commit is contained in:
Frédéric Bérat 2025-09-19 13:11:28 +02:00
parent 637af31dc7
commit 3b4a80c65e
9 changed files with 1919 additions and 0 deletions

75
glibc-RHEL-111120-1.patch Normal file
View File

@ -0,0 +1,75 @@
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.
diff --git a/dirent/Makefile b/dirent/Makefile
index 92587cab9a85203f..556f759f653349bd 100644
--- a/dirent/Makefile
+++ b/dirent/Makefile
@@ -22,16 +22,48 @@ 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
+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-scandir \
+ tst-scandir64 \
+ tst-seekdir \
+ # tests
CFLAGS-scandir.c += $(uses-callbacks)
CFLAGS-scandir64.c += $(uses-callbacks)

48
glibc-RHEL-111120-2.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 ffd5262cf5a6f885..1d595688f78ccd77 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;
}

526
glibc-RHEL-111120-3.patch Normal file
View File

@ -0,0 +1,526 @@
commit 1251e9ea49fba9f53bbf4f290f3db90c01931fa7
Author: Florian Weimer <fweimer@redhat.com>
Date: Thu Sep 12 09:40:25 2024 +0200
support: Add <support/readdir.h>
It allows to read directories using the six readdir variants
without writing type-specific code or using skeleton files
that are compiled four times.
The readdir_r subtest for support_readdir_expect_error revealed
bug 32124.
Reviewed-by: DJ Delorie <dj@redhat.com>
diff --git a/support/Makefile b/support/Makefile
index 480d3a91b8b9b625..6809c234e9314163 100644
--- a/support/Makefile
+++ b/support/Makefile
@@ -74,6 +74,7 @@ libsupport-routines = \
support_quote_blob \
support_quote_blob_wide \
support_quote_string \
+ support_readdir \
support_readdir_check \
support_readdir_r_check \
support_record_failure \
@@ -329,6 +330,7 @@ tests = \
tst-support_quote_blob \
tst-support_quote_blob_wide \
tst-support_quote_string \
+ tst-support_readdir \
tst-support_record_failure \
tst-test_compare \
tst-test_compare_blob \
diff --git a/support/readdir.h b/support/readdir.h
new file mode 100644
index 0000000000000000..7d7c7650d42efb70
--- /dev/null
+++ b/support/readdir.h
@@ -0,0 +1,85 @@
+/* Type-generic wrapper for readdir functions.
+ 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/>. */
+
+#ifndef SUPPORT_READDIR_H
+#define SUPPORT_READDIR_H
+
+#include <dirent.h>
+#include <stdbool.h>
+#include <stdint.h>
+
+__BEGIN_DECLS
+
+/* Definition independent of _FILE_OFFSET_BITS. */
+struct support_dirent
+{
+ uint64_t d_ino;
+ uint64_t d_off; /* 0 if d_off is not supported. */
+ uint32_t d_type;
+ char *d_name;
+};
+
+/* Operation to be performed by support_readdir below. */
+enum support_readdir_op
+ {
+ SUPPORT_READDIR,
+ SUPPORT_READDIR64,
+ SUPPORT_READDIR_R,
+ SUPPORT_READDIR64_R,
+ SUPPORT_READDIR64_COMPAT,
+ SUPPORT_READDIR64_R_COMPAT,
+ };
+
+/* Returns the last supported function. May exclude
+ SUPPORT_READDIR64_R_COMPAT if not implemented. */
+enum support_readdir_op support_readdir_op_last (void);
+
+/* Returns the name of the function that corresponds to the OP constant. */
+const char *support_readdir_function (enum support_readdir_op op);
+
+/* Returns the d_ino field width for OP, in bits. */
+unsigned int support_readdir_inode_width (enum support_readdir_op op);
+
+/* Returns the d_off field width for OP, in bits. Zero if not present. */
+unsigned int support_readdir_offset_width (enum support_readdir_op op);
+
+/* Returns true if OP is an _r variant with name length restrictions. */
+bool support_readdir_r_variant (enum support_readdir_op op);
+
+/* First, free E->d_name and set the field to NULL. Then call the
+ readdir variant as specified by OP. If successfully, copy fields
+ to E, make a copy of the entry name using strdup, and write its
+ addres sto E->d_name.
+
+ Return true if an entry was read, or false if the end of the
+ directory stream was reached. Terminates the process upon error.
+ The caller is expected to free E->d_name if the function is not
+ called again for this E.
+
+ Note that this function assumes that E->d_name has been initialized
+ to NULL or has been allocated by a previous call to this function. */
+bool support_readdir (DIR *stream, enum support_readdir_op op,
+ struct support_dirent *e) __nonnull ((1, 3));
+
+/* Checks that the readdir operation OP fails with errno value EXPECTED. */
+void support_readdir_expect_error (DIR *stream, enum support_readdir_op op,
+ int expected) __nonnull ((1));
+
+__END_DECLS
+
+#endif /* SUPPORT_READDIR_H */
diff --git a/support/support_readdir.c b/support/support_readdir.c
new file mode 100644
index 0000000000000000..10d808416f7a0456
--- /dev/null
+++ b/support/support_readdir.c
@@ -0,0 +1,318 @@
+/* Type-generic wrapper for readdir functions.
+ 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 <support/readdir.h>
+
+#include <dlfcn.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <support/check.h>
+#include <support/support.h>
+#include <support/xdirent.h>
+
+/* Copied from <olddirent.h>. */
+struct __old_dirent64
+ {
+ __ino_t d_ino;
+ __off64_t d_off;
+ unsigned short int d_reclen;
+ unsigned char d_type;
+ char d_name[256];
+ };
+
+static struct __old_dirent64 *(*readdir64_compat) (DIR *);
+static int (*readdir64_r_compat) (DIR *, struct __old_dirent64 *,
+ struct __old_dirent64 **);
+
+static void __attribute__ ((constructor))
+init (void)
+{
+ /* These compat symbols exists on alpha, i386, m67k , powerpc, s390,
+ sparc. at the same GLIBC_2.1 version. */
+ readdir64_compat = dlvsym (RTLD_DEFAULT, "readdir64", "GLIBC_2.1");
+ readdir64_r_compat = dlvsym (RTLD_DEFAULT, "readdir64_r", "GLIBC_2.1");
+}
+
+enum support_readdir_op
+support_readdir_op_last (void)
+{
+ if (readdir64_r_compat != NULL)
+ {
+ TEST_VERIFY (readdir64_compat != NULL);
+ return SUPPORT_READDIR64_R_COMPAT;
+ }
+ else
+ return SUPPORT_READDIR64_R;
+}
+
+const char *
+support_readdir_function (enum support_readdir_op op)
+{
+ switch (op)
+ {
+ case SUPPORT_READDIR:
+ return "readdir";
+ case SUPPORT_READDIR64:
+ return "readdir64";
+ case SUPPORT_READDIR_R:
+ return "readdir_r";
+ case SUPPORT_READDIR64_R:
+ return "readdir64_r";
+ case SUPPORT_READDIR64_COMPAT:
+ return "readdir64@GBLIC_2.1";
+ case SUPPORT_READDIR64_R_COMPAT:
+ return "readdir64_r@GBLIC_2.1";
+ }
+ FAIL_EXIT1 ("invalid support_readdir_op constant: %d", op);
+}
+
+unsigned int
+support_readdir_inode_width (enum support_readdir_op op)
+{
+ switch (op)
+ {
+ case SUPPORT_READDIR:
+ case SUPPORT_READDIR_R:
+ return sizeof ((struct dirent) { 0, }.d_ino) * 8;
+ case SUPPORT_READDIR64:
+ case SUPPORT_READDIR64_R:
+ return sizeof ((struct dirent64) { 0, }.d_ino) * 8;
+ case SUPPORT_READDIR64_COMPAT:
+ case SUPPORT_READDIR64_R_COMPAT:
+ return sizeof ((struct __old_dirent64) { 0, }.d_ino) * 8;
+ }
+ FAIL_EXIT1 ("invalid support_readdir_op constant: %d", op);
+}
+
+unsigned int
+support_readdir_offset_width (enum support_readdir_op op)
+{
+#ifdef _DIRENT_HAVE_D_OFF
+ switch (op)
+ {
+ case SUPPORT_READDIR:
+ case SUPPORT_READDIR_R:
+ return sizeof ((struct dirent) { 0, }.d_off) * 8;
+ case SUPPORT_READDIR64:
+ case SUPPORT_READDIR64_R:
+ return sizeof ((struct dirent64) { 0, }.d_off) * 8;
+ case SUPPORT_READDIR64_COMPAT:
+ case SUPPORT_READDIR64_R_COMPAT:
+ return sizeof ((struct __old_dirent64) { 0, }.d_off) * 8;
+ }
+#else
+ switch (op)
+ {
+ case SUPPORT_READDIR:
+ case SUPPORT_READDIR_R:
+ case SUPPORT_READDIR64:
+ case SUPPORT_READDIR64_R:
+ case SUPPORT_READDIR64_COMPAT:
+ case SUPPORT_READDIR64_R_COMPAT:
+ return 0;
+ }
+#endif
+ FAIL_EXIT1 ("invalid support_readdir_op constant: %d", op);
+}
+
+bool
+support_readdir_r_variant (enum support_readdir_op op)
+{
+ switch (op)
+ {
+ case SUPPORT_READDIR:
+ case SUPPORT_READDIR64:
+ case SUPPORT_READDIR64_COMPAT:
+ return false;
+ case SUPPORT_READDIR_R:
+ case SUPPORT_READDIR64_R:
+ case SUPPORT_READDIR64_R_COMPAT:
+ return true;
+ }
+ FAIL_EXIT1 ("invalid support_readdir_op constant: %d", op);
+}
+
+static bool
+copy_dirent (struct support_dirent *dst, struct dirent *src)
+{
+ if (src == NULL)
+ return false;
+ dst->d_ino = src->d_ino;
+#ifdef _DIRENT_HAVE_D_OFF
+ dst->d_off = src->d_off;
+#else
+ dst->d_off = 0;
+#endif
+ dst->d_type = src->d_type;
+ dst->d_name = xstrdup (src->d_name);
+ return true;
+}
+
+static bool
+copy_dirent64 (struct support_dirent *dst, struct dirent64 *src)
+{
+ if (src == NULL)
+ return false;
+ dst->d_ino = src->d_ino;
+#ifdef _DIRENT_HAVE_D_OFF
+ dst->d_off = src->d_off;
+#else
+ dst->d_off = 0;
+#endif
+ dst->d_type = src->d_type;
+ dst->d_name = xstrdup (src->d_name);
+ return true;
+}
+
+static bool
+copy_old_dirent64 (struct support_dirent *dst, struct __old_dirent64 *src)
+{
+ if (src == NULL)
+ return false;
+ dst->d_ino = src->d_ino;
+#ifdef _DIRENT_HAVE_D_OFF
+ dst->d_off = src->d_off;
+#else
+ dst->d_off = 0;
+#endif
+ dst->d_type = src->d_type;
+ dst->d_name = xstrdup (src->d_name);
+ return true;
+}
+
+bool
+support_readdir (DIR *stream, enum support_readdir_op op,
+ struct support_dirent *e)
+{
+ free (e->d_name);
+ e->d_name = NULL;
+ switch (op)
+ {
+ case SUPPORT_READDIR:
+ return copy_dirent (e, xreaddir (stream));
+ case SUPPORT_READDIR64:
+ return copy_dirent64 (e, xreaddir64 (stream));
+
+ /* The functions readdir_r, readdir64_r were deprecated in glibc 2.24. */
+ DIAG_PUSH_NEEDS_COMMENT;
+ DIAG_IGNORE_NEEDS_COMMENT (4.9, "-Wdeprecated-declarations");
+
+ case SUPPORT_READDIR_R:
+ {
+ struct dirent buf;
+ if (!xreaddir_r (stream, &buf))
+ return false;
+ return copy_dirent (e, &buf);
+ }
+ case SUPPORT_READDIR64_R:
+ {
+ struct dirent64 buf;
+ if (!xreaddir64_r (stream, &buf))
+ return false;
+ return copy_dirent64 (e, &buf);
+ }
+
+ DIAG_POP_NEEDS_COMMENT;
+
+ case SUPPORT_READDIR64_COMPAT:
+ if (readdir64_compat == NULL)
+ FAIL_EXIT1 ("readdir64 compat function not implemented");
+ return copy_old_dirent64 (e, readdir64_compat (stream));
+
+ case SUPPORT_READDIR64_R_COMPAT:
+ {
+ if (readdir64_r_compat == NULL)
+ FAIL_EXIT1 ("readdir64_r compat function not implemented");
+ struct __old_dirent64 buf;
+ struct __old_dirent64 *e1;
+ int ret = readdir64_r_compat (stream, &buf, &e1);
+ if (ret != 0)
+ {
+ errno = ret;
+ FAIL ("readdir64_r@GLIBC_2.1: %m");
+ return false;
+ }
+ if (e1 == NULL)
+ return false;
+ return copy_old_dirent64 (e, e1);
+ }
+ }
+ FAIL_EXIT1 ("support_readdir: invalid op argument %d", (int) op);
+}
+
+void
+support_readdir_expect_error (DIR *stream, enum support_readdir_op op,
+ int expected)
+{
+ switch (op)
+ {
+ case SUPPORT_READDIR:
+ errno = 0;
+ TEST_VERIFY (readdir (stream) == NULL);
+ TEST_COMPARE (errno, expected);
+ return;
+ case SUPPORT_READDIR64:
+ errno = 0;
+ TEST_VERIFY (readdir64 (stream) == NULL);
+ TEST_COMPARE (errno, expected);
+ return;
+
+ /* The functions readdir_r, readdir64_r were deprecated in glibc 2.24. */
+ DIAG_PUSH_NEEDS_COMMENT;
+ DIAG_IGNORE_NEEDS_COMMENT (4.9, "-Wdeprecated-declarations");
+
+ case SUPPORT_READDIR_R:
+ {
+ struct dirent buf;
+ struct dirent *e;
+ errno = readdir_r (stream, &buf, &e);
+ TEST_COMPARE (errno, expected);;
+ }
+ return;
+ case SUPPORT_READDIR64_R:
+ {
+ struct dirent64 buf;
+ struct dirent64 *e;
+ errno = readdir64_r (stream, &buf, &e);
+ TEST_COMPARE (errno, expected);;
+ }
+ return;
+
+ DIAG_POP_NEEDS_COMMENT;
+
+ case SUPPORT_READDIR64_COMPAT:
+ if (readdir64_compat == NULL)
+ FAIL_EXIT1 ("readdir64_r compat function not implemented");
+ errno = 0;
+ TEST_VERIFY (readdir64_compat (stream) == NULL);
+ TEST_COMPARE (errno, expected);
+ return;
+ case SUPPORT_READDIR64_R_COMPAT:
+ {
+ if (readdir64_r_compat == NULL)
+ FAIL_EXIT1 ("readdir64_r compat function not implemented");
+ struct __old_dirent64 buf;
+ struct __old_dirent64 *e;
+ errno = readdir64_r_compat (stream, &buf, &e);
+ TEST_COMPARE (errno, expected);
+ }
+ return;
+ }
+ FAIL_EXIT1 ("support_readdir_expect_error: invalid op argument %d",
+ (int) op);
+}
diff --git a/support/tst-support_readdir.c b/support/tst-support_readdir.c
new file mode 100644
index 0000000000000000..c0639571c7c3f516
--- /dev/null
+++ b/support/tst-support_readdir.c
@@ -0,0 +1,70 @@
+/* Test the support_readdir function.
+ 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 <support/readdir.h>
+
+#include <fcntl.h>
+#include <stdlib.h>
+#include <string.h>
+#include <support/check.h>
+#include <support/xdirent.h>
+#include <support/xunistd.h>
+
+static int
+do_test (void)
+{
+ DIR *reference_stream = xopendir (".");
+ struct dirent64 *reference = xreaddir64 (reference_stream);
+
+ for (enum support_readdir_op op = 0; op <= support_readdir_op_last (); ++op)
+ {
+ DIR *stream = xopendir (".");
+ struct support_dirent e;
+ memset (&e, 0xcc, sizeof (e));
+ e.d_name = NULL;
+ TEST_VERIFY (support_readdir (stream, op, &e));
+ TEST_COMPARE (e.d_ino, reference->d_ino);
+ if (support_readdir_offset_width (op) != 0)
+ TEST_COMPARE (e.d_off, reference->d_off);
+ else
+ TEST_COMPARE (e.d_off, 0);
+ TEST_COMPARE (e.d_type, reference->d_type);
+ TEST_COMPARE_STRING (e.d_name, reference->d_name);
+ free (e.d_name);
+ xclosedir (stream);
+ }
+
+ xclosedir (reference_stream);
+
+ /* Error injection test. */
+ int devnull = xopen ("/dev/null", O_RDONLY, 0);
+ for (enum support_readdir_op op = 0; op <= support_readdir_op_last (); ++op)
+ {
+ DIR *stream = xopendir (".");
+ /* A descriptor incompatible with readdir. */
+ xdup2 (devnull, dirfd (stream));
+ errno = -1;
+ support_readdir_expect_error (stream, op, ENOTDIR);
+ xclosedir (stream);
+ }
+ xclose (devnull);
+
+ return 0;
+}
+
+#include <support/test-driver.c>

34
glibc-RHEL-111120-4.patch Normal file
View File

@ -0,0 +1,34 @@
commit c9154cad66aa0b11ede62cc9190d3485c5ef6941
Author: Florian Weimer <fweimer@redhat.com>
Date: Thu Sep 12 18:26:04 2024 +0200
support: Fix Hurd build of tst-support_readdir
Check for the availability of the d_off member at compile time, not
run time.
Fixes commit 1251e9ea49fba9f53bbf4f290f3db90c01931fa7
("support: Add <support/readdir.h>").
diff --git a/support/tst-support_readdir.c b/support/tst-support_readdir.c
index c0639571c7c3f516..66be94fa802e727a 100644
--- a/support/tst-support_readdir.c
+++ b/support/tst-support_readdir.c
@@ -39,10 +39,13 @@ do_test (void)
e.d_name = NULL;
TEST_VERIFY (support_readdir (stream, op, &e));
TEST_COMPARE (e.d_ino, reference->d_ino);
- if (support_readdir_offset_width (op) != 0)
- TEST_COMPARE (e.d_off, reference->d_off);
- else
- TEST_COMPARE (e.d_off, 0);
+#ifdef _DIRENT_HAVE_D_OFF
+ TEST_VERIFY (support_readdir_offset_width (op) != 0);
+ TEST_COMPARE (e.d_off, reference->d_off);
+#else
+ TEST_COMPARE (support_readdir_offset_width (op), 0);
+ TEST_COMPARE (e.d_off, 0);
+#endif
TEST_COMPARE (e.d_type, reference->d_type);
TEST_COMPARE_STRING (e.d_name, reference->d_name);
free (e.d_name);

136
glibc-RHEL-111120-5.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 556f759f653349bd..f9056724f03125c0 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-scandir \
@@ -65,6 +66,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-dirfd.c += $(config-cflags-wno-ignored-attributes)
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-111120-6.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 227fbf21aef294f6..795bd935f0e95126 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 239f790648c6e6b6..065ca41a6e93e1c9 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 e6f5108c0a809353..e6b8867b7a361a62 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 e87882ee06d6deaf..7ad7e5945bc833c6 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);

261
glibc-RHEL-111120-7.patch Normal file
View File

@ -0,0 +1,261 @@
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>
diff --git a/dirent/Makefile b/dirent/Makefile
index f9056724f03125c0..91edcf9e70622938 100644
--- a/dirent/Makefile
+++ b/dirent/Makefile
@@ -61,6 +61,7 @@ tests := \
tst-closedir-leaks \
tst-fdopendir \
tst-fdopendir2 \
+ tst-readdir-long \
tst-scandir \
tst-scandir64 \
tst-seekdir \
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>

236
glibc-RHEL-111120-8.patch Normal file
View File

@ -0,0 +1,236 @@
commit 6aa1645f669322b36bda8e1fded6fd524d3e08ff
Author: Florian Weimer <fweimer@redhat.com>
Date: Sat Sep 21 19:32:34 2024 +0200
dirent: Add tst-rewinddir
It verifies that rewinddir allows restarting the directory
iteration.
Reviewed-by: DJ Delorie <dj@redhat.com>
diff --git a/dirent/Makefile b/dirent/Makefile
index 91edcf9e70622938..045c786575a7d5ff 100644
--- a/dirent/Makefile
+++ b/dirent/Makefile
@@ -62,6 +62,7 @@ tests := \
tst-fdopendir \
tst-fdopendir2 \
tst-readdir-long \
+ tst-rewinddir \
tst-scandir \
tst-scandir64 \
tst-seekdir \
diff --git a/dirent/tst-rewinddir.c b/dirent/tst-rewinddir.c
new file mode 100644
index 0000000000000000..1479766ebe8fc911
--- /dev/null
+++ b/dirent/tst-rewinddir.c
@@ -0,0 +1,207 @@
+/* Test for rewinddir, using FUSE.
+ 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 <stdio.h>
+#include <stdlib.h>
+#include <support/check.h>
+#include <support/fuse.h>
+#include <support/readdir.h>
+#include <support/support.h>
+#include <support/xdirent.h>
+
+/* Return the file name at the indicated directory offset. */
+static char *
+name_at_offset (unsigned int offset)
+{
+ if (offset <= 1)
+ return xstrdup (".." + !offset); /* "." or "..". */
+ else
+ /* Pad the name with a lot of zeros, so that the dirent buffer gets
+ filled more quickly. */
+ return xasprintf ("file%0240u", offset);
+}
+
+/* This many directory entries, including "." and "..". */
+enum { directory_entries = 200 };
+
+/* Add the directory entry at OFFSET to the stream D. */
+static uint64_t
+add_directory_entry (struct support_fuse_dirstream *d, uint64_t offset)
+{
+ if (offset >= directory_entries)
+ return 0;
+
+ char *name = name_at_offset (offset);
+ uint64_t ino = 1000 + offset; /* Arbitrary value, distinct from 1. */
+ uint32_t type = DT_REG;
+ if (offset <= 1)
+ {
+ type = DT_DIR;
+ ino = 1;
+ }
+
+ ++offset;
+ bool added = support_fuse_dirstream_add (d, ino, offset, type, name);
+ free (name);
+ 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);
+ }
+ }
+}
+
+/* Lists the entire directory from start to end. */
+static void
+verify_directory (DIR *dir, enum support_readdir_op op)
+{
+ struct support_dirent e = { 0, };
+
+ TEST_VERIFY (support_readdir (dir, op, &e));
+ TEST_COMPARE_STRING (e.d_name, ".");
+ TEST_VERIFY (support_readdir (dir, op, &e));
+ TEST_COMPARE_STRING (e.d_name, "..");
+ for (int i = 2; i < directory_entries; ++i)
+ {
+ char *expected = name_at_offset (i);
+ TEST_VERIFY (support_readdir (dir, op, &e));
+ TEST_COMPARE_STRING (e.d_name, expected);
+ free (expected);
+ }
+ TEST_VERIFY (!support_readdir (dir, op, &e));
+ free (e.d_name);
+}
+
+/* Run tests with rewinding after ENTRIES readdir calls. */
+static void
+rewind_after (unsigned int rewind_at)
+{
+ for (enum support_readdir_op op = 0; op <= support_readdir_op_last (); ++op)
+ {
+ printf ("info: testing %s (rewind_at=%u)\n",
+ support_readdir_function (op), rewind_at);
+
+ struct support_fuse *f = support_fuse_mount (fuse_thread, NULL);
+ DIR *dir = xopendir (support_fuse_mountpoint (f));
+ struct support_dirent e = { 0, };
+
+ switch (rewind_at)
+ {
+ case 0:
+ break;
+ case 1:
+ TEST_VERIFY (support_readdir (dir, op, &e));
+ TEST_COMPARE_STRING (e.d_name, ".");
+ break;
+ default:
+ TEST_VERIFY (support_readdir (dir, op, &e));
+ TEST_COMPARE_STRING (e.d_name, ".");
+ TEST_VERIFY (support_readdir (dir, op, &e));
+ TEST_COMPARE_STRING (e.d_name, "..");
+ for (int i = 2; i < directory_entries; ++i)
+ {
+ if (i == rewind_at)
+ break;
+ char *expected = name_at_offset (i);
+ TEST_VERIFY (support_readdir (dir, op, &e));
+ TEST_COMPARE_STRING (e.d_name, expected);
+ free (expected);
+ }
+ break;
+ }
+
+ errno = 0;
+ rewinddir (dir);
+ TEST_COMPARE (errno, 0);
+ verify_directory (dir, op);
+
+ free (e.d_name);
+ xclosedir (dir);
+ support_fuse_unmount (f);
+ }
+}
+
+static int
+do_test (void)
+{
+ support_fuse_init ();
+
+ /* One pass without rewinding to verify that the generated directory
+ content matches expectations. */
+ {
+ struct support_fuse *f = support_fuse_mount (fuse_thread, NULL);
+ DIR *dir = xopendir (support_fuse_mountpoint (f));
+ verify_directory (dir, SUPPORT_READDIR64);
+ xclosedir (dir);
+ support_fuse_unmount (f);
+ }
+
+ for (int do_unbuffered = 0; do_unbuffered < 2; ++do_unbuffered)
+ {
+ one_entry_per_getdents64 = do_unbuffered;
+
+ for (int i = 0; i < 20; ++i)
+ rewind_after (i);
+ rewind_after (50);
+ rewind_after (100);
+ rewind_after (150);
+ rewind_after (180);
+ rewind_after (199);
+ }
+
+ return 0;
+}
+
+#include <support/test-driver.c>

214
glibc-RHEL-111120-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 045c786575a7d5ff..11b772e3abb700eb 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 7ad7e5945bc833c6..c42a161ffcf9fd70 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)
{