From 79a8f93572f6bd89f0b6ef9da7a79bbe86cc1dd2 Mon Sep 17 00:00:00 2001 From: Francesco Cozzuto Date: Mon, 8 Apr 2024 00:38:13 +0200 Subject: [PATCH] Add io_create_file, io_open_file --- .gitignore | 3 +- README.txt | 2 +- example.c | 21 +++- io.c | 323 ++++++++++++++++++++++++++++++++++++++++++++++++----- io.h | 34 ++++-- 5 files changed, 340 insertions(+), 43 deletions(-) diff --git a/.gitignore b/.gitignore index 415402a..533f04e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ example -example.exe \ No newline at end of file +example.exe +file_*.txt \ No newline at end of file diff --git a/README.txt b/README.txt index d235eed..7b9be86 100644 --- a/README.txt +++ b/README.txt @@ -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 :) -It's released in the public domain, so you can just adapt the code for your own projects. Pull requests would be appreciated though. \ No newline at end of file +It's released in the public domain, so you can just adapt the code for your own projects. Pull requests would be appreciated though. diff --git a/example.c b/example.c index 3e69019..974f10d 100644 --- a/example.c +++ b/example.c @@ -1,6 +1,8 @@ #include #include "io.h" +#define NUM_OPS 3 + int main() { struct io_operation ops[100]; @@ -8,13 +10,26 @@ int main() if (!io_context_init(&ioc, ops, sizeof(ops)/sizeof(ops[0]))) 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"; - for (int i = 0; i < 10; i++) { - if (!io_start_send(&ioc, io_get_stdout(), msg, sizeof(msg), NULL)) + for (int i = 0; i < NUM_OPS; i++) { + if (io_start_send(&ioc, files[i], msg, sizeof(msg)-1)) + started++; + else fprintf(stderr, "ERROR\n"); } - for (int i = 0; i < 10; i++) { + for (int i = 0; i < started; i++) { struct io_event ev; io_wait(&ioc, &ev); fprintf(stderr, "CONCLUDED\n"); diff --git a/io.c b/io.c index 60418d0..57f5f5b 100644 --- a/io.c +++ b/io.c @@ -9,14 +9,51 @@ * 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 -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); +struct security_attr { + unsigned long size; + void *desc; + int inherit_handle; +}; + +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, 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); if (handle == IO_INVALID_HANDLE) return false; + + for (uint32_t i = 0; i < max_ops; i++) + ops[i].type = IO_VOID; ioc->handle = handle; 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, - 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); if (op == NULL) return false; @@ -58,14 +101,16 @@ bool io_start_recv(struct io_context *ioc, io_handle handle, if (!ok && GetLastError() != ERROR_IO_PENDING) return false; - op->user = user; op->type = IO_RECV; return true; } 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); if (op == NULL) return false; @@ -76,20 +121,133 @@ bool io_start_send(struct io_context *ioc, io_handle handle, if (!ok && GetLastError() != ERROR_IO_PENDING) return false; - op->user = user; op->type = IO_SEND; 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 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) { - // 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 @@ -97,6 +255,8 @@ void io_wait(struct io_context *ioc, struct io_event *ev) #if IO_PLATFORM_LINUX +#include +#include #include #include #include @@ -124,8 +284,10 @@ bool io_context_init(struct io_context *ioc, ioc->ops = 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].handle = IO_INVALID_HANDLE; + } struct io_uring_params p; void *sq_ptr, *cq_ptr; @@ -228,21 +390,57 @@ static bool start_oper(struct io_context *ioc, 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++) - if (ioc->ops[i].type == IO_VOID) - return &ioc->ops[i]; - return NULL; + assert(handle != IO_INVALID_HANDLE); + + /* + * 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, - 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_uring_sqe sqe; - op = alloc_op(ioc); + op = alloc_op(ioc, handle, &user); if (op == NULL) return false; @@ -255,19 +453,23 @@ bool io_start_recv(struct io_context *ioc, io_handle handle, if (!start_oper(ioc, sqe)) return false; - - op->user = user; + + op->user = user; op->type = IO_RECV; // Commit operation structure return true; } 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_uring_sqe sqe; - op = alloc_op(ioc); + op = alloc_op(ioc, handle, &user); if (op == NULL) return false; @@ -277,21 +479,25 @@ bool io_start_send(struct io_context *ioc, io_handle handle, sqe.addr = (uint64_t) src; sqe.len = num; sqe.user_data = (uint64_t) op; - + if (!start_oper(ioc, sqe)) return false; - + op->user = user; op->type = IO_SEND; // Commit operation structure 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_uring_sqe sqe; - op = alloc_op(ioc); + op = alloc_op(ioc, handle, &user); if (op == NULL) return false; @@ -351,4 +557,65 @@ void io_wait(struct io_context *ioc, struct io_event *ev) 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 diff --git a/io.h b/io.h index cc0022a..c316bfd 100644 --- a/io.h +++ b/io.h @@ -61,9 +61,15 @@ enum io_optype { }; struct io_operation { - enum io_optype type; // =IO_VOID when the struct is unused + void *user; + enum io_optype type; + + #if IO_PLATFORM_LINUX + io_handle handle; + #endif + #if IO_PLATFORM_WINDOWS struct io_overlap ov; #endif @@ -145,17 +151,16 @@ void io_context_free(struct io_context *ioc); * bytes actually written from "dst". */ 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. */ 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, - void *user); +bool io_start_accept(struct io_context *ioc, io_handle handle); /* * 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, 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, - const char *name, int flags); + const char *name, int flags, + void *user); io_handle io_create_file(struct io_context *ioc, - const char *name, int flags); - -io_handle io_listen(struct io_context *ioc, - const char *addr, int port); \ No newline at end of file + const char *name, int flags, + void *user);