@@ -41,6 +41,14 @@ do_test (void)
if (i == 400)
break;
}
+ if (i != 400)
+ {
+ /* Non-lfs opendir skips entries that can not be represented (for
+ instance if d_off is not an offset but rather an internal filesystem
+ representation. For this case there is no point in continue the
+ testcase. */
+ return 77;
+ }
printf ("going back past 4-th entry...\n");
@@ -465,6 +465,7 @@ ifeq ($(subdir),dirent)
sysdep_routines += getdirentries getdirentries64
tests += \
tst-getdents64 \
+ tst-opendir-nolfs \
tst-readdir64-compat \
# tests
endif # $(subdir) == dirent
@@ -50,6 +50,9 @@ __closedir (DIR *dirp)
#if !_DIRENT_MATCHES_DIRENT64
free (dirp->tbuffer);
#endif
+#ifndef __LP64__
+ dirstream_loc_clear (&dirp->locs);
+#endif
free ((void *) dirp);
@@ -21,6 +21,7 @@
#include <sys/types.h>
#include <libc-lock.h>
+#include <telldir.h>
/* Directory stream type.
@@ -37,7 +38,7 @@ 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. */
@@ -45,6 +46,9 @@ struct __dirstream
char *tbuffer; /* Translation buffer for non-LFS calls. */
size_t tbuffer_size; /* Size of translation buffer. */
#endif
+#ifndef __LP64__
+ struct dirstream_loc_t locs; /* off64_t to long int map for telldir. */
+#endif
/* Directory block. We must make sure that this block starts
at an address that is aligned adequately enough to store
@@ -150,6 +150,9 @@ __alloc_dir (int fd, bool close_fd, int flags,
dirp->offset = 0;
dirp->filepos = 0;
dirp->errcode = 0;
+#ifndef __LP64__
+ dirstream_loc_init (&dirp->locs);
+#endif
return dirp;
}
@@ -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
@@ -22,14 +22,40 @@
#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;
- dirp->offset = 0;
- dirp->filepos = pos;
+
+#ifndef __LP64__
+ 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;
+ }
+ struct dirstream_loc *loc = dirstream_loc_at (&dirp->locs, index);
+ filepos = loc->filepos;
+ }
+#else
+ filepos = pos;
+#endif
+
+ if (dirp->filepos != filepos)
+ {
+ __lseek64 (dirp->fd, filepos, SEEK_SET);
+ dirp->filepos = filepos;
+ dirp->offset = 0;
+ dirp->size = 0;
+ }
+
__libc_lock_unlock (dirp->lock);
}
@@ -18,16 +18,57 @@
#include <dirent.h>
#include <dirstream.h>
+#include <telldir.h>
/* Return the current position of DIRP. */
long int
telldir (DIR *dirp)
{
long int ret;
-
__libc_lock_lock (dirp->lock);
+
+#ifndef __LP64__
+ /* 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 (dirp->filepos < (1U << 31))
+ {
+ dsp.p.is_packed = 1;
+ dsp.p.info = dirp->filepos;
+ }
+ else
+ {
+ dsp.l = -1;
+
+ size_t i;
+ for (i = 0; i < dirstream_loc_size (&dirp->locs); i++)
+ {
+ struct dirstream_loc *loc = dirstream_loc_at (&dirp->locs, i);
+ if (loc->filepos == dirp->filepos)
+ break;
+ }
+ if (i == dirstream_loc_size (&dirp->locs))
+ {
+ dirstream_loc_add (&dirp->locs,
+ (struct dirstream_loc) { dirp->filepos });
+ if (!dirstream_loc_has_failed (&dirp->locs))
+ {
+ dsp.p.is_packed = 0;
+ /* This assignment might overflow, however most likely ENOMEM
+ would happen long before. */
+ dsp.p.info = i;
+ }
+ }
+ }
+
+ ret = dsp.l;
+
+#else
ret = dirp->filepos;
+#endif
__libc_lock_unlock (dirp->lock);
-
return ret;
}
new file mode 100644
@@ -0,0 +1,65 @@
+/* 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
+
+#ifndef __LP64__
+
+/* 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 is_packed:1;
+ unsigned long info:31;
+ } p;
+};
+
+_Static_assert (sizeof (long int) == sizeof (union dirstream_packed),
+ "sizeof (long int) != sizeof (union dirstream_packed)");
+
+/* telldir maintains a list of offsets that describe the obtained diretory
+ position if it can fit this information in the returned 'dirstream_packed'
+ struct. */
+
+struct dirstream_loc
+{
+ off64_t filepos;
+};
+
+# define DYNARRAY_STRUCT dirstream_loc_t
+# define DYNARRAY_ELEMENT struct dirstream_loc
+# define DYNARRAY_PREFIX dirstream_loc_
+# include <malloc/dynarray-skeleton.c>
+#else
+
+_Static_assert (sizeof (long int) == sizeof (off64_t),
+ "sizeof (long int) != sizeof (off64_t)");
+
+#endif /* __LP64__ */
+
+#endif /* _TELLDIR_H */
new file mode 100644
@@ -0,0 +1,146 @@
+/* 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 = xmalloc (nfiles * sizeof (long int));
+ struct dirent **ddirp = xmalloc (nfiles * sizeof (struct dirent *));
+
+ /* For non-LFS, the entry is skipped if it can not be converted. */
+ int count = 0;
+ for (; count < nfiles; count++)
+ {
+ tdirp[count] = telldir (dirp);
+ struct dirent *dp = readdir (dirp);
+ if (dp == NULL)
+ break;
+ 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) && in_off_t_range (entry.d_off))
+ {
+ TEST_COMPARE_STRING (entry.d_name, ddirp[i]->d_name);
+ TEST_COMPARE (entry.d_ino, ddirp[i]->d_ino);
+ TEST_COMPARE (entry.d_off, ddirp[i]->d_off);
+ 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;
+ }
+ }
+
+ /* direntries_read has been called more than once. */
+ 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>