Add io_create_file, io_open_file

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