#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <dirent.h>

#include <nllibc.h>

struct _dirdesc {
  int fd;
  int offset;
  int size;
  char buffer[sizeof(struct dirent) * 4];
  struct dirent dirent;
};

#if defined(__linux__)
struct linux_dirent {
  unsigned long  d_fileno;
  unsigned long  d_off;
  unsigned short d_reclen;
  char d_name[];
};

int getdents(int fd, char *buf, int nbytes);
int fdclosedir(DIR *dirp);
#endif

DIR *opendir(const char *filename)
{
  int fd;
  DIR *dirp;

  fd = open(filename, O_RDONLY);
  if (fd < 0)
    return NULL;

  dirp = fdopendir(fd);
  if (dirp == NULL)
    close(fd);

  return dirp;
}

DIR *fdopendir(int fd)
{
  struct _dirdesc *dirdesc;

  dirdesc = malloc(sizeof(*dirdesc));
  if (dirdesc != NULL) {
    memset(dirdesc, 0, sizeof(*dirdesc));
    dirdesc->fd = fd;
    dirdesc->offset = 0;
    dirdesc->size = sizeof(dirdesc->buffer);
  }

  return (DIR *)dirdesc;
}

struct dirent *readdir(DIR *dirp)
{
  struct _dirdesc *dirdesc;
#if !defined(__linux__)
  struct dirent *direntp;
#else
  struct linux_dirent *direntp;
#endif
  int r, minsize;

  dirdesc = (struct _dirdesc *)dirp;

  minsize =
    (((char *)&((struct dirent *)0)->d_reclen) - (char *)0)
    + sizeof(((struct dirent *)0)->d_reclen);

#if !defined(__linux__)
  direntp = (struct dirent *)dirdesc->buffer;
#else
  direntp = (struct linux_dirent *)dirdesc->buffer;
#endif

  while (1) {
    if (dirdesc->offset >= minsize) {
      if (dirdesc->offset >= direntp->d_reclen)
	break;
    }

    r = getdents(dirdesc->fd, dirdesc->buffer + dirdesc->offset,
		 dirdesc->size - dirdesc->offset);
    if (r <= 0)
      return NULL;

    dirdesc->offset += r;
  }

  memset(&dirdesc->dirent, 0, sizeof(dirdesc->dirent));

  dirdesc->dirent.d_fileno = direntp->d_fileno;
#if defined(__linux__)
  dirdesc->dirent.d_off    = direntp->d_off;
#endif
  dirdesc->dirent.d_reclen = direntp->d_reclen;
#if !defined(__linux__)
  dirdesc->dirent.d_type   = direntp->d_type;
#else
  dirdesc->dirent.d_type   = *((char *)direntp + direntp->d_reclen - 1);
#endif
#if defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__) || defined(__DragonFly__) || defined(__APPLE__)
  dirdesc->dirent.d_namlen = direntp->d_namlen;
#endif
  strncpy(dirdesc->dirent.d_name, direntp->d_name, sizeof(dirdesc->dirent.d_name));

  dirdesc->offset -= direntp->d_reclen;
  memmove(dirdesc->buffer, dirdesc->buffer + direntp->d_reclen, dirdesc->offset);

  return &dirdesc->dirent;
}

int closedir(DIR *dirp)
{
  return close(fdclosedir(dirp));
}

int fdclosedir(DIR *dirp)
{
  struct _dirdesc *dirdesc;
  int fd;
  dirdesc = (struct _dirdesc *)dirp;
  fd = dirdesc->fd;
  free(dirdesc);
  return fd;
}

int dirfd(DIR *dirp)
{
  struct _dirdesc *dirdesc;
  dirdesc = (struct _dirdesc *)dirp;
  return dirdesc->fd;
}
