Home | History | Annotate | Download | only in contrib
      1 /*
      2  * readdir accelerator
      3  *
      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.
     11  *
     12  * Compile using the command:
     13  *
     14  * gcc -o spd_readdir.so -shared -fpic spd_readdir.c -ldl
     15  *
     16  * Use it by setting the LD_PRELOAD environment variable:
     17  *
     18  * export LD_PRELOAD=/usr/local/sbin/spd_readdir.so
     19  *
     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
     31 /* Util we autoconfiscate spd_readdir... */
     32 #define HAVE___SECURE_GETENV	1
     33 #define HAVE_PRCTL		1
     34 #define HAVE_SYS_PRCTL_H	1
     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>
     54 #ifdef HAVE_SYS_PRCTL_H
     55 #include <sys/prctl.h>
     56 #else
     57 #define PR_GET_DUMPABLE 3
     58 #endif
     59 #include <pthread.h>
     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;
     71 	pthread_mutex_t lock; /* Mutex lock for this structure.  */
     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;
     89 static int (*real_readdir64_r)(DIR *dir, struct dirent64 *entry,
     90 			       struct dirent64 **result) = 0;
     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 
    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 
    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");
    132 	real_readdir64_r = dlsym(RTLD_NEXT, "readdir64_r");
    133 	real_telldir = dlsym(RTLD_NEXT, "telldir");
    134 	real_seekdir = dlsym(RTLD_NEXT, "seekdir");
    135 	real_dirfd = dlsym(RTLD_NEXT, "dirfd");
    136 	if ((cp = safe_getenv("SPD_READDIR_MAX_SIZE")) != NULL) {
    137 		max_dirsize = atol(cp);
    138 	}
    139 #ifdef DEBUG
    140 	if (safe_getenv("SPD_READDIR_DEBUG")) {
    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 
    151 	pthread_mutex_destroy(&(dirstruct->lock));
    152 
    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 
    189 static struct dir_s *alloc_dirstruct(DIR *dir)
    190 {
    191 	struct dir_s	*dirstruct;
    192 	static pthread_mutexattr_t mutexattr;
    193 	mutexattr.__align = PTHREAD_MUTEX_RECURSIVE;
    194 
    195 	dirstruct = malloc(sizeof(struct dir_s));
    196 	if (dirstruct)
    197 		memset(dirstruct, 0, sizeof(struct dir_s));
    198 	dirstruct->dir = dir;
    199 	pthread_mutex_init(&(dirstruct->lock), &mutexattr);
    200 	return dirstruct;
    201 }
    202 
    203 static void cache_dirstruct(struct dir_s *dirstruct)
    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 
    282 	DEBUG_DIR(printf("fdopendir(%d) (%d open)\n", fd, num_open++));
    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 
    350 	pthread_mutex_lock(&(dirstruct->lock));
    351 	if (dirstruct->pos >= dirstruct->num) {
    352 		*result = NULL;
    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;
    361 	}
    362 	pthread_mutex_unlock(&(dirstruct->lock));
    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 
    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 
    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 
    444 	pthread_mutex_lock(&(dirstruct->lock));
    445 	dirstruct->pos = 0;
    446 	free_cached_dir(dirstruct);
    447 	cache_dirstruct(dirstruct);
    448 	pthread_mutex_unlock(&(dirstruct->lock));
    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 }
    459