diff options
Diffstat (limited to 'sysdeps/unix/sysv/linux/tst-getdents64.c')
-rw-r--r-- | sysdeps/unix/sysv/linux/tst-getdents64.c | 163 |
1 files changed, 163 insertions, 0 deletions
diff --git a/sysdeps/unix/sysv/linux/tst-getdents64.c b/sysdeps/unix/sysv/linux/tst-getdents64.c new file mode 100644 index 0000000000..7e97191716 --- /dev/null +++ b/sysdeps/unix/sysv/linux/tst-getdents64.c @@ -0,0 +1,163 @@ +/* Test for reading directories with getdents64. + Copyright (C) 2019 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 <fcntl.h> +#include <limits.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <support/check.h> +#include <support/support.h> +#include <support/xunistd.h> +#include <sys/mman.h> +#include <unistd.h> + +/* Called by large_buffer_checks below. */ +static void +large_buffer_check (int fd, char *large_buffer, size_t large_buffer_size) +{ + xlseek (fd, 0, SEEK_SET); + ssize_t ret = getdents64 (fd, large_buffer, large_buffer_size); + if (ret < 0) + FAIL_EXIT1 ("getdents64 for buffer of %zu bytes failed: %m", + large_buffer_size); + if (ret < offsetof (struct dirent64, d_name)) + FAIL_EXIT1 ("getdents64 for buffer of %zu returned small value %zd", + large_buffer_size, ret); +} + +/* Bug 24740: Make sure that the system call argument is adjusted + properly for the int type. A large value should stay a large + value, and not wrap around to something small, causing the system + call to fail with EINVAL. */ +static void +large_buffer_checks (int fd) +{ + size_t large_buffer_size; + if (!__builtin_add_overflow (UINT_MAX, 2, &large_buffer_size)) + { + int flags = MAP_ANONYMOUS | MAP_PRIVATE; +#ifdef MAP_NORESERVE + flags |= MAP_NORESERVE; +#endif + void *large_buffer = mmap (NULL, large_buffer_size, + PROT_READ | PROT_WRITE, flags, -1, 0); + if (large_buffer == MAP_FAILED) + printf ("warning: could not allocate %zu bytes of memory," + " subtests skipped\n", large_buffer_size); + else + { + large_buffer_check (fd, large_buffer, INT_MAX); + large_buffer_check (fd, large_buffer, (size_t) INT_MAX + 1); + large_buffer_check (fd, large_buffer, (size_t) INT_MAX + 2); + large_buffer_check (fd, large_buffer, UINT_MAX); + large_buffer_check (fd, large_buffer, (size_t) UINT_MAX + 1); + large_buffer_check (fd, large_buffer, (size_t) UINT_MAX + 2); + xmunmap (large_buffer, large_buffer_size); + } + } +} + +static int +do_test (void) +{ + /* The test compares the iteration order with readdir64. */ + DIR *reference = opendir ("."); + TEST_VERIFY_EXIT (reference != NULL); + + int fd = xopen (".", O_RDONLY | O_DIRECTORY, 0); + TEST_VERIFY (fd >= 0); + + /* Perform two passes, with a rewind operating between passes. */ + for (int pass = 0; pass < 2; ++pass) + { + /* Check that we need to fill the buffer multiple times. */ + int read_count = 0; + + while (true) + { + /* Simple way to make sure that the memcpy below does not read + non-existing data. */ + 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; + ++read_count; + + 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); + + errno = 0; + struct dirent64 *refentry = readdir64 (reference); + if (refentry == NULL && errno == 0) + FAIL_EXIT1 ("readdir64 failed too early, at: %s", + entry.d_name); + else if (refentry == NULL) + FAIL_EXIT1 ("readdir64: %m"); + + TEST_COMPARE_STRING (entry.d_name, refentry->d_name); + TEST_COMPARE (entry.d_ino, refentry->d_ino); + TEST_COMPARE (entry.d_off, refentry->d_off); + TEST_COMPARE (entry.d_type, refentry->d_type); + + /* Offset zero is reserved for the first entry. */ + TEST_VERIFY (entry.d_off != 0); + + TEST_VERIFY_EXIT (entry.d_reclen <= end - current); + current += entry.d_reclen; + } + } + + /* We expect to have reached the end of the stream. */ + errno = 0; + TEST_VERIFY (readdir64 (reference) == NULL); + TEST_COMPARE (errno, 0); + + /* direntries_read has been called more than once. */ + TEST_VERIFY (read_count > 0); + + /* Rewind both directory streams. */ + xlseek (fd, 0, SEEK_SET); + rewinddir (reference); + } + + large_buffer_checks (fd); + + xclose (fd); + closedir (reference); + return 0; +} + +#include <support/test-driver.c> |