#include <dirent.h>
#include <errno.h>
#include <sys/syscall.h>

#define NAME_OFFSET(de) ((int) ((de)->d_name - (char *) (de)))
#define ROUND_UP(x) (((x)+3) & ~3)

/*
 * readdir fills up the buffer with the readdir system call. 
 *
 * The old-style kernel readdir interface accepted, but ignored, a
 * third parameter which was set to one by the library and which was
 * reserved to allow future libraries to read more than one dirent at
 * a time.  The return value in this case was simply the length of the
 * name of the dirent returned.
 *
 * Right now the readdir system call accepts a buffer size as its
 * third argument, and returns the number of bytes written into this
 * buffer.  The dirents returned are of variable length, and the
 * d_reclen member gives the length, in bytes, of each entry in the
 * buffer.  Currently only the ext2fs filesystem ever returns more
 * than one dirent, but all filesystems return the number of bytes
 * written.
 *
 * For compatibility with older kernels, the library must still accept
 * return values less than the length of a dirent.
 */
struct dirent *readdir(DIR * dir)
{
  int result;
  int count;
  struct dirent *de;
  char *p;

  if (!dir) {
    errno = EBADF;
    return NULL; 
  }

repeat:
  if (dir->dd_max == 1 || dir->dd_size <= dir->dd_loc) {
  	count = dir->dd_max;
#if defined(__PIC__) || defined (__pic__)
	__asm__ volatile ("pushl %%ebx\n\t"
			  "movl %%esi,%%ebx\n\t"
			  "int $0x80\n\t"
			  "popl %%ebx"
	:"=a" (result)
	:"0" (SYS_readdir),"S" (dir->dd_fd),
	"c" ((long) dir->dd_buf),"d" (count));
#else
    __asm__("int $0x80"
	:"=a" (result)
	:"0" (SYS_readdir),"b" (dir->dd_fd),
	"c" ((long) dir->dd_buf),"d" (count));
#endif
    if (result <= 0) {
      if (result < 0)
	errno = -result;
      return NULL;
    }

    if (result > dir->dd_max) {
	/* This should never happen on modern multi-dirent kernels.
           If it does occur, resort to old-style one-at-a-time dirent
           access, and assume the kernel has returned only one dirent
           this time. */
	result = 1;
	dir->dd_max = 1;
    }
    dir->dd_size = result;
    dir->dd_loc = 0;
  }

  de = (struct dirent *) (((char *)dir->dd_buf) + dir->dd_loc);

  /* Do some sanity checks to getting confused by old kernels */
  p = (char *) de;
  p += de->d_off;
  if (dir->dd_max > 1 && 
      (p > (((char *) dir->dd_buf) + dir->dd_size) ||
       de->d_reclen < 1 ||
       de->d_off < (NAME_OFFSET(de) + de->d_reclen))) {
    /* If the kernel doesn't appear to adhere to modern readdir()
       semantics, only rely on it to return one dirent at a time. */
    dir->dd_max = 1;
    dir->dd_size = 0;
    if (dir->dd_loc) {
      dir->dd_size = 0;
      goto repeat;
    }
  }

  dir->dd_loc += de->d_off;
  return de;
}
