579 lines
13 KiB
C
579 lines
13 KiB
C
/************************************************************/ /**
|
|
*
|
|
* @file: posix_io.c
|
|
* @author: Martin Fouilleul
|
|
* @date: 25/05/2023
|
|
*
|
|
*****************************************************************/
|
|
|
|
#include <errno.h>
|
|
#include <fcntl.h>
|
|
#include <limits.h>
|
|
#include <sys/stat.h>
|
|
#include <unistd.h>
|
|
|
|
#include "platform_io_common.c"
|
|
#include "platform_io_internal.c"
|
|
|
|
oc_file_desc oc_file_desc_nil()
|
|
{
|
|
return (-1);
|
|
}
|
|
|
|
bool oc_file_desc_is_nil(oc_file_desc fd)
|
|
{
|
|
return (fd < 0);
|
|
}
|
|
|
|
oc_io_error oc_io_raw_last_error()
|
|
{
|
|
oc_io_error error = OC_IO_OK;
|
|
switch(errno)
|
|
{
|
|
case EPERM:
|
|
case EACCES:
|
|
case EROFS:
|
|
error = OC_IO_ERR_PERM;
|
|
break;
|
|
|
|
case ENOENT:
|
|
error = OC_IO_ERR_NO_ENTRY;
|
|
break;
|
|
|
|
case EINTR:
|
|
error = OC_IO_ERR_INTERRUPT;
|
|
break;
|
|
|
|
case EIO:
|
|
error = OC_IO_ERR_PHYSICAL;
|
|
break;
|
|
|
|
case ENXIO:
|
|
error = OC_IO_ERR_NO_DEVICE;
|
|
break;
|
|
|
|
case EBADF:
|
|
// this should only happen when user tries to write/read to a file handle
|
|
// opened with readonly/writeonly access
|
|
error = OC_IO_ERR_PERM;
|
|
break;
|
|
|
|
case ENOMEM:
|
|
error = OC_IO_ERR_MEM;
|
|
break;
|
|
|
|
case EFAULT:
|
|
case EINVAL:
|
|
case EDOM:
|
|
error = OC_IO_ERR_ARG;
|
|
break;
|
|
|
|
case EBUSY:
|
|
case EAGAIN:
|
|
error = OC_IO_ERR_NOT_READY;
|
|
break;
|
|
|
|
case EEXIST:
|
|
error = OC_IO_ERR_EXISTS;
|
|
break;
|
|
|
|
case ENOTDIR:
|
|
error = OC_IO_ERR_NOT_DIR;
|
|
break;
|
|
|
|
case EISDIR:
|
|
error = OC_IO_ERR_DIR;
|
|
break;
|
|
|
|
case ENFILE:
|
|
case EMFILE:
|
|
error = OC_IO_ERR_MAX_FILES;
|
|
break;
|
|
|
|
case EFBIG:
|
|
error = OC_IO_ERR_FILE_SIZE;
|
|
break;
|
|
|
|
case ENOSPC:
|
|
case EDQUOT:
|
|
error = OC_IO_ERR_SPACE;
|
|
break;
|
|
|
|
case ELOOP:
|
|
error = OC_IO_ERR_MAX_LINKS;
|
|
break;
|
|
|
|
case ENAMETOOLONG:
|
|
error = OC_IO_ERR_PATH_LENGTH;
|
|
break;
|
|
|
|
case EOVERFLOW:
|
|
error = OC_IO_ERR_OVERFLOW;
|
|
break;
|
|
|
|
default:
|
|
error = OC_IO_ERR_UNKNOWN;
|
|
break;
|
|
}
|
|
return (error);
|
|
}
|
|
|
|
static int oc_io_convert_access_rights(oc_file_access rights)
|
|
{
|
|
int oflags = 0;
|
|
|
|
if(rights & OC_FILE_ACCESS_READ)
|
|
{
|
|
if(rights & OC_FILE_ACCESS_WRITE)
|
|
{
|
|
oflags = O_RDWR;
|
|
}
|
|
else
|
|
{
|
|
oflags = O_RDONLY;
|
|
}
|
|
}
|
|
else if(rights & OC_FILE_ACCESS_WRITE)
|
|
{
|
|
oflags = O_WRONLY;
|
|
}
|
|
return (oflags);
|
|
}
|
|
|
|
static int oc_io_convert_open_flags(oc_file_open_flags flags)
|
|
{
|
|
int oflags = 0;
|
|
|
|
if(flags & OC_FILE_OPEN_TRUNCATE)
|
|
{
|
|
oflags |= O_TRUNC;
|
|
}
|
|
if(flags & OC_FILE_OPEN_APPEND)
|
|
{
|
|
oflags |= O_APPEND;
|
|
}
|
|
if(flags & OC_FILE_OPEN_CREATE)
|
|
{
|
|
oflags |= O_CREAT;
|
|
}
|
|
if(flags & OC_FILE_OPEN_NO_FOLLOW)
|
|
{
|
|
oflags |= O_NOFOLLOW;
|
|
}
|
|
if(flags & OC_FILE_OPEN_SYMLINK)
|
|
{
|
|
oflags |= O_SYMLINK;
|
|
}
|
|
return (oflags);
|
|
}
|
|
|
|
static int oc_io_update_dir_flags_at(int dirFd, char* path, int flags)
|
|
{
|
|
struct stat s;
|
|
if(!fstatat(dirFd, path, &s, AT_SYMLINK_NOFOLLOW))
|
|
{
|
|
if((s.st_mode & S_IFMT) == S_IFDIR)
|
|
{
|
|
if(flags & O_WRONLY)
|
|
{
|
|
flags &= ~O_WRONLY;
|
|
}
|
|
else if(flags & O_RDWR)
|
|
{
|
|
flags &= ~O_RDWR;
|
|
flags |= O_RDONLY;
|
|
}
|
|
}
|
|
}
|
|
return (flags);
|
|
}
|
|
|
|
oc_file_desc oc_io_raw_open_at(oc_file_desc dirFd, oc_str8 path, oc_file_access accessRights, oc_file_open_flags openFlags)
|
|
{
|
|
int flags = oc_io_convert_access_rights(accessRights);
|
|
flags |= oc_io_convert_open_flags(openFlags);
|
|
|
|
mode_t mode = S_IRUSR
|
|
| S_IWUSR
|
|
| S_IRGRP
|
|
| S_IWGRP
|
|
| S_IROTH
|
|
| S_IWOTH;
|
|
|
|
oc_arena_scope scratch = oc_scratch_begin();
|
|
|
|
oc_file_desc fd = -1;
|
|
if(dirFd >= 0)
|
|
{
|
|
if(path.len && path.ptr[0] == '/')
|
|
{
|
|
//NOTE: if path is absolute, change for a relative one, otherwise openat ignores fd.
|
|
oc_str8_list list = { 0 };
|
|
oc_str8_list_push(scratch.arena, &list, OC_STR8("."));
|
|
oc_str8_list_push(scratch.arena, &list, path);
|
|
path = oc_str8_list_join(scratch.arena, list);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
dirFd = AT_FDCWD;
|
|
}
|
|
|
|
char* pathCStr = oc_str8_to_cstring(scratch.arena, path);
|
|
|
|
flags = oc_io_update_dir_flags_at(dirFd, pathCStr, flags);
|
|
|
|
fd = openat(dirFd, pathCStr, flags, mode);
|
|
oc_scratch_end(scratch);
|
|
|
|
return (fd);
|
|
}
|
|
|
|
void oc_io_raw_close(oc_file_desc fd)
|
|
{
|
|
close(fd);
|
|
}
|
|
|
|
static oc_file_perm oc_io_convert_perm_from_stat(u16 mode)
|
|
{
|
|
oc_file_perm perm = mode & 07777;
|
|
return (perm);
|
|
}
|
|
|
|
static oc_file_type oc_io_convert_type_from_stat(u16 mode)
|
|
{
|
|
oc_file_type type;
|
|
switch(mode & S_IFMT)
|
|
{
|
|
case S_IFIFO:
|
|
type = OC_FILE_FIFO;
|
|
break;
|
|
|
|
case S_IFCHR:
|
|
type = OC_FILE_CHARACTER;
|
|
break;
|
|
|
|
case S_IFDIR:
|
|
type = OC_FILE_DIRECTORY;
|
|
break;
|
|
|
|
case S_IFBLK:
|
|
type = OC_FILE_BLOCK;
|
|
break;
|
|
|
|
case S_IFREG:
|
|
type = OC_FILE_REGULAR;
|
|
break;
|
|
|
|
case S_IFLNK:
|
|
type = OC_FILE_SYMLINK;
|
|
break;
|
|
|
|
case S_IFSOCK:
|
|
type = OC_FILE_SOCKET;
|
|
break;
|
|
|
|
default:
|
|
type = OC_FILE_UNKNOWN;
|
|
break;
|
|
}
|
|
return (type);
|
|
}
|
|
|
|
oc_io_error oc_io_raw_fstat(oc_file_desc fd, oc_file_status* status)
|
|
{
|
|
oc_io_error error = OC_IO_OK;
|
|
struct stat s;
|
|
if(fstat(fd, &s))
|
|
{
|
|
error = oc_io_raw_last_error();
|
|
}
|
|
else
|
|
{
|
|
status->uid = s.st_ino;
|
|
status->perm = oc_io_convert_perm_from_stat(s.st_mode);
|
|
status->type = oc_io_convert_type_from_stat(s.st_mode);
|
|
status->size = s.st_size;
|
|
//TODO: times
|
|
}
|
|
return (error);
|
|
}
|
|
|
|
oc_io_error oc_io_raw_fstat_at(oc_file_desc dirFd, oc_str8 path, oc_file_open_flags flags, oc_file_status* status)
|
|
{
|
|
oc_arena_scope scratch = oc_scratch_begin();
|
|
|
|
if(dirFd >= 0)
|
|
{
|
|
if(path.len && path.ptr[0] == '/')
|
|
{
|
|
oc_str8_list list = { 0 };
|
|
oc_str8_list_push(scratch.arena, &list, OC_STR8("."));
|
|
oc_str8_list_push(scratch.arena, &list, path);
|
|
path = oc_str8_list_join(scratch.arena, list);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
dirFd = AT_FDCWD;
|
|
}
|
|
|
|
char* pathCStr = oc_str8_to_cstring(scratch.arena, path);
|
|
|
|
int statFlag = (flags & OC_FILE_OPEN_SYMLINK) ? AT_SYMLINK_NOFOLLOW : 0;
|
|
oc_io_error error = OC_IO_OK;
|
|
struct stat s;
|
|
if(fstatat(dirFd, pathCStr, &s, statFlag))
|
|
{
|
|
error = oc_io_raw_last_error();
|
|
}
|
|
else
|
|
{
|
|
status->uid = s.st_ino;
|
|
status->perm = oc_io_convert_perm_from_stat(s.st_mode);
|
|
status->type = oc_io_convert_type_from_stat(s.st_mode);
|
|
status->size = s.st_size;
|
|
//TODO: times
|
|
}
|
|
|
|
oc_scratch_end(scratch);
|
|
return (error);
|
|
}
|
|
|
|
oc_io_raw_read_link_result oc_io_raw_read_link_at(oc_arena* arena, oc_file_desc dirFd, oc_str8 path)
|
|
{
|
|
oc_arena_scope scratch = oc_scratch_begin_next(arena);
|
|
|
|
if(dirFd >= 0)
|
|
{
|
|
if(path.len && path.ptr[0] == '/')
|
|
{
|
|
oc_str8_list list = { 0 };
|
|
oc_str8_list_push(scratch.arena, &list, OC_STR8("."));
|
|
oc_str8_list_push(scratch.arena, &list, path);
|
|
path = oc_str8_list_join(scratch.arena, list);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
dirFd = AT_FDCWD;
|
|
}
|
|
|
|
char* pathCStr = oc_str8_to_cstring(scratch.arena, path);
|
|
|
|
oc_io_raw_read_link_result result = { 0 };
|
|
|
|
char buffer[PATH_MAX];
|
|
u64 bufferSize = PATH_MAX;
|
|
i64 r = readlinkat(dirFd, pathCStr, buffer, bufferSize);
|
|
|
|
if(r < 0)
|
|
{
|
|
result.error = oc_io_raw_last_error();
|
|
}
|
|
else
|
|
{
|
|
result.target.len = r;
|
|
result.target.ptr = oc_arena_push_array(arena, char, result.target.len);
|
|
memcpy(result.target.ptr, buffer, result.target.len);
|
|
}
|
|
|
|
oc_scratch_end(scratch);
|
|
return (result);
|
|
}
|
|
|
|
bool oc_io_raw_file_exists_at(oc_file_desc dirFd, oc_str8 path, oc_file_open_flags openFlags)
|
|
{
|
|
oc_arena_scope scratch = oc_scratch_begin();
|
|
|
|
if(dirFd >= 0)
|
|
{
|
|
if(path.len && path.ptr[0] == '/')
|
|
{
|
|
oc_str8_list list = { 0 };
|
|
oc_str8_list_push(scratch.arena, &list, OC_STR8("."));
|
|
oc_str8_list_push(scratch.arena, &list, path);
|
|
path = oc_str8_list_join(scratch.arena, list);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
dirFd = AT_FDCWD;
|
|
}
|
|
|
|
char* pathCStr = oc_str8_to_cstring(scratch.arena, path);
|
|
|
|
int flags = (openFlags & OC_FILE_OPEN_SYMLINK) ? AT_SYMLINK_NOFOLLOW : 0;
|
|
int r = faccessat(dirFd, pathCStr, F_OK, flags);
|
|
bool result = (r == 0);
|
|
|
|
oc_scratch_end(scratch);
|
|
return (result);
|
|
}
|
|
|
|
oc_io_cmp oc_io_close(oc_file_slot* slot, oc_io_req* req, oc_file_table* table)
|
|
{
|
|
oc_io_cmp cmp = { 0 };
|
|
if(slot->fd >= 0)
|
|
{
|
|
close(slot->fd);
|
|
}
|
|
oc_file_slot_recycle(table, slot);
|
|
return (cmp);
|
|
}
|
|
|
|
oc_io_cmp oc_io_fstat(oc_file_slot* slot, oc_io_req* req)
|
|
{
|
|
oc_io_cmp cmp = { 0 };
|
|
|
|
if(req->size < sizeof(oc_file_status))
|
|
{
|
|
cmp.error = OC_IO_ERR_ARG;
|
|
}
|
|
else
|
|
{
|
|
struct stat s;
|
|
if(fstat(slot->fd, &s))
|
|
{
|
|
slot->error = oc_io_raw_last_error();
|
|
cmp.error = slot->error;
|
|
}
|
|
else
|
|
{
|
|
oc_file_status* status = (oc_file_status*)req->buffer;
|
|
status->perm = oc_io_convert_perm_from_stat(s.st_mode);
|
|
status->type = oc_io_convert_type_from_stat(s.st_mode);
|
|
status->size = s.st_size;
|
|
//TODO: times
|
|
}
|
|
}
|
|
return (cmp);
|
|
}
|
|
|
|
oc_io_cmp oc_io_seek(oc_file_slot* slot, oc_io_req* req)
|
|
{
|
|
oc_io_cmp cmp = { 0 };
|
|
|
|
int whence;
|
|
switch(req->whence)
|
|
{
|
|
case OC_FILE_SEEK_CURRENT:
|
|
whence = SEEK_CUR;
|
|
break;
|
|
|
|
case OC_FILE_SEEK_SET:
|
|
whence = SEEK_SET;
|
|
break;
|
|
|
|
case OC_FILE_SEEK_END:
|
|
whence = SEEK_END;
|
|
}
|
|
cmp.result = lseek(slot->fd, req->offset, whence);
|
|
|
|
if(cmp.result < 0)
|
|
{
|
|
slot->error = oc_io_raw_last_error();
|
|
cmp.error = slot->error;
|
|
}
|
|
|
|
return (cmp);
|
|
}
|
|
|
|
oc_io_cmp oc_io_read(oc_file_slot* slot, oc_io_req* req)
|
|
{
|
|
oc_io_cmp cmp = { 0 };
|
|
|
|
cmp.result = read(slot->fd, req->buffer, req->size);
|
|
|
|
if(cmp.result < 0)
|
|
{
|
|
slot->error = oc_io_raw_last_error();
|
|
cmp.result = 0;
|
|
cmp.error = slot->error;
|
|
}
|
|
|
|
return (cmp);
|
|
}
|
|
|
|
oc_io_cmp oc_io_write(oc_file_slot* slot, oc_io_req* req)
|
|
{
|
|
oc_io_cmp cmp = { 0 };
|
|
|
|
cmp.result = write(slot->fd, req->buffer, req->size);
|
|
|
|
if(cmp.result < 0)
|
|
{
|
|
slot->error = oc_io_raw_last_error();
|
|
cmp.result = 0;
|
|
cmp.error = slot->error;
|
|
}
|
|
|
|
return (cmp);
|
|
}
|
|
|
|
oc_io_cmp oc_io_get_error(oc_file_slot* slot, oc_io_req* req)
|
|
{
|
|
oc_io_cmp cmp = { 0 };
|
|
cmp.result = slot->error;
|
|
return (cmp);
|
|
}
|
|
|
|
oc_io_cmp oc_io_wait_single_req_with_table(oc_io_req* req, oc_file_table* table)
|
|
{
|
|
oc_io_cmp cmp = { 0 };
|
|
|
|
oc_file_slot* slot = oc_file_slot_from_handle(table, req->handle);
|
|
if(!slot)
|
|
{
|
|
if(req->op != OC_IO_OPEN_AT)
|
|
{
|
|
cmp.error = OC_IO_ERR_HANDLE;
|
|
}
|
|
}
|
|
else if(slot->fatal
|
|
&& req->op != OC_IO_CLOSE
|
|
&& req->op != OC_OC_IO_ERROR)
|
|
{
|
|
cmp.error = OC_IO_ERR_PREV;
|
|
}
|
|
|
|
if(cmp.error == OC_IO_OK)
|
|
{
|
|
switch(req->op)
|
|
{
|
|
case OC_IO_OPEN_AT:
|
|
cmp = oc_io_open_at(slot, req, table);
|
|
break;
|
|
|
|
case OC_IO_FSTAT:
|
|
cmp = oc_io_fstat(slot, req);
|
|
break;
|
|
|
|
case OC_IO_CLOSE:
|
|
cmp = oc_io_close(slot, req, table);
|
|
break;
|
|
|
|
case OC_IO_READ:
|
|
cmp = oc_io_read(slot, req);
|
|
break;
|
|
|
|
case OC_IO_WRITE:
|
|
cmp = oc_io_write(slot, req);
|
|
break;
|
|
|
|
case OC_IO_SEEK:
|
|
cmp = oc_io_seek(slot, req);
|
|
break;
|
|
|
|
case OC_OC_IO_ERROR:
|
|
cmp = oc_io_get_error(slot, req);
|
|
break;
|
|
|
|
default:
|
|
cmp.error = OC_IO_ERR_OP;
|
|
break;
|
|
}
|
|
}
|
|
return (cmp);
|
|
}
|