bionic/libc/unistd/opendir.c
Bruce Beare a37f372973 readdir: fix interface to kernel getdents64 function
Issue:
  The kernel will pad the entry->d_reclen in a getdents64 call to a
  long-word boundary.  For very long records, this could exceed the
  size of a struct dirent. The mismatch in the size was causing error
  paranoid checking code in bionic to fail... thus causing an early
  "end" when reading the dirent structures from the kernel buffer.

Test:
 ls
 mkdir abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstu
 ls

Change-Id: I75d1f8e45e1655fdd7bac4a08a481d086f28073a
Author: Bruce Beare <bruce.j.beare@intel.com>
2011-12-19 09:38:48 -08:00

274 lines
6.4 KiB
C

/*
* Copyright (C) 2008 The Android Open Source Project
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
#include <unistd.h>
#include <dirent.h>
#include <memory.h>
#include <string.h>
#include <fcntl.h>
#include <stdlib.h>
#include <pthread.h>
#include <errno.h>
struct DIR
{
int _DIR_fd;
size_t _DIR_avail;
struct dirent* _DIR_next;
pthread_mutex_t _DIR_lock;
struct dirent _DIR_buff[15];
};
int dirfd(DIR* dirp)
{
return dirp->_DIR_fd;
}
DIR* opendir( const char* dirpath )
{
DIR* dir = malloc(sizeof(DIR));
if (!dir)
goto Exit;
dir->_DIR_fd = open(dirpath, O_RDONLY|O_DIRECTORY);
if (dir->_DIR_fd < 0)
{
free(dir);
dir = NULL;
}
else
{
dir->_DIR_avail = 0;
dir->_DIR_next = NULL;
pthread_mutex_init( &dir->_DIR_lock, NULL );
}
Exit:
return dir;
}
DIR* fdopendir(int fd)
{
DIR* dir = malloc(sizeof(DIR));
if (!dir)
return 0;
dir->_DIR_fd = fd;
dir->_DIR_avail = 0;
dir->_DIR_next = NULL;
pthread_mutex_init( &dir->_DIR_lock, NULL );
return dir;
}
static struct dirent*
_readdir_unlocked(DIR* dir)
{
struct dirent* entry;
#ifndef NDEBUG
unsigned reclen;
#endif
if ( !dir->_DIR_avail )
{
int rc;
for (;;) {
rc = getdents( dir->_DIR_fd, dir->_DIR_buff, sizeof(dir->_DIR_buff));
if (rc >= 0 || errno != EINTR)
break;
}
if (rc <= 0)
return NULL;
dir->_DIR_avail = rc;
dir->_DIR_next = dir->_DIR_buff;
}
entry = dir->_DIR_next;
/* perform some sanity checks here */
if (((long)(void*)entry & 3) != 0)
return NULL;
#ifndef NDEBUG
// paranoid testing of the interface with the kernel getdents64 system call
reclen = offsetof(struct dirent, d_name) + strlen(entry->d_name) + 1;
if ( reclen > sizeof(*entry) || reclen <= offsetof(struct dirent, d_name) )
goto Bad;
if ( (char*)entry + reclen > (char*)dir->_DIR_buff + sizeof(dir->_DIR_buff) )
goto Bad;
if ( !memchr( entry->d_name, 0, reclen - offsetof(struct dirent, d_name)) )
goto Bad;
#endif
dir->_DIR_next = (struct dirent*)((char*)entry + entry->d_reclen);
dir->_DIR_avail -= entry->d_reclen;
return entry;
Bad:
errno = EINVAL;
return NULL;
}
struct dirent*
readdir(DIR * dir)
{
struct dirent *entry = NULL;
pthread_mutex_lock( &dir->_DIR_lock );
entry = _readdir_unlocked(dir);
pthread_mutex_unlock( &dir->_DIR_lock );
return entry;
}
int readdir_r(DIR* dir, struct dirent *entry, struct dirent **result)
{
struct dirent* ent;
int save_errno = errno;
int retval;
*result = NULL;
errno = 0;
pthread_mutex_lock( &dir->_DIR_lock );
ent = _readdir_unlocked(dir);
retval = errno;
if (ent == NULL) {
if (!retval) {
errno = save_errno;
}
} else {
if (!retval) {
errno = save_errno;
*result = entry;
memcpy( entry, ent, ent->d_reclen );
}
}
pthread_mutex_unlock( &dir->_DIR_lock );
return retval;
}
int closedir(DIR *dir)
{
int rc;
rc = close(dir->_DIR_fd);
dir->_DIR_fd = -1;
pthread_mutex_destroy( &dir->_DIR_lock );
free(dir);
return rc;
}
void rewinddir(DIR *dir)
{
pthread_mutex_lock( &dir->_DIR_lock );
lseek( dir->_DIR_fd, 0, SEEK_SET );
dir->_DIR_avail = 0;
pthread_mutex_unlock( &dir->_DIR_lock );
}
int alphasort(const void *a, const void *b)
{
struct dirent **d1, **d2;
d1 = (struct dirent **) a;
d2 = (struct dirent **) b;
return strcmp((*d1)->d_name, (*d2)->d_name);
}
int scandir(const char *dir, struct dirent ***namelist,
int(*filter)(const struct dirent *),
int(*compar)(const struct dirent **, const struct dirent **))
{
DIR *d;
int n_elem = 0;
struct dirent *this_de, *de;
struct dirent **de_list = NULL;
int de_list_size = 0;
d = opendir(dir);
if (d == NULL) {
return -1;
}
while ((this_de = readdir(d)) != NULL) {
if (filter && (*filter)(this_de) == 0) {
continue;
}
if (n_elem == 0) {
de_list_size = 4;
de_list = (struct dirent **)
malloc(sizeof(struct dirent *)*de_list_size);
if (de_list == NULL) {
return -1;
}
}
else if (n_elem == de_list_size) {
struct dirent **de_list_new;
de_list_size += 10;
de_list_new = (struct dirent **)
realloc(de_list, sizeof(struct dirent *)*de_list_size);
if (de_list_new == NULL) {
free(de_list);
return -1;
}
de_list = de_list_new;
}
de = (struct dirent *) malloc(sizeof(struct dirent));
*de = *this_de;
de_list[n_elem++] = de;
}
closedir(d);
if (n_elem && compar) {
qsort(de_list, n_elem, sizeof(struct dirent *),
(int (*)(const void *, const void *)) compar);
}
*namelist = de_list;
return n_elem;
}