]>
Commit | Line | Data |
---|---|---|
a8283983 TT |
1 | /* |
2 | * readdir accelerator | |
3 | * | |
0b64f50f TT |
4 | * (C) Copyright 2003, 2004, 2008 by Theodore Ts'o. |
5 | * | |
6 | * 2008-06-08 Modified by Ross Boylan <RossBoylan stanfordalumni org> | |
7 | * Added support for readdir_r and readdir64_r calls. Note | |
8 | * this has not been tested on anything other than GNU/Linux i386, | |
9 | * and that the regular readdir wrapper will take slightly more | |
10 | * space than Ted's original since it now includes a lock. | |
a8283983 TT |
11 | * |
12 | * Compile using the command: | |
13 | * | |
0b64f50f | 14 | * gcc -o spd_readdir.so -shared -fpic spd_readdir.c -ldl |
a8283983 TT |
15 | * |
16 | * Use it by setting the LD_PRELOAD environment variable: | |
17 | * | |
18 | * export LD_PRELOAD=/usr/local/sbin/spd_readdir.so | |
19 | * | |
a8283983 TT |
20 | * %Begin-Header% |
21 | * This file may be redistributed under the terms of the GNU Public | |
22 | * License. | |
23 | * %End-Header% | |
24 | * | |
25 | */ | |
26 | ||
27 | #define ALLOC_STEPSIZE 100 | |
28 | #define MAX_DIRSIZE 0 | |
29 | ||
30 | #define DEBUG | |
635c7a57 TT |
31 | /* Util we autoconfiscate spd_readdir... */ |
32 | #define HAVE___SECURE_GETENV 1 | |
33 | #define HAVE_PRCTL 1 | |
34 | #define HAVE_SYS_PRCTL_H 1 | |
a8283983 TT |
35 | |
36 | #ifdef DEBUG | |
37 | #define DEBUG_DIR(x) {if (do_debug) { x; }} | |
38 | #else | |
39 | #define DEBUG_DIR(x) | |
40 | #endif | |
41 | ||
42 | #define _GNU_SOURCE | |
43 | #define __USE_LARGEFILE64 | |
44 | ||
45 | #include <stdio.h> | |
46 | #include <unistd.h> | |
47 | #include <sys/types.h> | |
48 | #include <sys/stat.h> | |
49 | #include <stdlib.h> | |
50 | #include <string.h> | |
51 | #include <dirent.h> | |
52 | #include <errno.h> | |
53 | #include <dlfcn.h> | |
635c7a57 TT |
54 | #ifdef HAVE_SYS_PRCTL_H |
55 | #include <sys/prctl.h> | |
56 | #else | |
57 | #define PR_GET_DUMPABLE 3 | |
58 | #endif | |
0b64f50f | 59 | #include <pthread.h> |
a8283983 TT |
60 | |
61 | struct dirent_s { | |
62 | unsigned long long d_ino; | |
63 | long long d_off; | |
64 | unsigned short int d_reclen; | |
65 | unsigned char d_type; | |
66 | char *d_name; | |
67 | }; | |
68 | ||
69 | struct dir_s { | |
70 | DIR *dir; | |
0b64f50f | 71 | pthread_mutex_t lock; /* Mutex lock for this structure. */ |
a8283983 TT |
72 | int num; |
73 | int max; | |
74 | struct dirent_s *dp; | |
75 | int pos; | |
76 | int direct; | |
77 | struct dirent ret_dir; | |
78 | struct dirent64 ret_dir64; | |
79 | }; | |
80 | ||
81 | static int (*real_closedir)(DIR *dir) = 0; | |
82 | static DIR *(*real_opendir)(const char *name) = 0; | |
83 | static DIR *(*real_fdopendir)(int fd) = 0; | |
84 | static void *(*real_rewinddir)(DIR *dirp) = 0; | |
85 | static struct dirent *(*real_readdir)(DIR *dir) = 0; | |
86 | static int (*real_readdir_r)(DIR *dir, struct dirent *entry, | |
87 | struct dirent **result) = 0; | |
88 | static struct dirent64 *(*real_readdir64)(DIR *dir) = 0; | |
0b64f50f TT |
89 | static int (*real_readdir64_r)(DIR *dir, struct dirent64 *entry, |
90 | struct dirent64 **result) = 0; | |
a8283983 TT |
91 | static off_t (*real_telldir)(DIR *dir) = 0; |
92 | static void (*real_seekdir)(DIR *dir, off_t offset) = 0; | |
93 | static int (*real_dirfd)(DIR *dir) = 0; | |
94 | static unsigned long max_dirsize = MAX_DIRSIZE; | |
95 | static int num_open = 0; | |
96 | #ifdef DEBUG | |
97 | static int do_debug = 0; | |
98 | #endif | |
99 | ||
635c7a57 TT |
100 | static char *safe_getenv(const char *arg) |
101 | { | |
102 | if ((getuid() != geteuid()) || (getgid() != getegid())) | |
103 | return NULL; | |
104 | #if HAVE_PRCTL | |
105 | if (prctl(PR_GET_DUMPABLE, 0, 0, 0, 0) == 0) | |
106 | return NULL; | |
107 | #else | |
108 | #if (defined(linux) && defined(SYS_prctl)) | |
109 | if (syscall(SYS_prctl, PR_GET_DUMPABLE, 0, 0, 0, 0) == 0) | |
110 | return NULL; | |
111 | #endif | |
112 | #endif | |
113 | ||
114 | #if HAVE___SECURE_GETENV | |
115 | return __secure_getenv(arg); | |
116 | #else | |
117 | return getenv(arg); | |
118 | #endif | |
119 | } | |
120 | ||
a8283983 TT |
121 | static void setup_ptr() |
122 | { | |
123 | char *cp; | |
124 | ||
125 | real_opendir = dlsym(RTLD_NEXT, "opendir"); | |
126 | real_fdopendir = dlsym(RTLD_NEXT, "fdopendir"); | |
127 | real_closedir = dlsym(RTLD_NEXT, "closedir"); | |
128 | real_rewinddir = dlsym(RTLD_NEXT, "rewinddir"); | |
129 | real_readdir = dlsym(RTLD_NEXT, "readdir"); | |
130 | real_readdir_r = dlsym(RTLD_NEXT, "readdir_r"); | |
131 | real_readdir64 = dlsym(RTLD_NEXT, "readdir64"); | |
0b64f50f | 132 | real_readdir64_r = dlsym(RTLD_NEXT, "readdir64_r"); |
a8283983 TT |
133 | real_telldir = dlsym(RTLD_NEXT, "telldir"); |
134 | real_seekdir = dlsym(RTLD_NEXT, "seekdir"); | |
135 | real_dirfd = dlsym(RTLD_NEXT, "dirfd"); | |
635c7a57 | 136 | if ((cp = safe_getenv("SPD_READDIR_MAX_SIZE")) != NULL) { |
a8283983 TT |
137 | max_dirsize = atol(cp); |
138 | } | |
139 | #ifdef DEBUG | |
635c7a57 | 140 | if (safe_getenv("SPD_READDIR_DEBUG")) { |
a8283983 TT |
141 | printf("initialized!\n"); |
142 | do_debug++; | |
143 | } | |
144 | #endif | |
145 | } | |
146 | ||
147 | static void free_cached_dir(struct dir_s *dirstruct) | |
148 | { | |
149 | int i; | |
150 | ||
0b64f50f TT |
151 | pthread_mutex_destroy(&(dirstruct->lock)); |
152 | ||
a8283983 TT |
153 | if (!dirstruct->dp) |
154 | return; | |
155 | ||
156 | for (i=0; i < dirstruct->num; i++) { | |
157 | free(dirstruct->dp[i].d_name); | |
158 | } | |
159 | free(dirstruct->dp); | |
160 | dirstruct->dp = 0; | |
161 | dirstruct->max = dirstruct->num = 0; | |
162 | } | |
163 | ||
164 | static int ino_cmp(const void *a, const void *b) | |
165 | { | |
166 | const struct dirent_s *ds_a = (const struct dirent_s *) a; | |
167 | const struct dirent_s *ds_b = (const struct dirent_s *) b; | |
168 | ino_t i_a, i_b; | |
169 | ||
170 | i_a = ds_a->d_ino; | |
171 | i_b = ds_b->d_ino; | |
172 | ||
173 | if (ds_a->d_name[0] == '.') { | |
174 | if (ds_a->d_name[1] == 0) | |
175 | i_a = 0; | |
176 | else if ((ds_a->d_name[1] == '.') && (ds_a->d_name[2] == 0)) | |
177 | i_a = 1; | |
178 | } | |
179 | if (ds_b->d_name[0] == '.') { | |
180 | if (ds_b->d_name[1] == 0) | |
181 | i_b = 0; | |
182 | else if ((ds_b->d_name[1] == '.') && (ds_b->d_name[2] == 0)) | |
183 | i_b = 1; | |
184 | } | |
185 | ||
186 | return (i_a - i_b); | |
187 | } | |
188 | ||
c64929d9 | 189 | static struct dir_s *alloc_dirstruct(DIR *dir) |
a8283983 TT |
190 | { |
191 | struct dir_s *dirstruct; | |
0b64f50f TT |
192 | static pthread_mutexattr_t mutexattr; |
193 | mutexattr.__align = PTHREAD_MUTEX_RECURSIVE; | |
a8283983 TT |
194 | |
195 | dirstruct = malloc(sizeof(struct dir_s)); | |
196 | if (dirstruct) | |
197 | memset(dirstruct, 0, sizeof(struct dir_s)); | |
198 | dirstruct->dir = dir; | |
0b64f50f | 199 | pthread_mutex_init(&(dirstruct->lock), &mutexattr); |
a8283983 TT |
200 | return dirstruct; |
201 | } | |
202 | ||
c64929d9 | 203 | static void cache_dirstruct(struct dir_s *dirstruct) |
a8283983 TT |
204 | { |
205 | struct dirent_s *ds, *dnew; | |
206 | struct dirent64 *d; | |
207 | ||
208 | while ((d = (*real_readdir64)(dirstruct->dir)) != NULL) { | |
209 | if (dirstruct->num >= dirstruct->max) { | |
210 | dirstruct->max += ALLOC_STEPSIZE; | |
211 | DEBUG_DIR(printf("Reallocating to size %d\n", | |
212 | dirstruct->max)); | |
213 | dnew = realloc(dirstruct->dp, | |
214 | dirstruct->max * sizeof(struct dir_s)); | |
215 | if (!dnew) | |
216 | goto nomem; | |
217 | dirstruct->dp = dnew; | |
218 | } | |
219 | ds = &dirstruct->dp[dirstruct->num++]; | |
220 | ds->d_ino = d->d_ino; | |
221 | ds->d_off = d->d_off; | |
222 | ds->d_reclen = d->d_reclen; | |
223 | ds->d_type = d->d_type; | |
224 | if ((ds->d_name = malloc(strlen(d->d_name)+1)) == NULL) { | |
225 | dirstruct->num--; | |
226 | goto nomem; | |
227 | } | |
228 | strcpy(ds->d_name, d->d_name); | |
229 | DEBUG_DIR(printf("readdir: %lu %s\n", | |
230 | (unsigned long) d->d_ino, d->d_name)); | |
231 | } | |
232 | qsort(dirstruct->dp, dirstruct->num, sizeof(struct dirent_s), ino_cmp); | |
233 | return; | |
234 | nomem: | |
235 | DEBUG_DIR(printf("No memory, backing off to direct readdir\n")); | |
236 | free_cached_dir(dirstruct); | |
237 | dirstruct->direct = 1; | |
238 | } | |
239 | ||
240 | DIR *opendir(const char *name) | |
241 | { | |
242 | DIR *dir; | |
243 | struct dir_s *dirstruct; | |
244 | struct stat st; | |
245 | ||
246 | if (!real_opendir) | |
247 | setup_ptr(); | |
248 | ||
249 | DEBUG_DIR(printf("Opendir(%s) (%d open)\n", name, num_open++)); | |
250 | dir = (*real_opendir)(name); | |
251 | if (!dir) | |
252 | return NULL; | |
253 | ||
254 | dirstruct = alloc_dirstruct(dir); | |
255 | if (!dirstruct) { | |
256 | (*real_closedir)(dir); | |
257 | errno = -ENOMEM; | |
258 | return NULL; | |
259 | } | |
260 | ||
261 | if (max_dirsize && (stat(name, &st) == 0) && | |
262 | (st.st_size > max_dirsize)) { | |
263 | DEBUG_DIR(printf("Directory size %ld, using direct readdir\n", | |
264 | st.st_size)); | |
265 | dirstruct->direct = 1; | |
266 | return (DIR *) dirstruct; | |
267 | } | |
268 | ||
269 | cache_dirstruct(dirstruct); | |
270 | return ((DIR *) dirstruct); | |
271 | } | |
272 | ||
273 | DIR *fdopendir(int fd) | |
274 | { | |
275 | DIR *dir; | |
276 | struct dir_s *dirstruct; | |
277 | struct stat st; | |
278 | ||
279 | if (!real_fdopendir) | |
280 | setup_ptr(); | |
281 | ||
0b64f50f | 282 | DEBUG_DIR(printf("fdopendir(%d) (%d open)\n", fd, num_open++)); |
a8283983 TT |
283 | dir = (*real_fdopendir)(fd); |
284 | if (!dir) | |
285 | return NULL; | |
286 | ||
287 | dirstruct = alloc_dirstruct(dir); | |
288 | if (!dirstruct) { | |
289 | (*real_closedir)(dir); | |
290 | errno = -ENOMEM; | |
291 | return NULL; | |
292 | } | |
293 | ||
294 | if (max_dirsize && (fstat(fd, &st) == 0) && | |
295 | (st.st_size > max_dirsize)) { | |
296 | DEBUG_DIR(printf("Directory size %ld, using direct readdir\n", | |
297 | st.st_size)); | |
298 | dirstruct->dir = dir; | |
299 | dirstruct->direct = 1; | |
300 | return (DIR *) dirstruct; | |
301 | } | |
302 | ||
303 | cache_dirstruct(dirstruct); | |
304 | return ((DIR *) dirstruct); | |
305 | } | |
306 | ||
307 | int closedir(DIR *dir) | |
308 | { | |
309 | struct dir_s *dirstruct = (struct dir_s *) dir; | |
310 | ||
311 | DEBUG_DIR(printf("Closedir (%d open)\n", --num_open)); | |
312 | if (dirstruct->dir) | |
313 | (*real_closedir)(dirstruct->dir); | |
314 | ||
315 | free_cached_dir(dirstruct); | |
316 | free(dirstruct); | |
317 | return 0; | |
318 | } | |
319 | ||
320 | struct dirent *readdir(DIR *dir) | |
321 | { | |
322 | struct dir_s *dirstruct = (struct dir_s *) dir; | |
323 | struct dirent_s *ds; | |
324 | ||
325 | if (dirstruct->direct) | |
326 | return (*real_readdir)(dirstruct->dir); | |
327 | ||
328 | if (dirstruct->pos >= dirstruct->num) | |
329 | return NULL; | |
330 | ||
331 | ds = &dirstruct->dp[dirstruct->pos++]; | |
332 | dirstruct->ret_dir.d_ino = ds->d_ino; | |
333 | dirstruct->ret_dir.d_off = ds->d_off; | |
334 | dirstruct->ret_dir.d_reclen = ds->d_reclen; | |
335 | dirstruct->ret_dir.d_type = ds->d_type; | |
336 | strncpy(dirstruct->ret_dir.d_name, ds->d_name, | |
337 | sizeof(dirstruct->ret_dir.d_name)); | |
338 | ||
339 | return (&dirstruct->ret_dir); | |
340 | } | |
341 | ||
342 | int readdir_r(DIR *dir, struct dirent *entry, struct dirent **result) | |
343 | { | |
344 | struct dir_s *dirstruct = (struct dir_s *) dir; | |
345 | struct dirent_s *ds; | |
346 | ||
347 | if (dirstruct->direct) | |
348 | return (*real_readdir_r)(dirstruct->dir, entry, result); | |
349 | ||
0b64f50f | 350 | pthread_mutex_lock(&(dirstruct->lock)); |
a8283983 TT |
351 | if (dirstruct->pos >= dirstruct->num) { |
352 | *result = NULL; | |
0b64f50f TT |
353 | } else { |
354 | ds = &dirstruct->dp[dirstruct->pos++]; | |
355 | entry->d_ino = ds->d_ino; | |
356 | entry->d_off = ds->d_off; | |
357 | entry->d_reclen = ds->d_reclen; | |
358 | entry->d_type = ds->d_type; | |
359 | strncpy(entry->d_name, ds->d_name, sizeof(entry->d_name)); | |
360 | *result = entry; | |
a8283983 | 361 | } |
0b64f50f | 362 | pthread_mutex_unlock(&(dirstruct->lock)); |
a8283983 TT |
363 | return 0; |
364 | } | |
365 | ||
366 | struct dirent64 *readdir64(DIR *dir) | |
367 | { | |
368 | struct dir_s *dirstruct = (struct dir_s *) dir; | |
369 | struct dirent_s *ds; | |
370 | ||
371 | if (dirstruct->direct) | |
372 | return (*real_readdir64)(dirstruct->dir); | |
373 | ||
374 | if (dirstruct->pos >= dirstruct->num) | |
375 | return NULL; | |
376 | ||
377 | ds = &dirstruct->dp[dirstruct->pos++]; | |
378 | dirstruct->ret_dir64.d_ino = ds->d_ino; | |
379 | dirstruct->ret_dir64.d_off = ds->d_off; | |
380 | dirstruct->ret_dir64.d_reclen = ds->d_reclen; | |
381 | dirstruct->ret_dir64.d_type = ds->d_type; | |
382 | strncpy(dirstruct->ret_dir64.d_name, ds->d_name, | |
383 | sizeof(dirstruct->ret_dir64.d_name)); | |
384 | ||
385 | return (&dirstruct->ret_dir64); | |
386 | } | |
387 | ||
0b64f50f TT |
388 | int readdir64_r (DIR *__restrict dir, |
389 | struct dirent64 *__restrict entry, | |
390 | struct dirent64 **__restrict result) | |
391 | { | |
392 | struct dir_s *dirstruct = (struct dir_s *) dir; | |
393 | struct dirent_s *ds; | |
394 | ||
395 | if (dirstruct->direct) | |
396 | return (*real_readdir64_r)(dir, entry, result); | |
397 | pthread_mutex_lock(&(dirstruct->lock)); | |
398 | if (dirstruct->pos >= dirstruct->num) { | |
399 | *result = NULL; | |
400 | } else { | |
401 | ds = &dirstruct->dp[dirstruct->pos++]; | |
402 | entry->d_ino = ds->d_ino; | |
403 | entry->d_off = ds->d_off; | |
404 | entry->d_reclen = ds->d_reclen; | |
405 | entry->d_type = ds->d_type; | |
406 | strncpy(entry->d_name, ds->d_name, | |
407 | sizeof(entry->d_name)); | |
408 | *result = entry; | |
409 | } | |
410 | pthread_mutex_unlock(&(dirstruct->lock)); | |
411 | return 0; | |
412 | } | |
413 | ||
a8283983 TT |
414 | off_t telldir(DIR *dir) |
415 | { | |
416 | struct dir_s *dirstruct = (struct dir_s *) dir; | |
417 | ||
418 | if (dirstruct->direct) | |
419 | return (*real_telldir)(dirstruct->dir); | |
420 | ||
421 | return ((off_t) dirstruct->pos); | |
422 | } | |
423 | ||
424 | void seekdir(DIR *dir, off_t offset) | |
425 | { | |
426 | struct dir_s *dirstruct = (struct dir_s *) dir; | |
427 | ||
428 | if (dirstruct->direct) { | |
429 | (*real_seekdir)(dirstruct->dir, offset); | |
430 | return; | |
431 | } | |
432 | ||
433 | dirstruct->pos = offset; | |
434 | } | |
435 | ||
436 | void rewinddir(DIR *dir) | |
437 | { | |
438 | struct dir_s *dirstruct = (struct dir_s *) dir; | |
439 | ||
440 | (*real_rewinddir)(dirstruct->dir); | |
441 | if (dirstruct->direct) | |
442 | return; | |
443 | ||
0b64f50f | 444 | pthread_mutex_lock(&(dirstruct->lock)); |
a8283983 TT |
445 | dirstruct->pos = 0; |
446 | free_cached_dir(dirstruct); | |
447 | cache_dirstruct(dirstruct); | |
0b64f50f | 448 | pthread_mutex_unlock(&(dirstruct->lock)); |
a8283983 TT |
449 | } |
450 | ||
451 | int dirfd(DIR *dir) | |
452 | { | |
453 | struct dir_s *dirstruct = (struct dir_s *) dir; | |
454 | int fd = (*real_dirfd)(dirstruct->dir); | |
455 | ||
456 | DEBUG_DIR(printf("dirfd %d, %p\n", fd, real_dirfd)); | |
457 | return fd; | |
458 | } |