diff --git a/lab/vtpc/.gitignore b/lab/vtpc/.gitignore
index fb90d94..b91b9c0 100644
--- a/lab/vtpc/.gitignore
+++ b/lab/vtpc/.gitignore
@@ -6,3 +6,9 @@ build/
# Python
__pycache__/
+
+.vscode
+
+.idea
+
+*.bin
diff --git a/lab/vtpc/.idea/.gitignore b/lab/vtpc/.idea/.gitignore
new file mode 100644
index 0000000..13566b8
--- /dev/null
+++ b/lab/vtpc/.idea/.gitignore
@@ -0,0 +1,8 @@
+# Default ignored files
+/shelf/
+/workspace.xml
+# Editor-based HTTP Client requests
+/httpRequests/
+# Datasource local storage ignored files
+/dataSources/
+/dataSources.local.xml
diff --git a/lab/vtpc/.idea/codeStyles/Project.xml b/lab/vtpc/.idea/codeStyles/Project.xml
new file mode 100644
index 0000000..81469bd
--- /dev/null
+++ b/lab/vtpc/.idea/codeStyles/Project.xml
@@ -0,0 +1,105 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/lab/vtpc/.idea/codeStyles/codeStyleConfig.xml b/lab/vtpc/.idea/codeStyles/codeStyleConfig.xml
new file mode 100644
index 0000000..79ee123
--- /dev/null
+++ b/lab/vtpc/.idea/codeStyles/codeStyleConfig.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/lab/vtpc/.idea/editor.xml b/lab/vtpc/.idea/editor.xml
new file mode 100644
index 0000000..008fac2
--- /dev/null
+++ b/lab/vtpc/.idea/editor.xml
@@ -0,0 +1,101 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/lab/vtpc/.idea/misc.xml b/lab/vtpc/.idea/misc.xml
new file mode 100644
index 0000000..0b76fe5
--- /dev/null
+++ b/lab/vtpc/.idea/misc.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/lab/vtpc/.idea/modules.xml b/lab/vtpc/.idea/modules.xml
new file mode 100644
index 0000000..7b7448b
--- /dev/null
+++ b/lab/vtpc/.idea/modules.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/lab/vtpc/.idea/vcs.xml b/lab/vtpc/.idea/vcs.xml
new file mode 100644
index 0000000..b2bdec2
--- /dev/null
+++ b/lab/vtpc/.idea/vcs.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/lab/vtpc/.idea/vtpc.iml b/lab/vtpc/.idea/vtpc.iml
new file mode 100644
index 0000000..f08604b
--- /dev/null
+++ b/lab/vtpc/.idea/vtpc.iml
@@ -0,0 +1,2 @@
+
+
\ No newline at end of file
diff --git a/lab/vtpc/lib/CMakeLists.txt b/lab/vtpc/lib/CMakeLists.txt
index e904b37..33730ae 100644
--- a/lab/vtpc/lib/CMakeLists.txt
+++ b/lab/vtpc/lib/CMakeLists.txt
@@ -9,3 +9,13 @@ target_include_directories(
PUBLIC
.
)
+
+add_executable(
+ io_load_vtpc
+ io_load.c
+ io_load_args.c
+ io_load_vtpc.c
+)
+
+target_include_directories(io_load_vtpc PRIVATE .)
+target_link_libraries(io_load_vtpc PRIVATE vtpc)
diff --git a/lab/vtpc/lib/io_load.c b/lab/vtpc/lib/io_load.c
new file mode 100644
index 0000000..637807e
--- /dev/null
+++ b/lab/vtpc/lib/io_load.c
@@ -0,0 +1,16 @@
+#include "io_load.h"
+
+#include
+
+int main(int argc, char* argv[]) {
+ options_t opts;
+ if (parse_args(argc, argv, &opts) != 0) {
+ print_usage(argv[0]);
+ return 1;
+ }
+
+ if (run_io_workload(&opts) != 0)
+ return 1;
+
+ return 0;
+}
diff --git a/lab/vtpc/lib/io_load.h b/lab/vtpc/lib/io_load.h
new file mode 100644
index 0000000..537690a
--- /dev/null
+++ b/lab/vtpc/lib/io_load.h
@@ -0,0 +1,32 @@
+#pragma once
+
+#include
+#include
+#include
+
+typedef enum {
+ MODE_READ,
+ MODE_WRITE
+} io_mode_t;
+
+typedef enum {
+ ORDER_SEQUENCE,
+ ORDER_RANDOM
+} io_order_t;
+
+typedef struct {
+ io_mode_t mode;
+ size_t block_size;
+ size_t block_count;
+ const char* path;
+ int repeat;
+ bool use_direct;
+ io_order_t order;
+ bool range_set;
+ off_t range_start;
+ off_t range_end;
+} options_t;
+
+void print_usage(const char* prog);
+int parse_args(int argc, char* argv[], options_t* opts);
+int run_io_workload(const options_t* opts);
diff --git a/lab/vtpc/lib/io_load_args.c b/lab/vtpc/lib/io_load_args.c
new file mode 100644
index 0000000..17ce494
--- /dev/null
+++ b/lab/vtpc/lib/io_load_args.c
@@ -0,0 +1,125 @@
+#include "io_load.h"
+
+#include
+#include
+#include
+#include
+
+static bool parse_range(const char* text, off_t* start, off_t* end, bool* range_set) {
+ const char* dash = strchr(text, '-');
+ if (dash == NULL)
+ return false;
+
+ char left[32];
+ char right[32];
+ size_t left_len = (size_t)(dash - text);
+ size_t right_len = strlen(dash + 1);
+ if (left_len == 0 || right_len == 0 ||
+ left_len >= sizeof(left) || right_len >= sizeof(right))
+ return false;
+
+ memcpy(left, text, left_len);
+ left[left_len] = '\0';
+ memcpy(right, dash + 1, right_len + 1);
+
+ errno = 0;
+ long long start_val = strtoll(left, NULL, 10);
+ long long end_val = strtoll(right, NULL, 10);
+ if (errno != 0 || start_val < 0 || end_val < 0)
+ return false;
+
+ *start = (off_t)start_val;
+ *end = (off_t)end_val;
+ *range_set = !(start_val == 0 && end_val == 0);
+ return true;
+}
+
+void print_usage(const char* prog) {
+ fprintf(stderr,
+ "Usage: %s --rw --block_size --block_count \n"
+ " --file [--range start-end] [--direct on|off]\n"
+ " [--type sequence|random] [--repeat N]\n",
+ prog);
+}
+
+int parse_args(int argc, char* argv[], options_t* opts) {
+ opts->mode = MODE_READ;
+ opts->block_size = 4096;
+ opts->block_count = 1024;
+ opts->path = NULL;
+ opts->repeat = 1;
+ opts->use_direct = false;
+ opts->order = ORDER_SEQUENCE;
+ opts->range_set = false;
+ opts->range_start = 0;
+ opts->range_end = 0;
+
+ for (int i = 1; i < argc; ++i) {
+ if (strcmp(argv[i], "--rw") == 0 && i + 1 < argc) {
+ const char* mode = argv[++i];
+ if (strcmp(mode, "read") == 0) {
+ opts->mode = MODE_READ;
+ } else if (strcmp(mode, "write") == 0) {
+ opts->mode = MODE_WRITE;
+ } else {
+ fprintf(stderr, "Unknown --rw value: %s\n", mode);
+ return -1;
+ }
+ } else if (strcmp(argv[i], "--block_size") == 0 && i + 1 < argc) {
+ opts->block_size = (size_t)strtoull(argv[++i], NULL, 10);
+ if (opts->block_size == 0) {
+ fprintf(stderr, "block_size must be > 0\n");
+ return -1;
+ }
+ } else if (strcmp(argv[i], "--block_count") == 0 && i + 1 < argc) {
+ opts->block_count = (size_t)strtoull(argv[++i], NULL, 10);
+ if (opts->block_count == 0) {
+ fprintf(stderr, "block_count must be > 0\n");
+ return -1;
+ }
+ } else if (strcmp(argv[i], "--file") == 0 && i + 1 < argc) {
+ opts->path = argv[++i];
+ } else if (strcmp(argv[i], "--repeat") == 0 && i + 1 < argc) {
+ opts->repeat = atoi(argv[++i]);
+ if (opts->repeat <= 0) {
+ fprintf(stderr, "repeat must be > 0\n");
+ return -1;
+ }
+ } else if (strcmp(argv[i], "--direct") == 0 && i + 1 < argc) {
+ const char* val = argv[++i];
+ if (strcmp(val, "on") == 0) {
+ opts->use_direct = true;
+ } else if (strcmp(val, "off") == 0) {
+ opts->use_direct = false;
+ } else {
+ fprintf(stderr, "--direct accepts on/off\n");
+ return -1;
+ }
+ } else if (strcmp(argv[i], "--type") == 0 && i + 1 < argc) {
+ const char* val = argv[++i];
+ if (strcmp(val, "sequence") == 0) {
+ opts->order = ORDER_SEQUENCE;
+ } else if (strcmp(val, "random") == 0) {
+ opts->order = ORDER_RANDOM;
+ } else {
+ fprintf(stderr, "--type accepts sequence/random\n");
+ return -1;
+ }
+ } else if (strcmp(argv[i], "--range") == 0 && i + 1 < argc) {
+ if (!parse_range(argv[++i], &opts->range_start, &opts->range_end, &opts->range_set)) {
+ fprintf(stderr, "Invalid range format\n");
+ return -1;
+ }
+ } else {
+ fprintf(stderr, "Unknown argument: %s\n", argv[i]);
+ return -1;
+ }
+ }
+
+ if (opts->path == NULL) {
+ fprintf(stderr, "--file is required\n");
+ return -1;
+ }
+
+ return 0;
+}
diff --git a/lab/vtpc/lib/io_load_runner.c b/lab/vtpc/lib/io_load_runner.c
new file mode 100644
index 0000000..9d2a07a
--- /dev/null
+++ b/lab/vtpc/lib/io_load_runner.c
@@ -0,0 +1,176 @@
+#include "io_load.h"
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+static void* allocate_buffer(size_t size) {
+ void* buffer = malloc(size);
+ if (buffer == NULL)
+ fprintf(stderr, "malloc failed\n");
+ return buffer;
+}
+
+static double seconds_between(const struct timespec* start,
+ const struct timespec* end) {
+ time_t sec = end->tv_sec - start->tv_sec;
+ long nsec = end->tv_nsec - start->tv_nsec;
+ if (nsec < 0) {
+ sec -= 1;
+ nsec += 1000000000L;
+ }
+ return (double)sec + (double)nsec / 1e9;
+}
+
+static int check_range(const options_t* opts, const struct stat* st) {
+ off_t range_start = opts->range_start;
+ off_t range_end = opts->range_end;
+
+ if (!opts->range_set) {
+ range_start = 0;
+ if (opts->mode == MODE_READ) {
+ range_end = st->st_size;
+ } else {
+ range_end = range_start + (off_t)(opts->block_size * opts->block_count);
+ }
+ }
+
+ if (opts->mode == MODE_READ && range_end > st->st_size) {
+ fprintf(stderr, "Range exceeds file size for read mode\n");
+ return -1;
+ }
+
+ if (range_end <= range_start) {
+ fprintf(stderr, "Invalid range\n");
+ return -1;
+ }
+
+ off_t span = range_end - range_start;
+ if (opts->order == ORDER_SEQUENCE &&
+ span < (off_t)(opts->block_size * opts->block_count)) {
+ fprintf(stderr, "Range is too small for sequential access\n");
+ return -1;
+ }
+
+ return 0;
+}
+
+static off_t pick_offset(const options_t* opts, off_t range_start, size_t index) {
+ if (opts->order == ORDER_RANDOM) {
+ off_t range_bytes = opts->range_end - opts->range_start;
+ if (range_bytes < (off_t)opts->block_size)
+ return opts->range_start;
+
+ off_t slots = range_bytes / (off_t)opts->block_size;
+ if (slots <= 0)
+ slots = 1;
+ off_t slot = (off_t)(rand() % (int)slots);
+ return opts->range_start + slot * (off_t)opts->block_size;
+ }
+
+ return range_start + (off_t)(index * opts->block_size);
+}
+
+int run_io_workload(const options_t* opts) {
+ int flags = (opts->mode == MODE_READ) ? O_RDONLY : (O_WRONLY | O_CREAT);
+ int fd = open(opts->path, flags, 0666);
+ if (fd < 0) {
+ fprintf(stderr, "Failed to open %s: %s\n", opts->path, strerror(errno));
+ return 1;
+ }
+
+#if defined(F_NOCACHE)
+ if (opts->use_direct && fcntl(fd, F_NOCACHE, 1) != 0)
+ fprintf(stderr, "fcntl failed: %s\n", strerror(errno));
+#else
+ if (opts->use_direct)
+ fprintf(stderr, "Warning: F_NOCACHE not supported on this platform\n");
+#endif
+
+ struct stat st;
+ if (fstat(fd, &st) != 0) {
+ fprintf(stderr, "fstat failed: %s\n", strerror(errno));
+ close(fd);
+ return 1;
+ }
+
+ options_t local_opts = *opts;
+ if (!local_opts.range_set) {
+ local_opts.range_start = 0;
+ if (local_opts.mode == MODE_READ)
+ local_opts.range_end = st.st_size;
+ else
+ local_opts.range_end = local_opts.range_start + (off_t)(local_opts.block_size * local_opts.block_count);
+ }
+
+ if (check_range(&local_opts, &st) != 0) {
+ close(fd);
+ return 1;
+ }
+
+ void* buffer = allocate_buffer(local_opts.block_size);
+ if (buffer == NULL) {
+ close(fd);
+ return 1;
+ }
+
+ if (local_opts.mode == MODE_WRITE) {
+ for (size_t i = 0; i < local_opts.block_size; ++i)
+ ((unsigned char*)buffer)[i] = (unsigned char)('A' + (i % 26));
+ }
+
+ srand(0);
+
+ struct timespec total_start = {0}, total_end = {0};
+ clock_gettime(CLOCK_MONOTONIC, &total_start);
+
+ for (int r = 0; r < local_opts.repeat; ++r) {
+ struct timespec start, end;
+ clock_gettime(CLOCK_MONOTONIC, &start);
+
+ for (size_t i = 0; i < local_opts.block_count; ++i) {
+ off_t offset = pick_offset(&local_opts, local_opts.range_start, i);
+ ssize_t done;
+ if (local_opts.mode == MODE_READ) {
+ done = pread(fd, buffer, local_opts.block_size, offset);
+ } else {
+ done = pwrite(fd, buffer, local_opts.block_size, offset);
+ }
+ if (done < 0) {
+ fprintf(stderr, "I/O error at block %zu: %s\n", i, strerror(errno));
+ free(buffer);
+ close(fd);
+ return 1;
+ }
+ if ((size_t)done != local_opts.block_size) {
+ fprintf(stderr, "Short transfer at block %zu\n", i);
+ free(buffer);
+ close(fd);
+ return 1;
+ }
+ }
+
+ clock_gettime(CLOCK_MONOTONIC, &end);
+ time_t sec = end.tv_sec - start.tv_sec;
+ long nsec = end.tv_nsec - start.tv_nsec;
+ if (nsec < 0) {
+ sec -= 1;
+ nsec += 1000000000L;
+ }
+ double elapsed = (double)sec + (double)nsec / 1e9;
+ printf("Iteration %d: blocks=%zu size=%zu bytes time=%.6f s\n", r + 1, local_opts.block_count, local_opts.block_size, elapsed);
+ }
+
+ clock_gettime(CLOCK_MONOTONIC, &total_end);
+ printf("Total time: %.6f s\n", seconds_between(&total_start, &total_end));
+
+ free(buffer);
+ close(fd);
+ return 0;
+}
diff --git a/lab/vtpc/lib/io_load_vtpc.c b/lab/vtpc/lib/io_load_vtpc.c
new file mode 100644
index 0000000..a24a8b5
--- /dev/null
+++ b/lab/vtpc/lib/io_load_vtpc.c
@@ -0,0 +1,194 @@
+#include "io_load.h"
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include "vtpc.h"
+
+static void* allocate_buffer(size_t size) {
+ void* buffer = malloc(size);
+ if (buffer == NULL)
+ fprintf(stderr, "malloc failed\n");
+ return buffer;
+}
+
+static double seconds_between(const struct timespec* start,
+ const struct timespec* end) {
+ time_t sec = end->tv_sec - start->tv_sec;
+ long nsec = end->tv_nsec - start->tv_nsec;
+ if (nsec < 0) {
+ sec -= 1;
+ nsec += 1000000000L;
+ }
+ return (double)sec + (double)nsec / 1e9;
+}
+
+static int check_range(const options_t* opts, const struct stat* st) {
+ off_t range_start = opts->range_start;
+ off_t range_end = opts->range_end;
+
+ if (!opts->range_set) {
+ range_start = 0;
+ if (opts->mode == MODE_READ) {
+ range_end = st->st_size;
+ } else {
+ range_end = range_start + (off_t)(opts->block_size * opts->block_count);
+ }
+ }
+
+ if (opts->mode == MODE_READ && range_end > st->st_size) {
+ fprintf(stderr, "Range exceeds file size for read mode\n");
+ return -1;
+ }
+
+ if (range_end <= range_start) {
+ fprintf(stderr, "Invalid range\n");
+ return -1;
+ }
+
+ off_t span = range_end - range_start;
+ if (opts->order == ORDER_SEQUENCE &&
+ span < (off_t)(opts->block_size * opts->block_count)) {
+ fprintf(stderr, "Range is too small for sequential access\n");
+ return -1;
+ }
+
+ return 0;
+}
+
+static off_t pick_offset(const options_t* opts, off_t range_start, size_t index) {
+ if (opts->order == ORDER_RANDOM) {
+ off_t range_bytes = opts->range_end - opts->range_start;
+ if (range_bytes < (off_t)opts->block_size)
+ return opts->range_start;
+
+ off_t slots = range_bytes / (off_t)opts->block_size;
+ if (slots <= 0)
+ slots = 1;
+ off_t slot = (off_t)(rand() % (int)slots);
+ return opts->range_start + slot * (off_t)opts->block_size;
+ }
+
+ return range_start + (off_t)(index * opts->block_size);
+}
+
+static int vtpc_seek(int fd, off_t offset) {
+ if (vtpc_lseek(fd, offset, SEEK_SET) == (off_t)-1) {
+ fprintf(stderr, "vtpc_lseek failed: %s\n", strerror(errno));
+ return -1;
+ }
+ return 0;
+}
+
+int run_io_workload(const options_t* opts) {
+ struct stat st;
+ memset(&st, 0, sizeof(st));
+ if (opts->mode == MODE_READ) {
+ if (stat(opts->path, &st) != 0) {
+ fprintf(stderr, "stat failed: %s\n", strerror(errno));
+ return 1;
+ }
+ }
+
+ int flags = (opts->mode == MODE_READ) ? O_RDONLY : (O_WRONLY | O_CREAT);
+ int fd = vtpc_open(opts->path, flags, 0666);
+ if (fd < 0) {
+ fprintf(stderr, "Failed to open %s via vtpc: %s\n", opts->path, strerror(errno));
+ return 1;
+ }
+
+ if (opts->use_direct) {
+ fprintf(stderr, "Note: --direct is handled inside vtpc_open (O_DIRECT/F_NOCACHE best-effort)\n");
+ }
+
+ options_t local_opts = *opts;
+ if (!local_opts.range_set) {
+ local_opts.range_start = 0;
+ if (local_opts.mode == MODE_READ)
+ local_opts.range_end = st.st_size;
+ else
+ local_opts.range_end = local_opts.range_start + (off_t)(local_opts.block_size * local_opts.block_count);
+ }
+
+ if (check_range(&local_opts, &st) != 0) {
+ vtpc_close(fd);
+ return 1;
+ }
+
+ void* buffer = allocate_buffer(local_opts.block_size);
+ if (buffer == NULL) {
+ vtpc_close(fd);
+ return 1;
+ }
+
+ if (local_opts.mode == MODE_WRITE) {
+ for (size_t i = 0; i < local_opts.block_size; ++i)
+ ((unsigned char*)buffer)[i] = (unsigned char)('A' + (i % 26));
+ }
+
+ srand(0);
+
+ struct timespec total_start = {0}, total_end = {0};
+ clock_gettime(CLOCK_MONOTONIC, &total_start);
+
+ for (int r = 0; r < local_opts.repeat; ++r) {
+ struct timespec start, end;
+ clock_gettime(CLOCK_MONOTONIC, &start);
+
+ for (size_t i = 0; i < local_opts.block_count; ++i) {
+ off_t offset = pick_offset(&local_opts, local_opts.range_start, i);
+ if (vtpc_seek(fd, offset) != 0) {
+ free(buffer);
+ vtpc_close(fd);
+ return 1;
+ }
+
+ ssize_t done;
+ if (local_opts.mode == MODE_READ) {
+ done = vtpc_read(fd, buffer, local_opts.block_size);
+ } else {
+ done = vtpc_write(fd, buffer, local_opts.block_size);
+ }
+
+ if (done < 0) {
+ fprintf(stderr, "I/O error at block %zu: %s\n", i, strerror(errno));
+ free(buffer);
+ vtpc_close(fd);
+ return 1;
+ }
+ if ((size_t)done != local_opts.block_size) {
+ fprintf(stderr, "Short transfer at block %zu\n", i);
+ free(buffer);
+ vtpc_close(fd);
+ return 1;
+ }
+ }
+
+ clock_gettime(CLOCK_MONOTONIC, &end);
+ time_t sec = end.tv_sec - start.tv_sec;
+ long nsec = end.tv_nsec - start.tv_nsec;
+ if (nsec < 0) {
+ sec -= 1;
+ nsec += 1000000000L;
+ }
+ double elapsed = (double)sec + (double)nsec / 1e9;
+ printf("Iteration %d: blocks=%zu size=%zu bytes time=%.6f s\n", r + 1, local_opts.block_count, local_opts.block_size, elapsed);
+ }
+
+ clock_gettime(CLOCK_MONOTONIC, &total_end);
+ printf("Total time (vtpc): %.6f s\n", seconds_between(&total_start, &total_end));
+
+ if (vtpc_fsync(fd) != 0)
+ fprintf(stderr, "vtpc_fsync failed: %s\n", strerror(errno));
+
+ free(buffer);
+ vtpc_close(fd);
+ return 0;
+}
diff --git a/lab/vtpc/lib/vtpc.c b/lab/vtpc/lib/vtpc.c
index 173ab1b..cf1838a 100644
--- a/lab/vtpc/lib/vtpc.c
+++ b/lab/vtpc/lib/vtpc.c
@@ -1,30 +1,453 @@
#include "vtpc.h"
+#include
#include
-#include
-#include
+#include
+#include
+#include
+#include
#include
+#define VTPC_MAX_FILES 128
+#define VTPC_PAGE_CAPACITY 64
+
+struct vtpc_page {
+ off_t base;
+ size_t valid;
+ int dirty;
+ int in_use;
+ uint64_t last_access;
+ char* data;
+};
+
+struct vtpc_file {
+ int fd;
+ int can_read;
+ int can_write;
+ int direct_io;
+ off_t position;
+ off_t file_size;
+ size_t page_size;
+ size_t capacity;
+ uint64_t access_clock;
+ struct vtpc_page* pages;
+};
+
+static struct vtpc_file* g_files[VTPC_MAX_FILES];
+
+static size_t vtpc_get_page_size(void) {
+ long page = sysconf(_SC_PAGESIZE);
+ if (page <= 0)
+ page = 4096;
+ return (size_t)page;
+}
+
+static off_t vtpc_align_down(off_t value, size_t align) {
+ off_t mod = value % (off_t)align;
+ if (mod < 0)
+ mod += (off_t)align;
+ return value - mod;
+}
+
+static size_t vtpc_min_size(size_t a, size_t b) {
+ return (a < b) ? a : b;
+}
+
+static size_t vtpc_max_size(size_t a, size_t b) {
+ return (a > b) ? a : b;
+}
+
+static struct vtpc_file* vtpc_lookup(int handle) {
+ if (handle < 0 || handle >= VTPC_MAX_FILES)
+ return NULL;
+ return g_files[handle];
+}
+
+static int vtpc_store(struct vtpc_file* file) {
+ for (int i = 0; i < VTPC_MAX_FILES; ++i) {
+ if (g_files[i] == NULL) {
+ g_files[i] = file;
+ return i;
+ }
+ }
+ errno = EMFILE;
+ return -1;
+}
+
+static void vtpc_drop(int fd) {
+ if (fd < 0 || fd >= VTPC_MAX_FILES)
+ return;
+ g_files[fd] = NULL;
+}
+
+static int vtpc_open_raw(const char* path, int mode, int access, int* direct_io) {
+ int fd = -1;
+ *direct_io = 0;
+
+#ifdef O_DIRECT
+ fd = open(path, mode | O_DIRECT, access);
+ if (fd >= 0) {
+ *direct_io = 1;
+ return fd;
+ }
+ if (errno != EINVAL && errno != EOPNOTSUPP)
+ return fd;
+#endif
+
+ fd = open(path, mode, access);
+ if (fd < 0)
+ return fd;
+
+#ifdef F_NOCACHE
+ if (fcntl(fd, F_NOCACHE, 1) == 0)
+ *direct_io = 1;
+#endif
+
+ return fd;
+}
+
+static int vtpc_alloc_pages(struct vtpc_file* file) {
+ file->pages = calloc(file->capacity, sizeof(*file->pages));
+ if (file->pages == NULL)
+ return -1;
+
+ for (size_t i = 0; i < file->capacity; ++i) {
+ if (posix_memalign((void**)&file->pages[i].data, file->page_size, file->page_size) != 0) {
+ for (size_t j = 0; j < i; ++j)
+ free(file->pages[j].data);
+ free(file->pages);
+ file->pages = NULL;
+ return -1;
+ }
+ memset(file->pages[i].data, 0, file->page_size);
+ }
+ return 0;
+}
+
+static struct vtpc_page* vtpc_find_page(struct vtpc_file* file, off_t base) {
+ for (size_t i = 0; i < file->capacity; ++i) {
+ if (file->pages[i].in_use && file->pages[i].base == base)
+ return &file->pages[i];
+ }
+ return NULL;
+}
+
+static int vtpc_flush_page(struct vtpc_file* file, struct vtpc_page* page) {
+ if (!page->in_use || !page->dirty)
+ return 0;
+
+ if (file->file_size <= page->base) {
+ page->dirty = 0;
+ return 0;
+ }
+
+ size_t len = (size_t)vtpc_min_size((size_t)(file->file_size - page->base), file->page_size);
+ if (len == 0) {
+ page->dirty = 0;
+ return 0;
+ }
+
+ size_t write_len = len;
+ if (file->direct_io)
+ write_len = file->page_size;
+
+ ssize_t written = pwrite(file->fd, page->data, write_len, page->base);
+ if (written < 0 || (size_t)written != write_len)
+ return -1;
+
+
+ if (write_len > len && ftruncate(file->fd, file->file_size) != 0)
+ return -1;
+
+ page->dirty = 0;
+ return 0;
+}
+
+static struct vtpc_page* vtpc_pick_victim(struct vtpc_file* file) {
+ struct vtpc_page* victim = NULL;
+ for (size_t i = 0; i < file->capacity; ++i) {
+ if (!file->pages[i].in_use)
+ continue;
+ if (victim == NULL || file->pages[i].last_access > victim->last_access)
+ victim = &file->pages[i];
+ }
+ return victim;
+}
+
+static struct vtpc_page* vtpc_prepare_page(struct vtpc_file* file, off_t base) {
+ struct vtpc_page* page = vtpc_find_page(file, base);
+ if (page != NULL)
+ return page;
+
+ for (size_t i = 0; i < file->capacity; ++i) {
+ if (!file->pages[i].in_use) {
+ page = &file->pages[i];
+ break;
+ }
+ }
+
+ if (page == NULL) {
+ page = vtpc_pick_victim(file);
+ if (page == NULL) {
+ errno = ENOMEM;
+ return NULL;
+ }
+ if (vtpc_flush_page(file, page) != 0)
+ return NULL;
+ }
+
+ page->in_use = 1;
+ page->base = base;
+ page->valid = 0;
+ page->dirty = 0;
+ page->last_access = 0;
+
+ ssize_t done = pread(file->fd, page->data, file->page_size, page->base);
+ if (done < 0) {
+ page->in_use = 0;
+ return NULL;
+ }
+#if defined(POSIX_FADV_DONTNEED)
+ (void)posix_fadvise(file->fd, page->base, (off_t)file->page_size, POSIX_FADV_DONTNEED);
+#endif
+
+ page->valid = (size_t)done;
+ if (page->valid < file->page_size)
+ memset(page->data + page->valid, 0, file->page_size - page->valid);
+
+ return page;
+}
+
+static int vtpc_flush_all(struct vtpc_file* file) {
+ int has_dirty = 0;
+ for (size_t i = 0; i < file->capacity; ++i) {
+ if (file->pages[i].in_use && file->pages[i].dirty) {
+ has_dirty = 1;
+ if (vtpc_flush_page(file, &file->pages[i]) != 0)
+ return -1;
+ }
+ }
+
+ if (has_dirty && file->can_write) {
+ if (ftruncate(file->fd, file->file_size) != 0)
+ return -1;
+ }
+
+ if (fsync(file->fd) != 0)
+ return -1;
+
+ return 0;
+}
+
int vtpc_open(const char* path, int mode, int access) {
- return open(path, mode, access);
+ size_t page_size = vtpc_get_page_size();
+
+ struct vtpc_file* file = calloc(1, sizeof(*file));
+ if (file == NULL)
+ return -1;
+
+ file->page_size = page_size;
+ file->capacity = VTPC_PAGE_CAPACITY;
+
+ int accmode = mode & O_ACCMODE;
+
+ int direct_io = 0;
+ int fd = -1;
+
+ if (accmode == O_WRONLY) {
+ int rw_mode = (mode & ~O_ACCMODE) | O_RDWR;
+ fd = vtpc_open_raw(path, rw_mode, access, &direct_io);
+ }
+
+ if (fd < 0)
+ fd = vtpc_open_raw(path, mode, access, &direct_io);
+
+ if (fd < 0) {
+ free(file);
+ return -1;
+ }
+
+ struct stat st;
+ if (fstat(fd, &st) != 0) {
+ close(fd);
+ free(file);
+ return -1;
+ }
+
+ file->fd = fd;
+ file->file_size = st.st_size;
+ file->position = 0;
+ file->direct_io = direct_io;
+ file->access_clock = 0;
+
+ file->can_read = (accmode == O_RDONLY || accmode == O_RDWR);
+ file->can_write = (accmode == O_WRONLY || accmode == O_RDWR);
+
+ if (vtpc_alloc_pages(file) != 0) {
+ close(fd);
+ free(file);
+ errno = ENOMEM;
+ return -1;
+ }
+
+ int handle = vtpc_store(file);
+ if (handle < 0) {
+ for (size_t i = 0; i < file->capacity; ++i)
+ free(file->pages[i].data);
+ free(file->pages);
+ close(fd);
+ free(file);
+ return -1;
+ }
+
+ return handle;
}
int vtpc_close(int fd) {
- return close(fd);
+ struct vtpc_file* file = vtpc_lookup(fd);
+ if (file == NULL) {
+ errno = EBADF;
+ return -1;
+ }
+
+ int result = 0;
+ if (vtpc_flush_all(file) != 0)
+ result = -1;
+
+ if (close(file->fd) != 0)
+ result = -1;
+
+ for (size_t i = 0; i < file->capacity; ++i)
+ free(file->pages[i].data);
+ free(file->pages);
+ free(file);
+ vtpc_drop(fd);
+ return result;
}
ssize_t vtpc_read(int fd, void* buf, size_t count) {
- return read(fd, buf, count);
+ struct vtpc_file* file = vtpc_lookup(fd);
+ if (file == NULL) {
+ errno = EBADF;
+ return -1;
+ }
+ if (!file->can_read) {
+ errno = EBADF;
+ return -1;
+ }
+ if (count == 0)
+ return 0;
+
+ size_t total = 0;
+ while (total < count) {
+ if (file->position >= file->file_size)
+ break;
+
+ off_t base = vtpc_align_down(file->position, file->page_size);
+ size_t page_off = (size_t)(file->position - base);
+ size_t max_in_page = file->page_size - page_off;
+
+ size_t remaining = count - total;
+ size_t available = vtpc_min_size((size_t)(file->file_size - file->position), max_in_page);
+ size_t chunk = vtpc_min_size(remaining, available);
+
+ if (chunk == 0)
+ break;
+
+ struct vtpc_page* page = vtpc_prepare_page(file, base);
+ if (page == NULL)
+ return -1;
+
+ page->last_access = ++file->access_clock;
+
+ if (page->valid < page_off + chunk)
+ chunk = (page->valid > page_off) ? (page->valid - page_off) : 0;
+
+ if (chunk == 0)
+ break;
+
+ memcpy((char*)buf + total, page->data + page_off, chunk);
+ total += chunk;
+ file->position += (off_t)chunk;
+ }
+
+ return (ssize_t)total;
}
ssize_t vtpc_write(int fd, const void* buf, size_t count) {
- return write(fd, buf, count);
+ struct vtpc_file* file = vtpc_lookup(fd);
+ if (file == NULL) {
+ errno = EBADF;
+ return -1;
+ }
+ if (!file->can_write) {
+ errno = EBADF;
+ return -1;
+ }
+ if (count == 0)
+ return 0;
+
+ size_t total = 0;
+ while (total < count) {
+ off_t base = vtpc_align_down(file->position, file->page_size);
+ size_t page_off = (size_t)(file->position - base);
+ size_t remaining = count - total;
+ size_t chunk = vtpc_min_size(remaining, file->page_size - page_off);
+
+ struct vtpc_page* page = vtpc_prepare_page(file, base);
+ if (page == NULL)
+ return -1;
+
+ memcpy(page->data + page_off, (const char*)buf + total, chunk);
+ page->valid = vtpc_min_size(file->page_size, vtpc_max_size(page->valid, page_off + chunk));
+ page->dirty = 1;
+ page->last_access = ++file->access_clock;
+
+ total += chunk;
+ file->position += (off_t)chunk;
+
+ off_t new_end = base + (off_t)vtpc_max_size(page->valid, page_off + chunk);
+ if (new_end > file->file_size)
+ file->file_size = new_end;
+ }
+
+ return (ssize_t)total;
}
off_t vtpc_lseek(int fd, off_t offset, int whence) {
- return lseek(fd, offset, whence);
+ struct vtpc_file* file = vtpc_lookup(fd);
+ if (file == NULL) {
+ errno = EBADF;
+ return (off_t)-1;
+ }
+
+ off_t base = 0;
+ if (whence == SEEK_SET) {
+ base = offset;
+ } else if (whence == SEEK_CUR) {
+ base = file->position + offset;
+ } else if (whence == SEEK_END) {
+ base = file->file_size + offset;
+ } else {
+ errno = EINVAL;
+ return (off_t)-1;
+ }
+
+ if (base < 0) {
+ errno = EINVAL;
+ return (off_t)-1;
+ }
+
+ file->position = base;
+ return base;
}
int vtpc_fsync(int fd) {
- return fsync(fd);
+ struct vtpc_file* file = vtpc_lookup(fd);
+ if (file == NULL) {
+ errno = EBADF;
+ return -1;
+ }
+
+ return vtpc_flush_all(file);
}