gint/src/fs/fugue/fugue_dir.c

169 lines
3.4 KiB
C

#include <gint/fs.h>
#include <gint/hardware.h>
#include <gint/bfile.h>
#include <dirent.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <stdlib.h>
#include "util.h"
#include "fugue.h"
typedef struct
{
/* Number of entries */
int count;
/* All entries */
struct dirent **entries;
/* Current position in directory */
off_t pos;
} dir_t;
/* There is no well-standardized API for reading from directory descriptors.
getdents(2) is a thing, but it's mostly Linux-specific and has no glibc
wrapper, so no userspace application is using it directly. We define our
directory descriptor interface to mimic opendir(3) for efficiency. */
ssize_t fugue_dir_read(void *data, void *buf, GUNUSED size_t size)
{
struct dirent **dirent_ptr = buf;
if(size < sizeof *dirent_ptr)
return 0;
dir_t *dp = data;
if(dp->pos >= dp->count)
*dirent_ptr = NULL;
else
*dirent_ptr = dp->entries[dp->pos++];
return sizeof *dirent_ptr;
}
ssize_t fugue_dir_pread(GUNUSED void *data, GUNUSED void *buf,
GUNUSED size_t size, GUNUSED off_t offset)
{
errno = EISDIR;
return -1;
}
ssize_t fugue_dir_write(GUNUSED void *data, GUNUSED const void *buf,
GUNUSED size_t size)
{
errno = EISDIR;
return -1;
}
off_t fugue_dir_lseek(void *data, off_t offset, int whence)
{
dir_t *dp = data;
if(whence == SEEK_CUR)
offset += dp->pos;
if(whence == SEEK_END)
offset += dp->count;
if(offset < 0 || offset >= dp->count) {
errno = EINVAL;
return -1;
}
dp->pos = offset;
return dp->pos;
}
int fugue_dir_close(void *data)
{
dir_t *dp = data;
if(dp && dp->entries) {
for(int i = 0; i < dp->count; i++)
free(dp->entries[i]);
free(dp->entries);
}
free(dp);
return 0;
}
const fs_descriptor_type_t fugue_dir_descriptor_type = {
.read = fugue_dir_read,
.pread = fugue_dir_pread,
.write = fugue_dir_write,
.lseek = fugue_dir_lseek,
.close = fugue_dir_close,
};
void *fugue_dir_explore(char const *path)
{
struct BFile_FileInfo info;
uint16_t *fc_path=NULL, *search=NULL;
dir_t *dp = malloc(sizeof *dp);
if(!dp) goto alloc_failure;
dp->count = 0;
dp->entries = NULL;
dp->pos = 0;
/* We allocate by batches of 8 */
int sd=-1, rc, allocated=0;
fc_path = malloc(512 * sizeof *fc_path);
if(!fc_path) goto alloc_failure;
search = utf8_to_fc_alloc(u"\\\\fls0\\", path, u"\\*");
if(!search) goto alloc_failure;
rc = BFile_FindFirst(search, &sd, fc_path, &info);
if(rc < 0) {
if(rc != BFile_EntryNotFound)
errno = bfile_error_to_errno(rc);
goto end;
}
do {
if(dp->count+1 > allocated) {
struct dirent **new_entries = realloc(dp->entries,
(allocated + 8) * sizeof *dp->entries);
if(!new_entries)
goto alloc_failure;
dp->entries = new_entries;
allocated += 8;
}
size_t name_length = fc_len(fc_path);
size_t s = sizeof(struct dirent) + name_length + 1;
struct dirent *ent = malloc(s);
if(!ent) goto alloc_failure;
ent->d_ino = 0;
ent->d_type = DT_UNKNOWN;
fc_to_utf8(ent->d_name, fc_path, name_length + 1);
if(info.type == BFile_Type_File)
ent->d_type = DT_REG;
if(info.type == BFile_Type_Directory)
ent->d_type = DT_DIR;
if(info.type == BFile_Type_Dot)
ent->d_type = DT_DIR;
if(info.type == BFile_Type_DotDot)
ent->d_type = DT_DIR;
dp->entries[dp->count++] = ent;
rc = BFile_FindNext(sd, fc_path, &info);
}
while(rc >= 0);
goto end;
alloc_failure:
errno = ENOMEM;
fugue_dir_close(dp);
end:
free(search);
free(fc_path);
if(sd >= 0)
BFile_FindClose(sd);
return dp;
}