summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to '9999/0006-linux-Set-internal-DIR-filepos-as-off64_t-BZ-23960-B.patch')
-rw-r--r--9999/0006-linux-Set-internal-DIR-filepos-as-off64_t-BZ-23960-B.patch574
1 files changed, 574 insertions, 0 deletions
diff --git a/9999/0006-linux-Set-internal-DIR-filepos-as-off64_t-BZ-23960-B.patch b/9999/0006-linux-Set-internal-DIR-filepos-as-off64_t-BZ-23960-B.patch
new file mode 100644
index 0000000..6450910
--- /dev/null
+++ b/9999/0006-linux-Set-internal-DIR-filepos-as-off64_t-BZ-23960-B.patch
@@ -0,0 +1,574 @@
+From 6d5975652503acffae0566500cc15f8efc89e30a Mon Sep 17 00:00:00 2001
+From: Adhemerval Zanella <adhemerval.zanella@linaro.org>
+Date: Mon, 13 Apr 2020 18:09:20 -0300
+Subject: [PATCH 3/3] linux: Set internal DIR filepos as off64_t (BZ #23960, BZ
+ #24050)
+
+It allows to obtain the expected entry offset on telldir and set
+it correctly on seekdir on platforms where long int is smaller
+than off64_t.
+
+On such cases opendir creates a map entry between the DIR d_off
+offset and the returned long int (the telldir return value).
+seekdir will then set the correct offset from the internal list
+using the telldir as the list key.
+
+It also removes the overflow check on readdir and the returned value
+will be truncated by the non-LFS off_t size. As Joseph has noted
+in BZ #23960 comment #22, d_off is an opaque value and since
+telldir/seekdir works regardless of the returned dirent d_off value.
+
+Finally it removes the requirement to check for overflow values on
+telldir (BZ #24050).
+
+Checked on x86_64-linux-gnu, i686-linux-gnu, powerpc-linux-gnu,
+and arm-linux-gnueabihf.
+---
+ include/dirent.h | 2 +-
+ sysdeps/unix/sysv/linux/Makefile | 2 +
+ sysdeps/unix/sysv/linux/alpha/bits/dirent.h | 3 +
+ sysdeps/unix/sysv/linux/bits/dirent.h | 4 +
+ sysdeps/unix/sysv/linux/closedir.c | 4 +
+ sysdeps/unix/sysv/linux/dirstream.h | 9 +-
+ sysdeps/unix/sysv/linux/opendir.c | 3 +
+ sysdeps/unix/sysv/linux/readdir.c | 21 ++-
+ sysdeps/unix/sysv/linux/readdir64.c | 11 ++
+ sysdeps/unix/sysv/linux/rewinddir.c | 5 +
+ sysdeps/unix/sysv/linux/seekdir.c | 30 +++-
+ sysdeps/unix/sysv/linux/telldir.c | 36 +++++
+ sysdeps/unix/sysv/linux/telldir.h | 70 ++++++++++
+ sysdeps/unix/sysv/linux/tst-opendir-lfs.c | 2 +
+ sysdeps/unix/sysv/linux/tst-opendir.c | 145 ++++++++++++++++++++
+ 15 files changed, 329 insertions(+), 18 deletions(-)
+ create mode 100644 sysdeps/unix/sysv/linux/telldir.h
+ create mode 100644 sysdeps/unix/sysv/linux/tst-opendir-lfs.c
+ create mode 100644 sysdeps/unix/sysv/linux/tst-opendir.c
+
+diff --git a/include/dirent.h b/include/dirent.h
+index d7567f5e86..17827176ba 100644
+--- a/include/dirent.h
++++ b/include/dirent.h
+@@ -1,8 +1,8 @@
+ #ifndef _DIRENT_H
++# include <dirent/dirent.h>
+ # ifndef _ISOMAC
+ # include <dirstream.h>
+ # endif
+-# include <dirent/dirent.h>
+ # ifndef _ISOMAC
+ # include <sys/stat.h>
+ # include <stdbool.h>
+diff --git a/sysdeps/unix/sysv/linux/Makefile b/sysdeps/unix/sysv/linux/Makefile
+index 415aa1f14d..d5b6e26e76 100644
+--- a/sysdeps/unix/sysv/linux/Makefile
++++ b/sysdeps/unix/sysv/linux/Makefile
+@@ -574,6 +574,8 @@ sysdep_routines += \
+
+ tests += \
+ tst-getdents64 \
++ tst-opendir \
++ tst-opendir-lfs \
+ tst-readdir64-compat \
+ # tests
+ endif # $(subdir) == dirent
+diff --git a/sysdeps/unix/sysv/linux/alpha/bits/dirent.h b/sysdeps/unix/sysv/linux/alpha/bits/dirent.h
+index b604576188..4e511e7654 100644
+--- a/sysdeps/unix/sysv/linux/alpha/bits/dirent.h
++++ b/sysdeps/unix/sysv/linux/alpha/bits/dirent.h
+@@ -54,4 +54,7 @@ struct dirent64
+ /* Inform libc code that these two types are effectively identical. */
+ #define _DIRENT_MATCHES_DIRENT64 1
+
++/* alpha 'long int' is enough to handle off64_t. */
++#define _DIRENT_OFFSET_TRANSLATION 0
++
+ #endif /* bits/dirent.h */
+diff --git a/sysdeps/unix/sysv/linux/bits/dirent.h b/sysdeps/unix/sysv/linux/bits/dirent.h
+index 8bf38f8188..7c5f3419cd 100644
+--- a/sysdeps/unix/sysv/linux/bits/dirent.h
++++ b/sysdeps/unix/sysv/linux/bits/dirent.h
+@@ -57,3 +57,7 @@ struct dirent64
+ #else
+ # define _DIRENT_MATCHES_DIRENT64 0
+ #endif
++
++/* The telldir function returns long int, which may not be large enough to
++ store off64_t values. In this case, translation is required. */
++#define _DIRENT_OFFSET_TRANSLATION (LONG_WIDTH < 64)
+diff --git a/sysdeps/unix/sysv/linux/closedir.c b/sysdeps/unix/sysv/linux/closedir.c
+index b86f79f133..3a4eba03f1 100644
+--- a/sysdeps/unix/sysv/linux/closedir.c
++++ b/sysdeps/unix/sysv/linux/closedir.c
+@@ -47,6 +47,10 @@ __closedir (DIR *dirp)
+ __libc_lock_fini (dirp->lock);
+ #endif
+
++#if _DIRENT_OFFSET_TRANSLATION
++ dirstream_loc_clear (&dirp->locs);
++#endif
++
+ free ((void *) dirp);
+
+ return __close_nocancel (fd);
+diff --git a/sysdeps/unix/sysv/linux/dirstream.h b/sysdeps/unix/sysv/linux/dirstream.h
+index 3d3643733a..fc2586566a 100644
+--- a/sysdeps/unix/sysv/linux/dirstream.h
++++ b/sysdeps/unix/sysv/linux/dirstream.h
+@@ -21,6 +21,7 @@
+ #include <sys/types.h>
+
+ #include <libc-lock.h>
++#include <telldir.h>
+
+ /* Directory stream type.
+
+@@ -37,10 +38,16 @@ struct __dirstream
+ size_t size; /* Total valid data in the block. */
+ size_t offset; /* Current offset into the block. */
+
+- off_t filepos; /* Position of next entry to read. */
++ off64_t filepos; /* Position of next entry to read. */
+
+ int errcode; /* Delayed error code. */
+
++#if _DIRENT_OFFSET_TRANSLATION
++ /* The array is used to map long to off_64 for telldir/seekdir for ABIs
++ where long can not fully represend a LFS off_t value. */
++ struct dirstream_loc_t locs;
++#endif
++
+ /* Directory block. We must make sure that this block starts
+ at an address that is aligned adequately enough to store
+ dirent entries. Using the alignment of "void *" is not
+diff --git a/sysdeps/unix/sysv/linux/opendir.c b/sysdeps/unix/sysv/linux/opendir.c
+index b5218138ec..fda2c5f9fe 100644
+--- a/sysdeps/unix/sysv/linux/opendir.c
++++ b/sysdeps/unix/sysv/linux/opendir.c
+@@ -129,6 +129,9 @@ __alloc_dir (int fd, bool close_fd, int flags,
+ dirp->offset = 0;
+ dirp->filepos = 0;
+ dirp->errcode = 0;
++#if _DIRENT_OFFSET_TRANSLATION
++ dirstream_loc_init (&dirp->locs);
++#endif
+
+ return dirp;
+ }
+diff --git a/sysdeps/unix/sysv/linux/readdir.c b/sysdeps/unix/sysv/linux/readdir.c
+index 4e2214f21e..d474f70a8d 100644
+--- a/sysdeps/unix/sysv/linux/readdir.c
++++ b/sysdeps/unix/sysv/linux/readdir.c
+@@ -75,25 +75,22 @@ __readdir_unlocked (DIR *dirp)
+ size_t new_reclen = ALIGN_UP (old_reclen - size_diff,
+ _Alignof (struct dirent));
+
+- if (!in_ino_t_range (inp->dp64.d_ino)
+- || !in_off_t_range (inp->dp64.d_off))
++ /* telldir can not return an error, so preallocate a map entry if
++ d_off can not be used directly. */
++ if (telldir_need_dirstream (inp->dp64.d_off))
+ {
+- /* Overflow. If there was at least one entry before this one,
+- return them without error, otherwise signal overflow. */
+- if (dirp->offset != 0)
+- {
+- __lseek64 (dirp->fd, dirp->offset, SEEK_SET);
+- outp = (void*)(outp->b - dirp->data);
+- return &outp->dp;
+- }
+- __set_errno (EOVERFLOW);
+- return NULL;
++ dirstream_loc_add (&dirp->locs, inp->dp64.d_off);
++ if (dirstream_loc_has_failed (&dirp->locs))
++ return NULL;
+ }
+
+ /* Copy the data from INP and access only OUTP. */
+ const uint64_t d_ino = inp->dp64.d_ino;
+ const int64_t d_off = inp->dp64.d_off;
+ const uint8_t d_type = inp->dp64.d_type;
++ /* This will clamp both d_off and d_ino values, which is required to
++ avoid return EOVERFLOW. The lelldir/seekdir uses the 'locs' value
++ if the value overflows. */
+ outp->dp.d_ino = d_ino;
+ outp->dp.d_off = d_off;
+ outp->dp.d_reclen = new_reclen;
+diff --git a/sysdeps/unix/sysv/linux/readdir64.c b/sysdeps/unix/sysv/linux/readdir64.c
+index e6f5108c0a..d61c5e6e26 100644
+--- a/sysdeps/unix/sysv/linux/readdir64.c
++++ b/sysdeps/unix/sysv/linux/readdir64.c
+@@ -68,6 +68,17 @@ __readdir64 (DIR *dirp)
+ dirp->offset += dp->d_reclen;
+ dirp->filepos = dp->d_off;
+
++#if _DIRENT_OFFSET_TRANSLATION
++ /* telldir can not return an error, so preallocate a map entry if
++ d_off can not be used directly. */
++ if (telldir_need_dirstream (dp->d_off))
++ {
++ dirstream_loc_add (&dirp->locs, dp->d_off);
++ if (dirstream_loc_has_failed (&dirp->locs))
++ dp = NULL;
++ }
++#endif
++
+ #if IS_IN (libc)
+ __libc_lock_unlock (dirp->lock);
+ #endif
+diff --git a/sysdeps/unix/sysv/linux/rewinddir.c b/sysdeps/unix/sysv/linux/rewinddir.c
+index af85000a28..4936be5c6a 100644
+--- a/sysdeps/unix/sysv/linux/rewinddir.c
++++ b/sysdeps/unix/sysv/linux/rewinddir.c
+@@ -33,6 +33,11 @@ __rewinddir (DIR *dirp)
+ dirp->offset = 0;
+ dirp->size = 0;
+ dirp->errcode = 0;
++
++#ifndef __LP64__
++ dirstream_loc_clear (&dirp->locs);
++#endif
++
+ #if IS_IN (libc)
+ __libc_lock_unlock (dirp->lock);
+ #endif
+diff --git a/sysdeps/unix/sysv/linux/seekdir.c b/sysdeps/unix/sysv/linux/seekdir.c
+index 52005878fc..cd49676750 100644
+--- a/sysdeps/unix/sysv/linux/seekdir.c
++++ b/sysdeps/unix/sysv/linux/seekdir.c
+@@ -22,14 +22,36 @@
+ #include <dirstream.h>
+
+ /* Seek to position POS in DIRP. */
+-/* XXX should be __seekdir ? */
+ void
+ seekdir (DIR *dirp, long int pos)
+ {
++ off64_t filepos;
++
+ __libc_lock_lock (dirp->lock);
+- (void) __lseek (dirp->fd, pos, SEEK_SET);
+- dirp->size = 0;
++
++#if _DIRENT_OFFSET_TRANSLATION
++ union dirstream_packed dsp = { .l = pos };
++ if (dsp.p.is_packed == 1)
++ filepos = dsp.p.info;
++ else
++ {
++ size_t index = dsp.p.info;
++
++ if (index >= dirstream_loc_size (&dirp->locs))
++ {
++ __libc_lock_unlock (dirp->lock);
++ return;
++ }
++ filepos = *dirstream_loc_at (&dirp->locs, index);
++ }
++#else
++ filepos = pos;
++#endif
++
++ __lseek64 (dirp->fd, filepos, SEEK_SET);
++ dirp->filepos = filepos;
+ dirp->offset = 0;
+- dirp->filepos = pos;
++ dirp->size = 0;
++
+ __libc_lock_unlock (dirp->lock);
+ }
+diff --git a/sysdeps/unix/sysv/linux/telldir.c b/sysdeps/unix/sysv/linux/telldir.c
+index c8dff30719..8d9e2c9db2 100644
+--- a/sysdeps/unix/sysv/linux/telldir.c
++++ b/sysdeps/unix/sysv/linux/telldir.c
+@@ -15,9 +15,12 @@
+ License along with the GNU C Library; if not, see
+ <https://www.gnu.org/licenses/>. */
+
++#include <stdio.h>
++#include <assert.h>
+ #include <dirent.h>
+
+ #include <dirstream.h>
++#include <telldir.h>
+
+ /* Return the current position of DIRP. */
+ long int
+@@ -26,7 +29,40 @@ telldir (DIR *dirp)
+ long int ret;
+
+ __libc_lock_lock (dirp->lock);
++
++#if _DIRENT_OFFSET_TRANSLATION
++ /* If the directory position fits in the packet structure, returns it.
++ Otherwise, check if the position is already been recorded in the
++ dynamic array. If not, add the new record. */
++
++ union dirstream_packed dsp;
++
++ if (!telldir_need_dirstream (dirp->filepos))
++ {
++ dsp.p.is_packed = 1;
++ dsp.p.info = dirp->filepos;
++ }
++ else
++ {
++ dsp.l = -1;
++
++ size_t i;
++ for (i = 0; ;i++)
++ {
++ /* It should be pre-allocated on readdir. */
++ assert (i < dirstream_loc_size (&dirp->locs));
++ if (*dirstream_loc_at (&dirp->locs, i) == dirp->filepos)
++ break;
++ }
++
++ dsp.p.is_packed = 0;
++ dsp.p.info = i;
++ }
++
++ ret = dsp.l;
++#else
+ ret = dirp->filepos;
++#endif
+ __libc_lock_unlock (dirp->lock);
+
+ return ret;
+diff --git a/sysdeps/unix/sysv/linux/telldir.h b/sysdeps/unix/sysv/linux/telldir.h
+new file mode 100644
+index 0000000000..7772129db0
+--- /dev/null
++++ b/sysdeps/unix/sysv/linux/telldir.h
+@@ -0,0 +1,70 @@
++/* Linux internal telldir definitions.
++ 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/>. */
++
++#ifndef _TELLDIR_H
++#define _TELLDIR_H 1
++
++#include <dirent.h>
++
++#if _DIRENT_OFFSET_TRANSLATION
++
++_Static_assert (sizeof (long int) < sizeof (__off64_t),
++ "sizeof (long int) >= sizeof (__off64_t)");
++
++# include <intprops.h>
++
++/* On platforms where 'long int' is smaller than 'off64_t' this is how the
++ returned value is encoded and returned by 'telldir'. If the directory
++ offset can be enconded in 31 bits it is returned in the 'info' member
++ with 'is_packed' set to 1.
++
++ Otherwise, the 'info' member describes an index in a dynamic array at
++ 'DIR' structure. */
++
++union dirstream_packed
++{
++ long int l;
++ struct
++ {
++ unsigned long int is_packed:1;
++ unsigned long int info:31;
++ } p;
++};
++
++/* telldir maintains a list of offsets that describe the obtained diretory
++ position if it can fit this information in the returned 'dirstream_packed'
++ struct. */
++
++# define DYNARRAY_STRUCT dirstream_loc_t
++# define DYNARRAY_ELEMENT off64_t
++# define DYNARRAY_PREFIX dirstream_loc_
++# include <malloc/dynarray-skeleton.c>
++
++static __always_inline bool
++telldir_need_dirstream (__off64_t d_off)
++{
++ return ! (TYPE_MINIMUM (off_t) <= d_off && d_off <= TYPE_MAXIMUM (off_t));
++}
++#else
++
++_Static_assert (sizeof (long int) == sizeof (off64_t),
++ "sizeof (long int) != sizeof (off64_t)");
++
++#endif /* __LP64__ */
++
++#endif /* _TELLDIR_H */
+diff --git a/sysdeps/unix/sysv/linux/tst-opendir-lfs.c b/sysdeps/unix/sysv/linux/tst-opendir-lfs.c
+new file mode 100644
+index 0000000000..1de1891fb4
+--- /dev/null
++++ b/sysdeps/unix/sysv/linux/tst-opendir-lfs.c
+@@ -0,0 +1,2 @@
++#define _FILE_OFFSET_BITS 64
++#include "tst-opendir.c"
+diff --git a/sysdeps/unix/sysv/linux/tst-opendir.c b/sysdeps/unix/sysv/linux/tst-opendir.c
+new file mode 100644
+index 0000000000..216ecf123f
+--- /dev/null
++++ b/sysdeps/unix/sysv/linux/tst-opendir.c
+@@ -0,0 +1,145 @@
++/* Check multiple telldir and seekdir.
++ 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 <dirent.h>
++#include <fcntl.h>
++#include <stdio.h>
++#include <stdlib.h>
++#include <string.h>
++#include <unistd.h>
++
++#include <support/check.h>
++#include <support/support.h>
++#include <support/temp_file.h>
++#include <support/xunistd.h>
++
++/* Some filesystems returns an arbitrary value for d_off direnty entry (ext4
++ for instance, where the value is an internal hash key). The idea of create
++ a large number of file is to try trigger a overflow d_off value in a entry
++ to check if telldir/seekdir does work corretly in such case. */
++static const char *dirname;
++/* The 2 extra files are '.' and '..'. */
++static const size_t nfiles = (1<<14) + 2;
++
++static inline bool
++in_ino_t_range (ino64_t v)
++{
++ ino_t s = v;
++ return s == v;
++}
++
++static inline bool
++in_off_t_range (off64_t v)
++{
++ off_t s = v;
++ return s == v;
++}
++
++static void
++do_prepare (int argc, char *argv[])
++{
++ dirname = support_create_temp_directory ("tst-opendir-nolfs-");
++
++ for (size_t i = 0; i < nfiles - 2; i++)
++ {
++ int fd = create_temp_file_in_dir ("tempfile.", dirname, NULL);
++ TEST_VERIFY_EXIT (fd > 0);
++ close (fd);
++ }
++}
++#define PREPARE do_prepare
++
++static int
++do_test (void)
++{
++ DIR *dirp = opendir (dirname);
++ TEST_VERIFY_EXIT (dirp != NULL);
++
++ long int *tdirp = xreallocarray (NULL, nfiles, sizeof (long int));
++ struct dirent **ddirp = xreallocarray (NULL, nfiles,
++ sizeof (struct dirent *));
++
++ /* For non-LFS, the entry is skipped if it can not be converted. */
++ int count = 0;
++ for (; count < nfiles; count++)
++ {
++ struct dirent *dp = readdir (dirp);
++ if (dp == NULL)
++ break;
++ tdirp[count] = telldir (dirp);
++ ddirp[count] = xmalloc (dp->d_reclen);
++ memcpy (ddirp[count], dp, dp->d_reclen);
++ }
++
++ closedir (dirp);
++
++ /* Check against the getdents64 syscall. */
++ int fd = xopen (dirname, O_RDONLY | O_DIRECTORY, 0);
++ int i = 0;
++ while (true)
++ {
++ struct
++ {
++ char buffer[1024];
++ struct dirent64 pad;
++ } data;
++
++ ssize_t ret = getdents64 (fd, &data.buffer, sizeof (data.buffer));
++ if (ret < 0)
++ FAIL_EXIT1 ("getdents64: %m");
++ if (ret == 0)
++ break;
++
++ char *current = data.buffer;
++ char *end = data.buffer + ret;
++ while (current != end)
++ {
++ struct dirent64 entry;
++ memcpy (&entry, current, sizeof (entry));
++ /* Truncate overlong strings. */
++ entry.d_name[sizeof (entry.d_name) - 1] = '\0';
++ TEST_VERIFY (strlen (entry.d_name) < sizeof (entry.d_name) - 1);
++
++ if (in_ino_t_range (entry.d_ino))
++ {
++ TEST_COMPARE_STRING (entry.d_name, ddirp[i]->d_name);
++ TEST_COMPARE (entry.d_ino, ddirp[i]->d_ino);
++ TEST_COMPARE (entry.d_type, ddirp[i]->d_type);
++
++ /* Offset zero is reserved for the first entry. */
++ TEST_VERIFY (entry.d_off != 0);
++
++ TEST_VERIFY_EXIT (entry.d_reclen <= end - current);
++ i++;
++ }
++
++ current += entry.d_reclen;
++ }
++ }
++
++ TEST_COMPARE (count, i);
++
++ free (tdirp);
++ for (int i = 0; i < count; i++)
++ free (ddirp[i]);
++ free (ddirp);
++
++ return 0;
++}
++
++#include <support/test-driver.c>
+--
+2.41.0
+