about summary refs log tree commit diff
path: root/sysdeps/unix/sysv/linux/tst-getdents64.c
blob: 3dd22a4e0380561ea2be4a004ffbe9aa6f834291 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
/* Test for reading directories with getdents64.
   Copyright (C) 2019-2024 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 void
do_test_large_size (void)
{
  int fd = xopen (".", O_RDONLY | O_DIRECTORY, 0);
  TEST_VERIFY (fd >= 0);
  large_buffer_checks (fd);

  xclose (fd);
}

static void
do_test_by_size (size_t buffer_size)
{
  /* 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[buffer_size];
            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);
    }

  xclose (fd);
  closedir (reference);
}

static int
do_test (void)
{
  do_test_by_size (512);
  do_test_by_size (1024);
  do_test_by_size (4096);

  do_test_large_size ();

  return 0;
}

#include <support/test-driver.c>