[io] Store last error in file handle, lockdown on fatal error

This commit is contained in:
Martin Fouilleul 2023-05-10 18:41:30 +02:00
parent 25f66b3954
commit e82225116b
2 changed files with 305 additions and 182 deletions

View File

@ -69,12 +69,22 @@ typedef struct io_req
typedef i32 io_error; typedef i32 io_error;
enum { enum {
IO_OK = 0, IO_OK = 0,
IO_ERR_INVALID, // invalid argument or argument combination IO_ERR_UNKNOWN,
IO_ERR_PERM, // access denied IO_ERR_OP, // unsupported operation
IO_ERR_PATH, // path does not exist
IO_ERR_EXISTS, // file already exists
IO_ERR_HANDLE, // invalid handle IO_ERR_HANDLE, // invalid handle
IO_ERR_MAX_FILES, IO_ERR_PREV, // previously had a fatal error (last error stored on handle)
IO_ERR_ARG, // invalid argument or argument combination
IO_ERR_PERM, // access denied
IO_ERR_SPACE, // no space left
IO_ERR_NO_FILE, // file does not exist
IO_ERR_EXISTS, // file already exists
IO_ERR_MAX_FILES, // max open files reached
IO_ERR_PATH_LENGTH, // path too long
IO_ERR_FILE_SIZE, // file too big
IO_ERR_OVERFLOW, // offset too big
IO_ERR_NOT_READY, // no data ready to be read/written
IO_ERR_MEM, // failed to allocate memory
//... //...
}; };

View File

