Add mock implementation for MoveFileExW Windows API function following the existing pattern in the codebase. The mock forwards calls to the real Windows API, allowing for future interception in the simulation framework if needed.
1968 lines
51 KiB
C
1968 lines
51 KiB
C
#include "tcp.h"
|
|
#ifdef BUILD_TEST
|
|
|
|
#include <assert.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <stdarg.h>
|
|
|
|
#include "system.h"
|
|
#include "chunk_server.h"
|
|
#include "metadata_server.h"
|
|
#include "simulation_client.h"
|
|
|
|
#ifdef _WIN32
|
|
#define NATIVE_HANDLE HANDLE
|
|
#else
|
|
#define NATIVE_HANDLE int
|
|
#endif
|
|
|
|
#define MAX_DESCRIPTORS 1024
|
|
#define MAX_ALLOCATIONS 128
|
|
#define MAX_PROCESSES 32
|
|
|
|
#define DATA_QUEUE_SIZE (1<<9)
|
|
|
|
typedef struct Process Process;
|
|
|
|
typedef enum {
|
|
DESC_EMPTY,
|
|
DESC_FILE,
|
|
DESC_SOCKET,
|
|
DESC_LISTEN_SOCKET,
|
|
DESC_CONNECTION_SOCKET,
|
|
DESC_DIRECTORY,
|
|
} DescriptorType;
|
|
|
|
typedef enum {
|
|
DESC_ADDR_VOID,
|
|
DESC_ADDR_IPV4,
|
|
DESC_ADDR_IPV6,
|
|
} DescriptorAddressType;
|
|
|
|
typedef struct {
|
|
DescriptorAddressType type;
|
|
union {
|
|
struct sockaddr_in ipv4;
|
|
struct sockaddr_in6 ipv6;
|
|
};
|
|
} DescriptorAddress;
|
|
|
|
typedef struct {
|
|
Process *process;
|
|
int descriptor_index;
|
|
uint32_t generation;
|
|
} DescriptorHandle;
|
|
|
|
typedef struct {
|
|
int head;
|
|
int used;
|
|
int size;
|
|
DescriptorHandle *items;
|
|
} AcceptQueue;
|
|
|
|
typedef struct {
|
|
int size;
|
|
int used;
|
|
char *data;
|
|
} DataQueue;
|
|
|
|
typedef enum {
|
|
CONNECTION_DELAYED,
|
|
CONNECTION_QUEUED,
|
|
CONNECTION_ESTABLISHED,
|
|
CONNECTION_FAILED,
|
|
} ConnectionState;
|
|
|
|
typedef struct {
|
|
|
|
// ------ Common ----------------
|
|
|
|
DescriptorType type;
|
|
uint32_t generation;
|
|
|
|
// ------ File ------------------
|
|
|
|
NATIVE_HANDLE real_fd;
|
|
|
|
// ------ Directory -------------
|
|
|
|
#ifdef _WIN32
|
|
HANDLE real_d;
|
|
#else
|
|
DIR *real_d;
|
|
#endif
|
|
|
|
// ------ Socket ----------------
|
|
|
|
// Events reported by the last "poll" call
|
|
// for this descriptor
|
|
int events;
|
|
|
|
// Events triggered since the last "poll"
|
|
// call. Note that these may include events
|
|
// not present in the "events" set.
|
|
int revents;
|
|
|
|
// Context for this descriptor, set by the
|
|
// last "poll" call.
|
|
void *context;
|
|
|
|
// Whether this socket is configured as non-blocking
|
|
bool is_nonblocking;
|
|
|
|
// Address bound to this descriptor by the
|
|
// "bind" call.
|
|
DescriptorAddress address;
|
|
|
|
// ------ Listen socket ---------
|
|
|
|
AcceptQueue accept_queue;
|
|
|
|
// ------ Connection socket -----
|
|
|
|
ConnectionState connection_state;
|
|
|
|
// When QUEUED, this refers to the peer listener
|
|
// socket. When ESTABLISHED, this refers to the
|
|
// peer connection socket.
|
|
DescriptorHandle connection_peer;
|
|
|
|
// Address of the last connect() call
|
|
// on this socket if it's still in the
|
|
// "DELAYED" state.
|
|
DescriptorAddress connect_address;
|
|
|
|
// Error number when the connection is FAILED
|
|
int connect_errno;
|
|
|
|
// Data written to this descriptor using "write"
|
|
// or "send".
|
|
DataQueue output_data;
|
|
|
|
// ------------------------------
|
|
|
|
} Descriptor;
|
|
|
|
typedef struct {
|
|
void *ptr;
|
|
size_t len;
|
|
char *file;
|
|
int line;
|
|
} Allocation;
|
|
|
|
typedef enum {
|
|
PROCESS_TYPE_METADATA_SERVER,
|
|
PROCESS_TYPE_CHUNK_SERVER,
|
|
PROCESS_TYPE_CLIENT,
|
|
} ProcessType;
|
|
|
|
struct Process {
|
|
|
|
int num_desc;
|
|
Descriptor desc[MAX_DESCRIPTORS];
|
|
|
|
int num_allocs;
|
|
Allocation allocs[MAX_ALLOCATIONS];
|
|
|
|
Time wakeup_time;
|
|
|
|
ProcessType type;
|
|
union {
|
|
ChunkServer chunk_server;
|
|
MetadataServer metadata_server;
|
|
SimulationClient simulation_client;
|
|
};
|
|
};
|
|
|
|
static int num_processes = 0;
|
|
static Process *processes[MAX_PROCESSES];
|
|
static Process *current_process = NULL;
|
|
static uint64_t current_time = 1;
|
|
static uint64_t seed;
|
|
|
|
// Helper to set socket errors correctly on Windows vs Linux
|
|
#ifdef _WIN32
|
|
#define SET_SOCKET_ERROR(err) WSASetLastError(err)
|
|
#define SOCKET_ERROR_WOULDBLOCK WSAEWOULDBLOCK
|
|
#define SOCKET_ERROR_AFNOSUPPORT WSAEAFNOSUPPORT
|
|
#define SOCKET_ERROR_MFILE WSAEMFILE
|
|
#define SOCKET_ERROR_BADF WSAEBADF
|
|
#define SOCKET_ERROR_NOTSOCK WSAENOTSOCK
|
|
#define SOCKET_ERROR_INVAL WSAEINVAL
|
|
#define SOCKET_ERROR_ADDRINUSE WSAEADDRINUSE
|
|
#define SOCKET_ERROR_DESTADDRREQ WSAEDESTADDRREQ
|
|
#define SOCKET_ERROR_CONNABORTED WSAECONNABORTED
|
|
#define SOCKET_ERROR_ISCONN WSAEISCONN
|
|
#define SOCKET_ERROR_INPROGRESS WSAEINPROGRESS
|
|
#define SOCKET_ERROR_OPNOTSUPP WSAEOPNOTSUPP
|
|
#define SOCKET_ERROR_NOTCONN WSAENOTCONN
|
|
#define SOCKET_ERROR_CONNRESET WSAECONNRESET
|
|
#define SOCKET_ERROR_PROTOOPT WSAENOPROTOOPT
|
|
#define SOCKET_ERROR_PIPE WSAESHUTDOWN // Closest to EPIPE on Windows
|
|
#else
|
|
#define SET_SOCKET_ERROR(err) (errno = (err))
|
|
#define SOCKET_ERROR_WOULDBLOCK EWOULDBLOCK
|
|
#define SOCKET_ERROR_AFNOSUPPORT EAFNOSUPPORT
|
|
#define SOCKET_ERROR_MFILE EMFILE
|
|
#define SOCKET_ERROR_BADF EBADF
|
|
#define SOCKET_ERROR_NOTSOCK ENOTSOCK
|
|
#define SOCKET_ERROR_INVAL EINVAL
|
|
#define SOCKET_ERROR_ADDRINUSE EADDRINUSE
|
|
#define SOCKET_ERROR_DESTADDRREQ EDESTADDRREQ
|
|
#define SOCKET_ERROR_CONNABORTED ECONNABORTED
|
|
#define SOCKET_ERROR_ISCONN EISCONN
|
|
#define SOCKET_ERROR_INPROGRESS EINPROGRESS
|
|
#define SOCKET_ERROR_OPNOTSUPP EOPNOTSUPP
|
|
#define SOCKET_ERROR_NOTCONN ENOTCONN
|
|
#define SOCKET_ERROR_CONNRESET ECONNRESET
|
|
#define SOCKET_ERROR_PROTOOPT ENOPROTOOPT
|
|
#define SOCKET_ERROR_PIPE EPIPE
|
|
#endif
|
|
|
|
static int count_non_empty_desc(Process *p)
|
|
{
|
|
int n = 0;
|
|
for (int i = 0; i < MAX_DESCRIPTORS; i++)
|
|
if (p->desc[i].type != DESC_EMPTY)
|
|
n++;
|
|
return n;
|
|
}
|
|
#define CHECK_NON_EMPTY_DESC_INVARIANT assert(count_non_empty_desc(current_process) == current_process->num_desc);
|
|
|
|
static void process_poll_array(Process *process,
|
|
void **contexts, struct pollfd *polled, int num_polled)
|
|
{
|
|
for (int i = 0, j = 0; j < process->num_desc; i++) {
|
|
|
|
assert(i < MAX_DESCRIPTORS);
|
|
Descriptor *desc = &process->desc[i];
|
|
if (desc->type == DESC_EMPTY)
|
|
continue;
|
|
j++;
|
|
|
|
desc->events = 0;
|
|
desc->revents = 0;
|
|
desc->context = NULL;
|
|
}
|
|
|
|
for (int i = 0; i < num_polled; i++) {
|
|
|
|
SOCKET fd = polled[i].fd;
|
|
if (fd == INVALID_SOCKET)
|
|
continue;
|
|
|
|
int idx = (int) fd;
|
|
process->desc[idx].events = polled[i].events;
|
|
process->desc[idx].revents = 0;
|
|
process->desc[idx].context = contexts[i];
|
|
}
|
|
}
|
|
|
|
#define MAX_ARGS 128
|
|
|
|
static bool is_space(char c)
|
|
{
|
|
return c == ' ' || c == '\t' || c == '\r' || c == '\n';
|
|
}
|
|
|
|
void startup_simulation(uint64_t seed_)
|
|
{
|
|
seed = seed_;
|
|
if (seed == 0)
|
|
seed = 1;
|
|
|
|
num_processes = 0;
|
|
current_process = NULL;
|
|
}
|
|
|
|
uint64_t simulation_random_number(void)
|
|
{
|
|
uint64_t x = seed;
|
|
x ^= x << 13;
|
|
x ^= x >> 7;
|
|
x ^= x << 17;
|
|
seed = x;
|
|
return x;
|
|
}
|
|
|
|
int spawn_simulated_process(char *args)
|
|
{
|
|
if (num_processes == MAX_PROCESSES)
|
|
return -1;
|
|
|
|
char mem[1<<10];
|
|
int args_len = strlen(args);
|
|
if (args_len >= (int) sizeof(mem))
|
|
return -1;
|
|
memcpy(mem, args, args_len);
|
|
mem[args_len] = '\0';
|
|
args = mem;
|
|
|
|
int argc = 0;
|
|
char *argv[MAX_ARGS];
|
|
for (int cur = 0;;) {
|
|
|
|
while (cur < args_len && is_space(args[cur]))
|
|
cur++;
|
|
|
|
if (cur == args_len || argc == MAX_ARGS)
|
|
break;
|
|
|
|
argv[argc++] = args + cur;
|
|
|
|
while (cur < args_len && !is_space(args[cur]))
|
|
cur++;
|
|
|
|
args[cur] = '\0';
|
|
if (cur < args_len)
|
|
cur++;
|
|
}
|
|
|
|
bool leader = getargb(argc, argv, "--leader");
|
|
bool client = getargb(argc, argv, "--client");
|
|
|
|
Process *process = malloc(sizeof(Process));
|
|
if (process == NULL)
|
|
return -1;
|
|
|
|
// Determine process type
|
|
if (client) {
|
|
process->type = PROCESS_TYPE_CLIENT;
|
|
} else if (leader) {
|
|
process->type = PROCESS_TYPE_METADATA_SERVER;
|
|
} else {
|
|
process->type = PROCESS_TYPE_CHUNK_SERVER;
|
|
}
|
|
|
|
process->num_desc = 0;
|
|
process->num_allocs = 0;
|
|
|
|
for (int i = 0; i < MAX_DESCRIPTORS; i++) {
|
|
process->desc[i].type = DESC_EMPTY;
|
|
process->desc[i].generation = 0;
|
|
}
|
|
|
|
void *contexts[MAX_CONNS+1];
|
|
struct pollfd polled[MAX_CONNS+1];
|
|
int num_polled;
|
|
int timeout = -1;
|
|
|
|
current_process = process;
|
|
|
|
switch (process->type) {
|
|
case PROCESS_TYPE_METADATA_SERVER:
|
|
num_polled = metadata_server_init(&process->metadata_server, argc, argv, contexts, polled, &timeout);
|
|
break;
|
|
case PROCESS_TYPE_CHUNK_SERVER:
|
|
num_polled = chunk_server_init(&process->chunk_server, argc, argv, contexts, polled, &timeout);
|
|
break;
|
|
case PROCESS_TYPE_CLIENT:
|
|
num_polled = simulation_client_init(&process->simulation_client, argc, argv, contexts, polled, &timeout);
|
|
break;
|
|
default:
|
|
num_polled = -1;
|
|
break;
|
|
}
|
|
|
|
current_process = NULL;
|
|
if (num_polled < 0) {
|
|
assert(0); // TODO
|
|
}
|
|
|
|
if (timeout < 0) {
|
|
process->wakeup_time = INVALID_TIME;
|
|
} else {
|
|
process->wakeup_time = current_time + timeout * 1000000;
|
|
}
|
|
|
|
process_poll_array(process, contexts, polled, num_polled);
|
|
|
|
processes[num_processes++] = process;
|
|
return 0;
|
|
}
|
|
|
|
static void free_process(Process *process)
|
|
{
|
|
switch (process->type) {
|
|
case PROCESS_TYPE_METADATA_SERVER:
|
|
metadata_server_free(&process->metadata_server);
|
|
break;
|
|
case PROCESS_TYPE_CHUNK_SERVER:
|
|
chunk_server_free(&process->chunk_server);
|
|
break;
|
|
case PROCESS_TYPE_CLIENT:
|
|
simulation_client_free(&process->simulation_client);
|
|
break;
|
|
}
|
|
free(process);
|
|
}
|
|
|
|
void cleanup_simulation(void)
|
|
{
|
|
for (int i = 0; i < num_processes; i++) {
|
|
current_process = processes[i];
|
|
free_process(processes[i]);
|
|
current_process = NULL;
|
|
}
|
|
}
|
|
|
|
static bool addr_eql_2(DescriptorAddress a, DescriptorAddress b)
|
|
{
|
|
if (a.type != b.type)
|
|
return false;
|
|
|
|
if (a.type == DESC_ADDR_IPV4) {
|
|
return a.ipv4.sin_family == b.ipv4.sin_family
|
|
&& a.ipv4.sin_port == b.ipv4.sin_port
|
|
&& !memcmp(&a.ipv4.sin_addr, &a.ipv4.sin_addr, sizeof(a.ipv4.sin_addr));
|
|
} else {
|
|
return a.ipv6.sin6_family == b.ipv6.sin6_family
|
|
&& a.ipv6.sin6_port == b.ipv6.sin6_port
|
|
&& !memcmp(&a.ipv6.sin6_addr, &a.ipv6.sin6_addr, sizeof(a.ipv6.sin6_addr));
|
|
}
|
|
}
|
|
|
|
static bool find_peer_by_address(DescriptorAddress address, DescriptorHandle *handle)
|
|
{
|
|
for (int i = 0; i < num_processes; i++) {
|
|
|
|
for (int j = 0, k = 0; k < processes[i]->num_desc; j++) {
|
|
|
|
assert(j < MAX_DESCRIPTORS);
|
|
Descriptor *desc = &processes[i]->desc[j];
|
|
if (desc->type == DESC_EMPTY)
|
|
continue;
|
|
k++;
|
|
|
|
if (desc->type == DESC_LISTEN_SOCKET &&
|
|
addr_eql_2(address, desc->address)) {
|
|
*handle = (DescriptorHandle) { processes[i], j, desc->generation };
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
static Descriptor *handle_to_desc(DescriptorHandle handle)
|
|
{
|
|
if (handle.process == NULL
|
|
|| handle.descriptor_index < 0
|
|
|| handle.descriptor_index >= MAX_DESCRIPTORS)
|
|
return NULL;
|
|
Process *process = handle.process;
|
|
Descriptor *desc = &process->desc[handle.descriptor_index];
|
|
if (desc->type == DESC_EMPTY || desc->generation != handle.generation)
|
|
return NULL;
|
|
return desc;
|
|
}
|
|
|
|
static void accept_queue_init(AcceptQueue *accept_queue, int size)
|
|
{
|
|
accept_queue->head = 0;
|
|
accept_queue->used = 0;
|
|
accept_queue->size = size;
|
|
accept_queue->items = malloc(size * sizeof(DescriptorHandle));
|
|
if (accept_queue->items == NULL) {
|
|
assert(0); // TODO
|
|
}
|
|
}
|
|
|
|
static void accept_queue_free(AcceptQueue *accept_queue)
|
|
{
|
|
free(accept_queue->items);
|
|
}
|
|
|
|
static bool accept_queue_push(AcceptQueue *accept_queue, DescriptorHandle handle)
|
|
{
|
|
if (accept_queue->used == accept_queue->size)
|
|
return false;
|
|
int tail = (accept_queue->head + accept_queue->used) % accept_queue->size;
|
|
accept_queue->items[tail] = handle;
|
|
accept_queue->used++;
|
|
return true;
|
|
}
|
|
|
|
static bool accept_queue_pop(AcceptQueue *accept_queue, DescriptorHandle *item)
|
|
{
|
|
if (accept_queue->used == 0)
|
|
return false;
|
|
*item = accept_queue->items[accept_queue->head];
|
|
accept_queue->head = (accept_queue->head + 1) % accept_queue->size;
|
|
accept_queue->used--;
|
|
return true;
|
|
}
|
|
|
|
static void accept_queue_remove(AcceptQueue *queue, DescriptorHandle handle)
|
|
{
|
|
int i = 0;
|
|
while (i < queue->used && (
|
|
queue->items[i].process != handle.process ||
|
|
queue->items[i].descriptor_index != handle.descriptor_index ||
|
|
queue->items[i].generation != handle.generation))
|
|
i++;
|
|
|
|
if (i == queue->used)
|
|
return;
|
|
|
|
for (; i < queue->used-1; i++) {
|
|
int u = (queue->head + i + 0) % queue->size;
|
|
int v = (queue->head + i + 1) % queue->size;
|
|
queue->items[u] = queue->items[v];
|
|
}
|
|
}
|
|
|
|
static bool accept_queue_empty(AcceptQueue *queue)
|
|
{
|
|
return queue->used == 0;
|
|
}
|
|
|
|
static void data_queue_init(DataQueue *queue, int size)
|
|
{
|
|
queue->used = 0;
|
|
queue->size = size;
|
|
queue->data = malloc(size * sizeof(char));
|
|
if (queue->data == NULL) {
|
|
assert(0); // TODO
|
|
}
|
|
}
|
|
|
|
static void data_queue_free(DataQueue *queue)
|
|
{
|
|
free(queue->data);
|
|
}
|
|
|
|
static int data_queue_read(DataQueue *queue, char *dst, int max)
|
|
{
|
|
int num = max;
|
|
if (num > queue->used)
|
|
num = queue->used;
|
|
|
|
if (num > 0) {
|
|
memcpy(dst, queue->data, num);
|
|
memmove(queue->data, queue->data + num, queue->used - num);
|
|
queue->used -= num;
|
|
}
|
|
|
|
return num;
|
|
}
|
|
|
|
static int data_queue_write(DataQueue *queue, char *src, int len)
|
|
{
|
|
int num = len;
|
|
if (num > queue->size - queue->used)
|
|
num = queue->size - queue->used;
|
|
|
|
memcpy(queue->data + queue->used, src, num);
|
|
queue->used += num;
|
|
|
|
return num;
|
|
}
|
|
|
|
static bool data_queue_empty(DataQueue *queue)
|
|
{
|
|
return queue->used == 0;
|
|
}
|
|
|
|
static bool data_queue_full(DataQueue *queue)
|
|
{
|
|
return queue->used == queue->size;
|
|
}
|
|
|
|
static int setup_poll_array(void **contexts, struct pollfd *polled)
|
|
{
|
|
int num_polled = 0;
|
|
|
|
for (int j = 0, k = 0; k < current_process->num_desc; j++) {
|
|
|
|
assert(j < MAX_DESCRIPTORS);
|
|
Descriptor *desc = ¤t_process->desc[j];
|
|
if (desc->type == DESC_EMPTY)
|
|
continue;
|
|
k++;
|
|
|
|
int revents = 0;
|
|
switch (desc->type) {
|
|
|
|
case DESC_FILE:
|
|
assert(0); // TODO: error
|
|
break;
|
|
|
|
case DESC_SOCKET:
|
|
// Ignore
|
|
break;
|
|
|
|
case DESC_LISTEN_SOCKET:
|
|
if (!accept_queue_empty(&desc->accept_queue))
|
|
revents |= POLLIN;
|
|
break;
|
|
|
|
case DESC_CONNECTION_SOCKET:
|
|
switch (desc->connection_state) {
|
|
|
|
case CONNECTION_DELAYED:
|
|
break;
|
|
|
|
case CONNECTION_QUEUED:
|
|
break;
|
|
|
|
case CONNECTION_ESTABLISHED:
|
|
{
|
|
Descriptor *peer = handle_to_desc(desc->connection_peer);
|
|
if (peer == NULL) {
|
|
revents |= POLLIN;
|
|
} else {
|
|
if (!data_queue_full(&desc->output_data))
|
|
revents |= POLLOUT;
|
|
if (!data_queue_empty(&peer->output_data))
|
|
revents |= POLLIN;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case CONNECTION_FAILED:
|
|
assert(0); // TODO
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
|
|
revents &= desc->events;
|
|
if (revents) {
|
|
polled[num_polled].fd = (SOCKET) j;
|
|
polled[num_polled].events = desc->events;
|
|
polled[num_polled].revents = revents;
|
|
contexts[num_polled] = desc->context;
|
|
num_polled++;
|
|
}
|
|
}
|
|
|
|
return num_polled;
|
|
}
|
|
|
|
static void update_network(void)
|
|
{
|
|
for (int i = 0; i < num_processes; i++) {
|
|
|
|
for (int j = 0, k = 0; k < processes[i]->num_desc; j++) {
|
|
|
|
Descriptor *desc = &processes[i]->desc[j];
|
|
if (desc->type == DESC_EMPTY)
|
|
continue;
|
|
k++;
|
|
|
|
if (desc->type != DESC_CONNECTION_SOCKET)
|
|
continue;
|
|
|
|
switch (desc->connection_state) {
|
|
|
|
case CONNECTION_DELAYED:
|
|
{
|
|
DescriptorHandle peer_handle;
|
|
if (!find_peer_by_address(desc->connect_address, &peer_handle)) {
|
|
desc->revents |= POLLOUT;
|
|
desc->connection_state = CONNECTION_FAILED;
|
|
desc->connect_errno = EHOSTUNREACH; // TODO: This only works on Linux, not Windows
|
|
break;
|
|
}
|
|
Descriptor *peer = handle_to_desc(peer_handle);
|
|
|
|
DescriptorHandle self_handle = { processes[i], j, desc->generation };
|
|
if (!accept_queue_push(&peer->accept_queue, self_handle)) {
|
|
desc->revents |= POLLOUT;
|
|
desc->connection_state = CONNECTION_FAILED;
|
|
desc->connect_errno = ECONNREFUSED; // TODO: This only works on Linux, not Windows
|
|
break;
|
|
}
|
|
|
|
peer->revents |= POLLIN;
|
|
|
|
desc->connection_state = CONNECTION_QUEUED;
|
|
desc->connection_peer = peer_handle;
|
|
}
|
|
break;
|
|
|
|
case CONNECTION_QUEUED:
|
|
{
|
|
if (handle_to_desc(desc->connection_peer) == NULL) {
|
|
// Listener closed before accepting
|
|
desc->revents |= POLLOUT;
|
|
desc->connection_state = CONNECTION_FAILED;
|
|
desc->connect_errno = ECONNREFUSED; // TODO: This only works on Linux, not Windows
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void update_simulation(void)
|
|
{
|
|
for (;;) {
|
|
int num_ready = 0;
|
|
for (int i = 0; i < num_processes; i++) {
|
|
|
|
current_process = processes[i];
|
|
|
|
void *contexts[MAX_CONNS+1];
|
|
struct pollfd polled[MAX_CONNS+1];
|
|
int num_polled = setup_poll_array(contexts, polled);
|
|
|
|
if (num_polled > 0) {
|
|
|
|
num_ready++;
|
|
|
|
int timeout = -1;
|
|
switch (current_process->type) {
|
|
case PROCESS_TYPE_METADATA_SERVER:
|
|
num_polled = metadata_server_step(
|
|
¤t_process->metadata_server,
|
|
contexts,
|
|
polled,
|
|
num_polled,
|
|
&timeout
|
|
);
|
|
break;
|
|
case PROCESS_TYPE_CHUNK_SERVER:
|
|
num_polled = chunk_server_step(
|
|
¤t_process->chunk_server,
|
|
contexts,
|
|
polled,
|
|
num_polled,
|
|
&timeout
|
|
);
|
|
break;
|
|
case PROCESS_TYPE_CLIENT:
|
|
num_polled = simulation_client_step(
|
|
¤t_process->simulation_client,
|
|
contexts,
|
|
polled,
|
|
num_polled,
|
|
&timeout
|
|
);
|
|
break;
|
|
}
|
|
|
|
if (num_polled < 0) {
|
|
assert(0); // TODO
|
|
}
|
|
|
|
if (timeout < 0) {
|
|
current_process->wakeup_time = INVALID_TIME;
|
|
} else {
|
|
current_process->wakeup_time = current_time + (Time) timeout * 1000000;
|
|
}
|
|
|
|
process_poll_array(current_process, contexts, polled, num_polled);
|
|
}
|
|
|
|
current_process = NULL;
|
|
|
|
update_network();
|
|
}
|
|
|
|
if (num_ready == 0)
|
|
break;
|
|
}
|
|
|
|
Process *next_process = NULL;
|
|
for (int i = 0; i < num_processes; i++)
|
|
if (processes[i]->wakeup_time != INVALID_TIME)
|
|
if (next_process == NULL || processes[i]->wakeup_time < next_process->wakeup_time)
|
|
next_process = processes[i];
|
|
|
|
if (next_process == NULL) {
|
|
assert(0); // Nothing to schedule next
|
|
}
|
|
|
|
assert(current_time <= next_process->wakeup_time);
|
|
current_time = next_process->wakeup_time;
|
|
|
|
current_process = next_process;
|
|
|
|
void *contexts[MAX_CONNS+1];
|
|
struct pollfd polled[MAX_CONNS+1];
|
|
int num_polled = setup_poll_array(contexts, polled);
|
|
|
|
int timeout = -1;
|
|
switch (current_process->type) {
|
|
case PROCESS_TYPE_METADATA_SERVER:
|
|
num_polled = metadata_server_step(
|
|
¤t_process->metadata_server,
|
|
contexts,
|
|
polled,
|
|
num_polled,
|
|
&timeout
|
|
);
|
|
break;
|
|
case PROCESS_TYPE_CHUNK_SERVER:
|
|
num_polled = chunk_server_step(
|
|
¤t_process->chunk_server,
|
|
contexts,
|
|
polled,
|
|
num_polled,
|
|
&timeout
|
|
);
|
|
break;
|
|
case PROCESS_TYPE_CLIENT:
|
|
num_polled = simulation_client_step(
|
|
¤t_process->simulation_client,
|
|
contexts,
|
|
polled,
|
|
num_polled,
|
|
&timeout
|
|
);
|
|
break;
|
|
}
|
|
|
|
if (num_polled < 0) {
|
|
assert(0); // TODO
|
|
}
|
|
|
|
if (timeout < 0) {
|
|
current_process->wakeup_time = INVALID_TIME;
|
|
} else {
|
|
current_process->wakeup_time = current_time + (Time) timeout * 1000000;
|
|
}
|
|
|
|
process_poll_array(current_process, contexts, polled, num_polled);
|
|
|
|
current_process = NULL;
|
|
|
|
update_network();
|
|
}
|
|
|
|
void *mock_malloc(size_t len)
|
|
{
|
|
return malloc(len);
|
|
}
|
|
|
|
void *mock_realloc(void *ptr, size_t len)
|
|
{
|
|
return realloc(ptr, len);
|
|
}
|
|
|
|
void mock_free(void *ptr)
|
|
{
|
|
free(ptr);
|
|
}
|
|
|
|
int mock_remove(char *path)
|
|
{
|
|
return remove(path);
|
|
}
|
|
|
|
int mock_rename(char *oldpath, char *newpath)
|
|
{
|
|
return rename(oldpath, newpath);
|
|
}
|
|
|
|
SOCKET mock_socket(int domain, int type, int protocol)
|
|
{
|
|
if (domain != AF_INET || type != SOCK_STREAM || protocol != 0) {
|
|
SET_SOCKET_ERROR(SOCKET_ERROR_AFNOSUPPORT); // Address family not supported
|
|
return INVALID_SOCKET;
|
|
}
|
|
|
|
if (current_process->num_desc == MAX_DESCRIPTORS) {
|
|
SET_SOCKET_ERROR(SOCKET_ERROR_MFILE); // Too many open files
|
|
return INVALID_SOCKET;
|
|
}
|
|
|
|
int idx = 0;
|
|
while (current_process->desc[idx].type != DESC_EMPTY) {
|
|
idx++;
|
|
assert(idx < MAX_DESCRIPTORS);
|
|
}
|
|
|
|
Descriptor *desc = ¤t_process->desc[idx];
|
|
desc->type = DESC_SOCKET;
|
|
desc->events = 0;
|
|
desc->revents = 0;
|
|
desc->context = NULL;
|
|
desc->is_nonblocking = false;
|
|
desc->address = (DescriptorAddress) { .type=DESC_ADDR_VOID };
|
|
|
|
current_process->num_desc++;
|
|
|
|
CHECK_NON_EMPTY_DESC_INVARIANT;
|
|
return (SOCKET) idx;
|
|
}
|
|
|
|
static DescriptorAddress convert_address(void *addr, size_t addr_len)
|
|
{
|
|
int family = ((struct sockaddr*) addr)->sa_family;
|
|
|
|
if (family == AF_INET && addr_len == sizeof(struct sockaddr_in))
|
|
return (DescriptorAddress) { .type=DESC_ADDR_IPV4, .ipv4=*(struct sockaddr_in*) addr };
|
|
|
|
if (family == AF_INET6 && addr_len != sizeof(struct sockaddr_in6))
|
|
return (DescriptorAddress) { .type=DESC_ADDR_IPV6, .ipv6=*(struct sockaddr_in6*) addr };
|
|
|
|
return (DescriptorAddress) { .type=DESC_ADDR_VOID };
|
|
}
|
|
|
|
int mock_bind(SOCKET fd, void *addr, size_t addr_len)
|
|
{
|
|
if (fd == INVALID_SOCKET || (int)fd < 0 || (int)fd >= MAX_DESCRIPTORS) {
|
|
SET_SOCKET_ERROR(SOCKET_ERROR_BADF); // Bad file descriptor
|
|
return -1;
|
|
}
|
|
|
|
int idx = (int) fd;
|
|
Descriptor *desc = ¤t_process->desc[idx];
|
|
if (desc->type != DESC_SOCKET) {
|
|
SET_SOCKET_ERROR(SOCKET_ERROR_NOTSOCK); // Socket operation on non-socket
|
|
return -1;
|
|
}
|
|
|
|
DescriptorAddress address = convert_address(addr, addr_len);
|
|
if (address.type == DESC_ADDR_VOID) {
|
|
SET_SOCKET_ERROR(SOCKET_ERROR_INVAL); // Invalid argument
|
|
return -1;
|
|
}
|
|
|
|
// Check if address is already in use by another socket
|
|
for (int i = 0; i < current_process->num_desc; i++) {
|
|
Descriptor *other = ¤t_process->desc[i];
|
|
if (other->type != DESC_EMPTY && i != idx) {
|
|
if (other->address.type == address.type) {
|
|
if (address.type == DESC_ADDR_IPV4 &&
|
|
other->address.ipv4.sin_port == address.ipv4.sin_port &&
|
|
other->address.ipv4.sin_addr.s_addr == address.ipv4.sin_addr.s_addr) {
|
|
SET_SOCKET_ERROR(SOCKET_ERROR_ADDRINUSE); // Address already in use
|
|
return -1;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
desc->address = address;
|
|
return 0;
|
|
}
|
|
|
|
int mock_listen(SOCKET fd, int backlog)
|
|
{
|
|
if (fd == INVALID_SOCKET || (int)fd < 0 || (int)fd >= MAX_DESCRIPTORS) {
|
|
SET_SOCKET_ERROR(SOCKET_ERROR_BADF); // Bad file descriptor
|
|
return -1;
|
|
}
|
|
|
|
int idx = (int) fd;
|
|
Descriptor *desc = ¤t_process->desc[idx];
|
|
if (desc->type != DESC_SOCKET) {
|
|
SET_SOCKET_ERROR(SOCKET_ERROR_NOTSOCK); // Socket operation on non-socket
|
|
return -1;
|
|
}
|
|
|
|
if (desc->address.type == DESC_ADDR_VOID) {
|
|
SET_SOCKET_ERROR(SOCKET_ERROR_DESTADDRREQ); // Destination address required (socket not bound)
|
|
return -1;
|
|
}
|
|
|
|
desc->type = DESC_LISTEN_SOCKET;
|
|
accept_queue_init(&desc->accept_queue, backlog);
|
|
|
|
return 0;
|
|
}
|
|
|
|
SOCKET mock_accept(SOCKET fd, void *addr, socklen_t *addr_len)
|
|
{
|
|
if (fd == INVALID_SOCKET || (int)fd < 0 || (int)fd >= MAX_DESCRIPTORS) {
|
|
SET_SOCKET_ERROR(SOCKET_ERROR_BADF); // Bad file descriptor
|
|
return INVALID_SOCKET;
|
|
}
|
|
|
|
int idx = (int) fd;
|
|
Descriptor *desc = ¤t_process->desc[idx];
|
|
if (desc->type != DESC_LISTEN_SOCKET) {
|
|
SET_SOCKET_ERROR(SOCKET_ERROR_INVAL); // Invalid argument (not a listening socket)
|
|
return INVALID_SOCKET;
|
|
}
|
|
|
|
DescriptorHandle peer_handle;
|
|
if (!accept_queue_pop(&desc->accept_queue, &peer_handle)) {
|
|
// Socket would block - abort if not configured as non-blocking
|
|
if (!desc->is_nonblocking) {
|
|
fprintf(stderr, "SIMULATION ERROR: accept() would block on socket %d but socket is not configured as non-blocking\n", (int)fd);
|
|
abort();
|
|
}
|
|
SET_SOCKET_ERROR(SOCKET_ERROR_WOULDBLOCK); // Would block (no pending connections)
|
|
return INVALID_SOCKET;
|
|
}
|
|
|
|
Descriptor *peer = handle_to_desc(peer_handle);
|
|
if (peer == NULL) {
|
|
// Peer closed without removing itself from the accept queue!
|
|
SET_SOCKET_ERROR(SOCKET_ERROR_CONNABORTED); // Connection aborted
|
|
return INVALID_SOCKET;
|
|
}
|
|
|
|
if (current_process->num_desc == MAX_DESCRIPTORS) {
|
|
SET_SOCKET_ERROR(SOCKET_ERROR_MFILE); // Too many open files
|
|
return INVALID_SOCKET;
|
|
}
|
|
int new_idx = 0;
|
|
while (current_process->desc[new_idx].type != DESC_EMPTY) {
|
|
new_idx++;
|
|
assert(new_idx < MAX_DESCRIPTORS);
|
|
}
|
|
Descriptor *new_desc = ¤t_process->desc[new_idx];
|
|
new_desc->type = DESC_CONNECTION_SOCKET;
|
|
new_desc->events = 0;
|
|
new_desc->revents = 0;
|
|
new_desc->context = NULL;
|
|
new_desc->is_nonblocking = false;
|
|
new_desc->address = (DescriptorAddress) { .type=DESC_ADDR_VOID };
|
|
new_desc->connection_state = CONNECTION_ESTABLISHED;
|
|
new_desc->connection_peer = peer_handle;
|
|
data_queue_init(&new_desc->output_data, DATA_QUEUE_SIZE);
|
|
|
|
peer->connection_peer = (DescriptorHandle) { current_process, new_idx, new_desc->generation };
|
|
peer->connection_state = CONNECTION_ESTABLISHED;
|
|
peer->revents |= POLLOUT;
|
|
data_queue_init(&peer->output_data, DATA_QUEUE_SIZE);
|
|
|
|
current_process->num_desc++;
|
|
CHECK_NON_EMPTY_DESC_INVARIANT;
|
|
return (SOCKET) new_idx;
|
|
}
|
|
|
|
int mock_getsockopt(SOCKET fd, int level, int optname, void *optval, socklen_t *optlen)
|
|
{
|
|
if (fd == INVALID_SOCKET || (int)fd < 0 || (int)fd >= MAX_DESCRIPTORS) {
|
|
SET_SOCKET_ERROR(SOCKET_ERROR_BADF); // Bad file descriptor
|
|
return -1;
|
|
}
|
|
|
|
int idx = (int) fd;
|
|
Descriptor *desc = ¤t_process->desc[idx];
|
|
if (desc->type == DESC_EMPTY) {
|
|
SET_SOCKET_ERROR(SOCKET_ERROR_BADF);
|
|
return -1;
|
|
}
|
|
|
|
// Only support SOL_SOCKET level for now
|
|
if (level != SOL_SOCKET) {
|
|
SET_SOCKET_ERROR(SOCKET_ERROR_PROTOOPT); // Protocol not available
|
|
return -1;
|
|
}
|
|
|
|
// Support SO_ERROR option
|
|
if (optname == SO_ERROR) {
|
|
if (*optlen < sizeof(int)) {
|
|
SET_SOCKET_ERROR(SOCKET_ERROR_INVAL);
|
|
return -1;
|
|
}
|
|
|
|
int val;
|
|
switch (desc->type) {
|
|
|
|
case DESC_EMPTY:
|
|
assert(0);
|
|
break;
|
|
|
|
case DESC_FILE:
|
|
SET_SOCKET_ERROR(SOCKET_ERROR_NOTSOCK);
|
|
return -1;
|
|
|
|
case DESC_SOCKET:
|
|
val = 0; // No error
|
|
break;
|
|
|
|
case DESC_LISTEN_SOCKET:
|
|
val = 0; // No error
|
|
break;
|
|
|
|
case DESC_CONNECTION_SOCKET:
|
|
switch (desc->connection_state) {
|
|
|
|
case CONNECTION_DELAYED:
|
|
val = 0; // No error
|
|
break;
|
|
|
|
case CONNECTION_QUEUED:
|
|
val = 0; // No error
|
|
break;
|
|
|
|
case CONNECTION_ESTABLISHED:
|
|
val = 0; // No error
|
|
break;
|
|
|
|
case CONNECTION_FAILED:
|
|
val = desc->connect_errno;
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
*(int*)optval = val;
|
|
*optlen = sizeof(int);
|
|
return 0;
|
|
}
|
|
|
|
SET_SOCKET_ERROR(SOCKET_ERROR_PROTOOPT);
|
|
return -1;
|
|
}
|
|
|
|
int mock_recv(SOCKET fd, void *dst, int len, int flags)
|
|
{
|
|
if (fd == INVALID_SOCKET || (int)fd < 0 || (int)fd >= MAX_DESCRIPTORS) {
|
|
SET_SOCKET_ERROR(SOCKET_ERROR_BADF); // Bad file descriptor
|
|
return -1;
|
|
}
|
|
|
|
if (flags != 0) {
|
|
SET_SOCKET_ERROR(SOCKET_ERROR_OPNOTSUPP); // Operation not supported
|
|
return -1;
|
|
}
|
|
|
|
int idx = (int) fd;
|
|
Descriptor *desc = ¤t_process->desc[idx];
|
|
if (desc->type != DESC_CONNECTION_SOCKET) {
|
|
SET_SOCKET_ERROR(SOCKET_ERROR_NOTCONN); // Transport endpoint is not connected
|
|
return -1;
|
|
}
|
|
|
|
if (desc->connection_state != CONNECTION_ESTABLISHED) {
|
|
SET_SOCKET_ERROR(SOCKET_ERROR_NOTCONN);
|
|
return -1;
|
|
}
|
|
|
|
Descriptor *peer = handle_to_desc(desc->connection_peer);
|
|
if (peer == NULL) {
|
|
// Peer closed - return 0 to indicate orderly shutdown
|
|
return 0;
|
|
}
|
|
|
|
DataQueue *input_data = &peer->output_data;
|
|
int bytes_read = data_queue_read(input_data, dst, len);
|
|
|
|
// If no data available, would block
|
|
if (bytes_read == 0) {
|
|
// Socket would block - abort if not configured as non-blocking
|
|
if (!desc->is_nonblocking) {
|
|
fprintf(stderr, "SIMULATION ERROR: recv() would block on socket %d but socket is not configured as non-blocking\n", (int)fd);
|
|
abort();
|
|
}
|
|
SET_SOCKET_ERROR(SOCKET_ERROR_WOULDBLOCK);
|
|
return -1;
|
|
}
|
|
|
|
return bytes_read;
|
|
}
|
|
|
|
int mock_send(SOCKET fd, void *src, int len, int flags)
|
|
{
|
|
if (fd == INVALID_SOCKET || (int)fd < 0 || (int)fd >= MAX_DESCRIPTORS) {
|
|
SET_SOCKET_ERROR(SOCKET_ERROR_BADF); // Bad file descriptor
|
|
return -1;
|
|
}
|
|
|
|
if (flags != 0) {
|
|
SET_SOCKET_ERROR(SOCKET_ERROR_OPNOTSUPP); // Operation not supported
|
|
return -1;
|
|
}
|
|
|
|
int idx = (int) fd;
|
|
Descriptor *desc = ¤t_process->desc[idx];
|
|
if (desc->type != DESC_CONNECTION_SOCKET) {
|
|
SET_SOCKET_ERROR(SOCKET_ERROR_NOTCONN); // Transport endpoint is not connected
|
|
return -1;
|
|
}
|
|
|
|
if (desc->connection_state != CONNECTION_ESTABLISHED) {
|
|
SET_SOCKET_ERROR(SOCKET_ERROR_NOTCONN);
|
|
return -1;
|
|
}
|
|
|
|
// Check if peer is still connected
|
|
Descriptor *peer = handle_to_desc(desc->connection_peer);
|
|
if (peer == NULL) {
|
|
SET_SOCKET_ERROR(SOCKET_ERROR_PIPE); // Broken pipe / connection shutdown
|
|
return -1;
|
|
}
|
|
|
|
// Write data to output queue
|
|
int bytes_written = data_queue_write(&desc->output_data, src, len);
|
|
|
|
// If queue is full, we would block
|
|
if (bytes_written < len) {
|
|
// Socket would block - abort if not configured as non-blocking
|
|
if (!desc->is_nonblocking && bytes_written == 0) {
|
|
fprintf(stderr, "SIMULATION ERROR: send() would block on socket %d but socket is not configured as non-blocking\n", (int)fd);
|
|
abort();
|
|
}
|
|
SET_SOCKET_ERROR(SOCKET_ERROR_WOULDBLOCK);
|
|
return bytes_written > 0 ? bytes_written : -1;
|
|
}
|
|
|
|
return bytes_written;
|
|
}
|
|
|
|
int mock_connect(SOCKET fd, void *addr, size_t addr_len)
|
|
{
|
|
if (fd == INVALID_SOCKET || (int)fd < 0 || (int)fd >= MAX_DESCRIPTORS) {
|
|
SET_SOCKET_ERROR(SOCKET_ERROR_BADF); // Bad file descriptor
|
|
return -1;
|
|
}
|
|
|
|
int idx = (int) fd;
|
|
Descriptor *desc = ¤t_process->desc[idx];
|
|
if (desc->type != DESC_SOCKET) {
|
|
SET_SOCKET_ERROR(SOCKET_ERROR_ISCONN); // Transport endpoint is already connected
|
|
return -1;
|
|
}
|
|
|
|
desc->type = DESC_CONNECTION_SOCKET;
|
|
desc->connection_state = CONNECTION_DELAYED;
|
|
desc->connect_address = convert_address(addr, addr_len);
|
|
if (desc->connect_address.type == DESC_ADDR_VOID) {
|
|
SET_SOCKET_ERROR(SOCKET_ERROR_INVAL); // Invalid argument
|
|
return -1;
|
|
}
|
|
|
|
// Return EINPROGRESS/WSAEWOULDBLOCK to indicate non-blocking connection in progress
|
|
SET_SOCKET_ERROR(SOCKET_ERROR_INPROGRESS);
|
|
return -1;
|
|
}
|
|
|
|
static int
|
|
wrap_native_file_into_desc(NATIVE_HANDLE handle)
|
|
{
|
|
if (current_process->num_desc == MAX_DESCRIPTORS) {
|
|
// TODO
|
|
return -1;
|
|
}
|
|
|
|
int idx = 0;
|
|
while (current_process->desc[idx].type != DESC_EMPTY) {
|
|
idx++;
|
|
assert(idx < MAX_DESCRIPTORS);
|
|
}
|
|
|
|
Descriptor *desc = ¤t_process->desc[idx];
|
|
|
|
desc->type = DESC_FILE;
|
|
desc->real_fd = handle;
|
|
|
|
current_process->num_desc++;
|
|
CHECK_NON_EMPTY_DESC_INVARIANT;
|
|
return idx;
|
|
}
|
|
|
|
static void close_desc(Descriptor *desc)
|
|
{
|
|
switch (desc->type) {
|
|
|
|
case DESC_EMPTY:
|
|
assert(0); // TODO
|
|
break;
|
|
|
|
case DESC_FILE:
|
|
#ifdef _WIN32
|
|
CloseHandle(desc->real_fd);
|
|
#else
|
|
close(desc->real_fd);
|
|
#endif
|
|
break;
|
|
|
|
case DESC_SOCKET:
|
|
// TODO
|
|
break;
|
|
|
|
case DESC_LISTEN_SOCKET:
|
|
accept_queue_free(&desc->accept_queue);
|
|
break;
|
|
|
|
case DESC_CONNECTION_SOCKET:
|
|
switch (desc->connection_state) {
|
|
|
|
case CONNECTION_DELAYED:
|
|
// TODO
|
|
break;
|
|
|
|
case CONNECTION_QUEUED:
|
|
{
|
|
Descriptor *peer = handle_to_desc(desc->connection_peer);
|
|
if (peer == NULL) break;
|
|
|
|
DescriptorHandle self_handle = { current_process, desc - current_process->desc, desc->generation };
|
|
accept_queue_remove(&peer->accept_queue, self_handle);
|
|
}
|
|
break;
|
|
|
|
case CONNECTION_ESTABLISHED:
|
|
data_queue_free(&desc->output_data);
|
|
break;
|
|
|
|
case CONNECTION_FAILED:
|
|
// TODO
|
|
break;
|
|
}
|
|
// TODO
|
|
break;
|
|
|
|
case DESC_DIRECTORY:
|
|
#ifdef _WIN32
|
|
FindClose(desc->real_d);
|
|
#else
|
|
closedir(desc->real_d);
|
|
#endif
|
|
break;
|
|
}
|
|
desc->type = DESC_EMPTY;
|
|
desc->generation++;
|
|
}
|
|
|
|
#ifdef _WIN32
|
|
|
|
int mock_closesocket(SOCKET fd)
|
|
{
|
|
if (fd == INVALID_SOCKET) {
|
|
// TODO: set error
|
|
return -1;
|
|
}
|
|
int idx = (int) fd;
|
|
|
|
Descriptor *desc = ¤t_process->desc[idx];
|
|
if (desc->type != DESC_SOCKET &&
|
|
desc->type != DESC_LISTEN_SOCKET &&
|
|
desc->type != DESC_CONNECTION_SOCKET) {
|
|
// TODO: set error
|
|
return -1;
|
|
}
|
|
|
|
close_desc(desc);
|
|
current_process->num_desc--;
|
|
CHECK_NON_EMPTY_DESC_INVARIANT;
|
|
return 0;
|
|
}
|
|
|
|
HANDLE mock_CreateFileW(WCHAR *lpFileName, DWORD dwDesiredAccess,
|
|
DWORD dwShareMode, LPSECURITY_ATTRIBUTES lpSecurityAttributes,
|
|
DWORD dwCreationDisposition, DWORD dwFlagsAndAttributes,
|
|
HANDLE hTemplateFile)
|
|
{
|
|
HANDLE handle = CreateFileW(lpFileName,
|
|
dwDesiredAccess, dwShareMode,
|
|
lpSecurityAttributes, dwCreationDisposition,
|
|
dwFlagsAndAttributes, hTemplateFile);
|
|
if (handle == INVALID_HANDLE_VALUE)
|
|
return INVALID_HANDLE_VALUE;
|
|
int fd = wrap_native_file_into_desc(handle);
|
|
if (fd < 0) {
|
|
CloseHandle(handle);
|
|
return INVALID_HANDLE_VALUE;
|
|
}
|
|
return (HANDLE) fd;
|
|
}
|
|
|
|
BOOL mock_CloseHandle(HANDLE handle)
|
|
{
|
|
if (handle == INVALID_HANDLE_VALUE || (int)handle < 0 || (int)handle >= MAX_DESCRIPTORS) {
|
|
SetLastError(ERROR_INVALID_HANDLE);
|
|
return FALSE;
|
|
}
|
|
int idx = (int) handle;
|
|
|
|
Descriptor *desc = ¤t_process->desc[idx];
|
|
if (desc->type != DESC_FILE) {
|
|
SetLastError(ERROR_INVALID_HANDLE);
|
|
return FALSE;
|
|
}
|
|
|
|
close_desc(desc);
|
|
current_process->num_desc--;
|
|
CHECK_NON_EMPTY_DESC_INVARIANT;
|
|
return TRUE;
|
|
}
|
|
|
|
BOOL mock_LockFile(HANDLE hFile,
|
|
DWORD dwFileOffsetLow,
|
|
DWORD dwFileOffsetHigh,
|
|
DWORD nNumberOfBytesToLockLow,
|
|
DWORD nNumberOfBytesToLockHigh)
|
|
{
|
|
if (hFile == INVALID_HANDLE_VALUE || (int)hFile < 0 || (int)hFile >= MAX_DESCRIPTORS) {
|
|
SetLastError(ERROR_INVALID_HANDLE);
|
|
return FALSE;
|
|
}
|
|
int idx = (int) hFile;
|
|
|
|
Descriptor *desc = ¤t_process->desc[idx];
|
|
if (desc->type != DESC_FILE) {
|
|
SetLastError(ERROR_INVALID_HANDLE);
|
|
return FALSE;
|
|
}
|
|
|
|
// Forward to real LockFile, last error is set by the real call
|
|
return LockFile(
|
|
desc->real_fd,
|
|
dwFileOffsetLow,
|
|
dwFileOffsetHigh,
|
|
nNumberOfBytesToLockLow,
|
|
nNumberOfBytesToLockHigh);
|
|
}
|
|
|
|
BOOL mock_UnlockFile(
|
|
HANDLE hFile,
|
|
DWORD dwFileOffsetLow,
|
|
DWORD dwFileOffsetHigh,
|
|
DWORD nNumberOfBytesToUnlockLow,
|
|
DWORD nNumberOfBytesToUnlockHigh)
|
|
{
|
|
if (hFile == INVALID_HANDLE_VALUE || (int)hFile < 0 || (int)hFile >= MAX_DESCRIPTORS) {
|
|
SetLastError(ERROR_INVALID_HANDLE);
|
|
return FALSE;
|
|
}
|
|
int idx = (int) hFile;
|
|
|
|
Descriptor *desc = ¤t_process->desc[idx];
|
|
if (desc->type != DESC_FILE) {
|
|
SetLastError(ERROR_INVALID_HANDLE);
|
|
return FALSE;
|
|
}
|
|
|
|
// Forward to real UnlockFile, last error is set by the real call
|
|
return UnlockFile(
|
|
desc->real_fd,
|
|
dwFileOffsetLow,
|
|
dwFileOffsetHigh,
|
|
nNumberOfBytesToUnlockLow,
|
|
nNumberOfBytesToUnlockHigh);
|
|
}
|
|
|
|
BOOL mock_FlushFileBuffers(HANDLE handle)
|
|
{
|
|
if (handle == INVALID_HANDLE_VALUE || (int)handle < 0 || (int)handle >= MAX_DESCRIPTORS) {
|
|
SetLastError(ERROR_INVALID_HANDLE);
|
|
return FALSE;
|
|
}
|
|
int idx = (int) handle;
|
|
|
|
Descriptor *desc = ¤t_process->desc[idx];
|
|
if (desc->type != DESC_FILE) {
|
|
SetLastError(ERROR_INVALID_HANDLE);
|
|
return FALSE;
|
|
}
|
|
|
|
// Forward to real FlushFileBuffers, last error is set by the real call
|
|
return FlushFileBuffers(desc->real_fd);
|
|
}
|
|
|
|
BOOL mock_ReadFile(HANDLE handle, char *dst, DWORD len, DWORD *num, OVERLAPPED *ov)
|
|
{
|
|
if (handle == INVALID_HANDLE_VALUE || (int)handle < 0 || (int)handle >= MAX_DESCRIPTORS) {
|
|
SetLastError(ERROR_INVALID_HANDLE);
|
|
return FALSE;
|
|
}
|
|
int idx = (int) handle;
|
|
|
|
Descriptor *desc = ¤t_process->desc[idx];
|
|
if (desc->type != DESC_FILE) {
|
|
SetLastError(ERROR_INVALID_HANDLE);
|
|
return FALSE;
|
|
}
|
|
|
|
// Forward to real ReadFile, last error is set by the real call
|
|
return ReadFile(desc->real_fd, dst, len, num, ov);
|
|
}
|
|
|
|
BOOL mock_WriteFile(HANDLE handle, char *src, DWORD len, DWORD *num, OVERLAPPED *ov)
|
|
{
|
|
if (handle == INVALID_HANDLE_VALUE || (int)handle < 0 || (int)handle >= MAX_DESCRIPTORS) {
|
|
SetLastError(ERROR_INVALID_HANDLE);
|
|
return FALSE;
|
|
}
|
|
int idx = (int) handle;
|
|
|
|
Descriptor *desc = ¤t_process->desc[idx];
|
|
if (desc->type != DESC_FILE) {
|
|
SetLastError(ERROR_INVALID_HANDLE);
|
|
return FALSE;
|
|
}
|
|
|
|
// Forward to real WriteFile, last error is set by the real call
|
|
return WriteFile(desc->real_fd, src, len, num, ov);
|
|
}
|
|
|
|
BOOL mock_GetFileSizeEx(HANDLE handle, LARGE_INTEGER *buf)
|
|
{
|
|
if (handle == INVALID_HANDLE_VALUE || (int)handle < 0 || (int)handle >= MAX_DESCRIPTORS) {
|
|
SetLastError(ERROR_INVALID_HANDLE);
|
|
return FALSE;
|
|
}
|
|
int idx = (int) handle;
|
|
|
|
Descriptor *desc = ¤t_process->desc[idx];
|
|
if (desc->type != DESC_FILE) {
|
|
SetLastError(ERROR_INVALID_HANDLE);
|
|
return FALSE;
|
|
}
|
|
|
|
// Forward to real GetFileSizeEx, last error is set by the real call
|
|
return GetFileSizeEx(desc->real_fd, buf);
|
|
}
|
|
|
|
char *mock__fullpath(char *path, char *dst, int cap)
|
|
{
|
|
return _fullpath(path, dst, cap);
|
|
}
|
|
|
|
int mock__mkdir(char *path)
|
|
{
|
|
return _mkdir(path);
|
|
}
|
|
|
|
BOOL mock_QueryPerformanceCounter(LARGE_INTEGER *lpPerformanceCount)
|
|
{
|
|
if (lpPerformanceCount == NULL) {
|
|
SetLastError(ERROR_INVALID_PARAMETER);
|
|
return FALSE;
|
|
}
|
|
|
|
// Use current_time (in nanoseconds) to generate deterministic performance counter
|
|
// Frequency is 10 MHz (10,000,000 counts per second)
|
|
const LONGLONG frequency = 10000000LL;
|
|
|
|
// Convert nanoseconds to performance counter ticks
|
|
LONGLONG count = (LONGLONG)(current_time * frequency) / 1000000000LL;
|
|
|
|
lpPerformanceCount->QuadPart = count;
|
|
return TRUE;
|
|
}
|
|
|
|
BOOL mock_QueryPerformanceFrequency(LARGE_INTEGER *lpFrequency)
|
|
{
|
|
if (lpFrequency == NULL) {
|
|
SetLastError(ERROR_INVALID_PARAMETER);
|
|
return FALSE;
|
|
}
|
|
|
|
// Return fixed frequency of 10 MHz for deterministic behavior
|
|
// This is a common frequency on modern systems
|
|
lpFrequency->QuadPart = 10000000LL; // 10 million counts per second
|
|
return TRUE;
|
|
}
|
|
|
|
int mock_ioctlsocket(SOCKET fd, long cmd, u_long *argp)
|
|
{
|
|
if (fd == INVALID_SOCKET || (int)fd < 0 || (int)fd >= MAX_DESCRIPTORS) {
|
|
SET_SOCKET_ERROR(SOCKET_ERROR_BADF);
|
|
return -1;
|
|
}
|
|
|
|
int idx = (int) fd;
|
|
Descriptor *desc = ¤t_process->desc[idx];
|
|
|
|
if (desc->type == DESC_EMPTY) {
|
|
SET_SOCKET_ERROR(SOCKET_ERROR_BADF);
|
|
return -1;
|
|
}
|
|
|
|
if (desc->type != DESC_SOCKET &&
|
|
desc->type != DESC_LISTEN_SOCKET &&
|
|
desc->type != DESC_CONNECTION_SOCKET) {
|
|
SET_SOCKET_ERROR(SOCKET_ERROR_NOTSOCK);
|
|
return -1;
|
|
}
|
|
|
|
if (cmd == FIONBIO) {
|
|
desc->is_nonblocking = (*argp != 0);
|
|
return 0;
|
|
}
|
|
|
|
SET_SOCKET_ERROR(SOCKET_ERROR_INVAL);
|
|
return -1;
|
|
}
|
|
|
|
HANDLE mock_FindFirstFileA(char *lpFileName, WIN32_FIND_DATAA *lpFindFileData)
|
|
{
|
|
HANDLE handle = FindFirstFileA(lpFileName, lpFindFileData);
|
|
if (handle == INVALID_HANDLE_VALUE)
|
|
return INVALID_HANDLE_VALUE;
|
|
|
|
if (current_process->num_desc == MAX_DESCRIPTORS) {
|
|
FindClose(handle);
|
|
SetLastError(ERROR_TOO_MANY_OPEN_FILES);
|
|
return INVALID_HANDLE_VALUE;
|
|
}
|
|
|
|
int idx = 0;
|
|
while (current_process->desc[idx].type != DESC_EMPTY) {
|
|
idx++;
|
|
assert(idx < MAX_DESCRIPTORS);
|
|
}
|
|
|
|
Descriptor *desc = ¤t_process->desc[idx];
|
|
desc->type = DESC_DIRECTORY;
|
|
desc->real_d = handle;
|
|
|
|
current_process->num_desc++;
|
|
CHECK_NON_EMPTY_DESC_INVARIANT;
|
|
return (HANDLE) idx;
|
|
}
|
|
|
|
BOOL mock_FindNextFileA(HANDLE hFindFile, WIN32_FIND_DATAA *lpFindFileData)
|
|
{
|
|
if (hFindFile == INVALID_HANDLE_VALUE || (int)hFindFile < 0 || (int)hFindFile >= MAX_DESCRIPTORS) {
|
|
SetLastError(ERROR_INVALID_HANDLE);
|
|
return FALSE;
|
|
}
|
|
int idx = (int) hFindFile;
|
|
|
|
Descriptor *desc = ¤t_process->desc[idx];
|
|
if (desc->type != DESC_DIRECTORY) {
|
|
SetLastError(ERROR_INVALID_HANDLE);
|
|
return FALSE;
|
|
}
|
|
|
|
// Forward to real FindNextFileA, last error is set by the real call
|
|
return FindNextFileA(desc->real_d, lpFindFileData);
|
|
}
|
|
|
|
BOOL mock_FindClose(HANDLE hFindFile)
|
|
{
|
|
if (hFindFile == INVALID_HANDLE_VALUE || (int)hFindFile < 0 || (int)hFindFile >= MAX_DESCRIPTORS) {
|
|
SetLastError(ERROR_INVALID_HANDLE);
|
|
return FALSE;
|
|
}
|
|
int idx = (int) hFindFile;
|
|
|
|
Descriptor *desc = ¤t_process->desc[idx];
|
|
if (desc->type != DESC_DIRECTORY) {
|
|
SetLastError(ERROR_INVALID_HANDLE);
|
|
return FALSE;
|
|
}
|
|
|
|
close_desc(desc);
|
|
current_process->num_desc--;
|
|
CHECK_NON_EMPTY_DESC_INVARIANT;
|
|
return TRUE;
|
|
}
|
|
|
|
BOOL mock_MoveFileExW(WCHAR *lpExistingFileName, WCHAR *lpNewFileName, DWORD dwFlags)
|
|
{
|
|
// Forward to real MoveFileExW, last error is set by the real call
|
|
return MoveFileExW(lpExistingFileName, lpNewFileName, dwFlags);
|
|
}
|
|
|
|
#else
|
|
|
|
int mock_clock_gettime(clockid_t clockid, struct timespec *tp)
|
|
{
|
|
if (tp == NULL) {
|
|
errno = EFAULT; // Bad address
|
|
return -1;
|
|
}
|
|
|
|
// Only support CLOCK_REALTIME and CLOCK_MONOTONIC for now
|
|
if (clockid != CLOCK_REALTIME && clockid != CLOCK_MONOTONIC) {
|
|
errno = EINVAL; // Invalid clock ID
|
|
return -1;
|
|
}
|
|
|
|
// Convert current_time (nanoseconds) to timespec for deterministic behavior
|
|
tp->tv_sec = current_time / 1000000000ULL;
|
|
tp->tv_nsec = current_time % 1000000000ULL;
|
|
return 0;
|
|
}
|
|
|
|
int mock_open(char *path, int flags, int mode)
|
|
{
|
|
int fd = open(path, flags, mode);
|
|
if (fd < 0) return -1;
|
|
|
|
int wrapped_fd = wrap_native_file_into_desc(fd);
|
|
if (wrapped_fd < 0) {
|
|
close(fd);
|
|
return -1;
|
|
}
|
|
|
|
return wrapped_fd;
|
|
}
|
|
|
|
int mock_close(int fd)
|
|
{
|
|
if (fd < 0 || fd >= MAX_DESCRIPTORS) {
|
|
errno = EBADF; // Bad file descriptor
|
|
return -1;
|
|
}
|
|
int idx = (int) fd;
|
|
|
|
Descriptor *desc = ¤t_process->desc[idx];
|
|
if (desc->type == DESC_EMPTY) {
|
|
errno = EBADF;
|
|
return -1;
|
|
}
|
|
|
|
close_desc(desc);
|
|
current_process->num_desc--;
|
|
CHECK_NON_EMPTY_DESC_INVARIANT;
|
|
return 0;
|
|
}
|
|
|
|
int mock_flock(int fd, int op)
|
|
{
|
|
if (fd < 0 || fd >= MAX_DESCRIPTORS) {
|
|
errno = EBADF; // Bad file descriptor
|
|
return -1;
|
|
}
|
|
int idx = fd;
|
|
|
|
Descriptor *desc = ¤t_process->desc[idx];
|
|
if (desc->type != DESC_FILE) {
|
|
errno = EBADF; // Not a file descriptor
|
|
return -1;
|
|
}
|
|
|
|
// Forward to real flock, errno is set by the real call
|
|
return flock(desc->real_fd, op);
|
|
}
|
|
|
|
int mock_fsync(int fd)
|
|
{
|
|
if (fd < 0 || fd >= MAX_DESCRIPTORS) {
|
|
errno = EBADF; // Bad file descriptor
|
|
return -1;
|
|
}
|
|
int idx = fd;
|
|
|
|
Descriptor *desc = ¤t_process->desc[idx];
|
|
if (desc->type != DESC_FILE) {
|
|
errno = EINVAL; // Invalid argument (not a file)
|
|
return -1;
|
|
}
|
|
|
|
// Forward to real fsync, errno is set by the real call
|
|
return fsync(desc->real_fd);
|
|
}
|
|
|
|
int mock_read(int fd, char *dst, int len)
|
|
{
|
|
if (fd < 0 || fd >= MAX_DESCRIPTORS) {
|
|
errno = EBADF; // Bad file descriptor
|
|
return -1;
|
|
}
|
|
int idx = fd;
|
|
|
|
Descriptor *desc = ¤t_process->desc[idx];
|
|
if (desc->type == DESC_EMPTY) {
|
|
errno = EBADF;
|
|
return -1;
|
|
}
|
|
|
|
if (desc->type == DESC_FILE) {
|
|
// Forward to real read, errno is set by the real call
|
|
return read(desc->real_fd, dst, len);
|
|
} else {
|
|
// Socket read
|
|
return mock_recv(fd, dst, len, 0);
|
|
}
|
|
}
|
|
|
|
int mock_write(int fd, char *src, int len)
|
|
{
|
|
if (fd < 0 || fd >= MAX_DESCRIPTORS) {
|
|
errno = EBADF; // Bad file descriptor
|
|
return -1;
|
|
}
|
|
int idx = fd;
|
|
|
|
Descriptor *desc = ¤t_process->desc[idx];
|
|
if (desc->type == DESC_EMPTY) {
|
|
errno = EBADF;
|
|
return -1;
|
|
}
|
|
|
|
if (desc->type == DESC_FILE) {
|
|
// Forward to real write, errno is set by the real call
|
|
return write(desc->real_fd, src, len);
|
|
} else {
|
|
// Socket write
|
|
return mock_send(fd, src, len, 0);
|
|
}
|
|
}
|
|
|
|
int mock_fstat(int fd, struct stat *buf)
|
|
{
|
|
if (fd < 0 || fd >= MAX_DESCRIPTORS) {
|
|
errno = EBADF; // Bad file descriptor
|
|
return -1;
|
|
}
|
|
int idx = fd;
|
|
|
|
Descriptor *desc = ¤t_process->desc[idx];
|
|
if (desc->type != DESC_FILE) {
|
|
errno = EBADF; // Not a file descriptor
|
|
return -1;
|
|
}
|
|
|
|
// Forward to real fstat, errno is set by the real call
|
|
return fstat(desc->real_fd, buf);
|
|
}
|
|
|
|
int mock_mkstemp(char *path)
|
|
{
|
|
int fd = mkstemp(path);
|
|
if (fd < 0) return fd;
|
|
|
|
int wrapped_fd = wrap_native_file_into_desc(fd);
|
|
if (wrapped_fd < 0) {
|
|
close(fd);
|
|
return -1;
|
|
}
|
|
|
|
return wrapped_fd;
|
|
}
|
|
|
|
char* mock_realpath(char *path, char *dst)
|
|
{
|
|
return realpath(path, dst);
|
|
}
|
|
|
|
int mock_mkdir(char *path, mode_t mode)
|
|
{
|
|
return mkdir(path, mode);
|
|
}
|
|
|
|
int mock_fcntl(int fd, int cmd, ...)
|
|
{
|
|
if (fd < 0 || fd >= MAX_DESCRIPTORS) {
|
|
errno = EBADF;
|
|
return -1;
|
|
}
|
|
|
|
int idx = fd;
|
|
Descriptor *desc = ¤t_process->desc[idx];
|
|
|
|
if (desc->type == DESC_EMPTY) {
|
|
errno = EBADF;
|
|
return -1;
|
|
}
|
|
|
|
if (cmd == F_GETFL) {
|
|
// Return flags - only O_NONBLOCK is tracked
|
|
if (desc->type == DESC_FILE) {
|
|
// For files, forward to real fcntl
|
|
return fcntl(desc->real_fd, cmd);
|
|
}
|
|
return desc->is_nonblocking ? O_NONBLOCK : 0;
|
|
}
|
|
|
|
if (cmd == F_SETFL) {
|
|
if (desc->type == DESC_FILE) {
|
|
// For files, forward to real fcntl
|
|
va_list args;
|
|
va_start(args, cmd);
|
|
int flags = va_arg(args, int);
|
|
va_end(args);
|
|
return fcntl(desc->real_fd, cmd, flags);
|
|
}
|
|
|
|
if (desc->type != DESC_SOCKET &&
|
|
desc->type != DESC_LISTEN_SOCKET &&
|
|
desc->type != DESC_CONNECTION_SOCKET) {
|
|
errno = EBADF;
|
|
return -1;
|
|
}
|
|
|
|
va_list args;
|
|
va_start(args, cmd);
|
|
int flags = va_arg(args, int);
|
|
va_end(args);
|
|
|
|
desc->is_nonblocking = (flags & O_NONBLOCK) != 0;
|
|
return 0;
|
|
}
|
|
|
|
errno = EINVAL;
|
|
return -1;
|
|
}
|
|
|
|
DIR *mock_opendir(char *name)
|
|
{
|
|
DIR *dir = opendir(name);
|
|
if (dir == NULL)
|
|
return NULL;
|
|
|
|
if (current_process->num_desc == MAX_DESCRIPTORS) {
|
|
closedir(dir);
|
|
errno = EMFILE; // Too many open files
|
|
return NULL;
|
|
}
|
|
|
|
int idx = 0;
|
|
while (current_process->desc[idx].type != DESC_EMPTY) {
|
|
idx++;
|
|
assert(idx < MAX_DESCRIPTORS);
|
|
}
|
|
|
|
Descriptor *desc = ¤t_process->desc[idx];
|
|
desc->type = DESC_DIRECTORY;
|
|
desc->real_d = dir;
|
|
|
|
current_process->num_desc++;
|
|
CHECK_NON_EMPTY_DESC_INVARIANT;
|
|
return (DIR *)(intptr_t) idx;
|
|
}
|
|
|
|
struct dirent *mock_readdir(DIR *dirp)
|
|
{
|
|
if (dirp == NULL) {
|
|
errno = EBADF; // Bad file descriptor
|
|
return NULL;
|
|
}
|
|
int idx = (int)(intptr_t) dirp;
|
|
|
|
if (idx < 0 || idx >= MAX_DESCRIPTORS) {
|
|
errno = EBADF;
|
|
return NULL;
|
|
}
|
|
|
|
Descriptor *desc = ¤t_process->desc[idx];
|
|
if (desc->type != DESC_DIRECTORY) {
|
|
errno = EBADF;
|
|
return NULL;
|
|
}
|
|
|
|
// Forward to real readdir, errno is set by the real call
|
|
return readdir(desc->real_d);
|
|
}
|
|
|
|
int mock_closedir(DIR *dirp)
|
|
{
|
|
if (dirp == NULL) {
|
|
errno = EBADF; // Bad file descriptor
|
|
return -1;
|
|
}
|
|
int idx = (int)(intptr_t) dirp;
|
|
|
|
if (idx < 0 || idx >= MAX_DESCRIPTORS) {
|
|
errno = EBADF;
|
|
return -1;
|
|
}
|
|
|
|
Descriptor *desc = ¤t_process->desc[idx];
|
|
if (desc->type != DESC_DIRECTORY) {
|
|
errno = EBADF;
|
|
return -1;
|
|
}
|
|
|
|
close_desc(desc);
|
|
current_process->num_desc--;
|
|
CHECK_NON_EMPTY_DESC_INVARIANT;
|
|
return 0;
|
|
}
|
|
|
|
#endif // !_WIN32
|
|
|
|
#endif // BUILD_TEST
|