summaryrefslogtreecommitdiff
blob: 4e1dc0d8d615a44188287208c65c4eadf554b3c7 (plain)
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
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
From 0718c1ca37fe6407bd4bad15dfae873719eabb6e Mon Sep 17 00:00:00 2001
From: Adhemerval Zanella <adhemerval.zanella@linaro.org>
Date: Tue, 20 Oct 2020 13:37:15 -0300
Subject: [PATCH 05/10] linux: Use getdents64 on non-LFS readdir

The opendir allocates a translation buffer to be used to return the
non-LFS readdir entry.  The obtained dirent64 struct is translated
to the temporary buffer on each readdir call.

Entries that overflow d_off/d_ino and the buffer reallocation failure
(in case of large d_name) are ignored.

Checked on x86_64-linux-gnu and i686-linux-gnu.
---
 sysdeps/unix/sysv/linux/closedir.c  |  4 ++
 sysdeps/unix/sysv/linux/dirstream.h |  5 ++
 sysdeps/unix/sysv/linux/opendir.c   | 21 +++++++
 sysdeps/unix/sysv/linux/readdir.c   | 94 +++++++++++++++++++++--------
 4 files changed, 98 insertions(+), 26 deletions(-)

diff --git a/sysdeps/unix/sysv/linux/closedir.c b/sysdeps/unix/sysv/linux/closedir.c
index eee0193fc4..d876d49d78 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_MATCHES_DIRENT64
+  free (dirp->tbuffer);
+#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 a0d8acf08d..064273cc31 100644
--- a/sysdeps/unix/sysv/linux/dirstream.h
+++ b/sysdeps/unix/sysv/linux/dirstream.h
@@ -41,6 +41,11 @@ struct __dirstream
 
     int errcode;		/* Delayed error code.  */
 
+#if !defined __OFF_T_MATCHES_OFF64_T || !defined __INO_T_MATCHES_INO64_T
+    char *tbuffer;		/* Translation buffer for non-LFS calls.  */
+    size_t tbuffer_size;	/* Size of translation buffer.  */
+#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 9e81d00630..bfd2f382a6 100644
--- a/sysdeps/unix/sysv/linux/opendir.c
+++ b/sysdeps/unix/sysv/linux/opendir.c
@@ -120,6 +120,27 @@ __alloc_dir (int fd, bool close_fd, int flags,
       return NULL;
     }
 
+#if !_DIRENT_MATCHES_DIRENT64
+  /* Allocates a translation buffer to use as the returned 'struct direct'
+     for non-LFS 'readdir' calls.
+
+     The initial NAME_MAX size should handle most cases, while readdir might
+     expand the buffer if required.  */
+  enum
+    {
+      tbuffer_size = sizeof (struct dirent) + NAME_MAX + 1
+    };
+  dirp->tbuffer = malloc (tbuffer_size);
+  if (dirp->tbuffer == NULL)
+    {
+      free (dirp);
+      if (close_fd)
+	__close_nocancel_nostatus (fd);
+      return NULL;
+    }
+  dirp->tbuffer_size = tbuffer_size;
+#endif
+
   dirp->fd = fd;
 #if IS_IN (libc)
   __libc_lock_init (dirp->lock);
diff --git a/sysdeps/unix/sysv/linux/readdir.c b/sysdeps/unix/sysv/linux/readdir.c
index c9a04dc160..c078146d7d 100644
--- a/sysdeps/unix/sysv/linux/readdir.c
+++ b/sysdeps/unix/sysv/linux/readdir.c
@@ -21,42 +21,84 @@
 #if !_DIRENT_MATCHES_DIRENT64
 #include <dirstream.h>
 
+/* Translate the DP64 entry to the non-LFS one in the translation buffer
+   at dirstream DS.  Return true is the translation was possible or
+   false if either an internal fields can be represented in the non-LFS
+   entry or if the translation can not be resized.  */
+static bool
+dirstream_entry (struct __dirstream *ds, const struct dirent64 *dp64)
+{
+  off_t d_off = dp64->d_off;
+  if (d_off != dp64->d_off)
+    return false;
+  ino_t d_ino = dp64->d_ino;
+  if (d_ino != dp64->d_ino)
+    return false;
+
+  /* Expand the translation buffer to hold the new name size.  */
+  size_t new_reclen = sizeof (struct dirent)
+		    + dp64->d_reclen - offsetof (struct dirent64, d_name);
+  if (new_reclen > ds->tbuffer_size)
+    {
+      char *newbuffer = realloc (ds->tbuffer, new_reclen);
+      if (newbuffer == NULL)
+	return false;
+      ds->tbuffer = newbuffer;
+      ds->tbuffer_size = new_reclen;
+    }
+
+  struct dirent *dp = (struct dirent *) ds->tbuffer;
+
+  dp->d_off = d_off;
+  dp->d_ino = d_ino;
+  dp->d_reclen = new_reclen;
+  dp->d_type = dp64->d_type;
+  memcpy (dp->d_name, dp64->d_name,
+	  dp64->d_reclen - offsetof (struct dirent64, d_name));
+
+  return true;
+}
+
 /* Read a directory entry from DIRP.  */
 struct dirent *
 __readdir_unlocked (DIR *dirp)
 {
-  struct dirent *dp;
   int saved_errno = errno;
 
-  if (dirp->offset >= dirp->size)
+  while (1)
     {
-      /* We've emptied out our buffer.  Refill it.  */
-
-      size_t maxread = dirp->allocation;
-      ssize_t bytes;
-
-      bytes = __getdents (dirp->fd, dirp->data, maxread);
-      if (bytes <= 0)
+      if (dirp->offset >= dirp->size)
+	{
+	  /* We've emptied out our buffer.  Refill it.  */
+	  ssize_t bytes = __getdents64 (dirp->fd, dirp->data,
+					dirp->allocation);
+	  if (bytes <= 0)
+	    {
+	      /* Linux may fail with ENOENT on some file systems if the
+		 directory inode is marked as dead (deleted).  POSIX
+		 treats this as a regular end-of-directory condition, so
+		 do not set errno in that case, to indicate success.  */
+	      if (bytes < 0 && errno == ENOENT)
+		__set_errno (saved_errno);
+	      return NULL;
+	    }
+	  dirp->size = bytes;
+
+ 	  /* Reset the offset into the buffer.  */
+	  dirp->offset = 0;
+ 	}
+
+      struct dirent64 *dp64 = (struct dirent64 *) &dirp->data[dirp->offset];
+      dirp->offset += dp64->d_reclen;
+
+      /* Skip entries which might overflow d_off/d_ino or if the translation
+	 buffer can't be resized.  */
+      if (dirstream_entry (dirp, dp64))
 	{
-	  /* Linux may fail with ENOENT on some file systems if the
-	     directory inode is marked as dead (deleted).  POSIX
-	     treats this as a regular end-of-directory condition, so
-	     do not set errno in that case, to indicate success.  */
-	  if (bytes == 0 || errno == ENOENT)
-	    __set_errno (saved_errno);
-	  return NULL;
+          dirp->filepos = dp64->d_off;
+	  return (struct dirent *) dirp->tbuffer;
 	}
-      dirp->size = (size_t) bytes;
-
-      /* Reset the offset into the buffer.  */
-      dirp->offset = 0;
     }
-
-  dp = (struct dirent *) &dirp->data[dirp->offset];
-  dirp->offset += dp->d_reclen;
-  dirp->filepos = dp->d_off;
-
-  return dp;
 }
 
 struct dirent *
-- 
2.38.2