Major refactoring in progress
This commit is contained in:
@@ -1,3 +1,5 @@
|
|||||||
example
|
example
|
||||||
example.exe
|
example.exe
|
||||||
|
example2
|
||||||
|
example2.exe
|
||||||
file_*.txt
|
file_*.txt
|
||||||
@@ -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
|
||||||
@@ -1,13 +1,16 @@
|
|||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include "io.h"
|
#include "io2.h"
|
||||||
|
|
||||||
#define NUM_OPS 3
|
#define NUM_OPS 3
|
||||||
|
|
||||||
int main()
|
int main()
|
||||||
{
|
{
|
||||||
|
io_global_init();
|
||||||
|
|
||||||
struct io_operation ops[100];
|
struct io_operation ops[100];
|
||||||
|
struct io_resource res[100];
|
||||||
struct io_context ioc;
|
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;
|
return -1;
|
||||||
|
|
||||||
io_handle files[NUM_OPS];
|
io_handle files[NUM_OPS];
|
||||||
@@ -36,5 +39,6 @@ int main()
|
|||||||
}
|
}
|
||||||
|
|
||||||
io_context_free(&ioc);
|
io_context_free(&ioc);
|
||||||
|
io_global_free();
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|||||||
+51
@@ -0,0 +1,51 @@
|
|||||||
|
#include <stdio.h>
|
||||||
|
#include <assert.h>
|
||||||
|
#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;
|
||||||
|
}
|
||||||
@@ -5,76 +5,73 @@
|
|||||||
|
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
/*
|
#define WIN32_LEAN_AND_MEAN
|
||||||
* Declare Windows symbols locally to avoid including windows.h
|
#include <winsock2.h>
|
||||||
*/
|
#include <ws2tcpip.h>
|
||||||
|
#include <mswsock.h>
|
||||||
|
|
||||||
#define GENERIC_READ 0x80000000ULL
|
bool io_global_init(void)
|
||||||
#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)
|
|
||||||
{
|
{
|
||||||
io_handle handle = CreateIoCompletionPort(IO_INVALID_HANDLE, NULL, 0, 1);
|
WSADATA data;
|
||||||
if (handle == IO_INVALID_HANDLE)
|
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;
|
return false;
|
||||||
|
|
||||||
for (uint32_t i = 0; i < max_ops; i++)
|
for (uint32_t i = 0; i < max_ops; i++)
|
||||||
ops[i].type = IO_VOID;
|
ops[i].type = IO_VOID;
|
||||||
|
|
||||||
ioc->handle = handle;
|
ioc->raw_handle = raw_handle;
|
||||||
|
ioc->max_res = max_res;
|
||||||
ioc->max_ops = max_ops;
|
ioc->max_ops = max_ops;
|
||||||
|
ioc->res = res;
|
||||||
ioc->ops = ops;
|
ioc->ops = ops;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void io_context_free(struct io_context *ioc)
|
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)
|
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,
|
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)
|
if (handle == IO_INVALID_HANDLE)
|
||||||
return false;
|
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)
|
if (op == NULL)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
@@ -102,6 +102,9 @@ bool io_start_recv(struct io_context *ioc, io_handle handle,
|
|||||||
return false;
|
return false;
|
||||||
|
|
||||||
op->type = IO_RECV;
|
op->type = IO_RECV;
|
||||||
|
op->handle = handle;
|
||||||
|
op->nextop = res->headop;
|
||||||
|
res->headop = op - ioc->ops;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -122,6 +125,9 @@ bool io_start_send(struct io_context *ioc, io_handle handle,
|
|||||||
return false;
|
return false;
|
||||||
|
|
||||||
op->type = IO_SEND;
|
op->type = IO_SEND;
|
||||||
|
op->handle = handle;
|
||||||
|
op->nextop = res->headop;
|
||||||
|
res->headop = op - ioc->ops;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -130,8 +136,50 @@ bool io_start_accept(struct io_context *ioc, io_handle handle)
|
|||||||
if (handle == IO_INVALID_HANDLE)
|
if (handle == IO_INVALID_HANDLE)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
// TODO
|
struct io_resource *rs = &ioc->res[handle];
|
||||||
return false;
|
|
||||||
|
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
|
static unsigned long
|
||||||
@@ -153,18 +201,22 @@ void io_wait(struct io_context *ioc, struct io_event *ev)
|
|||||||
void *user;
|
void *user;
|
||||||
struct io_overlap *ov;
|
struct io_overlap *ov;
|
||||||
unsigned long num;
|
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 (!ok) {
|
||||||
|
|
||||||
if (ov == NULL) {
|
if (ov == NULL) {
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* General failure
|
* General failure
|
||||||
*/
|
*/
|
||||||
|
|
||||||
ev->error = true;
|
ev->error = true;
|
||||||
ev->user = NULL; // The user must discriminate between general errors and specific operation errors through the user pointer. Not ideal
|
ev->user = NULL; // The user must discriminate between general errors and specific operation errors through the user pointer. Not ideal
|
||||||
ev->type = IO_VOID;
|
ev->type = IO_VOID;
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Operation failure
|
* Operation failure
|
||||||
*/
|
*/
|
||||||
@@ -179,7 +231,11 @@ void io_wait(struct io_context *ioc, struct io_event *ev)
|
|||||||
default:break;
|
default:break;
|
||||||
case IO_RECV: ev->num = 0; break;
|
case IO_RECV: ev->num = 0; break;
|
||||||
case IO_SEND: 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
|
op->type = IO_VOID; // Mark unused
|
||||||
@@ -197,7 +253,7 @@ void io_wait(struct io_context *ioc, struct io_event *ev)
|
|||||||
default:break;
|
default:break;
|
||||||
case IO_RECV: ev->num = num; break;
|
case IO_RECV: ev->num = num; break;
|
||||||
case IO_SEND: 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
|
op->type = IO_VOID; // Mark unused
|
||||||
@@ -250,6 +306,13 @@ io_handle io_create_file(struct io_context *ioc,
|
|||||||
return handle;
|
return handle;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
io_handle io_listen(struct io_context *ioc,
|
||||||
|
const char *addr, int port,
|
||||||
|
void *user)
|
||||||
|
{
|
||||||
|
return IO_INVALID_HANDLE;
|
||||||
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
||||||
@@ -259,9 +322,20 @@ io_handle io_create_file(struct io_context *ioc,
|
|||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
|
#include <arpa/inet.h>
|
||||||
#include <sys/mman.h>
|
#include <sys/mman.h>
|
||||||
|
#include <sys/socket.h>
|
||||||
#include <sys/syscall.h>
|
#include <sys/syscall.h>
|
||||||
|
|
||||||
|
bool io_global_init(void)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void io_global_free(void)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
static int
|
static int
|
||||||
io_uring_setup(unsigned entries,
|
io_uring_setup(unsigned entries,
|
||||||
struct io_uring_params *p)
|
struct io_uring_params *p)
|
||||||
@@ -367,6 +441,18 @@ void io_context_free(struct io_context *ioc)
|
|||||||
close(ioc->handle);
|
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,
|
static bool start_oper(struct io_context *ioc,
|
||||||
struct io_uring_sqe sqe)
|
struct io_uring_sqe sqe)
|
||||||
{
|
{
|
||||||
@@ -618,4 +704,51 @@ io_handle io_create_file(struct io_context *ioc,
|
|||||||
return fd;
|
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
|
#endif
|
||||||
|
|||||||
@@ -28,13 +28,13 @@
|
|||||||
* The OS handle type.
|
* The OS handle type.
|
||||||
*/
|
*/
|
||||||
#if IO_PLATFORM_LINUX
|
#if IO_PLATFORM_LINUX
|
||||||
typedef int io_handle;
|
typedef int io_raw_handle;
|
||||||
#define IO_INVALID_HANDLE -1
|
|
||||||
#elif IO_PLATFORM_WINDOWS
|
#elif IO_PLATFORM_WINDOWS
|
||||||
typedef void *io_handle;
|
typedef void *io_raw_handle;
|
||||||
#define IO_INVALID_HANDLE ((void*) -1)
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
typedef uint16_t io_handle;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Windows calls this structure OVERLAPPED
|
* Windows calls this structure OVERLAPPED
|
||||||
*/
|
*/
|
||||||
@@ -53,6 +53,18 @@ struct io_overlap {
|
|||||||
};
|
};
|
||||||
#endif
|
#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 {
|
enum io_optype {
|
||||||
IO_VOID,
|
IO_VOID,
|
||||||
IO_RECV,
|
IO_RECV,
|
||||||
@@ -60,18 +72,20 @@ enum io_optype {
|
|||||||
IO_ACCEPT,
|
IO_ACCEPT,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#define IO_SOCKADDR_IN_SIZE 16
|
||||||
|
|
||||||
struct io_operation {
|
struct io_operation {
|
||||||
|
|
||||||
void *user;
|
io_handle handle;
|
||||||
|
uint16_t nextop;
|
||||||
|
|
||||||
enum io_optype type;
|
enum io_optype type;
|
||||||
|
void *user;
|
||||||
#if IO_PLATFORM_LINUX
|
|
||||||
io_handle handle;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#if IO_PLATFORM_WINDOWS
|
#if IO_PLATFORM_WINDOWS
|
||||||
|
io_raw_handle accept_handle;
|
||||||
struct io_overlap ov;
|
struct io_overlap ov;
|
||||||
|
char accept_buffer[2 * (IO_SOCKADDR_IN_SIZE + 16)];
|
||||||
#endif
|
#endif
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -104,9 +118,11 @@ struct io_completion_queue {
|
|||||||
|
|
||||||
struct io_context {
|
struct io_context {
|
||||||
|
|
||||||
io_handle handle;
|
io_raw_handle raw_handle;
|
||||||
uint32_t max_ops;
|
uint16_t max_ops;
|
||||||
|
uint16_t max_res;
|
||||||
struct io_operation *ops;
|
struct io_operation *ops;
|
||||||
|
struct io_resource *res;
|
||||||
|
|
||||||
#if IO_PLATFORM_LINUX
|
#if IO_PLATFORM_LINUX
|
||||||
struct io_submission_queue submissions;
|
struct io_submission_queue submissions;
|
||||||
@@ -118,6 +134,7 @@ struct io_event {
|
|||||||
bool error;
|
bool error;
|
||||||
void *user;
|
void *user;
|
||||||
enum io_optype type;
|
enum io_optype type;
|
||||||
|
io_handle handle;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Operation-specific results
|
* Operation-specific results
|
||||||
@@ -125,15 +142,22 @@ struct io_event {
|
|||||||
union {
|
union {
|
||||||
uint32_t num; // recv, send
|
uint32_t num; // recv, send
|
||||||
io_handle handle; // accept
|
io_handle handle; // accept
|
||||||
};
|
} data;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
bool io_global_init(void);
|
||||||
|
|
||||||
|
void io_global_free(void);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Initialize an I/O context
|
* Initialize an I/O context
|
||||||
*/
|
*/
|
||||||
bool io_context_init(struct io_context *ioc,
|
bool io_context_init(struct io_context *ioc,
|
||||||
|
struct io_resource *res,
|
||||||
struct io_operation *ops,
|
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
|
* 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,
|
io_handle io_create_file(struct io_context *ioc,
|
||||||
const char *name, int flags,
|
const char *name, int flags,
|
||||||
void *user);
|
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);
|
||||||
@@ -0,0 +1,908 @@
|
|||||||
|
#include "io2.h"
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#if IO_PLATFORM_WINDOWS
|
||||||
|
#define WIN32_LEAN_AND_MEAN
|
||||||
|
#include <winsock2.h>
|
||||||
|
#include <ws2tcpip.h>
|
||||||
|
#include <mswsock.h>
|
||||||
|
#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
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
Reference in New Issue
Block a user