aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--include/dirent.h2
-rw-r--r--sysdeps/unix/sysv/linux/Makefile2
-rw-r--r--sysdeps/unix/sysv/linux/alpha/bits/dirent.h3
-rw-r--r--sysdeps/unix/sysv/linux/bits/dirent.h4
-rw-r--r--sysdeps/unix/sysv/linux/closedir.c4
-rw-r--r--sysdeps/unix/sysv/linux/dirstream.h9
-rw-r--r--sysdeps/unix/sysv/linux/opendir.c3
-rw-r--r--sysdeps/unix/sysv/linux/readdir.c21
-rw-r--r--sysdeps/unix/sysv/linux/readdir64.c11
-rw-r--r--sysdeps/unix/sysv/linux/rewinddir.c5
-rw-r--r--sysdeps/unix/sysv/linux/seekdir.c30
-rw-r--r--sysdeps/unix/sysv/linux/telldir.c36
-rw-r--r--sysdeps/unix/sysv/linux/telldir.h70
-rw-r--r--sysdeps/unix/sysv/linux/tst-opendir-lfs.c2
-rw-r--r--sysdeps/unix/sysv/linux/tst-opendir.c145
15 files changed, 329 insertions, 18 deletions
diff --git a/include/dirent.h b/include/dirent.h
index d7567f5..1782717 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 415aa1f..d5b6e26 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 c8a0cfe..586d755 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 ab34d98..bb02dcb 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 f1c2608..9585a6c 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 3cb313b..27193b5 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 4336196..3e2caab 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 72ba895..9577c2e 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 db1c621..306728b 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 c0fb7aa..1b158a5 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 939ccc4..38b6329 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 1e5c129..1ac4fce 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 0000000..7772129
--- /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 0000000..1de1891
--- /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 0000000..216ecf1
--- /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>