Added a simulation client that can run inside the TinyDFS simulation framework, enabling fully deterministic end-to-end testing of the distributed file system. **New Files:** - src/simulation_client.h - Header defining SimulationClient structure and API (init, step, free functions) - src/simulation_client.c - Implementation of simulation client with test operations (create dir, create file, write, read, list, delete) **Key Changes:** **System Framework (src/system.c):** - Added ProcessType enum (METADATA_SERVER, CHUNK_SERVER, CLIENT) to distinguish different simulated process types - Extended Process union to include SimulationClient - Modified spawn_simulated_process() to detect --client flag and initialize client processes using simulation_client_init() - Updated update_simulation() to call simulation_client_step() for client processes, handling timeout management - Fixed cleanup_simulation() to properly set current_process before freeing, preventing NULL pointer dereferences in mock_close() - Added simulated_time static variable (struct timespec) for deterministic clock mocking - Added helper function is_client() to detect client processes **API Updates (src/system.h):** - Exported startup_simulation() function for proper initialization - Added function declarations for simulation management **Test Updates (src/main_test.c):** - Added startup_simulation() call for proper initialization - Spawns simulation client process with "--client" flag - Increased iteration limit to 100,000 to allow operations to complete - Added progress logging every 10,000 iterations - Added explicit error checking and debug output **Simulation Client Features:** - Non-blocking operation model using tinydfs_isdone() and tinydfs_process_events() - State machine for sequential test operations - Comprehensive test scenario: 1. Create directory (/test_dir) 2. Create file (/test_dir/test_file.txt) 3. Write data to file 4. Read data back 5. List directory contents 6. Delete file - Proper integration with TinyDFS client library - Returns poll descriptors from tinydfs_process_events() - Timeout management for simulation scheduling **Bug Fixes:** - Fixed missing return statement in spawn_simulated_process() - Fixed NULL pointer dereference in cleanup by setting current_process - Added missing stdlib.h include for atoi() The simulation client successfully initializes and integrates into the simulation loop, demonstrating the framework's ability to run client code deterministically alongside servers.
1468 lines
38 KiB
C
1468 lines
38 KiB
C
#include <assert.h>
|
|
#include <stdlib.h>
|
|
#include <string.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,
|
|
} 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;
|
|
|
|
// ------ 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;
|
|
|
|
// 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;
|
|
|
|
// 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;
|
|
|
|
#ifndef _WIN32
|
|
// Simulated time for deterministic clock_gettime behavior
|
|
static struct timespec simulated_time = {0, 0};
|
|
#else
|
|
// On Windows, simulated_time is used for QueryPerformanceCounter
|
|
static struct timespec simulated_time = {0, 0};
|
|
#endif
|
|
|
|
// 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 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++) {
|
|
|
|
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];
|
|
}
|
|
}
|
|
|
|
static bool is_leader(int argc, char **argv)
|
|
{
|
|
for (int i = 0; i < argc; i++)
|
|
if (!strcmp("--leader", argv[i]) || !strcmp("-l", argv[i]))
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
static bool is_client(int argc, char **argv)
|
|
{
|
|
for (int i = 0; i < argc; i++)
|
|
if (!strcmp("--client", argv[i]) || !strcmp("-c", argv[i]))
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
#define MAX_ARGS 128
|
|
|
|
static bool is_space(char c)
|
|
{
|
|
return c == ' ' || c == '\t' || c == '\r' || c == '\n';
|
|
}
|
|
|
|
void startup_simulation(void)
|
|
{
|
|
num_processes = 0;
|
|
current_process = NULL;
|
|
}
|
|
|
|
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 = is_leader(argc, argv);
|
|
bool client = is_client(argc, argv);
|
|
|
|
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;
|
|
|
|
void *contexts[MAX_CONNS+1];
|
|
struct pollfd polled[MAX_CONNS+1];
|
|
int num_polled;
|
|
|
|
current_process = process;
|
|
|
|
switch (process->type) {
|
|
case PROCESS_TYPE_METADATA_SERVER:
|
|
num_polled = metadata_server_init(&process->metadata_server, argc, argv, contexts, polled);
|
|
break;
|
|
case PROCESS_TYPE_CHUNK_SERVER:
|
|
num_polled = chunk_server_init(&process->chunk_server, argc, argv, contexts, polled);
|
|
break;
|
|
case PROCESS_TYPE_CLIENT:
|
|
{
|
|
int timeout = -1;
|
|
num_polled = simulation_client_init(&process->simulation_client, argc, argv, contexts, polled, &timeout);
|
|
if (timeout < 0) {
|
|
process->wakeup_time = INVALID_TIME;
|
|
} else {
|
|
process->wakeup_time = current_time + timeout;
|
|
}
|
|
}
|
|
break;
|
|
default:
|
|
num_polled = -1;
|
|
break;
|
|
}
|
|
|
|
current_process = NULL;
|
|
if (num_polled < 0) {
|
|
// TODO
|
|
}
|
|
|
|
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++) {
|
|
|
|
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) {
|
|
// 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 data_queue_init(DataQueue *queue, int size)
|
|
{
|
|
queue->used = 0;
|
|
queue->size = size;
|
|
queue->data = malloc(size * sizeof(char));
|
|
if (queue->data == NULL) {
|
|
// 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;
|
|
}
|
|
|
|
void update_simulation(void)
|
|
{
|
|
// TODO: sort processes based on their wakeup time
|
|
|
|
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 = 0;
|
|
|
|
for (int j = 0, k = 0; k < current_process->num_desc; j++) {
|
|
|
|
Descriptor *desc = ¤t_process->desc[j];
|
|
if (desc->type == DESC_EMPTY)
|
|
continue;
|
|
k++;
|
|
|
|
if (desc->type != DESC_SOCKET &&
|
|
desc->type != DESC_LISTEN_SOCKET &&
|
|
desc->type != DESC_CONNECTION_SOCKET)
|
|
continue;
|
|
|
|
int revents = desc->events & desc->revents;
|
|
if (revents) {
|
|
polled[num_polled].fd = (SOCKET) j;
|
|
polled[num_polled].events = desc->events;
|
|
polled[num_polled].revents = revents;
|
|
num_polled++;
|
|
}
|
|
}
|
|
|
|
switch (current_process->type) {
|
|
case PROCESS_TYPE_METADATA_SERVER:
|
|
num_polled = metadata_server_step(¤t_process->metadata_server, contexts, polled, num_polled);
|
|
break;
|
|
case PROCESS_TYPE_CHUNK_SERVER:
|
|
num_polled = chunk_server_step(¤t_process->chunk_server, contexts, polled, num_polled);
|
|
break;
|
|
case PROCESS_TYPE_CLIENT:
|
|
{
|
|
int timeout = -1;
|
|
num_polled = simulation_client_step(¤t_process->simulation_client, contexts, polled, num_polled, &timeout);
|
|
if (timeout < 0) {
|
|
current_process->wakeup_time = INVALID_TIME;
|
|
} else {
|
|
current_process->wakeup_time = current_time + timeout;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
if (num_polled < 0) {
|
|
// TODO
|
|
}
|
|
|
|
process_poll_array(current_process, contexts, polled, num_polled);
|
|
|
|
current_process = NULL;
|
|
}
|
|
|
|
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->connection_state = CONNECTION_FAILED;
|
|
break;
|
|
}
|
|
|
|
DescriptorHandle self_handle = { processes[i], j, desc->generation };
|
|
Descriptor *peer = handle_to_desc(peer_handle);
|
|
if (!accept_queue_push(&peer->accept_queue, self_handle)) {
|
|
desc->connection_state = CONNECTION_FAILED;
|
|
break;
|
|
}
|
|
|
|
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->connection_state = CONNECTION_FAILED;
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
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++;
|
|
|
|
Descriptor *desc = ¤t_process->desc[idx];
|
|
desc->type = DESC_SOCKET;
|
|
desc->events = 0;
|
|
desc->revents = 0;
|
|
desc->context = NULL;
|
|
desc->address = (DescriptorAddress) { .type=DESC_ADDR_VOID };
|
|
|
|
current_process->num_desc++;
|
|
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)) {
|
|
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++;
|
|
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->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++;
|
|
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*)optval = 0; // No error
|
|
*optlen = sizeof(int);
|
|
return 0;
|
|
}
|
|
|
|
SET_SOCKET_ERROR(SOCKET_ERROR_PROTOOPT);
|
|
return -1;
|
|
}
|
|
|
|
int mock_setsockopt(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;
|
|
}
|
|
|
|
// Most socket options are ignored in simulation
|
|
// Just validate the call but don't actually apply settings
|
|
(void)optval;
|
|
(void)optlen;
|
|
(void)optname;
|
|
|
|
return 0; // Success (no-op)
|
|
}
|
|
|
|
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) {
|
|
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) {
|
|
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 NATIVE_HANDLE
|
|
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++;
|
|
|
|
Descriptor *desc = ¤t_process->desc[idx];
|
|
|
|
desc->type = DESC_FILE;
|
|
desc->real_fd = handle;
|
|
|
|
current_process->num_desc++;
|
|
return idx;
|
|
}
|
|
|
|
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 void close_desc(Descriptor *desc)
|
|
{
|
|
switch (desc->type) {
|
|
|
|
case DESC_EMPTY:
|
|
// 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:
|
|
data_queue_free(&desc->output_data);
|
|
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:
|
|
// TODO
|
|
break;
|
|
|
|
case CONNECTION_FAILED:
|
|
// TODO
|
|
break;
|
|
}
|
|
// TODO
|
|
break;
|
|
}
|
|
desc->type = DESC_EMPTY;
|
|
desc->generation++;
|
|
}
|
|
|
|
#ifdef _WIN32
|
|
|
|
int mock_closesocket(SOCKET fd)
|
|
{
|
|
if (fd == INVALID_SOCKET) {
|
|
// TODO
|
|
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
|
|
return -1;
|
|
}
|
|
|
|
close_desc(desc);
|
|
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;
|
|
return wrap_native_file_into_desc(handle);
|
|
}
|
|
|
|
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);
|
|
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 simulated time to generate deterministic performance counter
|
|
// Frequency is 10 MHz (10,000,000 counts per second)
|
|
const LONGLONG frequency = 10000000LL;
|
|
|
|
LONGLONG count = (LONGLONG)simulated_time.tv_sec * frequency;
|
|
count += ((LONGLONG)simulated_time.tv_nsec * 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;
|
|
}
|
|
|
|
#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;
|
|
}
|
|
|
|
// Return simulated time for deterministic behavior
|
|
*tp = simulated_time;
|
|
return 0;
|
|
}
|
|
|
|
int mock_open(char *path, int flags, int mode)
|
|
{
|
|
int fd = open(path, flags, mode);
|
|
if (fd < 0) return -1;
|
|
|
|
return wrap_native_file_into_desc(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);
|
|
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)
|
|
{
|
|
return mkstemp(path);
|
|
}
|
|
|
|
char* mock_realpath(char *path, char *dst)
|
|
{
|
|
return realpath(path, dst);
|
|
}
|
|
|
|
int mock_mkdir(char *path, mode_t mode)
|
|
{
|
|
return mkdir(path, mode);
|
|
}
|
|
|
|
#endif
|