diff --git a/build.sh b/build.sh new file mode 100644 index 0000000..321e24f --- /dev/null +++ b/build.sh @@ -0,0 +1,2 @@ +gcc example.c io.c -o example -Wall -Wextra -ggdb +gcc example2.c io.c -o example2 -Wall -Wextra -ggdb diff --git a/example.c b/example.c index 1e8caf4..a458409 100644 --- a/example.c +++ b/example.c @@ -1,5 +1,5 @@ #include -#include "io2.h" +#include "io.h" #define NUM_OPS 3 diff --git a/io.c b/io.c index bd18184..2d5d9c4 100644 --- a/io.c +++ b/io.c @@ -1,348 +1,80 @@ - #include "io.h" +#include + #if IO_PLATFORM_WINDOWS - -#include - #define WIN32_LEAN_AND_MEAN #include #include #include - -bool io_global_init(void) -{ - WSADATA data; - int ret = WSAStartup(MAKEWORD(2, 2), &data); - return ret == NO_ERROR; -} - -void io_global_free(void) -{ - WSACleanup(); -} - -bool io_context_init(struct io_context *ioc, - struct io_resource *res, - struct io_operation *ops, - uint16_t max_res, - uint16_t max_ops) -{ - io_raw_handle raw_handle = CreateIoCompletionPort(IO_INVALID_HANDLE, NULL, 0, 1); - if (raw_handle == INVALID_HANDLE_VALUE) - return false; - - for (uint32_t i = 0; i < max_ops; i++) - ops[i].type = IO_VOID; - - ioc->raw_handle = raw_handle; - ioc->max_res = max_res; - ioc->max_ops = max_ops; - ioc->res = res; - ioc->ops = ops; - return true; -} - -void io_context_free(struct io_context *ioc) -{ - for (uint32_t i = 0; i < ioc->max_res; i++) - if (ioc->res[i].type != IO_RES_VOID) - io_close(ioc, i); - CloseHandle(ioc->raw_handle); -} - -void io_close(struct io_context *ioc, - io_handle handle) -{ - // TODO: Check handle - - struct io_resource *res = &ioc->res[handle]; - - if (res->type == IO_RES_SOCKET) - closesocket(res->raw_handle); - else - CloseHandle(res->raw_handle); - - uint16_t op_idx = res->head_operation; - while (op_idx != -1) { - struct io_operation *op; - op = &ioc->ops[op_idx]; - op->type = IO_VOID; - op_idx = op->next_operation; - } - - res->typ = IO_RES_VOID; -} - -static struct io_operation *alloc_op(struct io_context *ioc) -{ - for (uint32_t i = 0; i < ioc->max_ops; i++) - if (ioc->ops[i].type == IO_VOID) - return &ioc->ops[i]; - return NULL; -} - -bool io_start_recv(struct io_context *ioc, io_handle handle, - void *dst, uint32_t max, void *user) -{ - if (handle == IO_INVALID_HANDLE) - return false; - - struct io_resource *res = &ioc->res[handle]; - - struct io_operation *op; - op = alloc_op(ioc); - if (op == NULL) - return false; - - memset(&op->ov, 0, sizeof(struct io_overlap)); - - int ok = ReadFile(handle, dst, max, NULL, &op->ov); - if (!ok && GetLastError() != ERROR_IO_PENDING) - return false; - - op->type = IO_RECV; - op->handle = handle; - op->nextop = res->headop; - res->headop = op - ioc->ops; - return true; -} - -bool io_start_send(struct io_context *ioc, io_handle handle, - void *src, uint32_t num) -{ - if (handle == IO_INVALID_HANDLE) - return false; - - struct io_operation *op = alloc_op(ioc); - if (op == NULL) - return false; - - memset(&op->ov, 0, sizeof(struct io_overlap)); - - int ok = WriteFile(handle, src, num, NULL, &op->ov); - if (!ok && GetLastError() != ERROR_IO_PENDING) - return false; - - op->type = IO_SEND; - op->handle = handle; - op->nextop = res->headop; - res->headop = op - ioc->ops; - return true; -} - -bool io_start_accept(struct io_context *ioc, io_handle handle) -{ - if (handle == IO_INVALID_HANDLE) - return false; - - struct io_resource *rs = &ioc->res[handle]; - - struct io_operation *op = alloc_op(ioc); - if (op == NULL) - return false; - - memset(&op->ov, 0, sizeof(struct io_overlap)); - - SOCKET new_raw_handle = socket(AF_INET, SOCK_STREAM, 0); - if (new_raw_handle == INVALID_HANDLE_VALUE) - return false; - - LPFN_ACCEPTEX lpfnAcceptEx = NULL; - GUID GuidAcceptEx = WSAID_ACCEPTEX; - - unsigned long num; - int ret = WSAIoctl(rs->raw_handle, - SIO_GET_EXTENSION_FUNCTION_POINTER, - &GuidAcceptEx, sizeof(GuidAcceptEx), - &lpfnAcceptEx, sizeof(lpfnAcceptEx), - &num, NULL, NULL); - if (ret == SOCKET_ERROR) { - closesocket(new_raw_handle); - return false; - } - - _Static_assert(IO_SOCKADDR_IN_SIZE == sizeof(struct sockaddr_in)); - - int ok = lpfnAcceptEx(handle2, rs->raw_handle, op->accept_buffer, - sizeof(op->accept_buffer) - ((sizeof(struct sockaddr_in) + 16) * 2), - sizeof(struct sockaddr_in) + 16, - sizeof(struct sockaddr_in) + 16, - &num, &op->ov); - if (!ok) { - closesocket(new_raw_handle); - return false; - } - - op->type = IO_ACCEPT; - op->handle = handle; - op->nextop = res->headop; - res->headop = op - ioc->ops; - op->accept_handle = new_raw_handle; - return true; -} - -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 *user; - struct io_overlap *ov; - unsigned long num; - int ok = GetQueuedCompletionStatus(ioc->raw_handle, &num, &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: - closesocket(op->accept_handle); - op->accept_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: op->accept_handle; 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; -} - -io_handle io_listen(struct io_context *ioc, - const char *addr, int port, - void *user) -{ - return IO_INVALID_HANDLE; -} - #endif - #if IO_PLATFORM_LINUX - #include -#include #include #include -#include +#include #include #include #include +#include +#include +#endif + +#define IO_DEBUG + +#ifdef IO_DEBUG +#include +#define DEBUG_LOG(fmt, ...) fprintf(stderr, "Log :: %s:%d :: " fmt, __FILE__, __LINE__, ## __VA_ARGS__); +#else +#define DEBUG_LOG(...) +#endif bool io_global_init(void) { + #if IO_PLATFORM_WINDOWS + WSADATA data; + return WSAStartup(MAKEWORD(2, 2), &data) == NO_ERROR; + #endif + + #if IO_PLATFORM_LINUX return true; + #endif } void io_global_free(void) { + #if IO_PLATFORM_WINDOWS + WSACleanup(); + #endif } +#if IO_PLATFORM_WINDOWS +bool io_init_windows(struct io_context *ioc) +{ + io_os_handle os_handle = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 1); + if (os_handle == INVALID_HANDLE_VALUE) + return false; + + ioc->os_handle = os_handle; + return true; +} +#endif + +#if IO_PLATFORM_WINDOWS +void io_free_windows(struct io_context *ioc) +{ + CloseHandle(ioc->os_handle); +} +#endif + +#if IO_PLATFORM_LINUX static int io_uring_setup(unsigned entries, struct io_uring_params *p) { return (int) syscall(SYS_io_uring_setup, entries, p); } - static int io_uring_enter(int ring_fd, unsigned int to_submit, unsigned int min_complete, unsigned int flags) @@ -350,19 +82,12 @@ io_uring_enter(int ring_fd, unsigned int to_submit, return (int) syscall(SYS_io_uring_enter, ring_fd, to_submit, min_complete, flags, NULL, 0); } +#endif -bool io_context_init(struct io_context *ioc, - struct io_operation *ops, - uint32_t max_ops) +#if IO_PLATFORM_LINUX +bool io_init_linux(struct io_context *ioc) { - ioc->ops = ops; - ioc->max_ops = max_ops; - - 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; /* See io_uring_setup(2) for io_uring_params.flags you can set */ @@ -371,7 +96,7 @@ bool io_context_init(struct io_context *ioc, if (fd < 0) return false; - ioc->handle = fd; + ioc->os_handle = fd; /* * io_uring communication happens via 2 shared kernel-user space ring @@ -435,26 +160,18 @@ bool io_context_init(struct io_context *ioc, ioc->completions.limit = p.cq_entries; return true; } +#endif -void io_context_free(struct io_context *ioc) +#if IO_PLATFORM_LINUX +void io_free_linux(struct io_context *ioc) { - close(ioc->handle); + close(ioc->os_handle); } +#endif -void io_close(struct io_context *ioc, - io_handle handle) -{ - for (uint32_t i = 0; i < ioc->max_ops; i++) - if (ioc->ops[i].handle == handle) { - assert(ioc->ops[i].type == IO_VOID); - ioc->ops[i].handle = IO_INVALID_HANDLE; - ioc->ops[i].user = NULL; - } - close(handle); -} - -static bool start_oper(struct io_context *ioc, - struct io_uring_sqe sqe) +#if IO_PLATFORM_LINUX +static bool start_uring_op(struct io_context *ioc, + struct io_uring_sqe sqe) { unsigned int mask = *ioc->submissions.mask; unsigned int tail = atomic_load(ioc->submissions.tail); @@ -469,221 +186,492 @@ static bool start_oper(struct io_context *ioc, atomic_store(ioc->submissions.tail, tail+1); - int ret = io_uring_enter(ioc->handle, 1, 0, 0); + int ret = io_uring_enter(ioc->os_handle, 1, 0, 0); if (ret < 0) return false; return true; } +#endif -static struct io_operation *alloc_op(struct io_context *ioc, - io_handle handle, void **user) +static void clear_res(struct io_resource *res) { - assert(handle != IO_INVALID_HANDLE); + res->type = IO_RES_VOID; + res->pending = 0; - /* - * Look for an empty struct and the struct associated to - * this handle. - */ - struct io_operation *ref = NULL; - struct io_operation *empty = NULL; + #if IO_PLATFORM_WINDOWS + res->os_handle = INVALID_HANDLE_VALUE; + #endif - 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; + #if IO_PLATFORM_LINUX + res->os_handle = -1; + #endif } -bool io_start_recv(struct io_context *ioc, io_handle handle, - void *dst, uint32_t max) +bool io_init(struct io_context *ioc, + struct io_resource *res, + struct io_operation *ops, + uint16_t max_res, + uint16_t max_ops) { - if (handle == IO_INVALID_HANDLE) - return false; + ioc->res = res; + ioc->ops = ops; + ioc->max_res = max_res; + ioc->max_ops = max_ops; - void *user; - struct io_operation *op; - struct io_uring_sqe sqe; - - op = alloc_op(ioc, handle, &user); - if (op == NULL) - return false; - - memset(&sqe, 0, sizeof(sqe)); - sqe.opcode = IORING_OP_READ; - sqe.fd = (int) handle; - sqe.addr = (uint64_t) dst; - sqe.len = max; - sqe.user_data = (uint64_t) op; + for (uint16_t i = 0; i < max_res; i++) { + res[i].gen = 0; + clear_res(&res[i]); + } - if (!start_oper(ioc, sqe)) - return false; + for (uint16_t i = 0; i < max_ops; i++) + ops[i].type = IO_VOID; - op->user = user; - op->type = IO_RECV; // Commit operation structure - return true; + #if IO_PLATFORM_WINDOWS + return io_init_windows(ioc); + #endif + + #if IO_PLATFORM_LINUX + return io_init_linux(ioc); + #endif } -bool io_start_send(struct io_context *ioc, io_handle handle, - void *src, uint32_t num) +static void +close_internal(struct io_context *ioc, + struct io_resource *res) { - if (handle == IO_INVALID_HANDLE) - return false; + #if IO_PLATFORM_WINDOWS + if (res->type == IO_RES_SOCKET) + closesocket((SOCKET) res->os_handle); + else + CloseHandle(res->os_handle); + #elif IO_PLATFORM_LINUX + close(res->os_handle); + #endif - void *user; - struct io_operation *op; - struct io_uring_sqe sqe; - - op = alloc_op(ioc, handle, &user); - if (op == NULL) - return false; - - memset(&sqe, 0, sizeof(sqe)); - sqe.opcode = IORING_OP_WRITE; - sqe.fd = (int) 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) -{ - if (handle == IO_INVALID_HANDLE) - return false; - - void *user; - struct io_operation *op; - struct io_uring_sqe sqe; - - op = alloc_op(ioc, handle, &user); - if (op == NULL) - return false; - - memset(&sqe, 0, sizeof(sqe)); - sqe.opcode = IORING_OP_ACCEPT; - sqe.fd = (int) handle; - sqe.user_data = (uint64_t) op; - - if (!start_oper(ioc, sqe)) - return false; - - op->user = user; - op->type = IO_ACCEPT; // Commit operation structure - return true; -} - -void io_wait(struct io_context *ioc, struct io_event *ev) -{ - /* --- Read barrier --- */ - unsigned int head = atomic_load(ioc->completions.head); - unsigned int tail = atomic_load(ioc->completions.tail); - - if (head == tail) { - - /* - * Completion queue is empty. Wait for some operations to complete. - */ - int ret = io_uring_enter(ioc->handle, 0, 1, IORING_ENTER_GETEVENTS); - if (ret < 0) { - ev->error = true; - return; + // Mark associated operation structures as unused + for (uint16_t i = 0, marked = 0; marked < res->pending; i++) { + struct io_operation *op; + op = &ioc->ops[i]; + if (op->type != IO_VOID && op->res == res) { + op->type = IO_VOID; + op->res = NULL; + marked++; } } - struct io_uring_cqe *cqe; - struct io_operation *op; + clear_res(res); - cqe = &ioc->completions.entries[head & (*ioc->completions.mask)]; + res->gen++; + if (res->gen == UINT16_MAX) + res->gen = 0; +} - op = (void*) cqe->user_data; - ev->user = op->user; - ev->type = op->type; - ev->error = cqe->res < 0; +static struct io_resource* +res_from_handle(struct io_context *ioc, io_handle handle) +{ + if (handle == IO_INVALID) + return NULL; - if (ev->error == false) { - switch (op->type) { - case IO_VOID: /* UNREACHABLE */ break; - case IO_RECV: ev->num = cqe->res; break; - case IO_SEND: ev->num = cqe->res; break; - case IO_ACCEPT: ev->handle = cqe->res; break; - } - } + static_assert(sizeof(uint32_t) == sizeof(io_handle)); + uint16_t idx = handle & 0xFFFF; + uint16_t gen = handle >> 16; + if (idx >= ioc->max_res) + return NULL; - op->type = IO_VOID; // Mark unused + struct io_resource *res = &ioc->res[idx]; + if (res->gen != gen) + return NULL; + + return res; +} - /* --- write barrier --- */ - atomic_store(ioc->completions.head, head+1); +static io_handle handle_from_res(struct io_context *ioc, + struct io_resource *res) +{ + io_handle handle; + static_assert(sizeof(uint32_t) == sizeof(io_handle)); + + uint32_t idx = res - ioc->res; + uint32_t gen = res->gen; + handle = idx | (gen << 16); + + assert(gen != UINT16_MAX); + assert(handle != IO_INVALID); + + return handle; +} + +void io_close(struct io_context *ioc, + io_handle handle) +{ + struct io_resource *res; + + res = res_from_handle(ioc, handle); + if (res == NULL) + return; + + close_internal(ioc, res); +} + +void io_free(struct io_context *ioc) +{ + for (uint16_t i = 0; i < ioc->max_res; i++) + if (ioc->res[i].type != IO_RES_VOID) + close_internal(ioc, &ioc->res[i]); + + #if IO_PLATFORM_WINDOWS + io_free_windows(ioc); + #endif + + #if IO_PLATFORM_LINUX + io_free_linux(ioc); + #endif } static struct io_operation* -unassociated_operation_struct(struct io_context *ioc) +find_unused_op(struct io_context *ioc) { - for (uint32_t i = 0; i < ioc->max_ops; i++) { + for (uint16_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) + if (op->type == IO_VOID) return op; } return NULL; } -io_handle io_open_file(struct io_context *ioc, - const char *name, - int flags, void *user) +#if IO_PLATFORM_LINUX +static bool io_recv_linux(struct io_context *ioc, + struct io_resource *res, + struct io_operation *op, + void *dst, uint32_t max) +{ + struct io_uring_sqe sqe; + memset(&sqe, 0, sizeof(sqe)); + sqe.opcode = IORING_OP_READ; + sqe.fd = (int) res->os_handle; + sqe.addr = (uint64_t) dst; + sqe.len = max; + sqe.user_data = (uint64_t) op; + return start_uring_op(ioc, sqe); +} +#endif + +#if IO_PLATFORM_LINUX +static bool io_send_linux(struct io_context *ioc, + struct io_resource *res, + struct io_operation *op, + void *src, uint32_t num) +{ + struct io_uring_sqe sqe; + memset(&sqe, 0, sizeof(sqe)); + sqe.opcode = IORING_OP_WRITE; + sqe.fd = (int) res->os_handle; + sqe.addr = (uint64_t) src; + sqe.len = num; + sqe.user_data = (uint64_t) op; + return start_uring_op(ioc, sqe); +} +#endif + +#if IO_PLATFORM_LINUX +static bool io_accept_linux(struct io_context *ioc, + struct io_resource *res, + struct io_operation *op, + io_os_handle os_handle) +{ + (void) ioc; + (void) res; + + struct io_uring_sqe sqe; + memset(&sqe, 0, sizeof(sqe)); + sqe.opcode = IORING_OP_ACCEPT; + sqe.fd = (int) os_handle; + sqe.user_data = (uint64_t) op; + return start_uring_op(ioc, sqe); +} +#endif + +#if IO_PLATFORM_WINDOWS +static bool io_recv_windows(struct io_context *ioc, + struct io_resource *res, + struct io_operation *op, + void *dst, uint32_t max) +{ + (void) ioc; + + memset(&op->ov, 0, sizeof(struct io_os_overlap)); + int ok = ReadFile(res->os_handle, dst, max, NULL, (OVERLAPPED*) &op->ov); + if (!ok && GetLastError() != ERROR_IO_PENDING) + return false; + return true; +} +#endif + +#if IO_PLATFORM_WINDOWS +static bool io_send_windows(struct io_context *ioc, + struct io_resource *res, + struct io_operation *op, + void *src, uint32_t num) +{ + (void) ioc; + + memset(&op->ov, 0, sizeof(struct io_os_overlap)); + int ok = ReadFile(res->os_handle, src, num, NULL, (OVERLAPPED*) &op->ov); + if (!ok && GetLastError() != ERROR_IO_PENDING) + return false; + return true; +} +#endif + +#if IO_PLATFORM_WINDOWS +static bool io_accept_windows(struct io_context *ioc, + struct io_resource *res, + struct io_operation *op, + io_os_handle os_handle) +{ + (void) ioc; + (void) res; + + memset(&op->ov, 0, sizeof(struct io_os_overlap)); + + SOCKET new_os_handle = socket(AF_INET, SOCK_STREAM, 0); + if (new_os_handle == INVALID_SOCKET) + return false; + + LPFN_ACCEPTEX lpfnAcceptEx = res->acceptfn; + + _Static_assert(IO_SOCKADDR_IN_SIZE == sizeof(struct sockaddr_in)); + + unsigned long num; + int ok = lpfnAcceptEx((SOCKET) os_handle, new_os_handle, res->accept_buffer, + sizeof(res->accept_buffer) - ((sizeof(struct sockaddr_in) + 16) * 2), + sizeof(struct sockaddr_in) + 16, + sizeof(struct sockaddr_in) + 16, + &num, (OVERLAPPED*) &op->ov); + if (!ok && GetLastError() != ERROR_IO_PENDING) { + DEBUG_LOG("AcceptEx failure\n"); + closesocket(new_os_handle); + return false; + } + + op->accepted = (io_os_handle) new_os_handle; + return true; +} +#endif + +bool io_recv(struct io_context *ioc, + void *user, io_handle handle, + void *dst, uint32_t max) { struct io_operation *op; - op = unassociated_operation_struct(ioc); + struct io_resource *res; + + res = res_from_handle(ioc, handle); + if (res == NULL) + return false; + + op = find_unused_op(ioc); if (op == NULL) - return IO_INVALID_HANDLE; + return false; + + enum io_optype type = IO_RECV; + + #if IO_PLATFORM_LINUX + if (!io_recv_linux(ioc, res, op, dst, max)) + return false; + #endif + + #if IO_PLATFORM_WINDOWS + if (!io_recv_windows(ioc, res, op, dst, max)) + return false; + #endif + + res->pending++; + op->res = res; + op->type = type; + op->user = user; + return true; +} + +bool io_send(struct io_context *ioc, + void *user, io_handle handle, + void *src, uint32_t num) +{ + struct io_operation *op; + struct io_resource *res; + + res = res_from_handle(ioc, handle); + if (res == NULL) + return false; + + op = find_unused_op(ioc); + if (op == NULL) + return false; + + enum io_optype type = IO_SEND; + + #if IO_PLATFORM_LINUX + if (!io_send_linux(ioc, res, op, src, num)) + return false; + #endif + + #if IO_PLATFORM_WINDOWS + if (!io_send_windows(ioc, res, op, src, num)) + return false; + #endif + + res->pending++; + op->res = res; + op->type = type; + op->user = user; + return true; +} + +bool io_accept(struct io_context *ioc, + void *user, io_handle handle) +{ + struct io_operation *op; + struct io_resource *res; + + res = res_from_handle(ioc, handle); + if (res == NULL) + return false; + + op = find_unused_op(ioc); + if (op == NULL) + return false; + + enum io_optype type = IO_ACCEPT; + + #if IO_PLATFORM_LINUX + if (!io_accept_linux(ioc, res, op, res->os_handle)) + return false; + #endif + + #if IO_PLATFORM_WINDOWS + if (!io_accept_windows(ioc, res, op, res->os_handle)) + return false; + #endif + + res->pending++; + op->res = res; + op->type = type; + op->user = user; + return true; +} + +static struct io_resource* +find_unused_res(struct io_context *ioc) +{ + for (uint16_t i = 0; i < ioc->max_res; i++) { + struct io_resource *res = &ioc->res[i]; + if (res->type == IO_RES_VOID) + return res; + } + return NULL; +} + +#if IO_PLATFORM_WINDOWS +static io_os_handle +io_open_file_windows(struct io_context *ioc, + const char *file, int flags) +{ + unsigned long access = 0; + if (flags & IO_ACCESS_RD) access |= GENERIC_READ; + if (flags & IO_ACCESS_WR) access |= GENERIC_WRITE; + + io_os_handle os_handle = CreateFileA(file, access, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED, NULL); + if (os_handle == INVALID_HANDLE_VALUE) + return INVALID_HANDLE_VALUE; + + if (CreateIoCompletionPort(os_handle, ioc->os_handle, 0, 0) == NULL) { + CloseHandle(os_handle); + return INVALID_HANDLE_VALUE; + } + + return os_handle; +} +#endif + +#if IO_PLATFORM_LINUX +static io_os_handle +io_open_file_linux(struct io_context *ioc, + const char *file, int flags) +{ + (void) ioc; 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; + return open(file, flags2); +} +#endif - op->handle = fd; - op->user = user; - return fd; +io_handle io_open_file(struct io_context *ioc, + const char *file, int flags) +{ + io_os_handle os_handle; + struct io_resource *res; + + res = find_unused_res(ioc); + if (res == NULL) + return IO_INVALID; + + #if IO_PLATFORM_WINDOWS + os_handle = io_open_file_windows(ioc, file, flags); + if (os_handle == INVALID_HANDLE_VALUE) + return IO_INVALID; + #endif + + #if IO_PLATFORM_LINUX + os_handle = io_open_file_linux(ioc, file, flags); + if (os_handle < 0) + return IO_INVALID; + #endif + + res->type = IO_RES_FILE; + res->pending = 0; + res->os_handle = os_handle; + return handle_from_res(ioc, res); } -io_handle io_create_file(struct io_context *ioc, - const char *name, - int flags, void *user) +#if IO_PLATFORM_WINDOWS +static io_os_handle +io_create_file_windows(struct io_context *ioc, + const char *file, int flags) { - struct io_operation *op; - op = unassociated_operation_struct(ioc); - if (op == NULL) - return IO_INVALID_HANDLE; + 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_os_handle os_handle = CreateFileA(file, GENERIC_WRITE, 0, NULL, flags2, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED, NULL); + if (os_handle == INVALID_HANDLE_VALUE) + return INVALID_HANDLE_VALUE; + + if (CreateIoCompletionPort(os_handle, ioc->os_handle, 0, 0) == NULL) { + CloseHandle(os_handle); + return INVALID_HANDLE_VALUE; + } + + return os_handle; +} +#endif + +#if IO_PLATFORM_LINUX +static io_os_handle +io_create_file_linux(struct io_context *ioc, + const char *file, int flags) +{ + (void) ioc; int flags2 = O_CREAT | O_WRONLY; @@ -695,60 +683,315 @@ io_handle io_create_file(struct io_context *ioc, } // TODO: is 0666 ok? - int fd = open(name, flags2, 0666); - if (fd < 0) - return IO_INVALID_HANDLE; + return open(file, flags2, 0666); +} +#endif - op->handle = fd; - op->user = user; - return fd; +io_handle io_create_file(struct io_context *ioc, + const char *file, int flags) +{ + io_os_handle os_handle; + struct io_resource *res; + + res = find_unused_res(ioc); + if (res == NULL) + return IO_INVALID; + + #if IO_PLATFORM_WINDOWS + os_handle = io_create_file_windows(ioc, file, flags); + if (os_handle == INVALID_HANDLE_VALUE) + return IO_INVALID; + #endif + + #if IO_PLATFORM_LINUX + os_handle = io_create_file_linux(ioc, file, flags); + if (os_handle < 0) + return IO_INVALID; + #endif + + res->type = IO_RES_FILE; + res->pending = 0; + res->os_handle = os_handle; + return handle_from_res(ioc, res); } -io_handle io_listen(struct io_context *ioc, - const char *addr, int port, - void *user) +io_handle io_start_server(struct io_context *ioc, + const char *addr, int port) { if (port < 1 || port > UINT16_MAX) - return IO_INVALID_HANDLE; + return IO_INVALID; struct in_addr addr2; if (addr == NULL) addr2.s_addr = INADDR_ANY; else { if (1 != inet_pton(AF_INET, addr, &addr2)) - return IO_INVALID_HANDLE; + return IO_INVALID; } - struct io_operation *op; - op = unassociated_operation_struct(ioc); - if (op == NULL) - return IO_INVALID_HANDLE; - - int fd = socket(AF_INET, SOCK_STREAM, 0); - if (fd < 0) - return IO_INVALID_HANDLE; + #if IO_PLATFORM_WINDOWS + SOCKET fd; + #endif + #if IO_PLATFORM_LINUX + int fd; + #endif + + struct io_resource *res; + + res = find_unused_res(ioc); + if (res == NULL) + return IO_INVALID; + + fd = socket(AF_INET, SOCK_STREAM, 0); + + #if IO_PLATFORM_LINUX + if (fd < 0) + return IO_INVALID; + #endif + + #if IO_PLATFORM_WINDOWS + if (fd == INVALID_SOCKET) + return IO_INVALID; + #endif + int one = 1; - setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)); + setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (char*) &one, sizeof(one)); struct sockaddr_in buf; buf.sin_family = AF_INET; buf.sin_port = htons(port); buf.sin_addr = addr2; if (bind(fd, (struct sockaddr*) &buf, sizeof(buf))) { + #if IO_PLATFORM_WINDOWS + closesocket(fd); + #else close(fd); - return IO_INVALID_HANDLE; + #endif + return IO_INVALID; } int backlog = 32; if (listen(fd, backlog)) { + #if IO_PLATFORM_WINDOWS + closesocket(fd); + #else close(fd); - return IO_INVALID_HANDLE; + #endif + return IO_INVALID; } - op->handle = fd; - op->user = user; - return (io_handle) fd; + #if IO_PLATFORM_WINDOWS + LPFN_ACCEPTEX lpfnAcceptEx = NULL; + GUID GuidAcceptEx = WSAID_ACCEPTEX; + unsigned long num; + int ret = WSAIoctl(fd, + SIO_GET_EXTENSION_FUNCTION_POINTER, + &GuidAcceptEx, sizeof(GuidAcceptEx), + &lpfnAcceptEx, sizeof(lpfnAcceptEx), + &num, NULL, NULL); + if (ret == SOCKET_ERROR) { + DEBUG_LOG("WSAIoctl failure\n"); + closesocket(fd); + return IO_INVALID; + } + if (CreateIoCompletionPort((HANDLE) fd, ioc->os_handle, 0, 0) == NULL) { + closesocket(fd); + return IO_INVALID; + } + res->acceptfn = lpfnAcceptEx; + #endif + + res->type = IO_RES_SOCKET; + res->pending = 0; + res->os_handle = (io_os_handle) fd; + return handle_from_res(ioc, res); } +#if IO_PLATFORM_WINDOWS +static struct io_operation* +op_from_ov(struct io_os_overlap *ov) +{ + return (struct io_operation*) ((char*) ov - offsetof(struct io_operation, ov)); +} #endif + +#if IO_PLATFORM_WINDOWS +static void +io_wait_windows(struct io_context *ioc, + struct io_event *ev) +{ + int timeout = -1; + + unsigned long timeout2; + if (timeout < 0) + timeout2 = INFINITE; + else + timeout2 = timeout; + + unsigned long long unused; + struct io_os_overlap *ov; + unsigned long num; + int ok = GetQueuedCompletionStatus(ioc->os_handle, &num, &unused, (OVERLAPPED**) &ov, timeout2); + + if (!ok) { + + if (ov == NULL) { + + /* + * General failure + */ + + ev->evtype = IO_ERROR; + ev->optype = IO_VOID; + ev->handle = IO_INVALID; + ev->user = NULL; + + } else { + + /* + * Operation failure + */ + + struct io_operation *op = op_from_ov(ov); + struct io_resource *res = op->res; + + ev->evtype = IO_ABORT; + ev->optype = op->type; + ev->handle = handle_from_res(ioc, res); + ev->user = op->user; + + if (op->type == IO_ACCEPT) + closesocket((SOCKET) op->accepted); + + op->type = IO_VOID; // Mark unused + + assert(res->pending > 0); + res->pending--; + } + return; + } + + struct io_operation *op = op_from_ov(ov); + struct io_resource *res = op->res; + + ev->evtype = IO_COMPLETE; + ev->optype = op->type; + ev->handle = handle_from_res(ioc, res); + ev->user = op->user; + + switch (op->type) { + + case IO_RECV: + case IO_SEND: + ev->num = num; + break; + + case IO_ACCEPT: + { + struct io_resource *res2; + + res2 = find_unused_res(ioc); + if (res2 == NULL) { + + closesocket((SOCKET) op->accepted); + + ev->evtype = IO_ABORT; + ev->optype = IO_ACCEPT; + ev->handle = handle_from_res(ioc, res); + ev->user = op->user; + + assert(res->pending > 0); + res->pending--; + op->type = IO_VOID; + return; + } + + res2->type = IO_RES_SOCKET; + res2->pending = 0; + res2->os_handle = op->accepted; + + ev->accepted = handle_from_res(ioc, res2); + } + break; + + default: + break; + } + + assert(res->pending > 0); + res->pending--; + + op->type = IO_VOID; // Mark unused +} +#endif + +#if IO_PLATFORM_LINUX +static void +io_wait_linux(struct io_context *ioc, + struct io_event *ev) +{ + /* --- Read barrier --- */ + unsigned int head = atomic_load(ioc->completions.head); + unsigned int tail = atomic_load(ioc->completions.tail); + + if (head == tail) { + + /* + * Completion queue is empty. Wait for some operations to complete. + */ + int ret = io_uring_enter(ioc->os_handle, 0, 1, IORING_ENTER_GETEVENTS); + if (ret < 0) { + ev->evtype = IO_ERROR; + ev->optype = IO_VOID; + ev->handle = IO_INVALID; + ev->user = NULL; + return; + } + } + + struct io_uring_cqe *cqe; + struct io_operation *op; + struct io_resource *res; + + cqe = &ioc->completions.entries[head & (*ioc->completions.mask)]; + + op = (void*) cqe->user_data; + res = op->res; + + ev->user = op->user; + ev->handle = handle_from_res(ioc, op->res); + ev->optype = op->type; + + if (cqe->res < 0) + ev->evtype = IO_ABORT; + else { + ev->evtype = IO_COMPLETE; + switch (op->type) { + case IO_RECV: ev->num = cqe->res; break; + case IO_SEND: ev->num = cqe->res; break; + case IO_ACCEPT: ev->accepted = cqe->res; break; + default:break; + } + } + + assert(res->pending > 0); + res->pending--; + op->type = IO_VOID; // Mark unused + + /* --- write barrier --- */ + atomic_store(ioc->completions.head, head+1); +} +#endif + +void io_wait(struct io_context *ioc, + struct io_event *ev) +{ + #if IO_PLATFORM_WINDOWS + io_wait_windows(ioc, ev); + #endif + + #if IO_PLATFORM_LINUX + io_wait_linux(ioc, ev); + #endif +} diff --git a/io.h b/io.h index ce35b49..9d81e82 100644 --- a/io.h +++ b/io.h @@ -1,45 +1,35 @@ +#include +#include #define IO_VERSION_MAJOR 0 #define IO_VERSION_MINOR 0 -# ifdef _WIN32 +#ifdef _WIN32 # define IO_PLATFORM_WINDOWS 1 # define IO_PLATFORM_LINUX 0 # define IO_PLATFORM_OTHER 0 -# elif __linux__ +#elif __linux__ # define IO_PLATFORM_WINDOWS 0 # define IO_PLATFORM_LINUX 1 # define IO_PLATFORM_OTHER 0 -# else +#else # define IO_PLATFORM_WINDOWS 0 # define IO_PLATFORM_LINUX 0 # define IO_PLATFORM_OTHER 1 -# endif - -#include -#include - -#if IO_PLATFORM_LINUX -#include -#include #endif -/* - * The OS handle type. - */ -#if IO_PLATFORM_LINUX -typedef int io_raw_handle; -#elif IO_PLATFORM_WINDOWS -typedef void *io_raw_handle; -#endif - -typedef uint16_t io_handle; - -/* - * Windows calls this structure OVERLAPPED - */ #if IO_PLATFORM_WINDOWS -struct io_overlap { +typedef void *io_os_handle; +#endif + +#if IO_PLATFORM_LINUX +typedef int io_os_handle; +#endif + +typedef uint32_t io_handle; +#define IO_INVALID ((uint32_t) -1) + +struct io_os_overlap { unsigned long *internal; unsigned long *internal_high; union { @@ -51,19 +41,6 @@ struct io_overlap { }; void *event; }; -#endif - -enum io_resource_type { - IO_RES_VOID, - IO_RES_FILE, - IO_RES_SOCKET, -}; - -struct io_resource { - enum io_resource_type type; - io_raw_handle raw_handle; - uint16_t headop; -}; enum io_optype { IO_VOID, @@ -76,15 +53,30 @@ enum io_optype { struct io_operation { - io_handle handle; - uint16_t nextop; - enum io_optype type; - void *user; + struct io_resource *res; + void *user; #if IO_PLATFORM_WINDOWS - io_raw_handle accept_handle; - struct io_overlap ov; + io_os_handle accepted; + struct io_os_overlap ov; + #endif +}; + +enum io_restype { + IO_RES_VOID, + IO_RES_FILE, + IO_RES_SOCKET, +}; + +struct io_resource { + enum io_restype type; + io_os_handle os_handle; + uint16_t pending; + uint16_t gen; + + #if IO_PLATFORM_WINDOWS + void *acceptfn; char accept_buffer[2 * (IO_SOCKADDR_IN_SIZE + 16)]; #endif }; @@ -117,12 +109,11 @@ struct io_completion_queue { #endif struct io_context { - - io_raw_handle raw_handle; - uint16_t max_ops; + io_os_handle os_handle; uint16_t max_res; + uint16_t max_ops; + struct io_resource *res; struct io_operation *ops; - struct io_resource *res; #if IO_PLATFORM_LINUX struct io_submission_queue submissions; @@ -130,70 +121,54 @@ struct io_context { #endif }; -struct io_event { - bool error; - void *user; - enum io_optype type; - io_handle handle; - - /* - * Operation-specific results - */ - union { - uint32_t num; // recv, send - io_handle handle; // accept - } data; +enum io_evtype { + IO_ERROR, + IO_ABORT, + IO_COMPLETE, }; +struct io_event { + enum io_evtype evtype; + enum io_optype optype; + io_handle handle; + void *user; + + union { + uint32_t num; + io_handle accepted; + }; +}; bool io_global_init(void); - void io_global_free(void); -/* - * Initialize an I/O context - */ -bool io_context_init(struct io_context *ioc, - struct io_resource *res, - struct io_operation *ops, - uint16_t max_res, - uint16_t max_ops); +bool io_init(struct io_context *ioc, + struct io_resource *res, + struct io_operation *ops, + uint16_t max_res, + uint16_t max_ops); -/* - * Deinitialize an I/O context. This will not close any previously - * created handles. - */ -void io_context_free(struct io_context *ioc); +void io_free(struct io_context *ioc); -/* - * Start an asynchronous receive operation on the handle. - * Only one pending receive operation per handle is supported. - * - * When the operation completes, one of the following calls to - * "io_wait" will return a completion event associated to this - * recv. The "num" field of the event will hold the number of - * bytes actually written from "dst". - */ -bool io_start_recv(struct io_context *ioc, io_handle handle, - 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); - - -bool io_start_accept(struct io_context *ioc, io_handle handle); - -/* - * Wait for the completion of an I/O event. - */ void io_wait(struct io_context *ioc, struct io_event *ev); +bool io_recv(struct io_context *ioc, + void *user, io_handle handle, + void *dsc, uint32_t max); + +bool io_send(struct io_context *ioc, + void *user, io_handle handle, + void *src, uint32_t num); + +bool io_accept(struct io_context *ioc, + void *user, io_handle handle); + +void io_close(struct io_context *ioc, + io_handle handle); + /* - * Flags for "io_open_file" and "io_create_file" + * Flags for io_open_file and io_create_file */ enum { IO_ACCESS_RD = 1 << 0, @@ -203,16 +178,10 @@ enum { }; io_handle io_open_file(struct io_context *ioc, - const char *name, int flags, - void *user); + const char *file, int flags); io_handle io_create_file(struct io_context *ioc, - const char *name, int flags, - void *user); + const char *file, int flags); -io_handle io_listen(struct io_context *ioc, - const char *addr, int port, - void *user); - -void io_close(struct io_context *ioc, - io_handle handle); \ No newline at end of file +io_handle io_start_server(struct io_context *ioc, + const char *addr, int port); diff --git a/io2.c b/io2.c deleted file mode 100644 index 82c6e59..0000000 --- a/io2.c +++ /dev/null @@ -1,946 +0,0 @@ -#include "io2.h" - -#include - -#if IO_PLATFORM_WINDOWS -#define WIN32_LEAN_AND_MEAN -#include -#include -#include -#endif - -#define IO_DEBUG - -#ifdef IO_DEBUG -#include -#define DEBUG_LOG(fmt, ...) fprintf(stderr, "Log :: %s:%d :: " fmt, __FILE__, __LINE__, ## __VA_ARGS__); -#else -#define DEBUG_LOG(...) -#endif - -bool io_global_init(void) -{ - #if IO_PLATFORM_WINDOWS - WSADATA data; - return WSAStartup(MAKEWORD(2, 2), &data) == NO_ERROR; - #endif - - #if IO_PLATFORM_LINUX - return true; - #endif -} - -void io_global_free(void) -{ - #if IO_PLATFORM_WINDOWS - WSACleanup(); - #endif -} - -#if IO_PLATFORM_WINDOWS -bool io_init_windows(struct io_context *ioc) -{ - io_os_handle os_handle = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 1); - if (os_handle == INVALID_HANDLE_VALUE) - return false; - - ioc->os_handle = os_handle; - return true; -} -#endif - -#if IO_PLATFORM_WINDOWS -void io_free_windows(struct io_context *ioc) -{ - CloseHandle(ioc->os_handle); -} -#endif - -#if IO_PLATFORM_LINUX -bool io_init_linux(struct io_context *ioc) -{ - - struct io_uring_params p; - void *sq_ptr, *cq_ptr; - /* See io_uring_setup(2) for io_uring_params.flags you can set */ - memset(&p, 0, sizeof(p)); - int fd = io_uring_setup(32, &p); - if (fd < 0) - return false; - - ioc->os_handle = fd; - - /* - * io_uring communication happens via 2 shared kernel-user space ring - * buffers, which can be jointly mapped with a single mmap() call in - * kernels >= 5.4. - */ - int sring_sz = p.sq_off.array + p.sq_entries * sizeof(unsigned); - int cring_sz = p.cq_off.cqes + p.cq_entries * sizeof(struct io_uring_cqe); - /* Rather than check for kernel version, the recommended way is to - * check the features field of the io_uring_params structure, which is a - * bitmask. If IORING_FEAT_SINGLE_MMAP is set, we can do away with the - * second mmap() call to map in the completion ring separately. - */ - if (p.features & IORING_FEAT_SINGLE_MMAP) { - if (cring_sz > sring_sz) - sring_sz = cring_sz; - cring_sz = sring_sz; - } - /* Map in the submission and completion queue ring buffers. - * Kernels < 5.4 only map in the submission queue, though. - */ - sq_ptr = mmap(0, sring_sz, PROT_READ | PROT_WRITE, - MAP_SHARED | MAP_POPULATE, fd, IORING_OFF_SQ_RING); - if (sq_ptr == MAP_FAILED) { - // TODO: Cleanup - return false; - } - if (p.features & IORING_FEAT_SINGLE_MMAP) { - cq_ptr = sq_ptr; - } else { - /* Map in the completion queue ring buffer in older kernels separately */ - cq_ptr = mmap(0, cring_sz, PROT_READ | PROT_WRITE, - MAP_SHARED | MAP_POPULATE, fd, IORING_OFF_CQ_RING); - if (cq_ptr == MAP_FAILED) { - // TODO: Cleanup - return false; - } - } - - /* Save useful fields for later easy reference */ - ioc->submissions.head = (_Atomic unsigned*) (sq_ptr + p.sq_off.head); - ioc->submissions.tail = (_Atomic unsigned*) (sq_ptr + p.sq_off.tail); - ioc->submissions.mask = (unsigned*) (sq_ptr + p.sq_off.ring_mask); - ioc->submissions.array = sq_ptr + p.sq_off.array; - ioc->submissions.limit = p.sq_entries; - - /* Map in the submission queue entries array */ - ioc->submissions.entries = mmap(0, p.sq_entries * sizeof(struct io_uring_sqe), - PROT_READ | PROT_WRITE, MAP_SHARED | MAP_POPULATE, - fd, IORING_OFF_SQES); - if (ioc->submissions.entries == MAP_FAILED) { - // TODO: Cleanup - return false; - } - - /* Save useful fields for later easy reference */ - ioc->completions.head = cq_ptr + p.cq_off.head; - ioc->completions.tail = cq_ptr + p.cq_off.tail; - ioc->completions.mask = cq_ptr + p.cq_off.ring_mask; - ioc->completions.entries = cq_ptr + p.cq_off.cqes; - ioc->completions.limit = p.cq_entries; - return true; -} -#endif - -#if IO_PLATFORM_LINUX -void io_free_linux(struct io_context *ioc) -{ - close(ioc->os_handle); -} -#endif - -#if IO_PLATFORM_LINUX -static bool start_uring_op(struct io_context *ioc, - struct io_uring_sqe sqe) -{ - // TODO - return false; -} -#endif - -static void clear_res(struct io_resource *res) -{ - res->type = IO_RES_VOID; - res->pending = 0; - - #if IO_PLATFORM_WINDOWS - res->os_handle = INVALID_HANDLE_VALUE; - #endif - - #if IO_PLATFORM_LINUX - res->os_handle = -1; - #endif -} - -bool io_init(struct io_context *ioc, - struct io_resource *res, - struct io_operation *ops, - uint16_t max_res, - uint16_t max_ops) -{ - ioc->res = res; - ioc->ops = ops; - ioc->max_res = max_res; - ioc->max_ops = max_ops; - - for (uint16_t i = 0; i < max_res; i++) { - res[i].gen = 0; - clear_res(&res[i]); - } - - for (uint16_t i = 0; i < max_ops; i++) - ops[i].type = IO_VOID; - - #if IO_PLATFORM_WINDOWS - return io_init_windows(ioc); - #endif - - #if IO_PLATFORM_LINUX - return io_init_linux(ioc); - #endif -} - -static void -close_internal(struct io_context *ioc, - struct io_resource *res) -{ - #if IO_PLATFORM_WINDOWS - if (res->type == IO_RES_SOCKET) - closesocket((SOCKET) res->os_handle); - else - CloseHandle(res->os_handle); - #elif IO_PLATFORM_LINUX - close(res->os_handle); - #endif - - // Mark associated operation structures as unused - for (uint16_t i = 0, marked = 0; marked < res->pending; i++) { - struct io_operation *op; - op = &ioc->ops[i]; - if (op->type != IO_VOID && op->res == res) { - op->type = IO_VOID; - op->res = NULL; - marked++; - } - } - - clear_res(res); - - res->gen++; - if (res->gen == UINT16_MAX) - res->gen = 0; -} - -static struct io_resource* -res_from_handle(struct io_context *ioc, io_handle handle) -{ - if (handle == IO_INVALID) - return NULL; - - static_assert(sizeof(uint32_t) == sizeof(io_handle)); - uint16_t idx = handle & 0xFFFF; - uint16_t gen = handle >> 16; - if (idx >= ioc->max_res) - return NULL; - - struct io_resource *res = &ioc->res[idx]; - if (res->gen != gen) - return NULL; - - return res; -} - -static io_handle handle_from_res(struct io_context *ioc, - struct io_resource *res) -{ - io_handle handle; - static_assert(sizeof(uint32_t) == sizeof(io_handle)); - - uint32_t idx = res - ioc->res; - uint32_t gen = res->gen; - handle = idx | (gen << 16); - - assert(gen != UINT16_MAX); - assert(handle != IO_INVALID); - - return handle; -} - -void io_close(struct io_context *ioc, - io_handle handle) -{ - struct io_resource *res; - - res = res_from_handle(ioc, handle); - if (res == NULL) - return; - - close_internal(ioc, res); -} - -void io_free(struct io_context *ioc) -{ - for (uint16_t i = 0; i < ioc->max_res; i++) - if (ioc->res[i].type != IO_RES_VOID) - close_internal(ioc, &ioc->res[i]); - - #if IO_PLATFORM_WINDOWS - io_free_windows(ioc); - #endif - - #if IO_PLATFORM_LINUX - io_free_linux(ioc); - #endif -} - -static struct io_operation* -find_unused_op(struct io_context *ioc) -{ - for (uint16_t i = 0; i < ioc->max_ops; i++) { - struct io_operation *op = &ioc->ops[i]; - if (op->type == IO_VOID) - return op; - } - return NULL; -} - -#if IO_PLATFORM_LINUX -static bool io_recv_linux(struct io_context *ioc, - struct io_resource *res, - struct io_operation *op, - void *dst, uint32_t max) -{ - struct io_uring_sqe sqe; - memset(&sqe, 0, sizeof(sqe)); - sqe.opcode = IORING_OP_READ; - sqe.fd = (int) res->os_handle; - sqe.addr = (uint64_t) dst; - sqe.len = max; - sqe.user_data = (uint64_t) op; - return start_uring_op(ioc, sqe); -} -#endif - -#if IO_PLATFORM_LINUX -static bool io_send_linux(struct io_context *ioc, - struct io_resource *res, - struct io_operation *op, - void *src, uint32_t num) -{ - struct io_uring_sqe sqe; - memset(&sqe, 0, sizeof(sqe)); - sqe.opcode = IORING_OP_WRITE; - sqe.fd = (int) res->os_handle; - sqe.addr = (uint64_t) sec; - sqe.len = num; - sqe.user_data = (uint64_t) op; - return start_uring_op(ioc, sqe); -} -#endif - -#if IO_PLATFORM_LINUX -static bool io_accept_linux(struct io_context *ioc, - struct io_resource *res, - struct io_operation *op, - io_os_handle os_handle) -{ - struct io_uring_sqe sqe; - memset(&sqe, 0, sizeof(sqe)); - sqe.opcode = IORING_OP_ACCEPT; - sqe.fd = (int) os_handle; - sqe.user_data = (uint64_t) op; - return start_uring_op(ioc, sqe); -} -#endif - -#if IO_PLATFORM_WINDOWS -static bool io_recv_windows(struct io_context *ioc, - struct io_resource *res, - struct io_operation *op, - void *dst, uint32_t max) -{ - (void) ioc; - - memset(&op->ov, 0, sizeof(struct io_os_overlap)); - int ok = ReadFile(res->os_handle, dst, max, NULL, (OVERLAPPED*) &op->ov); - if (!ok && GetLastError() != ERROR_IO_PENDING) - return false; - return true; -} -#endif - -#if IO_PLATFORM_WINDOWS -static bool io_send_windows(struct io_context *ioc, - struct io_resource *res, - struct io_operation *op, - void *src, uint32_t num) -{ - (void) ioc; - - memset(&op->ov, 0, sizeof(struct io_os_overlap)); - int ok = ReadFile(res->os_handle, src, num, NULL, (OVERLAPPED*) &op->ov); - if (!ok && GetLastError() != ERROR_IO_PENDING) - return false; - return true; -} -#endif - -#if IO_PLATFORM_WINDOWS -static bool io_accept_windows(struct io_context *ioc, - struct io_resource *res, - struct io_operation *op, - io_os_handle os_handle) -{ - (void) ioc; - (void) res; - - memset(&op->ov, 0, sizeof(struct io_os_overlap)); - - SOCKET new_os_handle = socket(AF_INET, SOCK_STREAM, 0); - if (new_os_handle == INVALID_SOCKET) - return false; - - LPFN_ACCEPTEX lpfnAcceptEx = res->acceptfn; - - _Static_assert(IO_SOCKADDR_IN_SIZE == sizeof(struct sockaddr_in)); - - unsigned long num; - int ok = lpfnAcceptEx((SOCKET) os_handle, new_os_handle, res->accept_buffer, - sizeof(res->accept_buffer) - ((sizeof(struct sockaddr_in) + 16) * 2), - sizeof(struct sockaddr_in) + 16, - sizeof(struct sockaddr_in) + 16, - &num, (OVERLAPPED*) &op->ov); - if (!ok && GetLastError() != ERROR_IO_PENDING) { - DEBUG_LOG("AcceptEx failure\n"); - closesocket(new_os_handle); - return false; - } - - op->accepted = (io_os_handle) new_os_handle; - return true; -} -#endif - -bool io_recv(struct io_context *ioc, - void *user, io_handle handle, - void *dst, uint32_t max) -{ - struct io_operation *op; - struct io_resource *res; - - res = res_from_handle(ioc, handle); - if (res == NULL) - return false; - - op = find_unused_op(ioc); - if (op == NULL) - return false; - - enum io_optype type = IO_RECV; - - #if IO_PLATFORM_LINUX - if (!io_recv_linux(ioc, res, op, dst, max)) - return false; - #endif - - #if IO_PLATFORM_WINDOWS - if (!io_recv_windows(ioc, res, op, dst, max)) - return false; - #endif - - res->pending++; - op->res = res; - op->type = type; - op->user = user; - return true; -} - -bool io_send(struct io_context *ioc, - void *user, io_handle handle, - void *src, uint32_t num) -{ - struct io_operation *op; - struct io_resource *res; - - res = res_from_handle(ioc, handle); - if (res == NULL) - return false; - - op = find_unused_op(ioc); - if (op == NULL) - return false; - - enum io_optype type = IO_SEND; - - #if IO_PLATFORM_LINUX - if (!io_send_linux(ioc, res, op, src, num)) - return false; - #endif - - #if IO_PLATFORM_WINDOWS - if (!io_send_windows(ioc, res, op, src, num)) - return false; - #endif - - res->pending++; - op->res = res; - op->type = type; - op->user = user; - return true; -} - -bool io_accept(struct io_context *ioc, - void *user, io_handle handle) -{ - struct io_operation *op; - struct io_resource *res; - - res = res_from_handle(ioc, handle); - if (res == NULL) - return false; - - op = find_unused_op(ioc); - if (op == NULL) - return false; - - enum io_optype type = IO_ACCEPT; - - #if IO_PLATFORM_LINUX - if (!io_accept_linux(ioc, res, op, res->os_handle)) - return false; - #endif - - #if IO_PLATFORM_WINDOWS - if (!io_accept_windows(ioc, res, op, res->os_handle)) - return false; - #endif - - res->pending++; - op->res = res; - op->type = type; - op->user = user; - return true; -} - -static struct io_resource* -find_unused_res(struct io_context *ioc) -{ - for (uint16_t i = 0; i < ioc->max_res; i++) { - struct io_resource *res = &ioc->res[i]; - if (res->type == IO_RES_VOID) - return res; - } - return NULL; -} - -#if IO_PLATFORM_WINDOWS -static io_os_handle -io_open_file_windows(struct io_context *ioc, - const char *file, int flags) -{ - unsigned long access = 0; - if (flags & IO_ACCESS_RD) access |= GENERIC_READ; - if (flags & IO_ACCESS_WR) access |= GENERIC_WRITE; - - io_os_handle os_handle = CreateFileA(file, access, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED, NULL); - if (os_handle == INVALID_HANDLE_VALUE) - return INVALID_HANDLE_VALUE; - - if (CreateIoCompletionPort(os_handle, ioc->os_handle, 0, 0) == NULL) { - CloseHandle(os_handle); - return INVALID_HANDLE_VALUE; - } - - return os_handle; -} -#endif - -#if IO_PLATFORM_LINUX -static io_os_handle -io_open_file_linux(struct io_context *ioc, - const char *file, int flags) -{ - int flags2 = 0; - if (flags & IO_ACCESS_RD) flags2 |= O_RDONLY; - if (flags & IO_ACCESS_WR) flags2 |= O_WRONLY; - - return open(name, flags2); -} -#endif - -io_handle io_open_file(struct io_context *ioc, - const char *file, int flags) -{ - io_os_handle os_handle; - struct io_resource *res; - - res = find_unused_res(ioc); - if (res == NULL) - return IO_INVALID; - - #if IO_PLATFORM_WINDOWS - os_handle = io_open_file_windows(ioc, file, flags); - if (os_handle == INVALID_HANDLE_VALUE) - return IO_INVALID; - #endif - - #if IO_PLATFORM_LINUX - os_handle = io_open_file_linux(ioc, file, flags); - if (os_handle < 0) - return IO_INVALID; - #endif - - res->type = IO_RES_FILE; - res->pending = 0; - res->os_handle = os_handle; - return handle_from_res(ioc, res); -} - -#if IO_PLATFORM_WINDOWS -static io_os_handle -io_create_file_windows(struct io_context *ioc, - const char *file, int flags) -{ - 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_os_handle os_handle = CreateFileA(file, GENERIC_WRITE, 0, NULL, flags2, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED, NULL); - if (os_handle == INVALID_HANDLE_VALUE) - return INVALID_HANDLE_VALUE; - - if (CreateIoCompletionPort(os_handle, ioc->os_handle, 0, 0) == NULL) { - CloseHandle(os_handle); - return INVALID_HANDLE_VALUE; - } - - return os_handle; -} -#endif - -#if IO_PLATFORM_LINUX -static io_os_handle -io_create_file_linux(struct io_context *ioc, - const char *file, int flags) -{ - 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? - return open(name, flags2, 0666); -} -#endif - -io_handle io_create_file(struct io_context *ioc, - const char *file, int flags) -{ - io_os_handle os_handle; - struct io_resource *res; - - res = find_unused_res(ioc); - if (res == NULL) - return IO_INVALID; - - #if IO_PLATFORM_WINDOWS - os_handle = io_create_file_windows(ioc, file, flags); - if (os_handle == INVALID_HANDLE_VALUE) - return IO_INVALID; - #endif - - #if IO_PLATFORM_LINUX - os_handle = io_create_file_linux(ioc, file, flags); - if (os_handle < 0) - return IO_INVALID; - #endif - - res->type = IO_RES_FILE; - res->pending = 0; - res->os_handle = os_handle; - return handle_from_res(ioc, res); -} - -io_handle io_start_server(struct io_context *ioc, - const char *addr, int port) -{ - if (port < 1 || port > UINT16_MAX) - return IO_INVALID; - - struct in_addr addr2; - if (addr == NULL) - addr2.s_addr = INADDR_ANY; - else { - if (1 != inet_pton(AF_INET, addr, &addr2)) - return IO_INVALID; - } - - #if IO_PLATFORM_WINDOWS - SOCKET fd; - #endif - - #if IO_PLATFORM_LINUX - int fd; - #endif - - struct io_resource *res; - - res = find_unused_res(ioc); - if (res == NULL) - return IO_INVALID; - - fd = socket(AF_INET, SOCK_STREAM, 0); - - #if IO_PLATFORM_LINUX - if (fd < 0) - return IO_INVALID; - #endif - - #if IO_PLATFORM_WINDOWS - if (fd == INVALID_SOCKET) - return IO_INVALID; - #endif - - int one = 1; - setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (char*) &one, sizeof(one)); - - struct sockaddr_in buf; - buf.sin_family = AF_INET; - buf.sin_port = htons(port); - buf.sin_addr = addr2; - if (bind(fd, (struct sockaddr*) &buf, sizeof(buf))) { - #if IO_PLATFORM_WINDOWS - closesocket(fd); - #else - close(fd); - #endif - return IO_INVALID; - } - - int backlog = 32; - if (listen(fd, backlog)) { - #if IO_PLATFORM_WINDOWS - closesocket(fd); - #else - close(fd); - #endif - return IO_INVALID; - } - - #if IO_PLATFORM_WINDOWS - LPFN_ACCEPTEX lpfnAcceptEx = NULL; - GUID GuidAcceptEx = WSAID_ACCEPTEX; - unsigned long num; - int ret = WSAIoctl(fd, - SIO_GET_EXTENSION_FUNCTION_POINTER, - &GuidAcceptEx, sizeof(GuidAcceptEx), - &lpfnAcceptEx, sizeof(lpfnAcceptEx), - &num, NULL, NULL); - if (ret == SOCKET_ERROR) { - DEBUG_LOG("WSAIoctl failure\n"); - closesocket(fd); - return IO_INVALID; - } - if (CreateIoCompletionPort((HANDLE) fd, ioc->os_handle, 0, 0) == NULL) { - closesocket(fd); - return IO_INVALID; - } - res->acceptfn = lpfnAcceptEx; - #endif - - res->type = IO_RES_SOCKET; - res->pending = 0; - res->os_handle = (io_os_handle) fd; - return handle_from_res(ioc, res); -} - -#if IO_PLATFORM_WINDOWS -static struct io_operation* -op_from_ov(struct io_os_overlap *ov) -{ - return (struct io_operation*) ((char*) ov - offsetof(struct io_operation, ov)); -} -#endif - -#if IO_PLATFORM_WINDOWS -static void -io_wait_windows(struct io_context *ioc, - struct io_event *ev) -{ - int timeout = -1; - - unsigned long timeout2; - if (timeout < 0) - timeout2 = INFINITE; - else - timeout2 = timeout; - - unsigned long long unused; - struct io_os_overlap *ov; - unsigned long num; - int ok = GetQueuedCompletionStatus(ioc->os_handle, &num, &unused, (OVERLAPPED**) &ov, timeout2); - - if (!ok) { - - if (ov == NULL) { - - /* - * General failure - */ - - ev->evtype = IO_ERROR; - ev->optype = IO_VOID; - ev->handle = IO_INVALID; - ev->user = NULL; - - } else { - - /* - * Operation failure - */ - - struct io_operation *op = op_from_ov(ov); - struct io_resource *res = op->res; - - ev->evtype = IO_ABORT; - ev->optype = op->type; - ev->handle = handle_from_res(ioc, res); - ev->user = op->user; - - if (op->type == IO_ACCEPT) - closesocket((SOCKET) op->accepted); - - op->type = IO_VOID; // Mark unused - - assert(res->pending > 0); - res->pending--; - } - return; - } - - struct io_operation *op = op_from_ov(ov); - struct io_resource *res = op->res; - - ev->evtype = IO_COMPLETE; - ev->optype = op->type; - ev->handle = handle_from_res(ioc, res); - ev->user = op->user; - - switch (op->type) { - - case IO_RECV: - case IO_SEND: - ev->num = num; - break; - - case IO_ACCEPT: - { - struct io_resource *res2; - - res2 = find_unused_res(ioc); - if (res2 == NULL) { - - closesocket((SOCKET) op->accepted); - - ev->evtype = IO_ABORT; - ev->optype = IO_ACCEPT; - ev->handle = handle_from_res(ioc, res); - ev->user = op->user; - - assert(res->pending > 0); - res->pending--; - op->type = IO_VOID; - return; - } - - res2->type = IO_RES_SOCKET; - res2->pending = 0; - res2->os_handle = op->accepted; - - ev->accepted = handle_from_res(ioc, res2); - } - break; - - default: - break; - } - - assert(res->pending > 0); - res->pending--; - - op->type = IO_VOID; // Mark unused -} -#endif - -#if IO_PLATFORM_LINUX -static void -io_wait_linux(struct io_context *ioc, - struct io_event *ev) -{ - /* --- Read barrier --- */ - unsigned int head = atomic_load(ioc->completions.head); - unsigned int tail = atomic_load(ioc->completions.tail); - - if (head == tail) { - - /* - * Completion queue is empty. Wait for some operations to complete. - */ - int ret = io_uring_enter(ioc->handle, 0, 1, IORING_ENTER_GETEVENTS); - if (ret < 0) { - ev->evtype = IO_ERROR; - ev->optype = IO_VOID; - ev->handle = IO_INVALID; - ev->user = NULL; - return; - } - } - - struct io_uring_cqe *cqe; - struct io_operation *op; - struct io_resource *res; - - cqe = &ioc->completions.entries[head & (*ioc->completions.mask)]; - - op = (void*) cqe->user_data; - res = op->res; - - ev->user = op->user; - ev->handle = handle_from_res(ioc, op->res); - ev->optype = op->type; - - if (cqe->res < 0) - ev->evtype = IO_ABORT; - else { - ev->evtype = IO_COMPLETE; - switch (op->type) { - case IO_RECV: ev->num = cqe->res; break; - case IO_SEND: ev->num = cqe->res; break; - case IO_ACCEPT: ev->accepted = cqe->res; break; - default:break; - } - } - - assert(res->pending > 0); - res->pending--; - op->type = IO_VOID; // Mark unused - - /* --- write barrier --- */ - atomic_store(ioc->completions.head, head+1); -} -#endif - -void io_wait(struct io_context *ioc, - struct io_event *ev) -{ - #if IO_PLATFORM_WINDOWS - io_wait_windows(ioc, ev); - #endif - - #if IO_PLATFORM_LINUX - io_wait_linux(ioc, ev); - #endif -} diff --git a/io2.h b/io2.h deleted file mode 100644 index 30722c5..0000000 --- a/io2.h +++ /dev/null @@ -1,155 +0,0 @@ -#include -#include - -#define IO_VERSION_MAJOR 0 -#define IO_VERSION_MINOR 0 - -#ifdef _WIN32 -# define IO_PLATFORM_WINDOWS 1 -# define IO_PLATFORM_LINUX 0 -# define IO_PLATFORM_OTHER 0 -#elif __linux__ -# define IO_PLATFORM_WINDOWS 0 -# define IO_PLATFORM_LINUX 1 -# define IO_PLATFORM_OTHER 0 -#else -# define IO_PLATFORM_WINDOWS 0 -# define IO_PLATFORM_LINUX 0 -# define IO_PLATFORM_OTHER 1 -#endif - -#if IO_PLATFORM_WINDOWS -typedef void *io_os_handle; -#endif - -#if IO_PLATFORM_LINUX -typedef int io_os_handle; -#endif - -typedef uint32_t io_handle; -#define IO_INVALID ((uint32_t) -1) - -struct io_os_overlap { - unsigned long *internal; - unsigned long *internal_high; - union { - struct { - unsigned long offset; - unsigned long offset_high; - }; - void *pointer; - }; - void *event; -}; - -enum io_optype { - IO_VOID, - IO_RECV, - IO_SEND, - IO_ACCEPT, -}; - -#define IO_SOCKADDR_IN_SIZE 16 - -struct io_operation { - - enum io_optype type; - struct io_resource *res; - void *user; - - #if IO_PLATFORM_WINDOWS - io_os_handle accepted; - struct io_os_overlap ov; - #endif -}; - -enum io_restype { - IO_RES_VOID, - IO_RES_FILE, - IO_RES_SOCKET, -}; - -struct io_resource { - enum io_restype type; - io_os_handle os_handle; - uint16_t pending; - uint16_t gen; - - #if IO_PLATFORM_WINDOWS - void *acceptfn; - char accept_buffer[2 * (IO_SOCKADDR_IN_SIZE + 16)]; - #endif -}; - -struct io_context { - io_os_handle os_handle; - uint16_t max_res; - uint16_t max_ops; - struct io_resource *res; - struct io_operation *ops; -}; - -enum io_evtype { - IO_ERROR, - IO_ABORT, - IO_COMPLETE, -}; - -struct io_event { - enum io_evtype evtype; - enum io_optype optype; - io_handle handle; - void *user; - - union { - uint32_t num; - io_handle accepted; - }; -}; - -bool io_global_init(void); -void io_global_free(void); - -bool io_init(struct io_context *ioc, - struct io_resource *res, - struct io_operation *ops, - uint16_t max_res, - uint16_t max_ops); - -void io_free(struct io_context *ioc); - -void io_wait(struct io_context *ioc, - struct io_event *ev); - -bool io_recv(struct io_context *ioc, - void *user, io_handle handle, - void *dsc, uint32_t max); - -bool io_send(struct io_context *ioc, - void *user, io_handle handle, - void *src, uint32_t num); - -bool io_accept(struct io_context *ioc, - void *user, io_handle handle); - -void io_close(struct io_context *ioc, - io_handle handle); - -/* - * 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 *file, int flags); - -io_handle io_create_file(struct io_context *ioc, - const char *file, int flags); - -io_handle io_start_server(struct io_context *ioc, - const char *addr, int port); diff --git a/makefile b/makefile deleted file mode 100644 index 1c3c897..0000000 --- a/makefile +++ /dev/null @@ -1,6 +0,0 @@ -all: - gcc example.c io2.c -o example -Wall -Wextra -ggdb - gcc example2.c io2.c -o example2 -Wall -Wextra -ggdb - -clean: - rm example example.exe