@ -21,6 +21,7 @@ typedef struct file_slot
u32 generation; u32 generation;
int fd; int fd;
io_error error; io_error error;
bool fatal;
list_elt freeListElt; list_elt freeListElt;
} file_slot; } file_slot;
@ -87,70 +88,78 @@ io_cmp io_open(io_req* req)
{ {
io_cmp cmp = {0}; io_cmp cmp = {0};
int flags = 0; file_slot* slot = file_slot_alloc(&__globalFileTable);
if(req->openFlags & IO_OPEN_READ) if(!slot)
{ {
if(req->openFlags & IO_OPEN_WRITE) cmp.error = IO_ERR_MAX_FILES;
cmp.result = 0;
}
else
{
file_handle handle = file_handle_from_slot(&__globalFileTable, slot);
cmp.result = handle.h;
int flags = 0;
if(req->openFlags & IO_OPEN_READ)
{ {
flags = O_RDWR; if(req->openFlags & IO_OPEN_WRITE)
{
flags = O_RDWR;
}
else
{
flags = O_RDONLY;
}
} }
else else if(req->openFlags & IO_OPEN_WRITE)
{ {
flags = O_RDONLY; flags = O_WRONLY;
} }
}
else if(req->openFlags & IO_OPEN_WRITE)
{
flags = O_WRONLY;
}
if(req->openFlags & IO_OPEN_TRUNCATE) if(req->openFlags & IO_OPEN_TRUNCATE)
{ {
flags |= O_TRUNC; flags |= O_TRUNC;
} }
if(req->openFlags & IO_OPEN_APPEND) if(req->openFlags & IO_OPEN_APPEND)
{ {
flags |= O_APPEND; flags |= O_APPEND;
} }
if(req->openFlags & IO_OPEN_CREATE) if(req->openFlags & IO_OPEN_CREATE)
{ {
flags |= O_CREAT; flags |= O_CREAT;
} }
mode_t mode = S_IRUSR mode_t mode = S_IRUSR
| S_IWUSR | S_IWUSR
| S_IRGRP | S_IRGRP
| S_IWGRP | S_IWGRP
| S_IROTH | S_IROTH
| S_IWOTH; | S_IWOTH;
//NOTE: build path //NOTE: build path
//////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////
//TODO: canonicalize directory path & check that it's inside local app folder //TODO: canonicalize directory path & check that it's inside local app folder
//////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////
mem_arena* scratch = mem_scratch(); mem_arena* scratch = mem_scratch();
mem_arena_marker mark = mem_arena_mark(scratch); mem_arena_marker mark = mem_arena_mark(scratch);
str8_list list = {0}; str8_list list = {0};
str8 execPath = mp_app_get_executable_path(scratch); str8 execPath = mp_app_get_executable_path(scratch);
str8 execDir = mp_path_directory(execPath); str8 execDir = mp_path_directory(execPath);
str8_list_push(scratch, &list, execDir); str8_list_push(scratch, &list, execDir);
str8_list_push(scratch, &list, STR8("/../data/")); str8_list_push(scratch, &list, STR8("/../data/"));
str8_list_push(scratch, &list, str8_from_buffer(req->size, req->buffer)); str8_list_push(scratch, &list, str8_from_buffer(req->size, req->buffer));
str8 absPath = str8_list_join(scratch, list); str8 absPath = str8_list_join(scratch, list);
char* absCString = str8_to_cstring(scratch, absPath); char* absCString = str8_to_cstring(scratch, absPath);
//NOTE: open //NOTE: open
int fd = open(absCString, flags, mode); int fd = open(absCString, flags, mode);
if(fd >= 0) if(fd >= 0)
{
file_slot* slot = file_slot_alloc(&__globalFileTable);
if(slot)
{ {
slot->fd = fd; slot->fd = fd;
file_handle handle = file_handle_from_slot(&__globalFileTable, slot); file_handle handle = file_handle_from_slot(&__globalFileTable, slot);
@ -158,156 +167,243 @@ io_cmp io_open(io_req* req)
} }
else else
{ {
cmp.error = IO_ERR_MAX_FILES; slot->fd = -1;
close(fd); slot->fatal = true;
switch(errno)
{
case EACCES:
case EROFS:
slot->error = IO_ERR_PERM;
break;
case EDQUOT:
case ENOSPC:
slot->error = IO_ERR_SPACE;
break;
case EEXIST:
slot->error = IO_ERR_EXISTS;
break;
case EFAULT:
case EINVAL:
case EISDIR:
slot->error = IO_ERR_ARG;
break;
case EMFILE:
case ENFILE:
slot->error = IO_ERR_MAX_FILES;
break;
case ENAMETOOLONG:
slot->error = IO_ERR_PATH_LENGTH;
break;
case ENOENT:
case ENOTDIR:
slot->error = IO_ERR_NO_FILE;
break;
case EOVERFLOW:
slot->error = IO_ERR_FILE_SIZE;
break;
default:
slot->error = IO_ERR_UNKNOWN;
break;
}
cmp.error = slot->error;
} }
mem_arena_clear_to(scratch, mark);
}
return(cmp);
}
io_cmp io_close(file_slot* slot, io_req* req)
{
io_cmp cmp = {0};
if(slot->fd >= 0)
{
close(slot->fd);
}
file_slot_recycle(&__globalFileTable, slot);
return(cmp);
}
io_cmp io_size(file_slot* slot, io_req* req)
{
io_cmp cmp = {0};
struct stat s;
if(fstat(slot->fd, &s))
{
slot->error = IO_ERR_UNKNOWN;
cmp.error = slot->error;
} }
else else
{
cmp.result = s.st_size;
}
return(cmp);
}
io_cmp io_pos(file_slot* slot, io_req* req)
{
io_cmp cmp = {0};
cmp.result = lseek(slot->fd, 0, SEEK_CUR);
if(cmp.result < 0)
{ {
switch(errno) switch(errno)
{ {
case EACCES: case EINVAL:
cmp.error = IO_ERR_PERM; slot->error = IO_ERR_ARG;
break;
case EOVERFLOW:
slot->error = IO_ERR_OVERFLOW;
break; break;
//TODO: convert open error codes to io_error
default: default:
cmp.error = IO_ERR_INVALID; slot->error = IO_ERR_UNKNOWN;
break;
} }
cmp.error = slot->error;
} }
mem_arena_clear_to(scratch, mark);
return(cmp); return(cmp);
} }
io_cmp io_close(io_req* req) io_cmp io_seek(file_slot* slot, io_req* req)
{ {
io_cmp cmp = {0}; io_cmp cmp = {0};
file_slot* slot = file_slot_from_handle(&__globalFileTable, req->handle);
if(slot)
{
close(slot->fd);
file_slot_recycle(&__globalFileTable, slot);
}
else
{
cmp.error = IO_ERR_HANDLE;
}
return(cmp);
}
io_cmp io_size(io_req* req) int whence;
{ switch(req->whence)
io_cmp cmp = {0};
file_slot* slot = file_slot_from_handle(&__globalFileTable, req->handle);
if(slot)
{ {
struct stat s; case IO_SEEK_CURRENT:
fstat(slot->fd, &s); whence = SEEK_CUR;
cmp.result = s.st_size; break;
}
else
{
cmp.error = IO_ERR_HANDLE;
}
return(cmp);
}
io_cmp io_pos(io_req* req) case IO_SEEK_SET:
{ whence = SEEK_SET;
io_cmp cmp = {0}; break;
file_slot* slot = file_slot_from_handle(&__globalFileTable, req->handle);
if(slot)
{
cmp.result = lseek(slot->fd, 0, SEEK_CUR);
//TODO: check for errors
}
else
{
cmp.error = IO_ERR_HANDLE;
}
return(cmp);
}
io_cmp io_seek(io_req* req) case IO_SEEK_END:
{ whence = SEEK_END;
io_cmp cmp = {0}; }
file_slot* slot = file_slot_from_handle(&__globalFileTable, req->handle); cmp.result = lseek(slot->fd, req->size, whence);
if(slot)
if(cmp.result < 0)
{ {
int whence; switch(errno)
switch(req->whence)
{ {
case IO_SEEK_CURRENT: case EINVAL:
whence = SEEK_CUR; slot->error = IO_ERR_ARG;
break; break;
case IO_SEEK_SET: case EOVERFLOW:
whence = SEEK_SET; slot->error = IO_ERR_OVERFLOW;
break; break;
case IO_SEEK_END: default:
whence = SEEK_END; slot->error = IO_ERR_UNKNOWN;
break;
} }
cmp.result = lseek(slot->fd, req->size, whence); cmp.error = slot->error;
//TODO: check for errors
}
else
{
cmp.error = IO_ERR_HANDLE;
} }
return(cmp); return(cmp);
} }
io_cmp io_read(io_req* req) io_cmp io_read(file_slot* slot, io_req* req)
{ {
io_cmp cmp = {0}; io_cmp cmp = {0};
file_slot* slot = file_slot_from_handle(&__globalFileTable, req->handle);
if(slot) cmp.result = read(slot->fd, req->buffer, req->size);
if(cmp.result < 0)
{ {
cmp.result = read(slot->fd, req->buffer, req->size); switch(errno)
//TODO: check for errors {
} case EAGAIN:
else slot->error = IO_ERR_NOT_READY;
{ break;
cmp.error = IO_ERR_HANDLE;
case EFAULT:
case EINVAL:
slot->error = IO_ERR_ARG;
break;
case ENOBUFS:
case ENOMEM:
slot->error = IO_ERR_MEM;
break;
default:
slot->error = IO_ERR_UNKNOWN;
break;
}
cmp.error = slot->error;
} }
return(cmp); return(cmp);
} }
io_cmp io_write(io_req* req) io_cmp io_write(file_slot* slot, io_req* req)
{ {
io_cmp cmp = {0}; io_cmp cmp = {0};
file_slot* slot = file_slot_from_handle(&__globalFileTable, req->handle);
if(slot) cmp.result = write(slot->fd, req->buffer, req->size);
if(cmp.result < 0)
{ {
cmp.result = write(slot->fd, req->buffer, req->size); switch(errno)
//TODO: check for errors {
} case EDQUOT:
else case ENOSPC:
{ slot->error = IO_ERR_SPACE;
cmp.error = IO_ERR_HANDLE; break;
case EFAULT:
case EINVAL:
slot->error = IO_ERR_ARG;
break;
case EAGAIN:
slot->error = IO_ERR_NOT_READY;
break;
case EFBIG:
slot->error = IO_ERR_FILE_SIZE;
break;
case ENOBUFS:
case ENOMEM:
slot->error = IO_ERR_MEM;
break;
default:
slot->error = IO_ERR_UNKNOWN;
break;
}
cmp.error = slot->error;
} }
return(cmp); return(cmp);
} }
io_cmp io_get_error(io_req* req) io_cmp io_get_error(file_slot* slot, io_req* req)
{ {
io_cmp cmp = {0}; io_cmp cmp = {0};
file_slot* slot = file_slot_from_handle(&__globalFileTable, req->handle); cmp.result = slot->error;
if(slot)
{
//TODO get error from slot
}
else
{
cmp.error = IO_ERR_HANDLE;
}
return(cmp); return(cmp);
} }
io_cmp io_wait_single_req(io_req* req) io_cmp io_wait_single_req(io_req* req)
{ {
mem_arena* scratch = mem_scratch(); mem_arena* scratch = mem_scratch();
@ -332,50 +428,67 @@ io_cmp io_wait_single_req(io_req* req)
if(bufferIndex + req->size > memSize) if(bufferIndex + req->size > memSize)
{ {
cmp.error = IO_ERR_INVALID; cmp.error = IO_ERR_ARG;
} }
else else
{ {
//TODO: avoid modifying req. //TODO: avoid modifying req.
req->buffer = memory + bufferIndex; req->buffer = memory + bufferIndex;
switch(req->op) file_slot* slot = 0;
if(req->op != IO_OP_OPEN)
{ {
case IO_OP_OPEN: slot = file_slot_from_handle(&__globalFileTable, req->handle);
cmp = io_open(req); if(!slot)
break; {
cmp.error = IO_ERR_HANDLE;
}
else if(slot->fatal && req->op != IO_OP_CLOSE)
{
cmp.error = IO_ERR_PREV;
}
}
case IO_OP_CLOSE: if(cmp.error == IO_OK)
cmp = io_close(req); {
break; switch(req->op)
{
case IO_OP_OPEN:
cmp = io_open(req);
break;
case IO_OP_SIZE: case IO_OP_CLOSE:
cmp = io_size(req); cmp = io_close(slot, req);
break; break;
case IO_OP_READ: case IO_OP_SIZE:
cmp = io_read(req); cmp = io_size(slot, req);
break; break;
case IO_OP_WRITE: case IO_OP_READ:
cmp = io_write(req); cmp = io_read(slot, req);
break; break;
case IO_OP_POS: case IO_OP_WRITE:
cmp = io_pos(req); cmp = io_write(slot, req);
break; break;
case IO_OP_SEEK: case IO_OP_POS:
cmp = io_seek(req); cmp = io_pos(slot, req);
break; break;
case IO_OP_ERROR: case IO_OP_SEEK:
cmp = io_get_error(req); cmp = io_seek(slot, req);
break; break;
default: case IO_OP_ERROR:
cmp.error = IO_ERR_INVALID; cmp = io_get_error(slot, req);
break; break;
default:
cmp.error = IO_ERR_OP;
break;
}
} }
} }