diff --git a/.gitignore b/.gitignore index 533f04e..6b4f3ac 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ example example.exe +example2 +example2.exe file_*.txt \ No newline at end of file diff --git a/build.bat b/build.bat new file mode 100644 index 0000000..d7a550d --- /dev/null +++ b/build.bat @@ -0,0 +1,2 @@ +gcc example.c io.c -o example -Wall -Wextra -ggdb +gcc example2.c io.c -o example2 -Wall -Wextra -ggdb -lws2_32 \ No newline at end of file diff --git a/example.c b/example.c index 974f10d..212c7f8 100644 --- a/example.c +++ b/example.c @@ -1,13 +1,16 @@ #include -#include "io.h" +#include "io2.h" #define NUM_OPS 3 int main() { + io_global_init(); + struct io_operation ops[100]; + struct io_resource res[100]; struct io_context ioc; - if (!io_context_init(&ioc, ops, sizeof(ops)/sizeof(ops[0]))) + if (!io_init(&ioc, res, ops, sizeof(res)/sizeof(res[0]), sizeof(ops)/sizeof(ops[0]))) return -1; io_handle files[NUM_OPS]; @@ -36,5 +39,6 @@ int main() } io_context_free(&ioc); + io_global_free(); return 0; } diff --git a/example2.c b/example2.c new file mode 100644 index 0000000..08dc214 --- /dev/null +++ b/example2.c @@ -0,0 +1,51 @@ +#include +#include +#include "io.h" + +#define MAX_RES 100 +#define MAX_OPS 100 + +int main(void) +{ + int port = 8080; + + fprintf(stderr, "port=%d\n", port); + + struct io_operation ops[MAX_OPS]; + struct io_resource res[MAX_RES]; + struct io_context ioc; + if (!io_init(&ioc, res, ops, MAX_RES, MAX_OPS)) { + fprintf(stderr, "Couldn't initialize I/O context\n"); + return -1; + } + + io_handle socket = io_start_server(&ioc, NULL, port); + if (socket == IO_INVALID) { + fprintf(stderr, "Couldn't start listening\n"); + return -1; + } + if (!io_accept(&ioc, socket, NULL)) { + fprintf(stderr, "Couldn't start accept operation\n"); + return -1; + } + + for (;;) { + struct io_event ev; + io_wait(&ioc, &ev); + + assert(ev.optype == IO_ACCEPT); + io_handle accepted = ev.accepted; + + fprintf(stderr, "Accepted\n"); + + io_close(&ioc, accepted); + + if (!io_accept(&ioc, socket)) { + fprintf(stderr, "Couldn't start accept operation\n"); + return -1; + } + } + + io_context_free(&ioc); + return 0; +} \ No newline at end of file diff --git a/io.c b/io.c index 57f5f5b..bd18184 100644 --- a/io.c +++ b/io.c @@ -5,76 +5,73 @@ #include -/* - * Declare Windows symbols locally to avoid including windows.h - */ +#define WIN32_LEAN_AND_MEAN +#include +#include +#include -#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 - -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, - uint32_t max_ops) +bool io_global_init(void) { - io_handle handle = CreateIoCompletionPort(IO_INVALID_HANDLE, NULL, 0, 1); - if (handle == IO_INVALID_HANDLE) + 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->handle = handle; + 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) { - CloseHandle(ioc->handle); + 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) @@ -86,12 +83,15 @@ 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 *dst, uint32_t max, void *user) { if (handle == IO_INVALID_HANDLE) return false; + + struct io_resource *res = &ioc->res[handle]; - struct io_operation *op = alloc_op(ioc); + struct io_operation *op; + op = alloc_op(ioc); if (op == NULL) return false; @@ -102,6 +102,9 @@ bool io_start_recv(struct io_context *ioc, io_handle handle, return false; op->type = IO_RECV; + op->handle = handle; + op->nextop = res->headop; + res->headop = op - ioc->ops; return true; } @@ -122,6 +125,9 @@ bool io_start_send(struct io_context *ioc, io_handle handle, return false; op->type = IO_SEND; + op->handle = handle; + op->nextop = res->headop; + res->headop = op - ioc->ops; return true; } @@ -130,8 +136,50 @@ bool io_start_accept(struct io_context *ioc, io_handle handle) if (handle == IO_INVALID_HANDLE) return false; - // TODO - 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 @@ -153,18 +201,22 @@ void io_wait(struct io_context *ioc, struct io_event *ev) void *user; struct io_overlap *ov; unsigned long num; - int ok = GetQueuedCompletionStatus(ioc->handle, &num, (unsigned long*) &user, &ov, convert_timeout(-1)); + 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 */ @@ -179,7 +231,11 @@ void io_wait(struct io_context *ioc, struct io_event *ev) 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; + + case IO_ACCEPT: + closesocket(op->accept_handle); + op->accept_handle = IO_INVALID_HANDLE; + break; } op->type = IO_VOID; // Mark unused @@ -197,7 +253,7 @@ void io_wait(struct io_context *ioc, struct io_event *ev) default:break; case IO_RECV: ev->num = num; break; case IO_SEND: ev->num = num; break; - case IO_ACCEPT: /* TODO */ break; + case IO_ACCEPT: op->accept_handle; break; } op->type = IO_VOID; // Mark unused @@ -250,6 +306,13 @@ io_handle io_create_file(struct io_context *ioc, return handle; } +io_handle io_listen(struct io_context *ioc, + const char *addr, int port, + void *user) +{ + return IO_INVALID_HANDLE; +} + #endif @@ -259,9 +322,20 @@ io_handle io_create_file(struct io_context *ioc, #include #include #include +#include #include +#include #include +bool io_global_init(void) +{ + return true; +} + +void io_global_free(void) +{ +} + static int io_uring_setup(unsigned entries, struct io_uring_params *p) @@ -367,6 +441,18 @@ void io_context_free(struct io_context *ioc) close(ioc->handle); } +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) { @@ -618,4 +704,51 @@ io_handle io_create_file(struct io_context *ioc, return fd; } +io_handle io_listen(struct io_context *ioc, + const char *addr, int port, + void *user) +{ + if (port < 1 || port > UINT16_MAX) + return IO_INVALID_HANDLE; + + 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; + } + + 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; + + int one = 1; + setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &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))) { + close(fd); + return IO_INVALID_HANDLE; + } + + int backlog = 32; + if (listen(fd, backlog)) { + close(fd); + return IO_INVALID_HANDLE; + } + + op->handle = fd; + op->user = user; + return (io_handle) fd; +} + #endif diff --git a/io.h b/io.h index c316bfd..dc853eb 100644 --- a/io.h +++ b/io.h @@ -28,13 +28,13 @@ * The OS handle type. */ #if IO_PLATFORM_LINUX -typedef int io_handle; -#define IO_INVALID_HANDLE -1 +typedef int io_raw_handle; #elif IO_PLATFORM_WINDOWS -typedef void *io_handle; -#define IO_INVALID_HANDLE ((void*) -1) +typedef void *io_raw_handle; #endif +typedef uint16_t io_handle; + /* * Windows calls this structure OVERLAPPED */ @@ -53,6 +53,18 @@ struct io_overlap { }; #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, IO_RECV, @@ -60,18 +72,20 @@ enum io_optype { IO_ACCEPT, }; +#define IO_SOCKADDR_IN_SIZE 16 + struct io_operation { - void *user; + io_handle handle; + uint16_t nextop; enum io_optype type; - - #if IO_PLATFORM_LINUX - io_handle handle; - #endif + void *user; #if IO_PLATFORM_WINDOWS + io_raw_handle accept_handle; struct io_overlap ov; + char accept_buffer[2 * (IO_SOCKADDR_IN_SIZE + 16)]; #endif }; @@ -104,9 +118,11 @@ struct io_completion_queue { struct io_context { - io_handle handle; - uint32_t max_ops; + io_raw_handle raw_handle; + uint16_t max_ops; + uint16_t max_res; struct io_operation *ops; + struct io_resource *res; #if IO_PLATFORM_LINUX struct io_submission_queue submissions; @@ -118,6 +134,7 @@ struct io_event { bool error; void *user; enum io_optype type; + io_handle handle; /* * Operation-specific results @@ -125,15 +142,22 @@ struct io_event { union { uint32_t num; // recv, send io_handle handle; // accept - }; + } data; }; + +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, - uint32_t max_ops); + uint16_t max_res, + uint16_t max_ops); /* * Deinitialize an I/O context. This will not close any previously @@ -185,3 +209,10 @@ io_handle io_open_file(struct io_context *ioc, io_handle io_create_file(struct io_context *ioc, const char *name, int flags, void *user); + +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 diff --git a/io2.c b/io2.c new file mode 100644 index 0000000..4b403dc --- /dev/null +++ b/io2.c @@ -0,0 +1,908 @@ +#include "io2.h" + +#include + +#if IO_PLATFORM_WINDOWS +#define WIN32_LEAN_AND_MEAN +#include +#include +#include +#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(IO_INVALID_HANDLE, 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(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++) { + strct 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; +} + +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(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_WINDOWS + 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) +{ + memset(&op->ov, 0, sizeof(struct io_os_overlap)); + int ok = ReadFile(res->os_handle, dst, max, NULL, &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) +{ + memset(&op->ov, 0, sizeof(struct io_os_overlap)); + int ok = ReadFile(res->os_handle, src, num, NULL, &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) +{ + memset(&op->ov, 0, sizeof(struct io_os_overlap)); + + SOCKET new_os_handle = socket(AF_INET, SOCK_STREAM, 0); + if (new_os_handle == INVALID_HANDLE_VALUE) + return false; + + LPFN_ACCEPTEX lpfnAcceptEx = NULL; + GUID GuidAcceptEx = WSAID_ACCEPTEX; + + unsigned long num; + int ret = WSAIoctl(res->os_handle, + SIO_GET_EXTENSION_FUNCTION_POINTER, + &GuidAcceptEx, sizeof(GuidAcceptEx), + &lpfnAcceptEx, sizeof(lpfnAcceptEx), + &num, NULL, NULL); + if (ret == SOCKET_ERROR) { + closesocket(new_os_handle); + return false; + } + + _Static_assert(IO_SOCKADDR_IN_SIZE == sizeof(struct sockaddr_in)); + + int ok = lpfnAcceptEx(handle2, os_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_os_handle); + return false; + } + + op->accepted = 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 == IO_INVALID_HANDLE) + return IO_INVALID_HANDLE; + + if (CreateIoCompletionPort(os_handle, ioc->os_handle, NULL, 0) == NULL) { + CloseHandle(os_handle); + return IO_INVALID_HANDLE; + } + + 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(handle, ioc->os_handle, NULL, 0) == NULL) { + CloseHandle(os_handle); + return INVALID_HANDLE_VALUE; + } + + return 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; + } + + io_os_handle os_handle; + struct io_resource *res; + + res = find_unused_res(ioc); + if (res == NULL) + return IO_INVALID; + + os_handle = socket(AF_INET, SOCK_STREAM, 0); + + #if IO_PLATFORM_LINUX + if (os_handle < 0) + return IO_INVALID; + #endif + + #if IO_PLATFORM_WINDOWS + if (os_handle == INVALID_HANDLE_VALUE) + return IO_INVALID; + #endif + + int one = 1; + setsockopt(os_handle, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)); + + struct sockaddr_in buf; + buf.sin_family = AF_INET; + buf.sin_port = htons(port); + buf.sin_addr = addr2; + if (bind(os_handle, (struct sockaddr*) &buf, sizeof(buf))) { + close(os_handle); + return IO_INVALID; + } + + int backlog = 32; + if (listen(os_handle, backlog)) { + close(os_handle); + return IO_INVALID; + } + + res->type = IO_RES_SOCKET; + res->pending = 0; + res->os_handle = os_handle; + return handle_from_res(ioc, res); +} + +#if IO_PLATFORM_WINDOWS + +static unsigned long +convert_timeout(int timeout) +{ + if (timeout < 0) + return ~0U; + else + return timeout; +} + +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) +{ + void *unused; + struct io_os_overlap *ov; + unsigned long num; + int ok = GetQueuedCompletionStatus(ioc->os_handle, &num, &unused, &ov, convert_timeout(-1)); + + 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(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(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 new file mode 100644 index 0000000..8c9b7aa --- /dev/null +++ b/io2.h @@ -0,0 +1,148 @@ + +#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 0 +#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 + struct io_os_handle accepted; + struct io_os_overlap ov; + char accept_buffer[2 * (IO_SOCKADDR_IN_SIZE + 16)]; + #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; +}; + +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 index c7f3eb4..1c3c897 100644 --- a/makefile +++ b/makefile @@ -1,5 +1,6 @@ all: - gcc example.c io.c -o example -Wall -Wextra -ggdb + 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 \ No newline at end of file + rm example example.exe