///////////////////////////////////////////////////////////////// // Includes #include "mockfs.h" #include #include #include ///////////////////////////////////////////////////////////////// // 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); }