/*
 * 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;
}
