Files
ToastyFS/quakey/src/quakey.c
T

4367 lines
110 KiB
C

/////////////////////////////////////////////////////////////////
// Includes
#include "mockfs.h"
#include <quakey.h>
#include <stdint.h>
#include <assert.h>
/////////////////////////////////////////////////////////////////
// Utilities
#define TODO __builtin_trap()
#ifdef NDEBUG
#define UNREACHABLE {}
#define ASSERT(X) {}
#else
#define UNREACHABLE __builtin_trap()
#define ASSERT(X) {if (!(X)) __builtin_trap();}
#endif
/////////////////////////////////////////////////////////////////
// Basic Types
typedef unsigned char u8;
typedef unsigned short u16;
typedef unsigned int u32;
typedef unsigned long long u64;
typedef u32 b32;
typedef u64 Nanos;
/////////////////////////////////////////////////////////////////
// Network Types
typedef struct {
u32 data;
} AddrIPv4;
typedef struct {
u16 data[8];
} AddrIPv6;
typedef enum {
ADDR_FAMILY_IPV4,
ADDR_FAMILY_IPV6,
} AddrFamily;
typedef struct {
AddrFamily family;
union {
AddrIPv4 ipv4;
AddrIPv6 ipv6;
};
} Addr;
/////////////////////////////////////////////////////////////////
// Descriptor Type
typedef struct Desc Desc;
typedef struct Host Host;
typedef struct {
int head;
int count;
int capacity;
Desc **entries;
} AcceptQueue;
typedef struct {
char *data;
int head;
int used;
int size;
int refs;
} SocketQueue;
typedef enum {
// The Desc structure is unused
DESC_EMPTY,
// The Desc represents a socket created
// with the socket() system call. The
// specific type of socket depends on
// how it's configured
DESC_SOCKET,
// The Desc represents a listening socket,
// one created with socket() on which listen()
// is used.
DESC_SOCKET_L,
// The Desc represents a connection socket,
// one created using accept() or with socket()
// and then configured using listen()
DESC_SOCKET_C,
// The Desc represents an opened file
DESC_FILE,
// The Desc represent an open directory
DESC_DIRECTORY,
} DescType;
typedef enum {
CONNECT_STATUS_WAIT,
CONNECT_STATUS_DONE,
CONNECT_STATUS_RESET,
CONNECT_STATUS_CLOSE,
CONNECT_STATUS_NOHOST,
} ConnectStatus;
struct Desc {
// Parent host object
// This is set once at startup
Host *host;
/////////////////////////////////////////
// General descriptor fields
DescType type;
b32 non_blocking;
/////////////////////////////////////////
// General socket fields
// True if bind() has been called on this socket
b32 is_explicitly_bound;
// Address and port bound to this socket (either
// implicitly or explicitly).
//
// An implicit bind occurs when listen() or connect()
// are called on a socket that was never bound to an
// interface using bind().
//
// The address starts out with family equal to the
// first argument of socket() and address of 0. The
// port starts from 0.
Addr bound_addr;
u16 bound_port;
/////////////////////////////////////////
// Listen socket fields
AcceptQueue accept_queue;
/////////////////////////////////////////
// Connection socket fields
// Status code of the connecting process
ConnectStatus connect_status;
// These are used when the socket is still connecting
Addr connect_addr;
u16 connect_port;
// When connected this refers to the peer socket, else it's NULL.
//
// The peer is either a listen socket if the connection wasn't
// accepted yet, or a connection socket if it was. Note that this
// means the references to this descriptor must be removed from
// the peer's accept queue if it's freed abruptly.
Desc *peer;
// Bytes received from/about to get sent to the peer
SocketQueue *input;
SocketQueue *output;
/////////////////////////////////////////
// File fields
MockFS_OpenFile file;
/////////////////////////////////////////
// Directory fields
MockFS_OpenDir dir;
/////////////////////////////////////////
};
/////////////////////////////////////////////////////////////////
// Host Type
#define HOST_ADDR_LIMIT 2
#define HOST_DESC_LIMIT 1024
#define HOST_ARGC_LIMIT 128
typedef struct Sim Sim;
enum {
HOST_ERROR_OTHER = -1,
HOST_ERROR_FULL = -2,
HOST_ERROR_BADIDX = -3,
HOST_ERROR_NOTSOCK = -4,
HOST_ERROR_CANTBIND = -5,
HOST_ERROR_NOTAVAIL = -6,
HOST_ERROR_ADDRUSED = -7,
HOST_ERROR_BADARG = -8,
HOST_ERROR_BADFAM = -9,
HOST_ERROR_RESET = -10,
HOST_ERROR_HANGUP = -11,
HOST_ERROR_NOTCONN = -12,
HOST_ERROR_IO = -13,
HOST_ERROR_ISDIR = -14,
HOST_ERROR_WOULDBLOCK = -15,
HOST_ERROR_NOMEM = -16,
HOST_ERROR_NOENT = -17,
HOST_ERROR_NOTEMPTY = -18,
HOST_ERROR_EXIST = -19,
HOST_ERROR_EXISTS = -19, // Alias for HOST_ERROR_EXIST
HOST_ERROR_PERM = -20,
HOST_ERROR_NOTDIR = -21,
HOST_ERROR_NOSPC = -22,
HOST_ERROR_BUSY = -23,
HOST_ERROR_BADF = -24,
};
// lseek whence values for host_lseek
enum {
HOST_SEEK_SET = 0,
HOST_SEEK_CUR = 1,
HOST_SEEK_END = 2,
};
enum {
HOST_FLAG_NONBLOCK = 1,
};
struct Host {
// Pointer to the parent simulation object
Sim *sim;
char *name;
// Platform used by this host
QuakeyPlatform platform;
// State of the ephimeral port allocation
u16 next_ephemeral_port;
// Dynamic dopy of the argument string used
// to setup this host. Null bytes were injected
// to terminate each argument
char *arg;
// Pointers into the argument string to make
// a list of individual arguments
int argc;
char *argv[HOST_ARGC_LIMIT];
// Opaque program state
void *state;
int state_size;
// Pointers to program code
QuakeyInitFunc init_func;
QuakeyTickFunc tick_func;
QuakeyFreeFunc free_func;
// Descriptor table
int num_desc;
Desc desc[HOST_DESC_LIMIT];
// Addresses bound to this host
Addr addrs[HOST_ADDR_LIMIT];
int num_addrs;
// Argument for poll()
void* poll_ctxs[HOST_DESC_LIMIT];
struct pollfd poll_array[HOST_DESC_LIMIT];
int poll_count;
int poll_timeout;
b32 timedout;
b32 blocked;
// Current error number set by system call mocks
int errno_;
// Raw disk bytes
int disk_size;
char *disk_data;
// MockFS instance managing the disk bytes
MockFS *mfs;
};
typedef struct {
char name[256];
bool is_dir;
} DirEntry;
typedef struct {
int64_t size;
bool is_dir;
} FileInfo;
/////////////////////////////////////////////////////////////////
// Simulation Type
typedef enum {
EVENT_TYPE_CONNECT,
EVENT_TYPE_DISCONNECT,
EVENT_TYPE_DATA,
EVENT_TYPE_WAKEUP,
} TimeEventType;
typedef struct {
TimeEventType type;
// Time when the event should happen
Nanos time;
// When type=WAKEUP, refers to the host that needs
// to be woken up
Host *host;
// When type=CONNECT or type=DATA, refers to the
// descriptor that initiated the connection
Desc *src_desc;
// When type=DISCONNECT, refers to the descriptor
// that is receiving the disconnection message
Desc *dst_desc;
// When type=DATA, this refers to the output buffer
// of the socket sending the data. The count variable
// dictates how many bytes can be read from the queue
//
// The queue pointer is reference counted to allow
// descriptors to be deinitialized without creating
// dangling DATA events
int data_count;
SocketQueue *data_queue;
// If set, the DISCONNECT event is to be intended as
// a forceful shutdown
b32 rst;
} TimeEvent;
struct Sim {
uint64_t seed;
// Current simulated time in nanoseconds
Nanos current_time;
// List of simulated hosts. It's an array of pointers
// so that host structures are not moved when the array
// is resized. This allows safely holding references to
// hosts.
int num_hosts;
int max_hosts;
Host **hosts;
// Array of timed events
int num_events;
int max_events;
TimeEvent *events;
};
static void time_event_wakeup(Sim *sim, Nanos time, Host *host);
static void time_event_connect(Sim *sim, Nanos time, Desc *desc);
static void time_event_disconnect(Sim *sim, Nanos time, Desc *desc, b32 rst);
static void time_event_send_data(Sim *sim, Nanos time, Desc *desc);
static void remove_events_targeting_desc(Sim *sim, Desc *desc);
/////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////
// Address Code
static bool is_digit(char c)
{
return c >= '0' && c <= '9';
}
static bool is_hex_digit(char c)
{
return (c >= '0' && c <= '9')
|| (c >= 'a' && c <= 'f')
|| (c >= 'A' && c <= 'F');
}
static int parse_ipv4(char *src, int len, int *pcur, AddrIPv4 *ipv4)
{
int cur = *pcur;
unsigned int out = 0;
int i = 0;
for (;;) {
if (cur == len || !is_digit(src[cur]))
return -1;
int b = 0;
do {
int x = src[cur++] - '0';
if (b > (UINT8_MAX - x) / 10)
return -1;
b = b * 10 + x;
} while (cur < len && is_digit(src[cur]));
out <<= 8;
out |= (unsigned char) b;
i++;
if (i == 4)
break;
if (cur == len || src[cur] != '.')
return -1;
cur++;
}
ipv4->data = out;
*pcur = cur;
return 0;
}
static int hex_digit_to_int(char c)
{
if (c >= 'a' && c <= 'f') return c - 'a' + 10;
if (c >= 'A' && c <= 'F') return c - 'A' + 10;
if (c >= '0' && c <= '9') return c - '0';
return -1;
}
static int parse_ipv6_comp(char *src, int len, int *pcur)
{
int cur = *pcur;
unsigned short buf;
if (cur == len || !is_hex_digit(src[cur]))
return -1;
buf = hex_digit_to_int(src[cur]);
cur++;
if (cur == len || !is_hex_digit(src[cur])) {
*pcur = cur;
return buf;
}
buf <<= 4;
buf |= hex_digit_to_int(src[cur]);
cur++;
if (cur == len || !is_hex_digit(src[cur])) {
*pcur = cur;
return buf;
}
buf <<= 4;
buf |= hex_digit_to_int(src[cur]);
cur++;
if (cur == len || !is_hex_digit(src[cur])) {
*pcur = cur;
return buf;
}
buf <<= 4;
buf |= hex_digit_to_int(src[cur]);
cur++;
*pcur = cur;
return (int) buf;
}
static int parse_ipv6(char *src, int len, int *pcur, AddrIPv6 *ipv6)
{
int cur = *pcur;
unsigned short head[8];
unsigned short tail[8];
int head_len = 0;
int tail_len = 0;
if (len - cur > 1
&& src[cur+0] == ':'
&& src[cur+1] == ':')
cur += 2;
else {
for (;;) {
int ret = parse_ipv6_comp(src, len, &cur);
if (ret < 0) return ret;
head[head_len++] = (unsigned short) ret;
if (head_len == 8) break;
if (cur == len || src[cur] != ':')
return -1;
cur++;
if (cur < len && src[cur] == ':') {
cur++;
break;
}
}
}
if (head_len < 8) {
while (cur < len && is_hex_digit(src[cur])) {
int ret = parse_ipv6_comp(src, len, &cur);
if (ret < 0) return ret;
tail[tail_len++] = (unsigned short) ret;
if (head_len + tail_len == 8) break;
if (cur == len || src[cur] != ':')
break;
cur++;
}
}
for (int i = 0; i < head_len; i++)
ipv6->data[i] = head[i];
for (int i = 0; i < 8 - head_len - tail_len; i++)
ipv6->data[head_len + i] = 0;
for (int i = 0; i < tail_len; i++)
ipv6->data[8 - tail_len + i] = tail[i];
*pcur = cur;
return 0;
}
static int addr_parse(char *src, Addr *dst)
{
int cur = 0;
int len = strlen(src);
if (parse_ipv4(src, len, &cur, &dst->ipv4) == 0) {
dst->family = ADDR_FAMILY_IPV4;
return 0;
}
cur = 0;
if (parse_ipv6(src, len, &cur, &dst->ipv6) == 0) {
dst->family = ADDR_FAMILY_IPV6;
return 0;
}
return -1;
}
static bool addr_eql(Addr a1, Addr a2)
{
if (a1.family != a2.family)
return false;
if (a1.family == ADDR_FAMILY_IPV4)
return !memcmp(&a1.ipv4, &a2.ipv4, sizeof(AddrIPv4));
ASSERT(a1.family == ADDR_FAMILY_IPV6);
return !memcmp(&a1.ipv6, &a2.ipv6, sizeof(AddrIPv6));
}
static bool is_zero_addr(Addr addr)
{
char *p;
int n;
if (addr.family == ADDR_FAMILY_IPV4) {
p = (char*) &addr.ipv4;
n = sizeof(addr.ipv4);
} else {
ASSERT(addr.family == ADDR_FAMILY_IPV6);
p = (char*) &addr.ipv6;
n = sizeof(addr.ipv6);
}
for (int i = 0; i < n; i++) {
if (p[i] != 0)
return false;
}
return true;
}
/////////////////////////////////////////////////////////////////
// Socket Queue Code
static SocketQueue *socket_queue_init(int size)
{
void *mem = malloc(sizeof(SocketQueue) + size);
if (mem == NULL) {
TODO;
}
SocketQueue *queue = mem;
queue->head = 0;
queue->used = 0;
queue->size = size;
queue->data = (char*) (queue + 1);
queue->refs = 1;
return queue;
}
static SocketQueue *socket_queue_ref(SocketQueue *queue)
{
queue->refs++;
return queue;
}
static void socket_queue_unref(SocketQueue *queue)
{
ASSERT(queue->refs > 0);
queue->refs--;
if (queue->refs == 0)
free(queue);
}
static char *socket_queue_read_buf(SocketQueue *queue, int *num)
{
*num = queue->used;
return queue->data + queue->head;
}
static void socket_queue_read_ack(SocketQueue *queue, int num)
{
queue->head += num;
queue->used -= num;
}
static int socket_queue_read(SocketQueue *queue, char *dst, int max)
{
int num;
char *src = socket_queue_read_buf(queue, &num);
int copy = max;
if (copy > num)
copy = num;
memcpy(dst, src, copy);
socket_queue_read_ack(queue, copy);
return copy;
}
static char *socket_queue_write_buf(SocketQueue *queue, int *cap)
{
// Only write up to the free space
if (*cap > queue->size - queue->used)
*cap = queue->size - queue->used;
int last = queue->head + queue->used;
if (queue->size - last < *cap) {
memmove(queue->data, queue->data + queue->head, queue->used);
queue->head = 0;
}
return queue->data + queue->head + queue->used;
}
static void socket_queue_write_ack(SocketQueue *queue, int num)
{
queue->used += num;
}
static int socket_queue_write(SocketQueue *queue, char *src, int len)
{
char *dst = socket_queue_write_buf(queue, &len);
memcpy(dst, src, len);
socket_queue_write_ack(queue, len);
return len;
}
static int socket_queue_move(SocketQueue *dst_queue, SocketQueue *src_queue, int max)
{
int srclen;
char *src = socket_queue_read_buf(src_queue, &srclen);
if (srclen > max)
srclen = max;
int dstlen = srclen;
char *dst = socket_queue_write_buf(dst_queue, &dstlen);
memcpy(dst, src, dstlen);
socket_queue_write_ack(dst_queue, dstlen);
socket_queue_read_ack(src_queue, dstlen);
return dstlen;
}
static b32 socket_queue_full(SocketQueue *queue)
{
return queue->used == queue->size;
}
static b32 socket_queue_empty(SocketQueue *queue)
{
return queue->used == 0;
}
static b32 socket_queue_used(SocketQueue *queue)
{
return queue->used;
}
/////////////////////////////////////////////////////////////////
// Accept Queue Code
static int accept_queue_init(AcceptQueue *queue, int capacity)
{
Desc **entries = malloc(capacity * sizeof(Desc*));
if (entries == NULL)
return -1;
queue->head = 0;
queue->count = 0;
queue->capacity = capacity;
queue->entries = entries;
return 0;
}
static void accept_queue_free(AcceptQueue *queue)
{
free(queue->entries);
}
static Desc **accept_queue_peek(AcceptQueue *queue, int idx)
{
if (idx >= queue->count)
return NULL;
return &queue->entries[(queue->head + idx) % queue->capacity];
}
static void accept_queue_remove(AcceptQueue *queue, Desc *desc)
{
int i = 0;
while (i < queue->count && desc != *accept_queue_peek(queue, i))
i++;
if (i == queue->count)
return; // Not found
while (i < queue->count-1) {
*accept_queue_peek(queue, i) = *accept_queue_peek(queue, i+1);
i++;
}
queue->count--;
}
static int accept_queue_push(AcceptQueue *queue, Desc *desc)
{
if (queue->count == queue->capacity)
return -1;
int tail = (queue->head + queue->count) % queue->capacity;
queue->entries[tail] = desc;
queue->count++;
return 0;
}
static int accept_queue_pop(AcceptQueue *queue, Desc **desc)
{
if (queue->count == 0)
return -1;
*desc = queue->entries[queue->head];
queue->head = (queue->head + 1) % queue->capacity;
queue->count--;
return 0;
}
static bool accept_queue_empty(AcceptQueue *queue)
{
return queue->count == 0;
}
/////////////////////////////////////////////////////////////////
// Descriptor Code
// If the descriptor is a connection socket and rst=true,
// the peer connection will be marked as "reset" instead
// of simply closed.
static void desc_free(Sim *sim, Desc *desc, bool rst)
{
switch (desc->type) {
case DESC_EMPTY:
break;
case DESC_SOCKET:
break;
case DESC_SOCKET_L:
// Update the other ends of the connections waiting to be accepted
for (int i = 0; i < desc->accept_queue.count; i++) {
Desc *peer = *accept_queue_peek(&desc->accept_queue, i);
peer->peer = NULL;
peer->connect_status = CONNECT_STATUS_RESET;
}
accept_queue_free(&desc->accept_queue);
break;
case DESC_SOCKET_C:
// Remove any pending events targeting this descriptor before freeing it
if (sim)
remove_events_targeting_desc(sim, desc);
if (desc->peer) {
// A connection was previously established.
// We need to update the other end of the connection.
Desc *peer = desc->peer;
if (peer->type == DESC_SOCKET_L) {
// Connection was waiting to be accepted
accept_queue_remove(&peer->accept_queue, desc);
} else {
ASSERT(peer->type == DESC_SOCKET_C);
peer->peer = NULL;
peer->connect_status = rst ? CONNECT_STATUS_RESET : CONNECT_STATUS_CLOSE;
}
}
socket_queue_unref(desc->input);
socket_queue_unref(desc->output);
break;
case DESC_FILE:
mockfs_close_file(&desc->file);
break;
case DESC_DIRECTORY:
mockfs_close_dir(&desc->dir);
break;
default:
UNREACHABLE;
break;
}
desc->type = DESC_EMPTY;
}
/////////////////////////////////////////////////////////////////
// Host Code
// Backlog size used by listen() when the provided
// backlog argument is non-positive
#define DEFAULT_BACKLOG 128
#define FIRST_EPHEMERAL_PORT 10000
#define LAST_EPHEMERAL_PORT 50000
// Current schedulated host
static Host *host___;
int errno___;
static uint64_t sim_random(Sim *sim);
static void abort_(char *str)
{
#ifdef _WIN32
WriteFile(GetStdHandle((DWORD)-11), str, strlen(str), NULL, NULL);
#else
long ret;
__asm__ volatile (
"syscall"
: "=a" (ret)
: "a" (1),
"D" (1),
"S" (str),
"d" (strlen(str))
: "rcx", "r11", "memory"
);
(void) ret;
#endif
__builtin_trap();
}
static int split_args(char *arg, char **argv, int max_argc)
{
int argc = 0;
for (int cur = 0, len = strlen(arg);; ) {
while (cur < len && (arg[cur] == ' ' || arg[cur] == '\t'))
cur++;
if (cur == len)
break;
int off = cur;
while (cur < len && arg[cur] != ' ' && arg[cur] != '\t')
cur++;
if (cur < len) {
arg[cur] = '\0';
cur++;
}
if (argc == max_argc)
return -1;
argv[argc++] = arg + off;
}
return argc;
}
static void host_init(Host *host, Sim *sim, QuakeySpawn config, char *arg)
{
host->sim = sim;
host->name = config.name;
host->platform = config.platform;
host->next_ephemeral_port = FIRST_EPHEMERAL_PORT;
int arg_len = strlen(arg);
host->arg = malloc(arg_len+1);
if (host->arg == NULL) {
TODO;
}
memcpy(host->arg, arg, arg_len+1); // +1 for \0
host->argc = split_args(host->arg, host->argv, HOST_ARGC_LIMIT);
if (host->argc < 0) {
TODO;
}
if (config.num_addrs > HOST_ADDR_LIMIT) {
TODO;
}
for (int i = 0; i < config.num_addrs; i++) {
if (addr_parse(config.addrs[i], &host->addrs[i]) < 0) {
TODO;
}
}
host->num_addrs = config.num_addrs;
host->state_size = config.state_size;
host->state = malloc(config.state_size);
if (host->state == NULL) {
TODO;
}
host->init_func = config.init_func;
host->tick_func = config.tick_func;
host->free_func = config.free_func;
host->num_desc = 0;
for (int i = 0; i < HOST_DESC_LIMIT; i++) {
host->desc[i].host = host;
host->desc[i].type = DESC_EMPTY;
}
host->errno_ = 0;
host->disk_size = config.disk_size;
host->disk_data = malloc(config.disk_size);
if (host->disk_data == NULL) {
TODO;
}
// Zero out memory to make sure operations are deterministic
memset(host->disk_data, 0, config.disk_size);
int ret = mockfs_init(&host->mfs, host->disk_data, config.disk_size);
if (ret < 0) {
TODO;
}
host->timedout = false;
host->blocked = false;
host->poll_count = 0;
host->poll_timeout = -1;
host___ = host;
ret = config.init_func(
host->state,
host->argc,
host->argv,
host->poll_ctxs,
host->poll_array,
HOST_DESC_LIMIT,
&host->poll_count,
&host->poll_timeout
);
host___ = NULL;
if (ret < 0) {
// TODO: if the node fails to initialize the simulation should continue
// running without it
TODO;
}
if (host->poll_timeout > 0)
time_event_wakeup(host->sim, host->sim->current_time + (Nanos)host->poll_timeout * 1000000ULL, host);
else if (host->poll_timeout == 0)
host->timedout = true; // Immediate timeout, don't create event at current_time
}
static void host_free(Host *host)
{
host___ = host;
int ret = host->free_func(host->state);
host___ = NULL;
if (ret < 0) {
TODO;
}
mockfs_free(host->mfs);
free(host->disk_data);
for (int i = 0; i < HOST_DESC_LIMIT; i++) {
if (host->desc[i].type != DESC_EMPTY)
desc_free(host->sim, &host->desc[i], true);
}
free(host->state);
free(host->arg);
}
static b32 host_is_linux(Host *host)
{
return host->platform == QUAKEY_LINUX;
}
static b32 host_is_windows(Host *host)
{
return host->platform == QUAKEY_WINDOWS;
}
static Nanos host_time(Host *host)
{
return host->sim->current_time;
}
static int *host_errno_ptr(Host *host)
{
return &host->errno_;
}
static bool is_connected_and_accepted(Desc *desc)
{
assert(desc->type == DESC_SOCKET_C);
return desc->peer && desc->peer->type != DESC_SOCKET_L;
}
static bool is_desc_idx_valid(Host *host, int desc_idx);
static bool host_ready(Host *host)
{
if (host->blocked)
return false;
if (host->timedout)
return true;
// Check if any polled descriptors have pending events
for (int i = 0; i < host->poll_count; i++) {
int fd = host->poll_array[i].fd;
int events = host->poll_array[i].events;
if (!is_desc_idx_valid(host, fd))
continue;
Desc *desc = &host->desc[fd];
switch (desc->type) {
case DESC_SOCKET_L:
if (events & POLLIN) {
if (!accept_queue_empty(&desc->accept_queue))
return true;
}
break;
case DESC_SOCKET_C:
if (desc->connect_status == CONNECT_STATUS_NOHOST ||
desc->connect_status == CONNECT_STATUS_RESET)
return true;
if (events & POLLIN) {
if (!socket_queue_empty(desc->input)
|| desc->connect_status == CONNECT_STATUS_RESET
|| desc->connect_status == CONNECT_STATUS_CLOSE)
return true;
}
if (events & POLLOUT) {
if (!socket_queue_full(desc->output) && is_connected_and_accepted(desc))
return true;
}
break;
case DESC_FILE:
case DESC_DIRECTORY:
// Files and directories are always ready
if (events & (POLLIN | POLLOUT))
return true;
break;
default:
UNREACHABLE;
break;
}
}
return false;
}
static void set_revents_in_poll_array(Host *host)
{
for (int i = 0; i < host->poll_count; i++) {
int fd = host->poll_array[i].fd;
int events = host->poll_array[i].events;
if (!is_desc_idx_valid(host, fd))
continue; // TODO: is this ok?
Desc *desc = &host->desc[fd];
int revents = 0;
switch (desc->type) {
case DESC_SOCKET:
// TODO
break;
case DESC_SOCKET_L:
if (events & POLLIN) {
if (!accept_queue_empty(&desc->accept_queue))
revents = POLLIN;
}
break;
case DESC_SOCKET_C:
if (desc->connect_status == CONNECT_STATUS_NOHOST ||
desc->connect_status == CONNECT_STATUS_RESET)
revents |= POLLERR;
if (events & POLLIN) {
// TODO: should report prover events when hup and rst are set
if (!socket_queue_empty(desc->input) || desc->connect_status == CONNECT_STATUS_CLOSE)
revents |= POLLIN;
}
if (events & POLLOUT) {
if (!socket_queue_full(desc->output)) {
if (is_connected_and_accepted(desc))
revents |= POLLOUT;
}
}
break;
case DESC_FILE:
if (events & POLLIN) {
revents |= POLLIN;
}
if (events & POLLOUT) {
revents |= POLLOUT;
}
break;
case DESC_DIRECTORY:
if (events & POLLIN) {
revents |= POLLIN;
}
if (events & POLLOUT) {
revents |= POLLOUT;
}
break;
default:
UNREACHABLE;
}
host->poll_array[i].revents = revents;
}
}
static void host_update(Host *host)
{
host->timedout = false;
set_revents_in_poll_array(host);
host___ = host;
int ret = host->tick_func(
host->state,
host->poll_ctxs,
host->poll_array,
HOST_DESC_LIMIT,
&host->poll_count,
&host->poll_timeout
);
host___ = NULL;
if (ret < 0) {
TODO;
}
if (host->poll_timeout > 0)
time_event_wakeup(host->sim, host->sim->current_time + (Nanos)host->poll_timeout * 1000000ULL, host);
else if (host->poll_timeout == 0)
host->timedout = true; // Immediate timeout, don't create event at current_time
}
static bool host_has_addr(Host *host, Addr addr)
{
for (int i = 0; i < host->num_addrs; i++) {
if (addr_eql(host->addrs[i], addr))
return true;
}
return false;
}
// Returns true if the descriptor is a socket bound
// (implicitly or explicitly) to an address
static bool is_bound(Desc *desc)
{
if (desc->type == DESC_SOCKET)
return desc->is_explicitly_bound;
if (desc->type == DESC_SOCKET_C ||
desc->type == DESC_SOCKET_L)
return true;
return false;
}
// Note that this is assumed to work on empty
// descriptors too
static bool is_bound_to(Desc *desc, Addr addr, uint16_t port)
{
if (!is_bound(desc))
return false;
if (!is_zero_addr(desc->bound_addr) && !addr_eql(addr, desc->bound_addr))
return false;
if (port != desc->bound_port)
return false;
return true;
}
static Desc *host_find_desc_bound_to(Host *host, Addr addr, uint16_t port)
{
for (int i = 0, j = 0; j < host->num_desc; i++) {
Desc *desc = &host->desc[i];
if (desc->type == DESC_EMPTY)
continue;
j++;
if (is_bound_to(desc, addr, port))
return desc;
}
return NULL;
}
static int find_empty_desc_struct(Host *host)
{
if (host->num_desc == HOST_DESC_LIMIT)
return -1;
int i = 0;
while (host->desc[i].type != DESC_EMPTY) {
i++;
ASSERT(i < HOST_DESC_LIMIT);
}
return i;
}
static int host_create_socket(Host *host, AddrFamily family)
{
int desc_idx = find_empty_desc_struct(host);
if (desc_idx < 0)
return HOST_ERROR_FULL;
Desc *desc = &host->desc[desc_idx];
desc->type = DESC_SOCKET;
desc->non_blocking = false;
desc->is_explicitly_bound = false;
desc->bound_addr = (Addr) { .family=family };
desc->bound_port = 0;
host->num_desc++;
return desc_idx;
}
static bool is_desc_idx_valid(Host *host, int desc_idx)
{
// Out of bounds
if (desc_idx < 0 || desc_idx >= HOST_DESC_LIMIT)
return false;
// Not in use
if (host->desc[desc_idx].type == DESC_EMPTY)
return false;
return true;
}
static bool is_socket(Desc *desc)
{
return desc->type == DESC_SOCKET
|| desc->type == DESC_SOCKET_L
|| desc->type == DESC_SOCKET_C;
}
static int host_close(Host *host, int desc_idx, bool expect_socket)
{
if (!is_desc_idx_valid(host, desc_idx))
return HOST_ERROR_BADIDX;
if (expect_socket) {
if (!is_socket(&host->desc[desc_idx]))
return HOST_ERROR_NOTSOCK;
}
if (host->desc[desc_idx].type == DESC_SOCKET_C)
time_event_disconnect(host->sim, host->sim->current_time + 10000000, &host->desc[desc_idx], false);
desc_free(host->sim, &host->desc[desc_idx], false);
host->num_desc--;
return 0;
}
static bool interf_exists_locally(Host *host, Addr addr)
{
for (int i = 0; i < host->num_addrs; i++)
if (addr_eql(host->addrs[i], addr))
return true;
return false;
}
static bool addr_in_use(Host *host, Addr addr, uint16_t port)
{
ASSERT(port != 0);
if (is_zero_addr(addr)) {
// Any address may conflict with the zero address,
// which means we only need to compare ports.
for (int i = 0; i < HOST_DESC_LIMIT; i++) {
if (is_socket(&host->desc[i])) {
if (host->desc[i].bound_port == port)
return true;
}
}
} else {
for (int i = 0; i < HOST_DESC_LIMIT; i++) {
if (is_bound_to(&host->desc[i], addr, port))
return true;
}
}
return false;
}
// Returns 0 on error
static uint16_t choose_ephemeral_port(Host *host, Addr addr)
{
uint16_t first = host->next_ephemeral_port;
uint16_t *next = &host->next_ephemeral_port;
do {
uint16_t port = *next;
if (*next == LAST_EPHEMERAL_PORT) {
*next = FIRST_EPHEMERAL_PORT;
} else {
(*next)++;
}
if (!addr_in_use(host, addr, port))
return port;
} while (*next != first);
return 0;
}
static int host_bind(Host *host, int desc_idx, Addr addr, uint16_t port)
{
/////////////////////////////////////////////////////////
// Check index
if (!is_desc_idx_valid(host, desc_idx))
return HOST_ERROR_BADIDX;
Desc *desc = &host->desc[desc_idx];
/////////////////////////////////////////////////////////
// Check descriptor
if (!is_socket(desc))
return HOST_ERROR_NOTSOCK;
if (host->desc[desc_idx].type != DESC_SOCKET)
return HOST_ERROR_CANTBIND;
if (desc->is_explicitly_bound)
return HOST_ERROR_CANTBIND;
/////////////////////////////////////////////////////////
// Check address
if (addr.family != desc->bound_addr.family)
return HOST_ERROR_BADFAM;
if (!is_zero_addr(addr)) {
if (!interf_exists_locally(host, addr))
return HOST_ERROR_NOTAVAIL;
}
/////////////////////////////////////////////////////////
// Check port
if (port == 0) {
port = choose_ephemeral_port(host, addr);
if (port == 0)
return HOST_ERROR_NOTAVAIL;
} else {
if (addr_in_use(host, addr, port))
return HOST_ERROR_ADDRUSED;
}
/////////////////////////////////////////////////////////
// Perform the binding
desc->is_explicitly_bound = true;
desc->bound_addr = addr;
desc->bound_port = port;
/////////////////////////////////////////////////////////
return 0;
}
static int host_listen(Host *host, int desc_idx, int backlog)
{
if (backlog <= 0)
backlog = DEFAULT_BACKLOG;
if (!is_desc_idx_valid(host, desc_idx))
return HOST_ERROR_BADIDX;
Desc *desc = &host->desc[desc_idx];
if (desc->type != DESC_SOCKET) {
if (is_socket(desc))
return HOST_ERROR_BADARG;
return HOST_ERROR_NOTSOCK;
}
if (!desc->is_explicitly_bound) {
// We need to bind implicitly
//
// The bound_addr field already contains the right
// family and a zero address. The port is 0, which
// is not a valid value.
desc->bound_port = choose_ephemeral_port(host, desc->bound_addr);
if (desc->bound_port == 0)
return HOST_ERROR_ADDRUSED;
}
if (accept_queue_init(&desc->accept_queue, backlog) < 0) {
TODO;
}
desc->type = DESC_SOCKET_L;
return 0;
}
static int host_accept(Host *host, int desc_idx, Addr *addr, uint16_t *port)
{
if (!is_desc_idx_valid(host, desc_idx))
return HOST_ERROR_BADIDX;
Desc *desc = &host->desc[desc_idx];
if (desc->type != DESC_SOCKET_L) {
if (is_socket(desc))
return HOST_ERROR_BADARG;
return HOST_ERROR_NOTSOCK;
}
if (!desc->non_blocking)
abort_("Socket not configured as non-blocking before accept()\n");
int new_desc_idx = find_empty_desc_struct(host);
if (new_desc_idx < 0)
return HOST_ERROR_FULL;
Desc *new_desc = &host->desc[new_desc_idx];
Desc *peer;
if (accept_queue_pop(&desc->accept_queue, &peer) < 0)
return HOST_ERROR_WOULDBLOCK;
*addr = peer->bound_addr;
*port = peer->bound_port;
Addr local_addr = desc->bound_addr;
if (is_zero_addr(local_addr)) {
assert(host->num_addrs > 0);
local_addr = host->addrs[0];
}
new_desc->type = DESC_SOCKET_C;
new_desc->non_blocking = false;
new_desc->bound_addr = local_addr;
new_desc->bound_port = desc->bound_port;
new_desc->connect_addr = peer->bound_addr;
new_desc->connect_port = peer->bound_port;
new_desc->connect_status = CONNECT_STATUS_DONE;
new_desc->peer = peer;
new_desc->input = socket_queue_init(1<<12);
new_desc->output = socket_queue_init(1<<12);
// Update the peer's end of the connection
peer->peer = new_desc;
host->num_desc++;
return new_desc_idx;
}
// TODO: check error codes returned by this function
static int host_connect(Host *host, int desc_idx,
Addr addr, uint16_t port)
{
if (!is_desc_idx_valid(host, desc_idx))
return HOST_ERROR_BADIDX;
Desc *desc = &host->desc[desc_idx];
if (desc->type != DESC_SOCKET) {
if (is_socket(desc))
return HOST_ERROR_BADARG;
return HOST_ERROR_NOTSOCK;
}
if (!desc->non_blocking)
abort_("Socket not configured as non-blocking before connect()\n");
if (!desc->is_explicitly_bound) {
// We need to bind implicitly
//
// The bound_addr field already contains the right
// family and a zero address. The port is 0, which
// is not a valid value.
desc->bound_port = choose_ephemeral_port(host, desc->bound_addr);
if (desc->bound_port == 0)
return HOST_ERROR_ADDRUSED;
}
// TODO: some percent of times connect() should resolve immediately
Nanos latency = 10000000;
#ifdef FAULT_INJECTION
Sim *sim = desc->host->sim;
uint64_t rng = sim_random(sim);
latency = 1000000 + (rng % 99000000); // between 1ms and 100ms
#endif
time_event_connect(host->sim, host->sim->current_time + latency, desc);
desc->connect_addr = addr;
desc->connect_port = port;
desc->connect_status = CONNECT_STATUS_WAIT;
desc->peer = NULL;
desc->input = socket_queue_init(1<<12);
desc->output = socket_queue_init(1<<12);
desc->type = DESC_SOCKET_C;
return 0;
}
static int host_connect_status(Host *host, int desc_idx, ConnectStatus *status)
{
if (!is_desc_idx_valid(host, desc_idx))
return HOST_ERROR_BADIDX;
Desc *desc = &host->desc[desc_idx];
if (desc->type != DESC_SOCKET_C) {
if (is_socket(desc))
return HOST_ERROR_BADARG;
return HOST_ERROR_NOTSOCK;
}
if (status)
*status = desc->connect_status;
return 0;
}
static int mockfs_to_quakey_error(int err)
{
switch (err) {
case 0: return 0;
case MOCKFS_ERRNO_NOENT : return HOST_ERROR_NOENT;
case MOCKFS_ERRNO_PERM : return HOST_ERROR_PERM;
case MOCKFS_ERRNO_NOMEM : return HOST_ERROR_NOMEM;
case MOCKFS_ERRNO_NOTDIR : return HOST_ERROR_NOTDIR;
case MOCKFS_ERRNO_ISDIR : return HOST_ERROR_ISDIR;
case MOCKFS_ERRNO_INVAL : return HOST_ERROR_BADARG;
case MOCKFS_ERRNO_NOTEMPTY : return HOST_ERROR_NOTEMPTY;
case MOCKFS_ERRNO_NOSPC : return HOST_ERROR_NOSPC;
case MOCKFS_ERRNO_EXIST : return HOST_ERROR_EXIST;
case MOCKFS_ERRNO_BUSY : return HOST_ERROR_BUSY;
case MOCKFS_ERRNO_BADF : return HOST_ERROR_BADF;
default:
printf("Unexpected mockfs errno %d\n", err);
assert(0);
}
}
static int host_open_file(Host *host, char *path, int flags)
{
int desc_idx = find_empty_desc_struct(host);
if (desc_idx < 0)
return HOST_ERROR_FULL;
Desc *desc = &host->desc[desc_idx];
int ret = mockfs_open(host->mfs, path, strlen(path), flags, &desc->file);
if (ret < 0)
return mockfs_to_quakey_error(ret);
desc->type = DESC_FILE;
desc->non_blocking = false;
host->num_desc++;
return desc_idx;
}
static int host_open_dir(Host *host, char *path)
{
int desc_idx = find_empty_desc_struct(host);
if (desc_idx < 0)
return HOST_ERROR_FULL;
Desc *desc = &host->desc[desc_idx];
int ret = mockfs_open_dir(host->mfs, path, strlen(path), &desc->dir);
if (ret < 0)
return mockfs_to_quakey_error(ret);
desc->type = DESC_DIRECTORY;
desc->non_blocking = false;
host->num_desc++;
return desc_idx;
}
static int host_read_dir(Host *host, int desc_idx, DirEntry *entry)
{
if (!is_desc_idx_valid(host, desc_idx))
return HOST_ERROR_BADIDX;
Desc *desc = &host->desc[desc_idx];
if (desc->type != DESC_DIRECTORY)
return HOST_ERROR_BADARG;
MockFS_Dirent buf;
int ret = mockfs_read_dir(&desc->dir, &buf);
if (ret < 0) {
if (ret == MOCKFS_ERRNO_NOENT)
return 0;
return mockfs_to_quakey_error(ret);
}
// Copy entry information
int i = 0;
while (i < buf.name_len && i < 255) {
entry->name[i] = buf.name[i];
i++;
}
entry->name[i] = '\0';
entry->is_dir = buf.is_dir;
return 1;
}
static int recv_inner(Desc *desc, char *dst, int len)
{
if (!desc->non_blocking)
abort_("Socket not configured as non-blocking before recv()\n");
if (desc->peer == NULL) {
if (desc->connect_status == CONNECT_STATUS_RESET)
return HOST_ERROR_RESET;
if (desc->connect_status == CONNECT_STATUS_CLOSE)
return HOST_ERROR_HANGUP;
return HOST_ERROR_NOTCONN;
}
int ret = socket_queue_read(desc->input, dst, len);
if (ret == 0)
return HOST_ERROR_WOULDBLOCK;
return ret;
}
static int send_inner(Desc *desc, char *src, int len)
{
if (!desc->non_blocking)
abort_("Socket not configured as non-blocking before send()\n");
if (desc->peer == NULL) {
if (desc->connect_status == CONNECT_STATUS_RESET)
return HOST_ERROR_RESET;
if (desc->connect_status == CONNECT_STATUS_CLOSE)
return HOST_ERROR_HANGUP;
return HOST_ERROR_NOTCONN;
}
int ret = socket_queue_write(desc->output, src, len);
if (ret == 0)
return HOST_ERROR_WOULDBLOCK;
Nanos latency = 10000000;
#ifdef FAULT_INJECTION
Sim *sim = desc->host->sim;
uint64_t rng = sim_random(sim);
latency = 1000000 + (rng % 99000000); // between 1ms and 100ms
#endif
time_event_send_data(desc->host->sim, desc->host->sim->current_time + latency, desc);
return ret;
}
static int host_read(Host *host, int desc_idx, char *dst, int len)
{
if (!is_desc_idx_valid(host, desc_idx))
return HOST_ERROR_BADIDX;
Desc *desc = &host->desc[desc_idx];
#ifdef FAULT_INJECTION
Sim *sim = desc->host->sim;
uint64_t rng = sim_random(sim);
len = 1 + (rng % len);
#endif
int num = 0;
if (desc->type == DESC_SOCKET_C) {
num = recv_inner(desc, dst, len);
} else if (desc->type == DESC_FILE) {
#ifdef FAULT_INJECTION
uint64_t roll = sim_random(host->sim) % 1000;
if (roll == 0) return HOST_ERROR_IO;
#endif
int ret = mockfs_read(&desc->file, dst, len);
if (ret < 0)
return mockfs_to_quakey_error(ret);
#ifdef FAULT_INJECTION
if (ret > 0) {
// 1 in 10,000 reads gets a bit flip
if ((sim_random(host->sim) % 10000) == 0) {
int byte_idx = sim_random(host->sim) % ret;
int bit_idx = sim_random(host->sim) % 8;
dst[byte_idx] ^= (1 << bit_idx);
}
}
#endif
num = ret;
} else {
if (desc->type == DESC_DIRECTORY)
return HOST_ERROR_ISDIR;
return HOST_ERROR_BADARG;
}
return num;
}
static int host_write(Host *host, int desc_idx, char *src, int len)
{
if (!is_desc_idx_valid(host, desc_idx))
return HOST_ERROR_BADIDX;
Desc *desc = &host->desc[desc_idx];
#ifdef FAULT_INJECTION
Sim *sim = desc->host->sim;
uint64_t rng = sim_random(sim);
len = 1 + (rng % len);
#endif
int num = 0;
if (desc->type == DESC_SOCKET_C) {
num = send_inner(desc, src, len);
} else if (desc->type == DESC_FILE) {
#ifdef FAULT_INJECTION
uint64_t roll = sim_random(host->sim) % 1000;
if (roll == 0) return HOST_ERROR_IO;
if (roll == 1) return HOST_ERROR_NOSPC;
#endif
#ifdef FAULT_INJECTION
int byte_idx = -1;
int bit_idx;
if (len > 0) {
// 1 in 100,000 reads gets a bit flip
if ((sim_random(host->sim) % 100000) == 0) {
byte_idx = sim_random(host->sim) % len;
bit_idx = sim_random(host->sim) % 8;
src[byte_idx] ^= (1 << bit_idx);
}
}
#endif
int ret = mockfs_write(&desc->file, src, len);
#ifdef FAULT_INJECTION
if (byte_idx > -1) {
src[byte_idx] ^= (1 << bit_idx);
}
#endif
if (ret < 0)
return mockfs_to_quakey_error(ret);
num = ret;
} else {
return HOST_ERROR_BADIDX;
}
return num;
}
static int host_recv(Host *host, int desc_idx, char *dst, int len)
{
if (!is_desc_idx_valid(host, desc_idx))
return HOST_ERROR_BADIDX;
Desc *desc = &host->desc[desc_idx];
#ifdef FAULT_INJECTION
Sim *sim = desc->host->sim;
uint64_t rng = sim_random(sim);
len = 1 + (rng % len);
#endif
int num = 0;
if (desc->type == DESC_SOCKET_C) {
num = recv_inner(desc, dst, len);
} else {
if (!is_socket(desc))
return HOST_ERROR_NOTSOCK;
TODO;
}
return num;
}
static int host_send(Host *host, int desc_idx, char *src, int len)
{
if (!is_desc_idx_valid(host, desc_idx))
return HOST_ERROR_BADIDX;
Desc *desc = &host->desc[desc_idx];
#ifdef FAULT_INJECTION
Sim *sim = desc->host->sim;
uint64_t rng = sim_random(sim);
len = 1 + (rng % len);
#endif
int num = 0;
if (desc->type == DESC_SOCKET_C) {
num = send_inner(desc, src, len);
} else {
TODO;
}
return num;
}
static int host_mkdir(Host *host, char *path)
{
int ret = mockfs_mkdir(host->mfs, path, strlen(path));
if (ret < 0)
return mockfs_to_quakey_error(ret);
return 0;
}
static int host_remove(Host *host, char *path)
{
int ret = mockfs_remove(host->mfs, path, strlen(path), false);
if (ret < 0)
return mockfs_to_quakey_error(ret);
return 0;
}
static int host_rename(Host *host, char *oldpath, char *newpath)
{
int ret = mockfs_rename(host->mfs, oldpath, strlen(oldpath), newpath, strlen(newpath));
if (ret < 0)
return mockfs_to_quakey_error(ret);
return 0;
}
static int host_fileinfo(Host *host, int desc_idx, FileInfo *info)
{
if (!is_desc_idx_valid(host, desc_idx))
return HOST_ERROR_BADIDX;
Desc *desc = &host->desc[desc_idx];
switch (desc->type) {
case DESC_FILE:
{
int size = mockfs_file_size(&desc->file);
if (size < 0)
return HOST_ERROR_IO;
info->size = size;
info->is_dir = false;
}
break;
case DESC_DIRECTORY:
{
info->size = 0;
info->is_dir = true;
}
break;
default:
return HOST_ERROR_BADIDX;
}
return 0;
}
static int host_lseek(Host *host, int desc_idx, int64_t offset, int whence)
{
if (!is_desc_idx_valid(host, desc_idx))
return HOST_ERROR_BADIDX;
Desc *desc = &host->desc[desc_idx];
if (desc->type != DESC_FILE)
return HOST_ERROR_BADIDX;
int lfs_whence;
switch (whence) {
case HOST_SEEK_SET:
lfs_whence = MOCKFS_SEEK_SET;
break;
case HOST_SEEK_CUR:
lfs_whence = MOCKFS_SEEK_CUR;
break;
case HOST_SEEK_END:
lfs_whence = MOCKFS_SEEK_END;
break;
default:
return HOST_ERROR_BADARG;
}
int ret = mockfs_lseek(&desc->file, offset, lfs_whence);
if (ret < 0)
return HOST_ERROR_BADARG;
return ret;
}
static int host_fsync(Host *host, int desc_idx)
{
if (!is_desc_idx_valid(host, desc_idx))
return HOST_ERROR_BADIDX;
Desc *desc = &host->desc[desc_idx];
if (desc->type != DESC_FILE)
return HOST_ERROR_BADIDX;
#ifdef FAULT_INJECTION
uint64_t roll = sim_random(host->sim) % 100;
if (roll == 0)
return HOST_ERROR_IO;
#endif
int ret = mockfs_sync(&desc->file);
if (ret < 0)
return mockfs_to_quakey_error(ret);
return 0;
}
static int host_setdescflags(Host *host, int desc_idx, int flags)
{
if (!is_desc_idx_valid(host, desc_idx))
return HOST_ERROR_BADIDX;
Desc *desc = &host->desc[desc_idx];
// TODO: check the descriptor type
desc->non_blocking = (flags & HOST_FLAG_NONBLOCK) != 0;
return 0;
}
static int host_getdescflags(Host *host, int desc_idx)
{
if (!is_desc_idx_valid(host, desc_idx))
return HOST_ERROR_BADIDX;
Desc *desc = &host->desc[desc_idx];
// TODO: check the descriptor type
int flags = 0;
if (desc->non_blocking)
flags |= HOST_FLAG_NONBLOCK;
return flags;
}
/////////////////////////////////////////////////////////////////
// Time Event Code
static void append_event(Sim *sim, TimeEvent event)
{
if (sim->num_events == sim->max_events) {
int n = 2 * sim->max_events;
if (n == 0)
n = 8;
TimeEvent *p = realloc(sim->events, n * sizeof(TimeEvent));
if (p == NULL) {
TODO;
}
sim->events = p;
sim->max_events = n;
}
sim->events[sim->num_events++] = event;
}
static void time_event_wakeup(Sim *sim, Nanos time, Host *host)
{
TimeEvent event = {
.type = EVENT_TYPE_WAKEUP,
.time = time,
.host = host,
.src_desc = NULL,
.dst_desc = NULL,
.data_count = 0,
.data_queue = NULL,
.rst = false,
};
append_event(sim, event);
}
static void time_event_connect(Sim *sim, Nanos time, Desc *desc)
{
TimeEvent event = {
.type = EVENT_TYPE_CONNECT,
.time = time,
.host = NULL,
.src_desc = desc,
.dst_desc = NULL,
.data_count = 0,
.data_queue = NULL,
.rst = false,
};
append_event(sim, event);
}
static b32 remove_connect_event(Sim *sim, Desc *desc)
{
int i = 0;
while (i < sim->num_events && (sim->events[i].type != EVENT_TYPE_CONNECT || sim->events[i].src_desc != desc))
i++;
if (i == sim->num_events)
return false;
sim->events[i] = sim->events[--sim->num_events];
return true;
}
// Remove all events that target a specific descriptor (DISCONNECT and DATA events)
static void remove_events_targeting_desc(Sim *sim, Desc *desc)
{
int i = 0;
while (i < sim->num_events) {
TimeEvent *event = &sim->events[i];
if (event->dst_desc == desc) {
// Free any resources associated with the event
if (event->type == EVENT_TYPE_DATA && event->data_queue) {
socket_queue_unref(event->data_queue);
}
// Remove by swapping with last element
sim->events[i] = sim->events[--sim->num_events];
// Don't increment i - need to check the swapped element
} else {
i++;
}
}
}
static void time_event_disconnect(Sim *sim, Nanos time, Desc *desc, b32 rst)
{
if (remove_connect_event(sim, desc))
return;
if (desc->peer == NULL)
return;
TimeEvent event = {
.type = EVENT_TYPE_DISCONNECT,
.time = time,
.host = NULL,
.src_desc = NULL,
.dst_desc = desc->peer,
.data_count = 0,
.data_queue = NULL,
.rst = rst,
};
append_event(sim, event);
}
static void time_event_send_data(Sim *sim, Nanos time, Desc *desc)
{
TimeEvent event = {
.type = EVENT_TYPE_DATA,
.time = time,
.host = NULL,
.src_desc = NULL,
.dst_desc = desc->peer,
.data_count = socket_queue_used(desc->output),
.data_queue = socket_queue_ref(desc->output),
.rst = false,
};
append_event(sim, event);
}
static void time_event_free(TimeEvent *event)
{
if (event->data_queue)
socket_queue_unref(event->data_queue);
}
static int sim_find_host(Sim *sim, Addr addr);
static b32 time_event_process(TimeEvent *event, Sim *sim)
{
b32 consumed = true;
switch (event->type) {
case EVENT_TYPE_CONNECT:
{
Desc *src_desc = event->src_desc;
assert(event->dst_desc == NULL);
int idx = sim_find_host(sim, src_desc->connect_addr);
if (idx < 0) {
src_desc->connect_status = CONNECT_STATUS_NOHOST;
break;
}
Host *peer_host = sim->hosts[idx];
Desc *peer = host_find_desc_bound_to(peer_host,
src_desc->connect_addr, src_desc->connect_port);
if (peer == NULL) {
// Peer host exists but the port isn't open. Reset the connection.
src_desc->connect_status = CONNECT_STATUS_RESET;
break;
}
assert(peer->type == DESC_SOCKET_L);
if (accept_queue_push(&peer->accept_queue, src_desc) < 0) {
// Accept queue is full
src_desc->connect_status = CONNECT_STATUS_RESET;
break;
}
src_desc->connect_status = CONNECT_STATUS_DONE;
src_desc->peer = peer; // Resolved!
}
break;
case EVENT_TYPE_DISCONNECT:
{
Desc *dst_desc = event->dst_desc;
assert(dst_desc->type == DESC_SOCKET_C);
assert(event->src_desc == NULL);
dst_desc->peer = NULL;
dst_desc->connect_status = event->rst
? CONNECT_STATUS_RESET
: CONNECT_STATUS_CLOSE;
}
break;
case EVENT_TYPE_DATA:
{
Desc *dst_desc = event->dst_desc;
assert(dst_desc->type == DESC_SOCKET_C);
assert(event->src_desc == NULL);
int num = socket_queue_move(dst_desc->input, event->data_queue, event->data_count);
if (num < 0) {
TODO;
}
event->data_count -= num;
if (event->data_count == 0) {
socket_queue_unref(event->data_queue);
} else {
// Reschedule to future time so time can advance
event->time = sim->current_time + 10000000;
consumed = false;
}
}
break;
case EVENT_TYPE_WAKEUP:
event->host->timedout = true;
break;
}
return consumed;
}
/////////////////////////////////////////////////////////////////
// Sim Code
static void sim_init(Sim *sim, uint64_t seed)
{
sim->seed = seed;
sim->current_time = 0;
sim->num_hosts = 0;
sim->max_hosts = 0;
sim->hosts = NULL;
sim->num_events = 0;
sim->max_events = 0;
sim->events = NULL;
}
static void sim_free(Sim *sim)
{
for (int i = 0; i < sim->num_hosts; i++)
host_free(sim->hosts[i]);
free(sim->hosts);
for (int i = 0; i < sim->num_events; i++)
time_event_free(&sim->events[i]);
free(sim->events);
}
static void sim_spawn(Sim *sim, QuakeySpawn config, char *arg)
{
if (sim->num_hosts == sim->max_hosts) {
int n = 2 * sim->max_hosts;
if (n == 0)
n = 8;
Host **p = realloc(sim->hosts, n * sizeof(Host*));
if (p == NULL) {
TODO;
}
sim->hosts = p;
sim->max_hosts = n;
}
Host *host = malloc(sizeof(Host));
if (host == NULL) {
TODO;
}
host_init(host, sim, config, arg);
sim->hosts[sim->num_hosts++] = host;
}
static int sim_find_host(Sim *sim, Addr addr)
{
for (int i = 0; i < sim->num_hosts; i++)
if (host_has_addr(sim->hosts[i], addr))
return i;
return -1;
}
static void advance_time_to_next_event(Sim *sim)
{
if (sim->num_events == 0)
return;
Nanos lowest_time = sim->events[0].time;
for (int i = 1; i < sim->num_events; i++)
if (lowest_time > sim->events[i].time)
lowest_time = sim->events[i].time;
sim->current_time = lowest_time;
}
static void process_events_at_current_time(Sim *sim)
{
bool deferred_disconnect = false;
for (int i = 0; i < sim->num_events; i++) {
if (sim->events[i].time == sim->current_time) {
if (sim->events[i].type == EVENT_TYPE_DISCONNECT) {
deferred_disconnect = true;
} else {
if (time_event_process(&sim->events[i], sim))
sim->events[i--] = sim->events[--sim->num_events];
}
}
}
if (deferred_disconnect) {
for (int i = 0; i < sim->num_events; i++) {
if (sim->events[i].time == sim->current_time)
if (sim->events[i].type == EVENT_TYPE_DISCONNECT) {
if (time_event_process(&sim->events[i], sim))
sim->events[i--] = sim->events[--sim->num_events];
}
}
}
}
static int find_first_ready_host(Sim *sim)
{
int i = 0;
while (i < sim->num_hosts && !host_ready(sim->hosts[i]))
i++;
if (i == sim->num_hosts)
return -1;
return i;
}
static void move_host_to_last(Sim *sim, int idx)
{
assert(idx > -1 && idx < sim->num_hosts);
Host *host = sim->hosts[idx];
for (int i = idx; i < sim->num_hosts-1; i++)
sim->hosts[i] = sim->hosts[i+1];
sim->hosts[sim->num_hosts-1] = host;
}
static b32 sim_update(Sim *sim)
{
if (sim->num_hosts == 0)
return false;
int host_idx;
for (;;) {
// Schedule the first host that's ready.
//
// If all host are waiting, advance the time to the
// next timed event and try again.
host_idx = find_first_ready_host(sim);
if (host_idx > -1)
break;
for (int i = 0; i < sim->num_hosts; i++)
sim->hosts[i]->blocked = false;
advance_time_to_next_event(sim);
process_events_at_current_time(sim);
}
move_host_to_last(sim, host_idx);
Host *host = sim->hosts[sim->num_hosts-1];
host_update(host);
if (host_ready(host))
host->blocked = true;
return true;
}
static uint64_t sim_random(Sim *sim)
{
uint64_t x = sim->seed;
x ^= x << 13;
x ^= x >> 7;
x ^= x << 17;
sim->seed = x;
return x;
}
/////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////
// Public Interface
/////////////////////////////////////////////////////////////////
// Quakey Object
int quakey_init(Quakey **quakey, QuakeyUInt64 seed)
{
Sim *sim = malloc(sizeof(Sim));
if (sim == NULL)
return -1;
sim_init(sim, seed);
if (quakey) {
*quakey = (void*) sim;
} else {
sim_free(sim);
free(sim);
}
return 0;
}
void quakey_free(Quakey *quakey)
{
if (quakey) {
sim_free((Sim*) quakey);
free(quakey);
}
}
void quakey_spawn(Quakey *quakey, QuakeySpawn config, char *arg)
{
return sim_spawn((Sim*) quakey, config, arg);
}
int quakey_schedule_one(Quakey *quakey)
{
return sim_update((Sim*) quakey);
}
QuakeyUInt64 quakey_random(void)
{
Host *host = host___;
if (host == NULL)
abort_("Call to mock_errno_ptr() with no node scheduled\n");
return sim_random(host->sim);
}
/////////////////////////////////////////////////////////////////
// Mock System Calls
int *mock_errno_ptr(void)
{
Host *host = host___;
if (host == NULL)
abort_("Call to mock_errno_ptr() with no node scheduled\n");
return host_errno_ptr(host);
}
#ifndef _WIN32
int mock_socket(int domain, int type, int protocol)
{
if (domain != AF_INET && domain != AF_INET6)
abort_("Quakey only supports socket() calls with doman=AF_INET or AF_INET6\n");
if (type != SOCK_STREAM)
abort_("Quakey only supports socket() calls with type=SOCK_STREAM\n");
if (protocol != 0)
abort_("Quakey only supports socket() calls with protocol=0\n");
Host *host = host___;
if (host == NULL)
abort_("Call to mock_socket() with no node scheduled\n");
AddrFamily family;
switch (domain) {
case AF_INET:
family = ADDR_FAMILY_IPV4;
break;
case AF_INET6:
family = ADDR_FAMILY_IPV6;
break;
default:
UNREACHABLE;
}
int ret = host_create_socket(host, family);
if (ret < 0) {
switch (ret) {
case HOST_ERROR_FULL:
*host_errno_ptr(host) = EMFILE;
return -1;
default:
break;
}
*host_errno_ptr(host) = EIO;
return -1;
}
int desc_idx = ret;
return desc_idx;
}
int mock_close(int fd)
{
Host *host = host___;
if (host == NULL)
abort_("Call to mock_close() with no node scheduled\n");
if (!host_is_linux(host))
abort_("Call to mock_close() not from Linux\n");
int desc_idx = fd;
int ret = host_close(host, desc_idx, false);
if (ret < 0) {
switch (ret) {
case HOST_ERROR_BADIDX:
*host_errno_ptr(host) = EBADF;
return -1;
default:
break;
}
*host_errno_ptr(host) = EIO;
return -1;
}
return 0;
}
static int convert_addr(void *addr, size_t addr_len,
Addr *converted_addr, uint16_t *converted_port)
{
int family = ((struct sockaddr*) addr)->sa_family;
switch (family) {
case AF_INET:
{
if (addr_len != sizeof(struct sockaddr_in))
return -1;
struct sockaddr_in *p = addr;
converted_addr->family = ADDR_FAMILY_IPV4;
converted_addr->ipv4.data = ntohl(((AddrIPv4*) &p->sin_addr)->data);
*converted_port = ntohs(p->sin_port);
}
break;
case AF_INET6:
{
if (addr_len != sizeof(struct sockaddr_in6))
return -1;
struct sockaddr_in6 *p = addr;
converted_addr->family = ADDR_FAMILY_IPV6;
converted_addr->ipv6 = *(AddrIPv6*) &p->sin6_addr; // TODO: convert to host byte order
*converted_port = ntohs(p->sin6_port);
}
break;
default:
abort_("Quakey only supports the AF_INET and AF_INET6 address families");
}
return 0;
}
int mock_bind(int fd, void *addr, unsigned long addr_len)
{
Host *host = host___;
if (host == NULL)
abort_("Call to mock_bind() with no node scheduled\n");
Addr converted_addr;
uint16_t converted_port;
int ret = convert_addr(addr, addr_len, &converted_addr, &converted_port);
if (ret < 0) {
*host_errno_ptr(host) = EINVAL;
return ret;
}
int desc_idx = fd;
ret = host_bind(host, desc_idx, converted_addr, converted_port);
if (ret < 0) {
switch (ret) {
case HOST_ERROR_BADIDX:
*host_errno_ptr(host) = EBADF;
return -1;
case HOST_ERROR_NOTSOCK:
*host_errno_ptr(host) = ENOTSOCK;
return -1;
case HOST_ERROR_CANTBIND:
*host_errno_ptr(host) = EINVAL;
return -1;
case HOST_ERROR_BADFAM:
*host_errno_ptr(host) = EAFNOSUPPORT;
return -1;
case HOST_ERROR_NOTAVAIL:
*host_errno_ptr(host) = EADDRNOTAVAIL;
return -1;
case HOST_ERROR_ADDRUSED:
*host_errno_ptr(host) = EADDRINUSE;
return -1;
default:
break;
}
*host_errno_ptr(host) = EIO;
return -1;
}
return 0;
}
int mock_listen(int fd, int backlog)
{
Host *host = host___;
if (host == NULL)
abort_("Call to mock_listen() with no node scheduled\n");
int desc_idx = fd;
int ret = host_listen(host, desc_idx, backlog);
if (ret < 0) {
switch (ret) {
case HOST_ERROR_BADIDX:
*host_errno_ptr(host) = EBADF;
return -1;
case HOST_ERROR_BADARG:
*host_errno_ptr(host) = EINVAL;
return -1;
case HOST_ERROR_NOTSOCK:
*host_errno_ptr(host) = ENOTSOCK;
return -1;
case HOST_ERROR_ADDRUSED:
*host_errno_ptr(host) = EADDRINUSE;
return -1;
}
*host_errno_ptr(host) = EIO;
return -1;
}
return 0;
}
int mock_connect(int fd, void *addr, unsigned long addr_len)
{
Host *host = host___;
if (host == NULL)
abort_("Call to mock_connect() with no node scheduled\n");
Addr converted_addr;
uint16_t converted_port;
int ret = convert_addr(addr, addr_len, &converted_addr, &converted_port);
if (ret < 0) {
*host_errno_ptr(host) = EINVAL;
return -1;
}
// TODO: connect() operations are only allowed on non-blocking
// sockets
int desc_idx = fd;
ret = host_connect(host, desc_idx, converted_addr, converted_port);
if (ret < 0) {
switch (ret) {
case HOST_ERROR_BADIDX:
*host_errno_ptr(host) = EBADF;
return -1;
case HOST_ERROR_NOTSOCK:
*host_errno_ptr(host) = ENOTSOCK;
return -1;
case HOST_ERROR_BADARG:
*host_errno_ptr(host) = EISCONN;
return -1;
case HOST_ERROR_ADDRUSED:
*host_errno_ptr(host) = EADDRINUSE;
return -1;
default:
break;
}
*host_errno_ptr(host) = EINPROGRESS;
return -1;
}
*host_errno_ptr(host) = EINPROGRESS;
return -1;
}
static int convert_linux_open_flags_to_mockfs(int flags)
{
int lfs_flags = 0;
// Convert other flags
if (flags & O_RDWR)
lfs_flags |= MOCKFS_O_RDWR;
if (flags & O_WRONLY)
lfs_flags |= MOCKFS_O_WRONLY;
if (flags & O_CREAT)
lfs_flags |= MOCKFS_O_CREAT;
if (flags & O_EXCL)
lfs_flags |= MOCKFS_O_EXCL;
if (flags & O_TRUNC)
lfs_flags |= MOCKFS_O_TRUNC;
if (flags & O_APPEND)
lfs_flags |= MOCKFS_O_APPEND;
return lfs_flags;
}
int mock_open(char *path, int flags, int mode)
{
Host *host = host___;
if (host == NULL)
abort_("Call to mock_open() with no node scheduled\n");
if (!host_is_linux(host))
abort_("Call to mock_open() not from Linux\n");
int converted_flags = convert_linux_open_flags_to_mockfs(flags);
int ret = host_open_file(host, path, converted_flags);
if (ret < 0) {
switch (ret) {
case HOST_ERROR_FULL:
*host_errno_ptr(host) = EMFILE;
return -1;
case HOST_ERROR_IO:
*host_errno_ptr(host) = EIO;
return -1;
default:
break;
}
*host_errno_ptr(host) = ENOENT;
return -1;
}
int desc_idx = ret;
return desc_idx;
}
int mock_read(int fd, char *dst, int len)
{
Host *host = host___;
if (host == NULL)
abort_("Call to mock_read() with no node scheduled\n");
if (!host_is_linux(host))
abort_("Call to mock_read() not from Linux\n");
int ret = host_read(host, fd, dst, len);
if (ret < 0) {
switch (ret) {
case HOST_ERROR_BADIDX:
*host_errno_ptr(host) = EBADF;
return -1;
case HOST_ERROR_BADARG:
*host_errno_ptr(host) = EINVAL;
return -1;
case HOST_ERROR_ISDIR:
*host_errno_ptr(host) = EISDIR;
return -1;
case HOST_ERROR_IO:
*host_errno_ptr(host) = EIO;
return -1;
}
*host_errno_ptr(host) = EIO;
return -1;
}
return ret;
}
int mock_write(int fd, char *src, int len)
{
Host *host = host___;
if (host == NULL)
abort_("Call to mock_write() with no node scheduled\n");
if (!host_is_linux(host))
abort_("Call to mock_write() not from Linux\n");
int ret = host_write(host, fd, src, len);
if (ret < 0) {
switch (ret) {
case HOST_ERROR_BADIDX:
*host_errno_ptr(host) = EBADF;
return -1;
case HOST_ERROR_IO:
*host_errno_ptr(host) = EIO;
return -1;
default:
break;
}
*host_errno_ptr(host) = EIO;
return -1;
}
return ret;
}
int mock_recv(int fd, char *dst, int len, int flags)
{
Host *host = host___;
if (host == NULL)
abort_("Call to mock_recv() with no node scheduled\n");
if (flags)
abort_("Call to mock_recv() with non-zero flags\n");
int ret = host_recv(host, fd, dst, len);
if (ret < 0) {
switch (ret) {
case HOST_ERROR_BADIDX:
*host_errno_ptr(host) = EBADF;
return -1;
case HOST_ERROR_NOTSOCK:
*host_errno_ptr(host) = ENOTSOCK;
return -1;
case HOST_ERROR_NOTCONN:
*host_errno_ptr(host) = ENOTCONN;
return -1;
case HOST_ERROR_RESET:
*host_errno_ptr(host) = ECONNRESET;
return -1;
case HOST_ERROR_HANGUP:
*host_errno_ptr(host) = 0;
return 0;
case HOST_ERROR_WOULDBLOCK:
*host_errno_ptr(host) = EAGAIN;
return -1;
default:
break;
}
*host_errno_ptr(host) = EIO;
return -1;
}
ASSERT(ret > 0);
return ret;
}
int mock_send(int fd, char *src, int len, int flags)
{
Host *host = host___;
if (host == NULL)
abort_("Call to mock_send() with no node scheduled\n");
if (flags)
abort_("Call to mock_send() with non-zero flags\n");
int ret = host_send(host, fd, src, len);
if (ret < 0) {
switch (ret) {
case HOST_ERROR_BADIDX:
*host_errno_ptr(host) = EBADF;
return -1;
case HOST_ERROR_RESET:
*host_errno_ptr(host) = ECONNRESET;
return -1;
case HOST_ERROR_HANGUP:
*host_errno_ptr(host) = EPIPE;
return -1;
case HOST_ERROR_WOULDBLOCK:
*host_errno_ptr(host) = EAGAIN;
return -1;
default:
break;
}
*host_errno_ptr(host) = EIO;
return -1;
}
return ret;
}
int mock_accept(int fd, void *addr, socklen_t *addr_len)
{
Host *host = host___;
if (host == NULL)
abort_("Call to mock_accept() with no node scheduled\n");
Addr peer_addr;
uint16_t peer_port;
int ret = host_accept(host, fd, &peer_addr, &peer_port);
if (ret < 0) {
switch (ret) {
case HOST_ERROR_BADIDX:
*host_errno_ptr(host) = EBADF;
return -1;
case HOST_ERROR_NOTSOCK:
*host_errno_ptr(host) = ENOTSOCK;
return -1;
case HOST_ERROR_BADARG:
*host_errno_ptr(host) = EINVAL;
return -1;
case HOST_ERROR_FULL:
*host_errno_ptr(host) = EMFILE;
return -1;
case HOST_ERROR_WOULDBLOCK:
*host_errno_ptr(host) = EAGAIN;
return -1;
default:
break;
}
*host_errno_ptr(host) = EIO;
return -1;
}
int new_fd = ret;
// Fill in the address if provided
if (addr != NULL && addr_len != NULL) {
if (peer_addr.family == ADDR_FAMILY_IPV4) {
struct sockaddr_in *sin = addr;
if (*addr_len >= sizeof(struct sockaddr_in)) {
sin->sin_family = AF_INET;
sin->sin_port = peer_port;
memcpy(&sin->sin_addr, &peer_addr.ipv4, sizeof(peer_addr.ipv4));
*addr_len = sizeof(struct sockaddr_in);
}
} else {
struct sockaddr_in6 *sin6 = addr;
if (*addr_len >= sizeof(struct sockaddr_in6)) {
sin6->sin6_family = AF_INET6;
sin6->sin6_port = peer_port;
memcpy(&sin6->sin6_addr, &peer_addr.ipv6, sizeof(peer_addr.ipv6));
*addr_len = sizeof(struct sockaddr_in6);
}
}
}
return new_fd;
}
int mock_getsockopt(int fd, int level, int optname, void *optval, socklen_t *optlen)
{
if (level != SOL_SOCKET)
abort_("Call to mock_getsockopt() with level other than SOL_SOCKET\n");
if (optname != SO_ERROR)
abort_("Call to mock_getsockopt() with option other than SO_ERROR\n");
Host *host = host___;
if (host == NULL)
abort_("Call to mock_getsockopt() with no node scheduled\n");
ConnectStatus status;
int ret = host_connect_status(host, fd, &status);
if (ret < 0) {
TODO;
}
int out;
switch (status) {
case CONNECT_STATUS_WAIT:
out = 0;
break;
case CONNECT_STATUS_DONE:
out = 0;
break;
case CONNECT_STATUS_RESET:
out = ECONNRESET;
break;
case CONNECT_STATUS_CLOSE:
out = ECONNRESET;
break;
case CONNECT_STATUS_NOHOST:
out = ETIMEDOUT;
break;
default:
UNREACHABLE;
break;
}
if (*optlen < sizeof(out))
memcpy(optval, &out, *optlen);
else {
memcpy(optval, &out, sizeof(out));
*optlen = sizeof(out);
}
return 0;
}
int mock_remove(char *path)
{
Host *host = host___;
if (host == NULL)
abort_("Call to mock_remove() with no node scheduled\n");
if (!host_is_linux(host))
abort_("Call to mock_remove() not from Linux\n");
int ret = host_remove(host, path);
if (ret < 0) {
switch (ret) {
case HOST_ERROR_NOENT:
*host_errno_ptr(host) = ENOENT;
break;
case HOST_ERROR_NOTEMPTY:
*host_errno_ptr(host) = ENOTEMPTY;
break;
default:
*host_errno_ptr(host) = EIO;
break;
}
return -1;
}
return 0;
}
int mock_rename(char *oldpath, char *newpath)
{
Host *host = host___;
if (host == NULL)
abort_("Call to mock_rename() with no node scheduled\n");
if (!host_is_linux(host))
abort_("Call to mock_rename() not from Linux\n");
int ret = host_rename(host, oldpath, newpath);
if (ret < 0) {
switch (ret) {
case HOST_ERROR_NOENT:
*host_errno_ptr(host) = ENOENT;
break;
case HOST_ERROR_EXIST:
*host_errno_ptr(host) = EEXIST;
break;
case HOST_ERROR_NOTEMPTY:
*host_errno_ptr(host) = ENOTEMPTY;
break;
case HOST_ERROR_ISDIR:
*host_errno_ptr(host) = EISDIR;
break;
default:
*host_errno_ptr(host) = EIO;
break;
}
return -1;
}
return 0;
}
int mock_clock_gettime(clockid_t clockid, struct timespec *tp)
{
Host *host = host___;
if (host == NULL)
abort_("Call to mock_clock_gettime() with no node scheduled\n");
if (!host_is_linux(host))
abort_("Call to mock_clock_gettime() not from Linux\n");
if (tp == NULL) {
*host_errno_ptr(host) = EINVAL;
return -1;
}
// Both CLOCK_REALTIME and CLOCK_MONOTONIC use the same
// simulated time. In simulation, they're equivalent since
// we don't model wall-clock vs monotonic differences.
if (clockid != CLOCK_REALTIME && clockid != CLOCK_MONOTONIC) {
*host_errno_ptr(host) = EINVAL;
return -1;
}
// Get current time
Nanos now = host_time(host);
// Convert nanoseconds to timespec
// 1 second = 1,000,000,000 nanoseconds
tp->tv_sec = (time_t) (now / 1000000000ULL);
tp->tv_nsec = (int64_t) (now % 1000000000ULL);
return 0;
}
int mock_flock(int fd, int op)
{
// TODO
return 0;
}
int mock_fsync(int fd)
{
Host *host = host___;
if (host == NULL)
abort_("Call to mock_fsync() with no node scheduled\n");
if (!host_is_linux(host))
abort_("Call to mock_fsync() not from Linux\n");
int ret = host_fsync(host, fd);
if (ret < 0) {
if (ret == HOST_ERROR_BADIDX)
*host_errno_ptr(host) = EBADF;
else
*host_errno_ptr(host) = EINVAL;
return -1;
}
return 0;
}
off_t mock_lseek(int fd, off_t offset, int whence)
{
Host *host = host___;
if (host == NULL)
abort_("Call to mock_lseek() with no node scheduled\n");
if (!host_is_linux(host))
abort_("Call to mock_lseek() not from Linux\n");
// Convert POSIX whence to HOST whence
int host_whence;
switch (whence) {
case SEEK_SET:
host_whence = HOST_SEEK_SET;
break;
case SEEK_CUR:
host_whence = HOST_SEEK_CUR;
break;
case SEEK_END:
host_whence = HOST_SEEK_END;
break;
default:
*host_errno_ptr(host) = EINVAL;
return (off_t)-1;
}
int ret = host_lseek(host, fd, offset, host_whence);
if (ret < 0) {
if (ret == HOST_ERROR_BADIDX)
*host_errno_ptr(host) = EBADF;
else
*host_errno_ptr(host) = EINVAL;
return (off_t)-1;
}
return (off_t)ret;
}
int mock_fstat(int fd, struct stat *buf)
{
Host *host = host___;
if (host == NULL)
abort_("Call to mock_fstat() with no node scheduled\n");
if (!host_is_linux(host))
abort_("Call to mock_fstat() not from Linux\n");
if (buf == NULL) {
*host_errno_ptr(host) = EINVAL;
return -1;
}
FileInfo info;
int ret = host_fileinfo(host, fd, &info);
if (ret < 0) {
if (ret == HOST_ERROR_BADIDX) {
*host_errno_ptr(host) = EBADF;
} else {
*host_errno_ptr(host) = EIO;
}
return -1;
}
memset(buf, 0, sizeof(*buf));
if (info.is_dir) {
buf->st_mode = S_IFDIR | 0755; // Directory with rwxr-xr-x permissions
buf->st_size = 0;
} else {
buf->st_mode = S_IFREG | 0644; // Regular file with rw-r--r-- permissions
buf->st_size = (off_t) info.size;
}
return 0;
}
int mock_mkstemp(char *path)
{
TODO;
}
char *mock_realpath(char *path, char *dst)
{
Host *host = host___;
if (host == NULL)
abort_("Call to mock_realpath() with no node scheduled\n");
if (!host_is_linux(host))
abort_("Call to mock_realpath() not from Linux\n");
if (path == NULL) {
*host_errno_ptr(host) = EINVAL;
return NULL;
}
// Temporary buffer for path normalization
char temp[4096];
int temp_len = 0;
// Copy path to temp
for (int i = 0; path[i] != '\0' && temp_len < (int)sizeof(temp) - 1; i++) {
temp[temp_len++] = path[i];
}
temp[temp_len] = '\0';
// Result buffer for the normalized absolute path
char result[4096];
int result_len = 0;
// If path doesn't start with '/', prepend '/' (mock has no CWD, uses root)
const char *src = temp;
if (temp[0] != '/') {
result[result_len++] = '/';
}
// Parse path components and resolve . and ..
while (*src != '\0') {
// Skip consecutive slashes
while (*src == '/') src++;
if (*src == '\0') break;
// Find end of this component
const char *end = src;
while (*end != '\0' && *end != '/') end++;
int comp_len = (int)(end - src);
if (comp_len == 1 && src[0] == '.') {
// Current directory - skip it
} else if (comp_len == 2 && src[0] == '.' && src[1] == '.') {
// Parent directory - remove last component from result
if (result_len > 1) {
// Find the last slash before the current position
result_len--; // Move back from current position
while (result_len > 0 && result[result_len - 1] != '/') {
result_len--;
}
if (result_len == 0) {
result_len = 1; // Keep the root slash
}
}
} else {
// Regular component - add it
if (result_len > 1 || (result_len == 1 && result[0] != '/')) {
if (result_len < (int)sizeof(result) - 1)
result[result_len++] = '/';
}
for (int i = 0; i < comp_len && result_len < (int)sizeof(result) - 1; i++) {
result[result_len++] = src[i];
}
}
src = end;
}
// Ensure we have at least root
if (result_len == 0) {
result[result_len++] = '/';
}
result[result_len] = '\0';
// Unlike _fullpath, realpath requires the path to exist
// Try to open as file first, then as directory
int fd = host_open_file(host, result, MOCKFS_O_RDONLY);
if (fd >= 0) {
host_close(host, fd, false);
} else {
// Try as directory
fd = host_open_dir(host, result);
if (fd >= 0) {
host_close(host, fd, false);
} else {
// Path doesn't exist
*host_errno_ptr(host) = ENOENT;
return NULL;
}
}
// Allocate buffer if dst is NULL
if (dst == NULL) {
dst = malloc(result_len + 1);
if (dst == NULL) {
*host_errno_ptr(host) = ENOMEM;
return NULL;
}
}
// Copy result to destination
for (int i = 0; i <= result_len; i++) {
dst[i] = result[i];
}
return dst;
}
int mock_mkdir(char *path, mode_t mode)
{
Host *host = host___;
if (host == NULL)
abort_("Call to mock_mkdir() with no node scheduled\n");
if (!host_is_linux(host))
abort_("Call to mock_mkdir() not from Linux\n");
// LittleFS doesn't use mode, but we accept it for API compatibility
(void) mode;
int ret = host_mkdir(host, path);
if (ret < 0) {
switch (ret) {
case HOST_ERROR_EXIST:
*host_errno_ptr(host) = EEXIST;
return -1;
case HOST_ERROR_NOENT:
// Parent directory doesn't exist
*host_errno_ptr(host) = ENOENT;
return -1;
default:
*host_errno_ptr(host) = EIO;
return -1;
}
}
return 0;
}
int mock_fcntl(int fd, int cmd, int flags)
{
Host *host = host___;
if (host == NULL)
abort_("Call to mock_fcntl() with no node scheduled\n");
if (!host_is_linux(host))
abort_("Call to mock_fcntl() not from Linux\n");
switch (cmd) {
case F_GETFL:
{
int ret = host_getdescflags(host, fd);
if (ret < 0) {
*host_errno_ptr(host) = EBADF;
return -1;
}
int flags = 0;
if (ret & HOST_FLAG_NONBLOCK)
flags |= O_NONBLOCK;
return flags;
}
break;
case F_SETFL:
{
int host_flags = 0;
if (flags & O_NONBLOCK)
host_flags |= HOST_FLAG_NONBLOCK;
int ret = host_setdescflags(host, fd, host_flags);
if (ret < 0) {
*host_errno_ptr(host) = EBADF;
return -1;
}
return 0;
}
break;
default:
*host_errno_ptr(host) = EINVAL;
return -1;
}
}
typedef struct {
int fd; // Descriptor index
struct dirent entry; // Current entry (returned by readdir)
} DIR_;
DIR *mock_opendir(char *name)
{
Host *host = host___;
if (host == NULL)
abort_("Call to mock_opendir() with no node scheduled\n");
if (!host_is_linux(host))
abort_("Call to mock_opendir() not from Linux\n");
int ret = host_open_dir(host, name);
if (ret < 0) {
switch (ret) {
case HOST_ERROR_FULL:
*host_errno_ptr(host) = EMFILE;
return NULL;
case HOST_ERROR_NOENT:
*host_errno_ptr(host) = ENOENT;
return NULL;
case HOST_ERROR_IO:
default:
*host_errno_ptr(host) = EIO;
return NULL;
}
}
// Allocate DIR structure
DIR_ *dirp = malloc(sizeof(DIR_));
if (dirp == NULL) {
// Close the descriptor since we can't return it
host_close(host, ret, false);
*host_errno_ptr(host) = EMFILE;
return NULL;
}
dirp->fd = ret;
return (DIR*) dirp;
}
struct dirent* mock_readdir(DIR *dirp)
{
Host *host = host___;
if (host == NULL)
abort_("Call to mock_readdir() with no node scheduled\n");
if (!host_is_linux(host))
abort_("Call to mock_readdir() not from Linux\n");
DIR_ *dirp_ = (DIR_*) dirp;
if (dirp_ == NULL) {
*host_errno_ptr(host) = EBADF;
return NULL;
}
DirEntry entry;
int ret = host_read_dir(host, dirp_->fd, &entry);
if (ret < 0) {
switch (ret) {
case HOST_ERROR_BADIDX:
*host_errno_ptr(host) = EBADF;
return NULL;
case HOST_ERROR_BADARG:
*host_errno_ptr(host) = EBADF;
return NULL;
case HOST_ERROR_IO:
default:
*host_errno_ptr(host) = EIO;
return NULL;
}
}
if (ret == 0) {
// End of directory - return NULL without setting errno
return NULL;
}
// Copy to the DIR's entry buffer
int i = 0;
while (entry.name[i] != '\0' && i < 255) {
dirp_->entry.d_name[i] = entry.name[i];
i++;
}
dirp_->entry.d_name[i] = '\0';
dirp_->entry.d_type = entry.is_dir ? DT_DIR : DT_REG;
return &dirp_->entry;
}
int mock_closedir(DIR *dirp)
{
Host *host = host___;
if (host == NULL)
abort_("Call to mock_closedir() with no node scheduled\n");
if (!host_is_linux(host))
abort_("Call to mock_closedir() not from Linux\n");
DIR_ *dirp_ = (DIR_*) dirp;
if (dirp_ == NULL) {
*host_errno_ptr(host) = EBADF;
return -1;
}
int ret = host_close(host, dirp_->fd, false);
if (ret < 0) {
switch (ret) {
case HOST_ERROR_BADIDX:
*host_errno_ptr(host) = EBADF;
free(dirp_);
return -1;
default:
*host_errno_ptr(host) = EIO;
free(dirp_);
return -1;
}
}
free(dirp_);
return 0;
}
#else
int mock_GetLastError(void)
{
Host *host = host___;
if (host == NULL)
abort_("Call to mock_GetLastError() with no node scheduled\n");
if (!host_is_windows(host))
abort_("Call to mock_GetLastError() not from Windows\n");
// Note that technically on windows errno and GetLastError
// are different things. Here we use errno_ to store the
// GetLastError value and assume the user will not access
// errno.
return *host_errno_ptr(host);
}
int mock_WSAGetLastError(void)
{
return mock_GetLastError();
}
void mock_SetLastError(int err)
{
Host *host = host___;
if (host == NULL)
abort_("Call to mock_SetLastError() with no node scheduled\n");
if (!host_is_windows(host))
abort_("Call to mock_SetLastError() not from Windows\n");
*host_errno_ptr(host) = err;
}
void mock_WSASetLastError(int err)
{
return mock_SetLastError(err);
}
int mock_closesocket(SOCKET fd)
{
Host *host = host___;
if (host == NULL)
abort_("Call to mock_closesocket() with no node scheduled\n");
if (!host_is_windows(host))
abort_("Call to mock_closesocket() not from Windows\n");
int desc_idx = fd;
int ret = host_close(host, desc_idx, true); // expect_socket = true
if (ret < 0) {
switch (ret) {
case HOST_ERROR_BADIDX:
case HOST_ERROR_NOTSOCK:
// Windows uses WSAGetLastError(), but for simplicity we just return error
return -1;
default:
break;
}
return -1;
}
return 0;
}
int mock_ioctlsocket(SOCKET fd, long cmd, unsigned long *argp)
{
TODO;
}
// Helper function to convert wide string to narrow string (ASCII subset)
static int wchar_to_char(WCHAR *src, char *dst, int dst_size)
{
int i = 0;
while (src[i] != 0) {
if (i >= dst_size - 1)
return -1; // Buffer too small
if (src[i] > 127)
return -1; // Non-ASCII character
dst[i] = (char) src[i];
i++;
}
dst[i] = '\0';
return i; // Return length
}
// Convert Windows access flags and creation disposition to LFS flags
static int convert_windows_flags_to_lfs(DWORD dwDesiredAccess,
DWORD dwCreationDisposition, bool *truncate)
{
int lfs_flags = 0;
// Convert access mode
if ((dwDesiredAccess & GENERIC_READ) && (dwDesiredAccess & GENERIC_WRITE))
lfs_flags = LFS_O_RDWR;
else if (dwDesiredAccess & GENERIC_WRITE)
lfs_flags = LFS_O_WRONLY;
else
lfs_flags = LFS_O_RDONLY;
*truncate = false;
// Convert creation disposition
switch (dwCreationDisposition) {
case CREATE_NEW:
// Creates a new file, fails if file exists
lfs_flags |= LFS_O_CREAT | LFS_O_EXCL;
break;
case CREATE_ALWAYS:
// Creates a new file, always (truncates if exists)
lfs_flags |= LFS_O_CREAT | LFS_O_TRUNC;
*truncate = true;
break;
case OPEN_EXISTING:
// Opens file only if it exists, fails otherwise
// No extra flags needed - LFS will fail if file doesn't exist
break;
case OPEN_ALWAYS:
// Opens file if it exists, creates if it doesn't
lfs_flags |= LFS_O_CREAT;
break;
case TRUNCATE_EXISTING:
// Opens and truncates, fails if file doesn't exist
lfs_flags |= LFS_O_TRUNC;
*truncate = true;
break;
default:
return -1; // Invalid creation disposition
}
return lfs_flags;
}
HANDLE mock_CreateFileW(WCHAR *lpFileName,
DWORD dwDesiredAccess, DWORD dwShareMode,
LPSECURITY_ATTRIBUTES lpSecurityAttributes,
DWORD dwCreationDisposition,
DWORD dwFlagsAndAttributes, HANDLE hTemplateFile)
{
Host *host = host___;
if (host == NULL)
abort_("Call to mock_CreateFileW() with no node scheduled\n");
if (!host_is_windows(host))
abort_("Call to mock_CreateFileW() not from Windows\n");
// lpSecurityAttributes and hTemplateFile are typically NULL
(void) lpSecurityAttributes;
(void) hTemplateFile;
(void) dwShareMode; // Share mode not implemented in simulation
(void) dwFlagsAndAttributes; // Attributes not implemented in simulation
// Convert wide string path to narrow string
char path[MAX_PATH];
if (wchar_to_char(lpFileName, path, MAX_PATH) < 0) {
*host_errno_ptr(host) = ERROR_INVALID_PARAMETER;
return INVALID_HANDLE_VALUE;
}
// Convert Windows flags to LFS flags
bool truncate;
int lfs_flags = convert_windows_flags_to_lfs(dwDesiredAccess, dwCreationDisposition, &truncate);
if (lfs_flags < 0) {
*host_errno_ptr(host) = ERROR_INVALID_PARAMETER;
return INVALID_HANDLE_VALUE;
}
int ret = host_open_file(host, path, lfs_flags);
if (ret < 0) {
switch (ret) {
case HOST_ERROR_FULL:
*host_errno_ptr(host) = ERROR_NOT_ENOUGH_MEMORY;
return INVALID_HANDLE_VALUE;
case HOST_ERROR_EXISTS:
// CREATE_NEW with existing file
*host_errno_ptr(host) = ERROR_FILE_EXISTS;
return INVALID_HANDLE_VALUE;
case HOST_ERROR_NOENT:
*host_errno_ptr(host) = ERROR_FILE_NOT_FOUND;
return INVALID_HANDLE_VALUE;
default:
*host_errno_ptr(host) = ERROR_ACCESS_DENIED;
return INVALID_HANDLE_VALUE;
}
}
int desc_idx = ret;
// For OPEN_ALWAYS, if the file already existed, set ERROR_ALREADY_EXISTS
// (but still return success). This is Windows behavior.
if (dwCreationDisposition == OPEN_ALWAYS) {
// We can't easily detect this case here, so we skip it for now
// A full implementation would check if the file was newly created
}
*host_errno_ptr(host) = ERROR_SUCCESS;
return (HANDLE)(long long)desc_idx;
}
BOOL mock_CloseHandle(HANDLE handle)
{
Host *host = host___;
if (host == NULL)
abort_("Call to mock_CloseHandle() with no node scheduled\n");
if (!host_is_windows(host))
abort_("Call to mock_CloseHandle() not from Windows\n");
if (handle == INVALID_HANDLE_VALUE || handle == NULL) {
*host_errno_ptr(host) = ERROR_INVALID_HANDLE;
return 0; // FALSE
}
int desc_idx = (int)(long long)handle;
// CloseHandle is for file handles, not sockets
// (sockets use closesocket on Windows)
int ret = host_close(host, desc_idx, false);
if (ret < 0) {
switch (ret) {
case HOST_ERROR_BADIDX:
*host_errno_ptr(host) = ERROR_INVALID_HANDLE;
return 0; // FALSE
default:
*host_errno_ptr(host) = ERROR_INVALID_HANDLE;
return 0; // FALSE
}
}
*host_errno_ptr(host) = ERROR_SUCCESS;
return 1; // TRUE
}
BOOL mock_LockFile(HANDLE hFile, DWORD dwFileOffsetLow, DWORD dwFileOffsetHigh, DWORD nNumberOfBytesToLockLow, DWORD nNumberOfBytesToLockHigh)
{
TODO;
}
BOOL mock_UnlockFile(HANDLE hFile, DWORD dwFileOffsetLow, DWORD dwFileOffsetHigh, DWORD nNumberOfBytesToUnlockLow, DWORD nNumberOfBytesToUnlockHigh)
{
TODO;
}
BOOL mock_FlushFileBuffers(HANDLE handle)
{
TODO;
}
BOOL mock_ReadFile(HANDLE handle, char *dst, DWORD len, DWORD *num, OVERLAPPED *ov)
{
Host *host = host___;
if (host == NULL)
abort_("Call to mock_ReadFile() with no node scheduled\n");
if (!host_is_windows(host))
abort_("Call to mock_ReadFile() not from Windows\n");
// We don't support overlapped (async) I/O
if (ov != NULL)
abort_("Quakey does not support overlapped I/O in ReadFile\n");
if (handle == INVALID_HANDLE_VALUE || handle == NULL) {
*host_errno_ptr(host) = ERROR_INVALID_HANDLE;
return 0; // FALSE
}
if (dst == NULL && len > 0) {
*host_errno_ptr(host) = ERROR_INVALID_PARAMETER;
return 0; // FALSE
}
int desc_idx = (int)(long long)handle;
int ret = host_read(host, desc_idx, dst, (int)len);
if (ret < 0) {
switch (ret) {
case HOST_ERROR_BADIDX:
*host_errno_ptr(host) = ERROR_INVALID_HANDLE;
return 0; // FALSE
case HOST_ERROR_BADARG:
case HOST_ERROR_ISDIR:
*host_errno_ptr(host) = ERROR_ACCESS_DENIED;
return 0; // FALSE
case HOST_ERROR_IO:
default:
*host_errno_ptr(host) = ERROR_ACCESS_DENIED;
return 0; // FALSE
}
}
if (num != NULL)
*num = (DWORD)ret;
*host_errno_ptr(host) = ERROR_SUCCESS;
return 1; // TRUE
}
BOOL mock_WriteFile(HANDLE handle, char *src, DWORD len, DWORD *num, OVERLAPPED *ov)
{
Host *host = host___;
if (host == NULL)
abort_("Call to mock_WriteFile() with no node scheduled\n");
if (!host_is_windows(host))
abort_("Call to mock_WriteFile() not from Windows\n");
// We don't support overlapped (async) I/O
if (ov != NULL)
abort_("Quakey does not support overlapped I/O in WriteFile\n");
if (handle == INVALID_HANDLE_VALUE || handle == NULL) {
*host_errno_ptr(host) = ERROR_INVALID_HANDLE;
return 0; // FALSE
}
if (src == NULL && len > 0) {
*host_errno_ptr(host) = ERROR_INVALID_PARAMETER;
return 0; // FALSE
}
int desc_idx = (int)(long long)handle;
int ret = host_write(host, desc_idx, src, (int)len);
if (ret < 0) {
switch (ret) {
case HOST_ERROR_BADIDX:
*host_errno_ptr(host) = ERROR_INVALID_HANDLE;
return 0; // FALSE
case HOST_ERROR_IO:
default:
*host_errno_ptr(host) = ERROR_ACCESS_DENIED;
return 0; // FALSE
}
}
if (num != NULL)
*num = (DWORD)ret;
*host_errno_ptr(host) = ERROR_SUCCESS;
return 1; // TRUE
}
DWORD mock_SetFilePointer(HANDLE hFile, LONG lDistanceToMove, PLONG lpDistanceToMoveHigh, DWORD dwMoveMethod)
{
Host *host = host___;
if (host == NULL)
abort_("Call to mock_SetFilePointer() with no node scheduled\n");
if (!host_is_windows(host))
abort_("Call to mock_SetFilePointer() not from Windows\n");
if (hFile == INVALID_HANDLE_VALUE || hFile == NULL) {
*host_errno_ptr(host) = ERROR_INVALID_HANDLE;
return INVALID_SET_FILE_POINTER;
}
int desc_idx = (int)(long long)hFile;
// Convert Windows move method to HOST whence
int host_whence;
switch (dwMoveMethod) {
case FILE_BEGIN:
host_whence = HOST_SEEK_SET;
break;
case FILE_CURRENT:
host_whence = HOST_SEEK_CUR;
break;
case FILE_END:
host_whence = HOST_SEEK_END;
break;
default:
*host_errno_ptr(host) = ERROR_INVALID_PARAMETER;
return INVALID_SET_FILE_POINTER;
}
// Build 64-bit offset
int64_t offset;
if (lpDistanceToMoveHigh != NULL) {
// 64-bit seek: combine high and low parts
offset = ((int64_t)(*lpDistanceToMoveHigh) << 32) | ((uint32_t)lDistanceToMove);
} else {
// 32-bit seek: use signed extension
offset = (int64_t)lDistanceToMove;
}
int ret = host_lseek(host, desc_idx, offset, host_whence);
if (ret < 0) {
switch (ret) {
case HOST_ERROR_BADIDX:
*host_errno_ptr(host) = ERROR_INVALID_HANDLE;
return INVALID_SET_FILE_POINTER;
case HOST_ERROR_BADARG:
*host_errno_ptr(host) = ERROR_NEGATIVE_SEEK;
return INVALID_SET_FILE_POINTER;
default:
*host_errno_ptr(host) = ERROR_INVALID_PARAMETER;
return INVALID_SET_FILE_POINTER;
}
}
int64_t new_pos = (int64_t)ret;
// Set high part if requested
if (lpDistanceToMoveHigh != NULL)
*lpDistanceToMoveHigh = (LONG)(new_pos >> 32);
*host_errno_ptr(host) = ERROR_SUCCESS;
return (DWORD)(new_pos & 0xFFFFFFFF);
}
BOOL mock_GetFileSizeEx(HANDLE handle, LARGE_INTEGER *buf)
{
Host *host = host___;
if (host == NULL)
abort_("Call to mock_GetFileSizeEx() with no node scheduled\n");
if (!host_is_windows(host))
abort_("Call to mock_GetFileSizeEx() not from Windows\n");
if (handle == INVALID_HANDLE_VALUE || handle == NULL) {
*host_errno_ptr(host) = ERROR_INVALID_HANDLE;
return 0; // FALSE
}
if (buf == NULL) {
*host_errno_ptr(host) = ERROR_INVALID_PARAMETER;
return 0; // FALSE
}
int desc_idx = (int)(long long)handle;
FileInfo info;
int ret = host_fileinfo(host, desc_idx, &info);
if (ret < 0) {
switch (ret) {
case HOST_ERROR_BADIDX:
*host_errno_ptr(host) = ERROR_INVALID_HANDLE;
return 0; // FALSE
case HOST_ERROR_IO:
default:
*host_errno_ptr(host) = ERROR_ACCESS_DENIED;
return 0; // FALSE
}
}
buf->QuadPart = (LONGLONG)info.size;
*host_errno_ptr(host) = ERROR_SUCCESS;
return 1; // TRUE
}
BOOL mock_QueryPerformanceCounter(LARGE_INTEGER *lpPerformanceCount)
{
Host *host = host___;
if (host == NULL)
abort_("Call to mock_QueryPerformanceCounter() with no node scheduled\n");
if (!host_is_windows(host))
abort_("Call to mock_QueryPerformanceCounter() not from Windows\n");
if (lpPerformanceCount == NULL)
return 0; // FALSE
// Get current time in nanoseconds and convert to performance counter units
// We use nanoseconds directly as the counter value (frequency = 1,000,000,000)
Nanos now = host_time(host);
lpPerformanceCount->QuadPart = (LONGLONG)now;
return 1; // TRUE
}
BOOL mock_QueryPerformanceFrequency(LARGE_INTEGER *lpFrequency)
{
Host *host = host___;
if (host == NULL)
abort_("Call to mock_QueryPerformanceFrequency() with no node scheduled\n");
if (!host_is_windows(host))
abort_("Call to mock_QueryPerformanceFrequency() not from Windows\n");
if (lpFrequency == NULL)
return 0; // FALSE
// Frequency is 1 billion (nanoseconds per second)
// This matches our counter which counts in nanoseconds
lpFrequency->QuadPart = 1000000000LL;
return 1; // TRUE
}
char *mock__fullpath(char *path, char *dst, int cap)
{
Host *host = host___;
if (host == NULL)
abort_("Call to mock__fullpath() with no node scheduled\n");
if (!host_is_windows(host))
abort_("Call to mock__fullpath() not from Windows\n");
if (path == NULL) {
*host_errno_ptr(host) = EINVAL;
return NULL;
}
// Temporary buffer for path normalization
// We'll build the absolute path here
char temp[4096];
int temp_len = 0;
// Copy path to temp, converting backslashes to forward slashes
for (int i = 0; path[i] != '\0' && temp_len < (int)sizeof(temp) - 1; i++) {
if (path[i] == '\\') {
temp[temp_len++] = '/';
} else {
temp[temp_len++] = path[i];
}
}
temp[temp_len] = '\0';
// Result buffer for the normalized absolute path
char result[4096];
int result_len = 0;
// If path doesn't start with '/', prepend '/' (mock has no CWD, uses root)
const char *src = temp;
if (temp[0] != '/') {
result[result_len++] = '/';
}
// Parse path components and resolve . and ..
while (*src != '\0') {
// Skip consecutive slashes
while (*src == '/') src++;
if (*src == '\0') break;
// Find end of this component
const char *end = src;
while (*end != '\0' && *end != '/') end++;
int comp_len = (int)(end - src);
if (comp_len == 1 && src[0] == '.') {
// Current directory - skip it
} else if (comp_len == 2 && src[0] == '.' && src[1] == '.') {
// Parent directory - remove last component from result
if (result_len > 1) {
// Find the last slash before the current position
result_len--; // Move back from current position
while (result_len > 0 && result[result_len - 1] != '/') {
result_len--;
}
if (result_len == 0) {
result_len = 1; // Keep the root slash
}
}
} else {
// Regular component - add it
if (result_len > 1 || (result_len == 1 && result[0] != '/')) {
if (result_len < (int)sizeof(result) - 1)
result[result_len++] = '/';
}
for (int i = 0; i < comp_len && result_len < (int)sizeof(result) - 1; i++) {
result[result_len++] = src[i];
}
}
src = end;
}
// Ensure we have at least root
if (result_len == 0) {
result[result_len++] = '/';
}
result[result_len] = '\0';
// Allocate buffer if dst is NULL
if (dst == NULL) {
dst = malloc(result_len + 1);
if (dst == NULL) {
*host_errno_ptr(host) = ENOMEM;
return NULL;
}
} else {
// Check if result fits in the provided buffer
if (result_len + 1 > cap) {
*host_errno_ptr(host) = ERANGE;
return NULL;
}
}
// Copy result to destination
for (int i = 0; i <= result_len; i++) {
dst[i] = result[i];
}
return dst;
}
int mock__mkdir(char *path)
{
Host *host = host___;
if (host == NULL)
abort_("Call to mock__mkdir() with no node scheduled\n");
if (!host_is_windows(host))
abort_("Call to mock__mkdir() not from Windows\n");
int ret = host_mkdir(host, path);
if (ret < 0) {
switch (ret) {
case HOST_ERROR_EXIST:
*host_errno_ptr(host) = EEXIST;
return -1;
case HOST_ERROR_NOENT:
// Parent directory doesn't exist
*host_errno_ptr(host) = ENOENT;
return -1;
default:
*host_errno_ptr(host) = EIO;
return -1;
}
}
return 0;
}
// Structure to track Windows find handle state
typedef struct {
int fd; // Descriptor index for the directory
} FindHandle;
// Helper function to populate WIN32_FIND_DATAA from a DirEntry
static void populate_find_data(WIN32_FIND_DATAA *data, DirEntry *entry)
{
// Clear the structure
for (int i = 0; i < (int)sizeof(WIN32_FIND_DATAA); i++)
((char *)data)[i] = 0;
// Set file attributes
data->dwFileAttributes = entry->is_dir ? FILE_ATTRIBUTE_DIRECTORY : FILE_ATTRIBUTE_NORMAL;
// Copy filename
int i = 0;
while (entry->name[i] != '\0' && i < MAX_PATH - 1) {
data->cFileName[i] = entry->name[i];
i++;
}
data->cFileName[i] = '\0';
}
HANDLE mock_FindFirstFileA(char *lpFileName, WIN32_FIND_DATAA *lpFindFileData)
{
Host *host = host___;
if (host == NULL)
abort_("Call to mock_FindFirstFileA() with no node scheduled\n");
if (!host_is_windows(host))
abort_("Call to mock_FindFirstFileA() not from Windows\n");
if (lpFileName == NULL || lpFindFileData == NULL) {
*host_errno_ptr(host) = ERROR_INVALID_PARAMETER;
return INVALID_HANDLE_VALUE;
}
// Extract directory path from the search pattern
// The pattern is typically "path\*" or "path\*.ext"
// We need to find the last path separator and extract the directory
char dirpath[MAX_PATH];
int len = 0;
while (lpFileName[len] != '\0' && len < MAX_PATH - 1) {
dirpath[len] = lpFileName[len];
len++;
}
dirpath[len] = '\0';
// Find the last path separator (either '/' or '\')
int last_sep = -1;
for (int i = 0; i < len; i++) {
if (dirpath[i] == '/' || dirpath[i] == '\\')
last_sep = i;
}
// If we found a separator, truncate to get the directory path
// If the pattern is just "*", use "." as the directory
if (last_sep >= 0) {
dirpath[last_sep] = '\0';
} else {
// No separator found - use current directory
dirpath[0] = '.';
dirpath[1] = '\0';
}
// Open the directory
int ret = host_open_dir(host, dirpath);
if (ret < 0) {
switch (ret) {
case HOST_ERROR_FULL:
*host_errno_ptr(host) = ERROR_NOT_ENOUGH_MEMORY;
return INVALID_HANDLE_VALUE;
case HOST_ERROR_NOENT:
*host_errno_ptr(host) = ERROR_PATH_NOT_FOUND;
return INVALID_HANDLE_VALUE;
case HOST_ERROR_IO:
default:
*host_errno_ptr(host) = ERROR_ACCESS_DENIED;
return INVALID_HANDLE_VALUE;
}
}
// Allocate find handle structure
FindHandle *fh = malloc(sizeof(FindHandle));
if (fh == NULL) {
host_close(host, ret, false);
*host_errno_ptr(host) = ERROR_NOT_ENOUGH_MEMORY;
return INVALID_HANDLE_VALUE;
}
fh->fd = ret;
// Read the first entry
DirEntry entry;
int read_ret = host_read_dir(host, fh->fd, &entry);
if (read_ret < 0) {
host_close(host, fh->fd, false);
free(fh);
switch (read_ret) {
case HOST_ERROR_BADIDX:
case HOST_ERROR_BADARG:
*host_errno_ptr(host) = ERROR_INVALID_HANDLE;
return INVALID_HANDLE_VALUE;
default:
*host_errno_ptr(host) = ERROR_ACCESS_DENIED;
return INVALID_HANDLE_VALUE;
}
}
if (read_ret == 0) {
// Empty directory - no files found
host_close(host, fh->fd, false);
free(fh);
*host_errno_ptr(host) = ERROR_FILE_NOT_FOUND;
return INVALID_HANDLE_VALUE;
}
// Populate the find data structure
populate_find_data(lpFindFileData, &entry);
*host_errno_ptr(host) = ERROR_SUCCESS;
return (HANDLE)fh;
}
BOOL mock_FindNextFileA(HANDLE hFindFile, WIN32_FIND_DATAA *lpFindFileData)
{
Host *host = host___;
if (host == NULL)
abort_("Call to mock_FindNextFileA() with no node scheduled\n");
if (!host_is_windows(host))
abort_("Call to mock_FindNextFileA() not from Windows\n");
if (hFindFile == INVALID_HANDLE_VALUE || hFindFile == NULL || lpFindFileData == NULL) {
*host_errno_ptr(host) = ERROR_INVALID_HANDLE;
return 0; // FALSE
}
FindHandle *fh = (FindHandle *)hFindFile;
// Read the next entry
DirEntry entry;
int ret = host_read_dir(host, fh->fd, &entry);
if (ret < 0) {
switch (ret) {
case HOST_ERROR_BADIDX:
case HOST_ERROR_BADARG:
*host_errno_ptr(host) = ERROR_INVALID_HANDLE;
return 0; // FALSE
default:
*host_errno_ptr(host) = ERROR_ACCESS_DENIED;
return 0; // FALSE
}
}
if (ret == 0) {
// No more files
*host_errno_ptr(host) = ERROR_NO_MORE_FILES;
return 0; // FALSE
}
// Populate the find data structure
populate_find_data(lpFindFileData, &entry);
*host_errno_ptr(host) = ERROR_SUCCESS;
return 1; // TRUE
}
BOOL mock_FindClose(HANDLE hFindFile)
{
Host *host = host___;
if (host == NULL)
abort_("Call to mock_FindClose() with no node scheduled\n");
if (!host_is_windows(host))
abort_("Call to mock_FindClose() not from Windows\n");
if (hFindFile == INVALID_HANDLE_VALUE || hFindFile == NULL) {
*host_errno_ptr(host) = ERROR_INVALID_HANDLE;
return 0; // FALSE
}
FindHandle *fh = (FindHandle *)hFindFile;
int ret = host_close(host, fh->fd, false);
if (ret < 0) {
free(fh);
switch (ret) {
case HOST_ERROR_BADIDX:
*host_errno_ptr(host) = ERROR_INVALID_HANDLE;
return 0; // FALSE
default:
*host_errno_ptr(host) = ERROR_ACCESS_DENIED;
return 0; // FALSE
}
}
free(fh);
*host_errno_ptr(host) = ERROR_SUCCESS;
return 1; // TRUE
}
BOOL mock_MoveFileExW(WCHAR *lpExistingFileName, WCHAR *lpNewFileName, DWORD dwFlags)
{
Host *host = host___;
if (host == NULL)
abort_("Call to mock_MoveFileExW() with no node scheduled\n");
if (!host_is_windows(host))
abort_("Call to mock_MoveFileExW() not from Windows\n");
// Validate parameters
if (lpExistingFileName == NULL) {
*host_errno_ptr(host) = ERROR_INVALID_PARAMETER;
return 0; // FALSE
}
// lpNewFileName can be NULL only with MOVEFILE_DELAY_UNTIL_REBOOT
// (marks file for deletion on reboot), but we don't support that
if (lpNewFileName == NULL) {
if (dwFlags & MOVEFILE_DELAY_UNTIL_REBOOT) {
// We don't simulate reboot, so just succeed without doing anything
*host_errno_ptr(host) = ERROR_SUCCESS;
return 1; // TRUE
}
*host_errno_ptr(host) = ERROR_INVALID_PARAMETER;
return 0; // FALSE
}
// Convert wide string paths to narrow strings
char oldpath[MAX_PATH];
char newpath[MAX_PATH];
if (wchar_to_char(lpExistingFileName, oldpath, MAX_PATH) < 0) {
*host_errno_ptr(host) = ERROR_INVALID_PARAMETER;
return 0; // FALSE
}
if (wchar_to_char(lpNewFileName, newpath, MAX_PATH) < 0) {
*host_errno_ptr(host) = ERROR_INVALID_PARAMETER;
return 0; // FALSE
}
// If MOVEFILE_REPLACE_EXISTING is not set and destination exists, fail
// We need to check this before calling host_rename
if (!(dwFlags & MOVEFILE_REPLACE_EXISTING)) {
// Try to check if destination exists by attempting to open it
int check = host_open_file(host, newpath, LFS_O_RDONLY);
if (check >= 0) {
// File exists, close it and return error
host_close(host, check, false);
*host_errno_ptr(host) = ERROR_ALREADY_EXISTS;
return 0; // FALSE
}
}
int ret = host_rename(host, oldpath, newpath);
if (ret < 0) {
switch (ret) {
case HOST_ERROR_NOENT:
*host_errno_ptr(host) = ERROR_FILE_NOT_FOUND;
break;
case HOST_ERROR_EXIST:
*host_errno_ptr(host) = ERROR_ALREADY_EXISTS;
break;
case HOST_ERROR_NOTEMPTY:
*host_errno_ptr(host) = ERROR_ACCESS_DENIED;
break;
case HOST_ERROR_ISDIR:
*host_errno_ptr(host) = ERROR_ACCESS_DENIED;
break;
default:
*host_errno_ptr(host) = ERROR_ACCESS_DENIED;
break;
}
return 0; // FALSE
}
*host_errno_ptr(host) = ERROR_SUCCESS;
return 1; // TRUE
}
#endif
void *mock_malloc(size_t size)
{
Host *host = host___;
if (host == NULL)
abort_("Call to mock_malloc() with no node scheduled\n");
#ifdef FAULT_INJECTION
if ((sim_random(host->sim) % 1000) == 0)
return NULL;
#endif
return malloc(size);
}
void *mock_realloc(void *ptr, size_t size)
{
Host *host = host___;
if (host == NULL)
abort_("Call to mock_realloc() with no node scheduled\n");
#ifdef FAULT_INJECTION
if ((sim_random(host->sim) % 1000) == 0)
return NULL;
#endif
return realloc(ptr, size);
}
void mock_free(void *ptr)
{
Host *host = host___;
if (host == NULL)
abort_("Call to mock_free() with no node scheduled\n");
free(ptr);
}