@@ -31,7 +31,7 @@ routines := opendir closedir readdir readdir_r rewinddir \
scandir-cancel scandir-tail scandir64-tail
tests := list tst-seekdir opendir-tst1 bug-readdir1 tst-fdopendir \
- tst-fdopendir2 tst-scandir tst-scandir64
+ tst-fdopendir2 tst-scandir tst-scandir64 tst-seekdir2 \
CFLAGS-scandir.c += $(uses-callbacks)
CFLAGS-scandir64.c += $(uses-callbacks)
new file mode 100644
@@ -0,0 +1,156 @@
+/* Check multiple telldir and seekdir.
+ Copyright (C) 2020 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 <stdlib.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <string.h>
+
+#include <support/temp_file.h>
+#include <support/support.h>
+#include <support/check.h>
+
+/* Some filesystems returns a 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;
+static const size_t nfiles = 10240;
+
+static void
+do_prepare (int argc, char *argv[])
+{
+ dirname = support_create_temp_directory ("tst-seekdir2-");
+
+ for (size_t i = 0; i < nfiles; i++)
+ {
+ int fd = create_temp_file_in_dir ("tempfile.", dirname, NULL);
+ TEST_VERIFY_EXIT (fd > 0);
+ close (fd);
+ }
+}
+#define PREPARE do_prepare
+
+/* Check for old non Large File Support (LFS). */
+static int
+do_test_not_lfs (void)
+{
+ DIR *dirp;
+ struct dirent *dp;
+ size_t dirp_count;
+
+ dirp = opendir (dirname);
+ TEST_VERIFY_EXIT (dirp != NULL);
+
+ dirp_count = 0;
+ for (dp = readdir (dirp);
+ dp != NULL;
+ dp = readdir (dirp))
+ dirp_count++;
+
+ /* The 2 extra files are '.' and '..'. */
+ TEST_COMPARE (dirp_count, nfiles + 2);
+
+ rewinddir (dirp);
+
+ long *tdirp = xmalloc (dirp_count * sizeof (long));
+ struct dirent *ddirp = xmalloc (dirp_count * sizeof (struct dirent));
+
+ size_t i = 0;
+ do
+ {
+ tdirp[i] = telldir (dirp);
+ dp = readdir (dirp);
+ TEST_VERIFY_EXIT (dp != NULL);
+ memcpy (&ddirp[i], dp, sizeof (struct dirent));
+ } while (++i < dirp_count);
+
+ for (i = 0; i < dirp_count - 1; i++)
+ {
+ seekdir (dirp, tdirp[i]);
+ dp = readdir (dirp);
+ TEST_COMPARE (strcmp (dp->d_name, ddirp[i].d_name), 0);
+ TEST_COMPARE (dp->d_ino, ddirp[i].d_ino);
+ TEST_COMPARE (dp->d_off, ddirp[i].d_off);
+ }
+
+ closedir (dirp);
+
+ return 0;
+}
+
+/* Same as before but with LFS support. */
+static int
+do_test_lfs (void)
+{
+ DIR *dirp;
+ struct dirent64 *dp;
+ size_t dirp_count;
+
+ dirp = opendir (dirname);
+ TEST_VERIFY_EXIT (dirp != NULL);
+
+ dirp_count = 0;
+ for (dp = readdir64 (dirp);
+ dp != NULL;
+ dp = readdir64 (dirp))
+ dirp_count++;
+
+ /* The 2 extra files are '.' and '..'. */
+ TEST_COMPARE (dirp_count, nfiles + 2);
+
+ rewinddir (dirp);
+
+ long *tdirp = xmalloc (dirp_count * sizeof (long));
+ struct dirent64 *ddirp = xmalloc (dirp_count * sizeof (struct dirent64));
+
+ size_t i = 0;
+ do
+ {
+ tdirp[i] = telldir (dirp);
+ dp = readdir64 (dirp);
+ TEST_VERIFY_EXIT (dp != NULL);
+ memcpy (&ddirp[i], dp, sizeof (struct dirent64));
+ } while (++i < dirp_count);
+
+ for (i = 0; i < dirp_count - 1; i++)
+ {
+ seekdir (dirp, tdirp[i]);
+ dp = readdir64 (dirp);
+ TEST_COMPARE (strcmp (dp->d_name, ddirp[i].d_name), 0);
+ TEST_COMPARE (dp->d_ino, ddirp[i].d_ino);
+ TEST_COMPARE (dp->d_off, ddirp[i].d_off);
+ }
+
+ closedir (dirp);
+
+ return 0;
+}
+
+static int
+do_test (void)
+{
+ do_test_not_lfs ();
+ do_test_lfs ();
+
+ return 0;
+}
+
+#include <support/test-driver.c>
@@ -60,14 +60,12 @@ add_temp_file (const char *name)
}
int
-create_temp_file (const char *base, char **filename)
+create_temp_file_in_dir (const char *base, const char *dir, char **filename)
{
char *fname;
int fd;
- fname = (char *) xmalloc (strlen (test_dir) + 1 + strlen (base)
- + sizeof ("XXXXXX"));
- strcpy (stpcpy (stpcpy (stpcpy (fname, test_dir), "/"), base), "XXXXXX");
+ fname = xasprintf ("%s/%sXXXXXX", dir, base);
fd = mkstemp (fname);
if (fd == -1)
@@ -86,6 +84,12 @@ create_temp_file (const char *base, char **filename)
return fd;
}
+int
+create_temp_file (const char *base, char **filename)
+{
+ return create_temp_file_in_dir (base, test_dir, filename);
+}
+
char *
support_create_temp_directory (const char *base)
{
@@ -32,6 +32,13 @@ void add_temp_file (const char *name);
*FILENAME. */
int create_temp_file (const char *base, char **filename);
+/* Create a temporary file in directory DIR. Return the opened file
+ descriptor on success, or -1 on failure. Write the file name to
+ *FILENAME if FILENAME is not NULL. In this case, the caller is
+ expected to free *FILENAME. */
+int create_temp_file_in_dir (const char *base, const char *dir,
+ char **filename);
+
/* Create a temporary directory and schedule it for deletion. BASE is
used as a prefix for the unique directory name, which the function
returns. The caller should free this string. */
@@ -43,6 +43,10 @@ __closedir (DIR *dirp)
fd = dirp->fd;
+#ifndef __LP64__
+ dirstream_loc_clear (&dirp->locs);
+#endif
+
#if IS_IN (libc)
__libc_lock_fini (dirp->lock);
#endif
@@ -21,6 +21,7 @@
#include <sys/types.h>
#include <libc-lock.h>
+#include <telldir.h>
/* Directory stream type.
@@ -37,10 +38,14 @@ 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. */
+#ifndef __LP64__
+ 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
@@ -131,6 +131,9 @@ __alloc_dir (int fd, bool close_fd, int flags, const struct stat64 *statp)
dirp->offset = 0;
dirp->filepos = 0;
dirp->errcode = 0;
+#ifndef __LP64__
+ dirstream_loc_init (&dirp->locs);
+#endif
return dirp;
}
@@ -17,6 +17,7 @@
<https://www.gnu.org/licenses/>. */
#include <dirent.h>
+#include <unistd.h>
#if !_DIRENT_MATCHES_DIRENT64
#include <readdir.h>
@@ -85,15 +85,12 @@ dirstream_ret_entry (struct __dirstream *ds)
dp->d_ino = dp64->d_ino;
dp->d_off = dp64->d_off;
- if (dp->d_off != dp64->d_off)
- /* Overflow. */
- return NULL;
const size_t size_diff = (offsetof (struct dirent64, d_name)
- offsetof (struct dirent, d_name));
const size_t alignment = _Alignof (struct dirent);
- size_t new_reclen = (dp64->d_reclen - size_diff + alignment - 1)
- & ~(alignment - 1);
+ size_t new_reclen = (dp64->d_reclen - size_diff + alignment - 1)
+ & ~(alignment - 1);
if (new_reclen > return_buffer_size)
/* Overflow. */
return NULL;
@@ -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;
+
+ 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))
+ 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,59 @@
#include <dirent.h>
#include <dirstream.h>
+#include <telldir.h>
/* Return the current position of DIRP. */
long int
telldir (DIR *dirp)
{
- long int ret;
+#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;
+ size_t i;
__libc_lock_lock (dirp->lock);
- ret = dirp->filepos;
+
+ if (dirp->filepos < (1U << 31))
+ {
+ dsp.p.is_packed = 1;
+ dsp.p.info = dirp->filepos;
+ goto out;
+ }
+
+ dsp.l = -1;
+
+ 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))
+ goto out;
+ }
+
+ dsp.p.is_packed = 0;
+ /* This assignment might overflow, however most likely ENOMEM would happen
+ long before. */
+ dsp.p.info = i;
+
+out:
__libc_lock_unlock (dirp->lock);
+ return dsp.l;
+#else
+ long int ret;
+ __libc_lock_lock (dirp->lock);
+ ret = dirp->filepos;
+ __libc_lock_unlock (dirp->lock);
return ret;
+#endif
}
new file mode 100644
@@ -0,0 +1,64 @@
+/* Linux internal telldir definitions.
+ Copyright (C) 2020 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 will mantain 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 */