#include <dirent.h>
#include <dirent_private.h>
#include <errno.h>
#include <limits.h>
#include <stdlib.h>
#include <string.h>
#include <errno_private.h>
#include <ErrnoMaintainer.h>
#include <syscalls.h>
#include <syscall_utils.h>
#define DIR_BUFFER_SIZE 4096
struct __DIR {
int fd;
short next_entry;
unsigned short entries_left;
long seek_position;
long current_position;
struct dirent first_entry;
};
static int
do_seek_dir(DIR* dir)
{
if (dir->seek_position == dir->current_position)
return 0;
if (dir->seek_position < dir->current_position) {
status_t status = _kern_rewind_dir(dir->fd);
if (status < 0) {
__set_errno(status);
return -1;
}
dir->current_position = 0;
dir->entries_left = 0;
}
while (dir->seek_position > dir->current_position) {
ssize_t count;
long toSkip = dir->seek_position - dir->current_position;
if (toSkip == dir->entries_left) {
dir->current_position = dir->seek_position;
dir->entries_left = 0;
return 0;
}
if (toSkip < dir->entries_left) {
for (; toSkip > 0; toSkip--) {
struct dirent* entry = (struct dirent*)
((uint8*)&dir->first_entry + dir->next_entry);
dir->entries_left--;
dir->next_entry += entry->d_reclen;
}
dir->current_position = dir->seek_position;
return 0;
}
dir->current_position += dir->entries_left;
dir->entries_left = 0;
count = _kern_read_dir(dir->fd, &dir->first_entry,
(char*)dir + DIR_BUFFER_SIZE - (char*)&dir->first_entry, USHRT_MAX);
if (count <= 0) {
if (count < 0)
__set_errno(count);
return -1;
}
dir->next_entry = 0;
dir->entries_left = count;
}
return 0;
}
DIR*
__create_dir_struct(int fd)
{
DIR* dir = (DIR*)malloc(DIR_BUFFER_SIZE);
if (dir == NULL) {
__set_errno(B_NO_MEMORY);
return NULL;
}
dir->fd = fd;
dir->entries_left = 0;
dir->seek_position = 0;
dir->current_position = 0;
return dir;
}
DIR*
fdopendir(int fd)
{
if (fd < 0) {
__set_errno(EBADF);
return NULL;
}
int dirFD = _kern_open_dir(fd, NULL);
if (dirFD < 0) {
__set_errno(dirFD);
return NULL;
}
if (dup2(dirFD, fd) == -1) {
close(fd);
} else {
close(dirFD);
dirFD = fd;
fcntl(dirFD, F_SETFD, FD_CLOEXEC);
}
DIR* dir = __create_dir_struct(dirFD);
if (dir == NULL) {
close(dirFD);
return NULL;
}
return dir;
}
DIR*
opendir(const char* path)
{
DIR* dir;
int fd = _kern_open_dir(AT_FDCWD, path);
if (fd < 0) {
__set_errno(fd);
return NULL;
}
if ((dir = __create_dir_struct(fd)) == NULL) {
_kern_close(fd);
return NULL;
}
return dir;
}
int
closedir(DIR* dir)
{
int status;
if (dir == NULL) {
__set_errno(B_BAD_VALUE);
return -1;
}
status = _kern_close(dir->fd);
free(dir);
RETURN_AND_SET_ERRNO(status);
}
struct dirent*
readdir(DIR* dir)
{
ssize_t count;
if (dir->seek_position != dir->current_position) {
if (do_seek_dir(dir) != 0)
return NULL;
}
if (dir->entries_left > 0) {
struct dirent *dirent
= (struct dirent *)((uint8 *)&dir->first_entry + dir->next_entry);
dir->entries_left--;
dir->next_entry += dirent->d_reclen;
dir->seek_position++;
dir->current_position++;
return dirent;
}
count = _kern_read_dir(dir->fd, &dir->first_entry,
(char*)dir + DIR_BUFFER_SIZE - (char*)&dir->first_entry, USHRT_MAX);
if (count <= 0) {
if (count < 0)
__set_errno(count);
return NULL;
}
dir->entries_left = count - 1;
dir->next_entry = dir->first_entry.d_reclen;
dir->seek_position++;
dir->current_position++;
return &dir->first_entry;
}
int
readdir_r(DIR* dir, struct dirent* entry, struct dirent** _result)
{
BPrivate::ErrnoMaintainer _;
errno = 0;
struct dirent* dirent = readdir(dir);
if (dirent == NULL) {
*_result = NULL;
return errno;
}
memcpy(entry, dirent, dirent->d_reclen);
*_result = entry;
return 0;
}
void
rewinddir(DIR* dir)
{
dir->seek_position = 0;
}
void
seekdir(DIR* dir, long int position)
{
dir->seek_position = position;
}
long int
telldir(DIR* dir)
{
return dir->seek_position;
}
int
dirfd(DIR* dir)
{
return dir->fd;
}