Add io_create_file, io_open_file

This commit is contained in:
2024-04-08 00:38:13 +02:00
parent 9870c2544c
commit 79a8f93572
5 changed files with 340 additions and 43 deletions
+2 -1
View File
@@ -1,2 +1,3 @@
example example
example.exe example.exe
file_*.txt
+1 -1
View File
@@ -4,4 +4,4 @@ TinyIO is a cross-platform (Windows and Linux) API for asynchronous I/O operatio
<<< WARNING >>> It's likely that the io_uring code has some bugs, so be wary! Also bug reports are appreciated :) <<< WARNING >>> It's likely that the io_uring code has some bugs, so be wary! Also bug reports are appreciated :)
It's released in the public domain, so you can just adapt the code for your own projects. Pull requests would be appreciated though. It's released in the public domain, so you can just adapt the code for your own projects. Pull requests would be appreciated though.
+18 -3
View File
@@ -1,6 +1,8 @@
#include <stdio.h> #include <stdio.h>
#include "io.h" #include "io.h"
#define NUM_OPS 3
int main() int main()
{ {
struct io_operation ops[100]; struct io_operation ops[100];
@@ -8,13 +10,26 @@ int main()
if (!io_context_init(&ioc, ops, sizeof(ops)/sizeof(ops[0]))) if (!io_context_init(&ioc, ops, sizeof(ops)/sizeof(ops[0])))
return -1; return -1;
io_handle files[NUM_OPS];
for (int i = 0; i < NUM_OPS; i++) {
char name[1<<8];
snprintf(name, sizeof(name), "file_%d.txt", i);
files[i] = io_create_file(&ioc, name, IO_CREATE_CANTEXIST, NULL);
if (files[i] == IO_INVALID_HANDLE)
fprintf(stderr, "Couldn't create '%s'\n", name);
}
int started = 0;
char msg[] = "Hello, world!\n"; char msg[] = "Hello, world!\n";
for (int i = 0; i < 10; i++) { for (int i = 0; i < NUM_OPS; i++) {
if (!io_start_send(&ioc, io_get_stdout(), msg, sizeof(msg), NULL)) if (io_start_send(&ioc, files[i], msg, sizeof(msg)-1))
started++;
else
fprintf(stderr, "ERROR\n"); fprintf(stderr, "ERROR\n");
} }
for (int i = 0; i < 10; i++) { for (int i = 0; i < started; i++) {
struct io_event ev; struct io_event ev;
io_wait(&ioc, &ev); io_wait(&ioc, &ev);
fprintf(stderr, "CONCLUDED\n"); fprintf(stderr, "CONCLUDED\n");
+295 -28
View File
@@ -9,14 +9,51 @@
* Declare Windows symbols locally to avoid including windows.h * Declare Windows symbols locally to avoid including windows.h
*/ */
#define GENERIC_READ 0x80000000ULL
#define GENERIC_WRITE 0x40000000ULL
#define CREATE_NEW 1
#define CREATE_ALWAYS 2
#define OPEN_EXISTING 3
#define OPEN_ALWAYS 4
#define FILE_ATTRIBUTE_NORMAL 0x00000080ULL
#define FILE_FLAG_OVERLAPPED 0x40000000ULL
#define ERROR_IO_PENDING 997l #define ERROR_IO_PENDING 997l
extern int ReadFile(void *handle, void *dst, unsigned long max, unsigned long *num, struct io_overlap *ov); struct security_attr {
extern int WriteFile(void *handle, const void *src, unsigned long max, unsigned long *num, struct io_overlap *ov); unsigned long size;
extern void *CreateIoCompletionPort(void *handle, void *existing_ioport, unsigned long *ptr, unsigned long num_threads); void *desc;
extern int CloseHandle(void *handle); int inherit_handle;
extern unsigned long GetLastError(); };
extern int GetQueuedCompletionStatus(void *ioport, unsigned long *num, unsigned long *key, struct io_overlap **ov, unsigned long timeout);
extern void*
CreateFileA(const char *name, unsigned long access,
unsigned long share, struct security_attr *sec,
unsigned long creation, unsigned long flags,
void *template);
extern int
ReadFile(void *handle, void *dst, unsigned long max,
unsigned long *num, struct io_overlap *ov);
extern int
WriteFile(void *handle, const void *src, unsigned long max,
unsigned long *num, struct io_overlap *ov);
extern void*
CreateIoCompletionPort(void *handle, void *existing_ioport,
unsigned long *ptr, unsigned long num_threads);
extern int
CloseHandle(void *handle);
extern unsigned long
GetLastError();
extern int
GetQueuedCompletionStatus(void *ioport, unsigned long *num, unsigned long *key, struct io_overlap **ov, unsigned long timeout);
bool io_context_init(struct io_context *ioc, bool io_context_init(struct io_context *ioc,
struct io_operation *ops, struct io_operation *ops,
@@ -25,6 +62,9 @@ bool io_context_init(struct io_context *ioc,
io_handle handle = CreateIoCompletionPort(IO_INVALID_HANDLE, NULL, 0, 1); io_handle handle = CreateIoCompletionPort(IO_INVALID_HANDLE, NULL, 0, 1);
if (handle == IO_INVALID_HANDLE) if (handle == IO_INVALID_HANDLE)
return false; return false;
for (uint32_t i = 0; i < max_ops; i++)
ops[i].type = IO_VOID;
ioc->handle = handle; ioc->handle = handle;
ioc->max_ops = max_ops; ioc->max_ops = max_ops;
@@ -46,8 +86,11 @@ static struct io_operation *alloc_op(struct io_context *ioc)
} }
bool io_start_recv(struct io_context *ioc, io_handle handle, bool io_start_recv(struct io_context *ioc, io_handle handle,
void *dst, uint32_t max, void *user) void *dst, uint32_t max)
{ {
if (handle == IO_INVALID_HANDLE)
return false;
struct io_operation *op = alloc_op(ioc); struct io_operation *op = alloc_op(ioc);
if (op == NULL) if (op == NULL)
return false; return false;
@@ -58,14 +101,16 @@ bool io_start_recv(struct io_context *ioc, io_handle handle,
if (!ok && GetLastError() != ERROR_IO_PENDING) if (!ok && GetLastError() != ERROR_IO_PENDING)
return false; return false;
op->user = user;
op->type = IO_RECV; op->type = IO_RECV;
return true; return true;
} }
bool io_start_send(struct io_context *ioc, io_handle handle, bool io_start_send(struct io_context *ioc, io_handle handle,
void *src, uint32_t num, void *user) void *src, uint32_t num)
{ {
if (handle == IO_INVALID_HANDLE)
return false;
struct io_operation *op = alloc_op(ioc); struct io_operation *op = alloc_op(ioc);
if (op == NULL) if (op == NULL)
return false; return false;
@@ -76,20 +121,133 @@ bool io_start_send(struct io_context *ioc, io_handle handle,
if (!ok && GetLastError() != ERROR_IO_PENDING) if (!ok && GetLastError() != ERROR_IO_PENDING)
return false; return false;
op->user = user;
op->type = IO_SEND; op->type = IO_SEND;
return true; return true;
} }
bool io_start_accept(struct io_context *ioc, io_handle handle, void *user) bool io_start_accept(struct io_context *ioc, io_handle handle)
{ {
if (handle == IO_INVALID_HANDLE)
return false;
// TODO // TODO
return false; return false;
} }
static unsigned long
convert_timeout(int timeout)
{
if (timeout < 0)
return ~0U;
else
return timeout;
}
static struct io_operation *op_from_ov(struct io_overlap *ov)
{
return (struct io_operation*) ((char*) ov - offsetof(struct io_operation, ov));
}
void io_wait(struct io_context *ioc, struct io_event *ev) void io_wait(struct io_context *ioc, struct io_event *ev)
{ {
// TODO void *user;
struct io_overlap *ov;
unsigned long num;
int ok = GetQueuedCompletionStatus(ioc->handle, &num, (unsigned long*) &user, &ov, convert_timeout(-1));
if (!ok) {
if (ov == NULL) {
/*
* General failure
*/
ev->error = true;
ev->user = NULL; // The user must discriminate between general errors and specific operation errors through the user pointer. Not ideal
ev->type = IO_VOID;
} else {
/*
* Operation failure
*/
struct io_operation *op = op_from_ov(ov);
ev->error = true;
ev->user = user;
ev->type = op->type;
switch (op->type) {
default:break;
case IO_RECV: ev->num = 0; break;
case IO_SEND: ev->num = 0; break;
case IO_ACCEPT: ev->handle = IO_INVALID_HANDLE; break;
}
op->type = IO_VOID; // Mark unused
}
return;
}
struct io_operation *op = op_from_ov(ov);
ev->error = false;
ev->user = user;
ev->type = op->type;
switch (op->type) {
default:break;
case IO_RECV: ev->num = num; break;
case IO_SEND: ev->num = num; break;
case IO_ACCEPT: /* TODO */ break;
}
op->type = IO_VOID; // Mark unused
}
io_handle io_open_file(struct io_context *ioc,
const char *name,
int flags, void *user)
{
unsigned long access = 0;
if (flags & IO_ACCESS_RD) access |= GENERIC_READ;
if (flags & IO_ACCESS_WR) access |= GENERIC_WRITE;
io_handle handle = CreateFileA(name, access, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED, NULL);
if (handle == IO_INVALID_HANDLE)
return IO_INVALID_HANDLE;
if (CreateIoCompletionPort(handle, ioc->handle, user, 0) == NULL) {
CloseHandle(handle);
return IO_INVALID_HANDLE;
}
return handle;
}
io_handle io_create_file(struct io_context *ioc,
const char *name,
int flags, void *user)
{
unsigned long flags2 = 0;
if (flags & IO_CREATE_CANTEXIST)
flags2 = CREATE_NEW;
else {
if (flags & IO_CREATE_OVERWRITE)
flags2 = CREATE_ALWAYS;
else
flags2 = OPEN_ALWAYS;
}
io_handle handle = CreateFileA(name, GENERIC_WRITE, 0, NULL, flags2, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED, NULL);
if (handle == IO_INVALID_HANDLE)
return IO_INVALID_HANDLE;
if (CreateIoCompletionPort(handle, ioc->handle, user, 0) == NULL) {
CloseHandle(handle);
return IO_INVALID_HANDLE;
}
return handle;
} }
#endif #endif
@@ -97,6 +255,8 @@ void io_wait(struct io_context *ioc, struct io_event *ev)
#if IO_PLATFORM_LINUX #if IO_PLATFORM_LINUX
#include <fcntl.h>
#include <assert.h>
#include <string.h> #include <string.h>
#include <unistd.h> #include <unistd.h>
#include <sys/mman.h> #include <sys/mman.h>
@@ -124,8 +284,10 @@ bool io_context_init(struct io_context *ioc,
ioc->ops = ops; ioc->ops = ops;
ioc->max_ops = max_ops; ioc->max_ops = max_ops;
for (uint32_t i = 0; i < max_ops; i++) for (uint32_t i = 0; i < max_ops; i++) {
ioc->ops[i].type = IO_VOID; ioc->ops[i].type = IO_VOID;
ioc->ops[i].handle = IO_INVALID_HANDLE;
}
struct io_uring_params p; struct io_uring_params p;
void *sq_ptr, *cq_ptr; void *sq_ptr, *cq_ptr;
@@ -228,21 +390,57 @@ static bool start_oper(struct io_context *ioc,
return true; return true;
} }
static struct io_operation *alloc_op(struct io_context *ioc) static struct io_operation *alloc_op(struct io_context *ioc,
io_handle handle, void **user)
{ {
for (uint32_t i = 0; i < ioc->max_ops; i++) assert(handle != IO_INVALID_HANDLE);
if (ioc->ops[i].type == IO_VOID)
return &ioc->ops[i]; /*
return NULL; * Look for an empty struct and the struct associated to
* this handle.
*/
struct io_operation *ref = NULL;
struct io_operation *empty = NULL;
for (uint32_t i = 0; i < ioc->max_ops; i++) {
struct io_operation *op = &ioc->ops[i];
if (op->handle == handle) {
ref = op;
if (empty) break;
} else {
if (op->type == IO_VOID && op->handle == IO_INVALID_HANDLE) {
empty = op;
if (ref) break;
}
}
}
assert(ref && empty);
*user = ref->user;
/*
* If the reference slot is unused, use that one,
* else use the first empty one.
*/
if (ref->type == IO_VOID)
return ref;
else
return empty;
} }
bool io_start_recv(struct io_context *ioc, io_handle handle, bool io_start_recv(struct io_context *ioc, io_handle handle,
void *dst, uint32_t max, void *user) void *dst, uint32_t max)
{ {
if (handle == IO_INVALID_HANDLE)
return false;
void *user;
struct io_operation *op; struct io_operation *op;
struct io_uring_sqe sqe; struct io_uring_sqe sqe;
op = alloc_op(ioc); op = alloc_op(ioc, handle, &user);
if (op == NULL) if (op == NULL)
return false; return false;
@@ -255,19 +453,23 @@ bool io_start_recv(struct io_context *ioc, io_handle handle,
if (!start_oper(ioc, sqe)) if (!start_oper(ioc, sqe))
return false; return false;
op->user = user; op->user = user;
op->type = IO_RECV; // Commit operation structure op->type = IO_RECV; // Commit operation structure
return true; return true;
} }
bool io_start_send(struct io_context *ioc, io_handle handle, bool io_start_send(struct io_context *ioc, io_handle handle,
void *src, uint32_t num, void *user) void *src, uint32_t num)
{ {
if (handle == IO_INVALID_HANDLE)
return false;
void *user;
struct io_operation *op; struct io_operation *op;
struct io_uring_sqe sqe; struct io_uring_sqe sqe;
op = alloc_op(ioc); op = alloc_op(ioc, handle, &user);
if (op == NULL) if (op == NULL)
return false; return false;
@@ -277,21 +479,25 @@ bool io_start_send(struct io_context *ioc, io_handle handle,
sqe.addr = (uint64_t) src; sqe.addr = (uint64_t) src;
sqe.len = num; sqe.len = num;
sqe.user_data = (uint64_t) op; sqe.user_data = (uint64_t) op;
if (!start_oper(ioc, sqe)) if (!start_oper(ioc, sqe))
return false; return false;
op->user = user; op->user = user;
op->type = IO_SEND; // Commit operation structure op->type = IO_SEND; // Commit operation structure
return true; return true;
} }
bool io_start_accept(struct io_context *ioc, io_handle handle, void *user) bool io_start_accept(struct io_context *ioc, io_handle handle)
{ {
if (handle == IO_INVALID_HANDLE)
return false;
void *user;
struct io_operation *op; struct io_operation *op;
struct io_uring_sqe sqe; struct io_uring_sqe sqe;
op = alloc_op(ioc); op = alloc_op(ioc, handle, &user);
if (op == NULL) if (op == NULL)
return false; return false;
@@ -351,4 +557,65 @@ void io_wait(struct io_context *ioc, struct io_event *ev)
atomic_store(ioc->completions.head, head+1); atomic_store(ioc->completions.head, head+1);
} }
static struct io_operation*
unassociated_operation_struct(struct io_context *ioc)
{
for (uint32_t i = 0; i < ioc->max_ops; i++) {
struct io_operation *op = &ioc->ops[i];
if (op->type == IO_VOID && op->handle == IO_INVALID_HANDLE)
return op;
}
return NULL;
}
io_handle io_open_file(struct io_context *ioc,
const char *name,
int flags, void *user)
{
struct io_operation *op;
op = unassociated_operation_struct(ioc);
if (op == NULL)
return IO_INVALID_HANDLE;
int flags2 = 0;
if (flags & IO_ACCESS_RD) flags2 |= O_RDONLY;
if (flags & IO_ACCESS_WR) flags2 |= O_WRONLY;
io_handle fd = open(name, flags2);
if (fd < 0)
return IO_INVALID_HANDLE;
op->handle = fd;
op->user = user;
return fd;
}
io_handle io_create_file(struct io_context *ioc,
const char *name,
int flags, void *user)
{
struct io_operation *op;
op = unassociated_operation_struct(ioc);
if (op == NULL)
return IO_INVALID_HANDLE;
int flags2 = O_CREAT | O_WRONLY;
if (flags & IO_CREATE_CANTEXIST)
flags2 |= O_EXCL;
else {
if (flags & IO_CREATE_OVERWRITE)
flags2 |= O_TRUNC;
}
// TODO: is 0666 ok?
int fd = open(name, flags2, 0666);
if (fd < 0)
return IO_INVALID_HANDLE;
op->handle = fd;
op->user = user;
return fd;
}
#endif #endif
+24 -10
View File
@@ -61,9 +61,15 @@ enum io_optype {
}; };
struct io_operation { struct io_operation {
enum io_optype type; // =IO_VOID when the struct is unused
void *user; void *user;
enum io_optype type;
#if IO_PLATFORM_LINUX
io_handle handle;
#endif
#if IO_PLATFORM_WINDOWS #if IO_PLATFORM_WINDOWS
struct io_overlap ov; struct io_overlap ov;
#endif #endif
@@ -145,17 +151,16 @@ void io_context_free(struct io_context *ioc);
* bytes actually written from "dst". * bytes actually written from "dst".
*/ */
bool io_start_recv(struct io_context *ioc, io_handle handle, bool io_start_recv(struct io_context *ioc, io_handle handle,
void *dst, uint32_t max, void *user); void *dst, uint32_t max);
/* /*
* Works like "io_start_recv" but for sending. * Works like "io_start_recv" but for sending.
*/ */
bool io_start_send(struct io_context *ioc, io_handle handle, bool io_start_send(struct io_context *ioc, io_handle handle,
void *src, uint32_t num, void *user); void *src, uint32_t num);
bool io_start_accept(struct io_context *ioc, io_handle handle, bool io_start_accept(struct io_context *ioc, io_handle handle);
void *user);
/* /*
* Wait for the completion of an I/O event. * Wait for the completion of an I/O event.
@@ -163,11 +168,20 @@ bool io_start_accept(struct io_context *ioc, io_handle handle,
void io_wait(struct io_context *ioc, void io_wait(struct io_context *ioc,
struct io_event *ev); struct io_event *ev);
/*
* Flags for "io_open_file" and "io_create_file"
*/
enum {
IO_ACCESS_RD = 1 << 0,
IO_ACCESS_WR = 1 << 1,
IO_CREATE_OVERWRITE = 1 << 2,
IO_CREATE_CANTEXIST = 1 << 3,
};
io_handle io_open_file(struct io_context *ioc, io_handle io_open_file(struct io_context *ioc,
const char *name, int flags); const char *name, int flags,
void *user);
io_handle io_create_file(struct io_context *ioc, io_handle io_create_file(struct io_context *ioc,
const char *name, int flags); const char *name, int flags,
void *user);
io_handle io_listen(struct io_context *ioc,
const char *addr, int port);