#include #include #include #include #include #include // For quicksort and boyer-moore #include #include #include #include #include #include #include #include #define EYNFS_BLOCK_SIZE 512 // For now, fixed block size // Optional integration point: when the interactive shell is linked in, it // exposes the current physical drive. Keep this weak so the driver can still // link in non-shell contexts. __attribute__((weak)) uint8_t get_current_physical_drive(void); static uint8_t eynfs_default_drive(void) { // If the weak symbol is not provided by the final link, its address is 0. if (&get_current_physical_drive) return get_current_physical_drive(); return 0; } // Accept an optional drive prefix of the form "N:/path" (N is decimal). // On success, returns 1 and updates *path_inout to point past the prefix. static int eynfs_parse_drive_prefix(const char** path_inout, uint8_t* out_drive) { const char* p = path_inout ? *path_inout : NULL; if (!p || !out_drive) return 0; // Parse decimal digits. uint32_t drive = 0; int ndigits = 0; while (*p >= '0' && *p <= '9') { drive = drive * 10u + (uint32_t)(*p - '0'); ndigits++; if (drive > 255u) return 0; p++; } if (ndigits == 0) return 0; if (p[0] != ':' || p[1] != '/') return 0; *out_drive = (uint8_t)drive; *path_inout = p + 1; // Keep leading '/', skip ':' return 1; } // EYNFS uses filesystem-relative block numbers on disk: // block 0 => superblock at absolute LBA EYNFS_SUPERBLOCK_LBA // block 1 => bitmap, etc. static inline uint32_t eynfs_block_to_lba(uint32_t fs_block) { return EYNFS_SUPERBLOCK_LBA + fs_block; } // Performance optimization: Block cache // Dynamically sized to avoid a fixed baseline .bss cost on low-memory systems. // Eviction is true LRU with clean-first preference to avoid unnecessary writeback. typedef struct { uint32_t block_num; uint32_t last_use; uint8_t* data; uint8_t dirty; uint8_t valid; } eynfs_cache_entry_t; static eynfs_cache_entry_t* g_block_cache = NULL; static uint8_t* g_block_cache_data = NULL; static uint32_t g_block_cache_capacity = 0; static uint32_t g_block_cache_use_clock = 0; static int g_block_cache_perma_disabled = 0; static int g_block_cache_corruption_reported = 0; static uint32_t cache_hits = 0; static uint32_t cache_misses = 0; static uint32_t eynfs_block_cache_compute_capacity(void) { // Heuristic: roughly 1 cached sector per 128 physical frames, clamped. // This keeps the cache small on the default 9MB target, but scales gently. uint32_t frames = vmm_get_total_frames(); uint32_t desired = frames / 128u; if (desired < 16u) desired = 16u; if (desired > 128u) desired = 128u; return desired; } static int eynfs_block_cache_is_enabled(void) { if (g_block_cache_perma_disabled) return 0; return g_block_cache && g_block_cache_data && g_block_cache_capacity; } static void eynfs_serial_write_cstr(const char* s) { if (!s) return; serial_write(SERIAL_COM1, s, (int)strlen(s)); } static void eynfs_serial_write_hex32(uint32 v) { static const char* hex = "0123456789ABCDEF"; char buf[10]; buf[0] = '0'; buf[1] = 'x'; for (int i = 0; i < 8; i++) { uint32 shift = (uint32)(28 - i * 4); buf[2 + i] = hex[(v >> shift) & 0xFu]; } serial_write(SERIAL_COM1, buf, (int)sizeof(buf)); } static void eynfs_serial_write_u32(uint32 v) { char buf[11]; int pos = 0; if (v == 0) { buf[pos++] = '0'; } else { char tmp[11]; int tpos = 0; while (v && tpos < (int)sizeof(tmp)) { tmp[tpos++] = (char)('0' + (v % 10u)); v /= 10u; } while (tpos-- > 0) { buf[pos++] = tmp[tpos]; } } serial_write(SERIAL_COM1, buf, pos); } static void eynfs_block_cache_disable_permanently(const char* why) { if (g_block_cache_perma_disabled) return; g_block_cache_perma_disabled = 1; // Don't attempt to free: the heap/slab may already be corrupted. // Also prevent future allocation attempts. g_block_cache = NULL; g_block_cache_data = NULL; g_block_cache_capacity = 1; if (!g_block_cache_corruption_reported) { g_block_cache_corruption_reported = 1; eynfs_serial_write_cstr("[EYNFS] block cache disabled: "); eynfs_serial_write_cstr(why ? why : "(no reason)"); eynfs_serial_write_cstr("\n"); } } static int eynfs_block_cache_validate_or_disable(const char* where) { if (!eynfs_block_cache_is_enabled()) return 0; uint32 total_frames = vmm_get_total_frames(); uint32 physmap_bytes = total_frames * PAGE_SIZE; uint32 physmap_start = KERNEL_BASE; uint32 physmap_end = physmap_start + physmap_bytes; uint32 cache_ptr = (uint32)g_block_cache; uint32 data_ptr = (uint32)g_block_cache_data; if (cache_ptr < KERNEL_BASE || cache_ptr >= physmap_end) { if (!g_block_cache_corruption_reported) { eynfs_serial_write_cstr("[EYNFS] cache corruption at "); eynfs_serial_write_cstr(where ? where : "?"); eynfs_serial_write_cstr(": g_block_cache="); eynfs_serial_write_hex32(cache_ptr); eynfs_serial_write_cstr(" physmap_end="); eynfs_serial_write_hex32(physmap_end); eynfs_serial_write_cstr("\n"); } eynfs_block_cache_disable_permanently("corrupt g_block_cache"); return 0; } if (data_ptr < KERNEL_BASE || data_ptr >= physmap_end) { if (!g_block_cache_corruption_reported) { eynfs_serial_write_cstr("[EYNFS] cache corruption at "); eynfs_serial_write_cstr(where ? where : "?"); eynfs_serial_write_cstr(": g_block_cache_data="); eynfs_serial_write_hex32(data_ptr); eynfs_serial_write_cstr(" physmap_end="); eynfs_serial_write_hex32(physmap_end); eynfs_serial_write_cstr("\n"); } eynfs_block_cache_disable_permanently("corrupt g_block_cache_data"); return 0; } if (g_block_cache_capacity < 1u || g_block_cache_capacity > 1024u) { if (!g_block_cache_corruption_reported) { eynfs_serial_write_cstr("[EYNFS] cache corruption at "); eynfs_serial_write_cstr(where ? where : "?"); eynfs_serial_write_cstr(": g_block_cache_capacity="); eynfs_serial_write_u32((uint32)g_block_cache_capacity); eynfs_serial_write_cstr("\n"); } eynfs_block_cache_disable_permanently("corrupt g_block_cache_capacity"); return 0; } // Ensure each entry's data pointer is the expected slice into g_block_cache_data. // SECURITY-INVARIANT: Cache entry pointers must stay within the kernel physmap. for (uint32_t i = 0; i < g_block_cache_capacity; i++) { uint8_t* expected = &g_block_cache_data[i * EYNFS_BLOCK_SIZE]; if (g_block_cache[i].data != expected) { if (!g_block_cache_corruption_reported) { eynfs_serial_write_cstr("[EYNFS] cache corruption at "); eynfs_serial_write_cstr(where ? where : "?"); eynfs_serial_write_cstr(": entry "); eynfs_serial_write_u32((uint32)i); eynfs_serial_write_cstr(" data="); eynfs_serial_write_hex32((uint32)g_block_cache[i].data); eynfs_serial_write_cstr(" expected="); eynfs_serial_write_hex32((uint32)expected); eynfs_serial_write_cstr("\n"); } eynfs_block_cache_disable_permanently("corrupt cache entry data pointer"); return 0; } } return 1; } static int eynfs_block_cache_alloc_if_needed(void) { if (eynfs_block_cache_is_enabled()) return 1; if (g_block_cache_perma_disabled) return 0; if (g_block_cache_capacity != 0) return 0; g_block_cache_capacity = eynfs_block_cache_compute_capacity(); g_block_cache = (eynfs_cache_entry_t*)malloc(sizeof(eynfs_cache_entry_t) * g_block_cache_capacity); g_block_cache_data = (uint8_t*)malloc(EYNFS_BLOCK_SIZE * g_block_cache_capacity); if (!g_block_cache || !g_block_cache_data) { if (g_block_cache) free(g_block_cache); if (g_block_cache_data) free(g_block_cache_data); g_block_cache = NULL; g_block_cache_data = NULL; g_block_cache_capacity = 0; return 0; } for (uint32_t i = 0; i < g_block_cache_capacity; i++) { g_block_cache[i].valid = 0; g_block_cache[i].dirty = 0; g_block_cache[i].last_use = 0; g_block_cache[i].block_num = 0; g_block_cache[i].data = &g_block_cache_data[i * EYNFS_BLOCK_SIZE]; } g_block_cache_use_clock = 1; return 1; } static int eynfs_block_cache_choose_victim_index(uint32_t* out_index, int* out_need_writeback) { if (!out_index || !out_need_writeback) return 0; if (!eynfs_block_cache_is_enabled()) return 0; // Prefer an invalid slot. Otherwise choose clean LRU. Only evict dirty LRU // when no clean slot exists. int found_clean = 0; uint32_t clean_best_i = 0; uint32_t clean_best_use = 0xFFFFFFFFu; int found_dirty = 0; uint32_t dirty_best_i = 0; uint32_t dirty_best_use = 0xFFFFFFFFu; for (uint32_t i = 0; i < g_block_cache_capacity; i++) { if (!g_block_cache[i].valid) { *out_index = i; *out_need_writeback = 0; return 1; } if (g_block_cache[i].dirty) { if (!found_dirty || g_block_cache[i].last_use < dirty_best_use) { found_dirty = 1; dirty_best_use = g_block_cache[i].last_use; dirty_best_i = i; } continue; } if (!found_clean || g_block_cache[i].last_use < clean_best_use) { found_clean = 1; clean_best_use = g_block_cache[i].last_use; clean_best_i = i; } } if (found_clean) { *out_index = clean_best_i; *out_need_writeback = 0; return 1; } if (found_dirty) { *out_index = dirty_best_i; *out_need_writeback = 1; return 1; } return 0; } // Performance optimization: Free block tracking #define EYNFS_FREE_BLOCK_CACHE_SIZE 64 static uint32_t free_block_cache[EYNFS_FREE_BLOCK_CACHE_SIZE]; static uint32_t free_block_cache_count = 0; static uint8_t free_block_cache_valid = 0; // Performance optimization: Directory entry cache typedef struct { uint32_t dir_block; eynfs_dir_entry_t* entries; int count; uint8_t sorted; // 1 if entries are sorted by name } eynfs_dir_cache_entry_t; #define EYNFS_DIR_CACHE_SIZE 8 static eynfs_dir_cache_entry_t dir_cache[EYNFS_DIR_CACHE_SIZE]; static int eynfs_ctx_check(uint32 cap, uint32 cost) { command_context_t* ctx = current_command_context; if (ctx && !cap_check(ctx->caps, cap)) return 0; if (ctx) { scheduler_account(ctx->wo, cost); scheduler_yield_if_needed(ctx->wo); if (sched_det_is_enabled()) ctx->det_seq++; } return 1; } // Initialize caches static void eynfs_init_caches() { // Initialize (allocate) block cache (void)eynfs_block_cache_alloc_if_needed(); // Initialize directory cache for (int i = 0; i < EYNFS_DIR_CACHE_SIZE; i++) { dir_cache[i].entries = NULL; dir_cache[i].count = 0; dir_cache[i].sorted = 0; } // Initialize free block cache free_block_cache_valid = 0; free_block_cache_count = 0; } // Block cache functions int eynfs_cache_get_block(uint8 drive, uint32_t block_num, uint8_t* data) { if (!eynfs_ctx_check(CAP_READ_FS, SCHED_COST_FS)) return -1; if (!eynfs_block_cache_alloc_if_needed()) { // Cache disabled (allocation failure). Fall back to direct I/O. uint32_t lba = eynfs_block_to_lba(block_num); if (ata_read_sector(drive, lba, data) != 0) { static int g_eynfs_read_fail_printed = 0; if (!g_eynfs_read_fail_printed) { g_eynfs_read_fail_printed = 1; char sbuf[160]; int n = snprintf(sbuf, sizeof(sbuf), "[EYNFS] read fail drive=%d fs_block=%d lba=%d\n", (int)drive, (int)block_num, (int)lba); if (n > 0) serial_write(SERIAL_COM1, sbuf, n); } return -1; } cache_misses++; return 0; } if (!eynfs_block_cache_validate_or_disable("eynfs_cache_get_block")) { // Cache was disabled due to corruption; fall back to direct I/O. uint32_t lba = eynfs_block_to_lba(block_num); if (ata_read_sector(drive, lba, data) != 0) return -1; cache_misses++; return 0; } // Look for block in cache for (uint32_t i = 0; i < g_block_cache_capacity; i++) { if (g_block_cache[i].valid && g_block_cache[i].block_num == block_num) { // Cache hit - copy data memcpy(data, g_block_cache[i].data, EYNFS_BLOCK_SIZE); g_block_cache[i].last_use = g_block_cache_use_clock++; cache_hits++; return 0; } } // Cache miss - read from disk uint32_t lba = eynfs_block_to_lba(block_num); if (ata_read_sector(drive, lba, data) != 0) { static int g_eynfs_read_fail_printed = 0; if (!g_eynfs_read_fail_printed) { g_eynfs_read_fail_printed = 1; char sbuf[160]; int n = snprintf(sbuf, sizeof(sbuf), "[EYNFS] read fail drive=%d fs_block=%d lba=%d\n", (int)drive, (int)block_num, (int)lba); if (n > 0) serial_write(SERIAL_COM1, sbuf, n); } return -1; } uint32_t victim_index = 0; int need_writeback = 0; if (eynfs_block_cache_choose_victim_index(&victim_index, &need_writeback)) { if (need_writeback) { if (!eynfs_ctx_check(CAP_WRITE_FS, SCHED_COST_FS)) return -1; ata_write_sector(drive, eynfs_block_to_lba(g_block_cache[victim_index].block_num), g_block_cache[victim_index].data); } // Cache the new block g_block_cache[victim_index].block_num = block_num; memcpy(g_block_cache[victim_index].data, data, EYNFS_BLOCK_SIZE); g_block_cache[victim_index].valid = 1; g_block_cache[victim_index].dirty = 0; g_block_cache[victim_index].last_use = g_block_cache_use_clock++; } cache_misses++; return 0; } int eynfs_cache_write_block(uint8 drive, uint32_t block_num, const uint8_t* data) { if (!eynfs_ctx_check(CAP_WRITE_FS, SCHED_COST_FS)) return -1; if (!eynfs_block_cache_alloc_if_needed()) { // Cache disabled; preserve existing behavior. return ata_write_sector(drive, eynfs_block_to_lba(block_num), data); } if (!eynfs_block_cache_validate_or_disable("eynfs_cache_write_block")) { // Cache was disabled due to corruption; preserve on-disk semantics. return ata_write_sector(drive, eynfs_block_to_lba(block_num), data); } // Look for block in cache for (uint32_t i = 0; i < g_block_cache_capacity; i++) { if (g_block_cache[i].valid && g_block_cache[i].block_num == block_num) { // Update cache memcpy(g_block_cache[i].data, data, EYNFS_BLOCK_SIZE); g_block_cache[i].dirty = 1; g_block_cache[i].last_use = g_block_cache_use_clock++; return 0; } } // Not in cache - write directly to disk (keep on-disk semantics the same) if (ata_write_sector(drive, eynfs_block_to_lba(block_num), data) != 0) return -1; // Opportunistically cache the freshly written block as clean. uint32_t victim_index = 0; int need_writeback = 0; if (eynfs_block_cache_choose_victim_index(&victim_index, &need_writeback)) { if (need_writeback) { // Avoid cascading writeback pressure; keep the just-written sector durable. ata_write_sector(drive, eynfs_block_to_lba(g_block_cache[victim_index].block_num), g_block_cache[victim_index].data); } g_block_cache[victim_index].block_num = block_num; memcpy(g_block_cache[victim_index].data, data, EYNFS_BLOCK_SIZE); g_block_cache[victim_index].valid = 1; g_block_cache[victim_index].dirty = 0; g_block_cache[victim_index].last_use = g_block_cache_use_clock++; } return 0; } static void eynfs_cache_flush(uint8 drive) { if (!eynfs_ctx_check(CAP_WRITE_FS, SCHED_COST_FS)) return; if (!eynfs_block_cache_is_enabled()) return; for (uint32_t i = 0; i < g_block_cache_capacity; i++) { if (g_block_cache[i].valid && g_block_cache[i].dirty) { ata_write_sector(drive, eynfs_block_to_lba(g_block_cache[i].block_num), g_block_cache[i].data); g_block_cache[i].dirty = 0; } } } static void eynfs_cache_invalidate_block(uint32_t block_num) { if (!eynfs_block_cache_is_enabled()) return; for (uint32_t i = 0; i < g_block_cache_capacity; i++) { if (g_block_cache[i].valid && g_block_cache[i].block_num == block_num) { g_block_cache[i].valid = 0; g_block_cache[i].dirty = 0; } } } static void eynfs_dir_cache_invalidate(uint32_t dir_block) { for (int i = 0; i < EYNFS_DIR_CACHE_SIZE; i++) { if (dir_cache[i].entries && dir_cache[i].dir_block == dir_block) { free(dir_cache[i].entries); dir_cache[i].entries = NULL; dir_cache[i].count = 0; dir_cache[i].sorted = 0; } } } // Directory cache functions static eynfs_dir_cache_entry_t* eynfs_dir_cache_find(uint32_t dir_block) { for (int i = 0; i < EYNFS_DIR_CACHE_SIZE; i++) { if (dir_cache[i].entries && dir_cache[i].dir_block == dir_block) { // Don't set sorted flag since we're not actually sorting return &dir_cache[i]; } } return NULL; } static eynfs_dir_cache_entry_t* eynfs_dir_cache_alloc() { // Find free slot or evict least recently used for (int i = 0; i < EYNFS_DIR_CACHE_SIZE; i++) { if (!dir_cache[i].entries) { // Allocate space for directory entries (4KB limit for low-memory systems) dir_cache[i].entries = (eynfs_dir_entry_t*)malloc(4096); if (dir_cache[i].entries) { return &dir_cache[i]; } } } // Evict first entry (simple LRU) if (dir_cache[0].entries) { free(dir_cache[0].entries); dir_cache[0].entries = (eynfs_dir_entry_t*)malloc(4096); if (dir_cache[0].entries) { return &dir_cache[0]; } } return NULL; } // Binary search for directory entries (requires sorted entries) static int eynfs_binary_search_dir(const eynfs_dir_entry_t* entries, int count, const char* name) { int left = 0; int right = count - 1; while (left <= right) { int mid = (left + right) / 2; if (entries[mid].name[0] == '\0') { // Skip empty entries right = mid - 1; continue; } int cmp = strncmp(entries[mid].name, name, EYNFS_NAME_MAX); if (cmp == 0) { return mid; } else if (cmp < 0) { left = mid + 1; } else { right = mid - 1; } } return -1; } // Helper: Read the free block bitmap static uint32_t eynfs_bitmap_block_count(const eynfs_superblock_t *sb) { // 1 bit per filesystem block. uint32_t bitmap_bytes = (sb->total_blocks + 7u) / 8u; return (bitmap_bytes + EYNFS_BLOCK_SIZE - 1u) / EYNFS_BLOCK_SIZE; } static int eynfs_read_bitmap_block(uint8 drive, const eynfs_superblock_t *sb, uint32_t bitmap_block_idx, uint8 *bitmap_block) { if (!eynfs_ctx_check(CAP_READ_FS, SCHED_COST_FS)) return -1; uint32_t fs_block = sb->free_block_map + bitmap_block_idx; return ata_read_sector(drive, eynfs_block_to_lba(fs_block), bitmap_block); } static int eynfs_write_bitmap_block(uint8 drive, const eynfs_superblock_t *sb, uint32_t bitmap_block_idx, const uint8 *bitmap_block) { if (!eynfs_ctx_check(CAP_WRITE_FS, SCHED_COST_FS)) return -1; uint32_t fs_block = sb->free_block_map + bitmap_block_idx; return ata_write_sector(drive, eynfs_block_to_lba(fs_block), bitmap_block); } static int eynfs_bitmap_set_used(uint8 drive, const eynfs_superblock_t *sb, uint32_t fs_block_num, uint8 used) { if (fs_block_num >= sb->total_blocks) return -1; if (!eynfs_ctx_check(CAP_WRITE_FS, SCHED_COST_FS)) return -1; if (current_command_context) { fs_txn_touch(drive, FS_TXN_TAG_SUPERBLOCK, 1u); } uint32_t byte_index = fs_block_num / 8u; uint32_t bit_index = fs_block_num % 8u; uint32_t bitmap_block_idx = byte_index / EYNFS_BLOCK_SIZE; uint32_t offset_in_block = byte_index % EYNFS_BLOCK_SIZE; uint32_t bitmap_blocks = eynfs_bitmap_block_count(sb); if (bitmap_block_idx >= bitmap_blocks) return -1; uint8 bitmap[EYNFS_BLOCK_SIZE]; if (eynfs_read_bitmap_block(drive, sb, bitmap_block_idx, bitmap) != 0) return -1; if (used) { bitmap[offset_in_block] |= (uint8)(1u << bit_index); } else { bitmap[offset_in_block] &= (uint8)~(1u << bit_index); } if (eynfs_write_bitmap_block(drive, sb, bitmap_block_idx, bitmap) != 0) return -1; return 0; } // Optimized block allocation using free block cache static int eynfs_alloc_block_optimized(uint8 drive, eynfs_superblock_t *sb) { // Try cache first if (free_block_cache_valid && free_block_cache_count > 0) { uint32_t block = free_block_cache[--free_block_cache_count]; return block; } // Refill cache (bitmap can span multiple blocks) free_block_cache_count = 0; uint32_t bitmap_blocks = eynfs_bitmap_block_count(sb); for (uint32_t b = 0; b < bitmap_blocks && free_block_cache_count < EYNFS_FREE_BLOCK_CACHE_SIZE; ++b) { if (current_command_context && ((b & 0x3Fu) == 0u)) { scheduler_account(current_command_context->wo, SCHED_COST_FS); scheduler_yield_if_needed(current_command_context->wo); if (sched_det_is_enabled()) current_command_context->det_seq++; } uint8 bitmap[EYNFS_BLOCK_SIZE]; if (eynfs_read_bitmap_block(drive, sb, b, bitmap) != 0) return -1; for (uint32_t byte = 0; byte < EYNFS_BLOCK_SIZE && free_block_cache_count < EYNFS_FREE_BLOCK_CACHE_SIZE; ++byte) { if (bitmap[byte] == 0xFF) continue; for (uint32_t bit = 0; bit < 8 && free_block_cache_count < EYNFS_FREE_BLOCK_CACHE_SIZE; ++bit) { if (bitmap[byte] & (1u << bit)) continue; uint32_t fs_block_num = (b * EYNFS_BLOCK_SIZE + byte) * 8u + bit; if (fs_block_num >= sb->total_blocks) break; free_block_cache[free_block_cache_count++] = fs_block_num; } } } if (free_block_cache_count > 0) { free_block_cache_valid = 1; uint32_t block = free_block_cache[--free_block_cache_count]; return block; } return -1; // No free blocks } // Read the EYNFS superblock from disk int eynfs_read_superblock(uint8 drive, uint32 lba, eynfs_superblock_t *sb) { if (!eynfs_ctx_check(CAP_READ_FS, SCHED_COST_FS)) return -1; if (current_command_context) { fs_txn_touch(drive, FS_TXN_TAG_SUPERBLOCK, (uint32)sizeof(*sb)); } uint8 buf[EYNFS_BLOCK_SIZE]; if (ata_read_sector(drive, lba, buf) != 0) { return -1; } memcpy(sb, buf, sizeof(eynfs_superblock_t)); // Initialize caches on first superblock read static int caches_initialized = 0; if (!caches_initialized) { eynfs_init_caches(); caches_initialized = 1; } return 0; } // Write the EYNFS superblock to disk int eynfs_write_superblock(uint8 drive, uint32 lba, const eynfs_superblock_t *sb) { if (!eynfs_ctx_check(CAP_WRITE_FS, SCHED_COST_FS)) return -1; if (current_command_context) { fs_txn_touch(drive, FS_TXN_TAG_SUPERBLOCK, (uint32)sizeof(*sb)); } uint8 buf[EYNFS_BLOCK_SIZE] = {0}; memcpy(buf, sb, sizeof(eynfs_superblock_t)); if (ata_write_sector(drive, lba, buf) != 0) { return -1; } return 0; } // Read a directory table from disk (multi-block chain) int eynfs_read_dir_table(uint8 drive, uint32 lba, eynfs_dir_entry_t *entries, size_t max_entries) { if (!eynfs_ctx_check(CAP_READ_FS, SCHED_COST_FS)) return -1; size_t total_entries = 0; uint32_t current_block = lba; uint8 buf[EYNFS_BLOCK_SIZE]; while (current_block && total_entries < max_entries) { if (current_command_context) { scheduler_account(current_command_context->wo, SCHED_COST_FS); scheduler_yield_if_needed(current_command_context->wo); if (sched_det_is_enabled()) current_command_context->det_seq++; } if (ata_read_sector(drive, eynfs_block_to_lba(current_block), buf) != 0) return -1; uint32_t next_block = *(uint32_t*)buf; size_t entry_count = (EYNFS_BLOCK_SIZE - 4) / sizeof(eynfs_dir_entry_t); size_t entries_to_copy = entry_count; if (max_entries - total_entries < entry_count) entries_to_copy = max_entries - total_entries; memcpy(&entries[total_entries], buf + 4, entries_to_copy * sizeof(eynfs_dir_entry_t)); // Ensure all names are null-terminated for (size_t j = 0; j < entries_to_copy; ++j) { entries[total_entries + j].name[EYNFS_NAME_MAX - 1] = '\0'; } total_entries += entries_to_copy; if (total_entries >= max_entries) break; current_block = next_block; } return (int)total_entries; } // Helper: Write a single directory block static int eynfs_write_dir_block(uint8 drive, uint32_t block_num, const eynfs_dir_entry_t *entries, size_t num_entries, uint32_t next_block) { if (!eynfs_ctx_check(CAP_WRITE_FS, SCHED_COST_FS)) return -1; uint8 buf[EYNFS_BLOCK_SIZE] = {0}; *(uint32_t*)buf = next_block; size_t entries_to_write = (EYNFS_BLOCK_SIZE - 4) / sizeof(eynfs_dir_entry_t); if (num_entries < entries_to_write) entries_to_write = num_entries; memcpy(buf + 4, entries, entries_to_write * sizeof(eynfs_dir_entry_t)); return ata_write_sector(drive, eynfs_block_to_lba(block_num), buf); } // Helper: Count directory entries without allocating memory int eynfs_count_dir_entries(uint8 drive, uint32_t lba) { if (!eynfs_ctx_check(CAP_READ_FS, SCHED_COST_FS)) return -1; int total_entries = 0; uint32_t current_block = lba; uint8 buf[EYNFS_BLOCK_SIZE]; int block_count = 0; // Limit to reasonable number of blocks to prevent excessive allocation const int max_blocks = 32; // 32 blocks = ~288 entries max (much more reasonable) while (current_block && block_count < max_blocks) { if (current_command_context) { scheduler_account(current_command_context->wo, SCHED_COST_FS); scheduler_yield_if_needed(current_command_context->wo); if (sched_det_is_enabled()) current_command_context->det_seq++; } if (ata_read_sector(drive, eynfs_block_to_lba(current_block), buf) != 0) return -1; uint32_t next_block = *(uint32_t*)buf; size_t entry_count = (EYNFS_BLOCK_SIZE - 4) / sizeof(eynfs_dir_entry_t); total_entries += entry_count; current_block = next_block; block_count++; } // If we hit the limit, return a conservative estimate if (block_count >= max_blocks && current_block) { printf("%cWarning: Directory has more than %d blocks, limiting allocation\n", 255, 165, 0, max_blocks); } return total_entries; } // Helper: Free a chain of directory blocks static int eynfs_free_dir_chain(uint8 drive, uint32_t first_block) { if (!eynfs_ctx_check(CAP_READ_FS | CAP_WRITE_FS, SCHED_COST_FS)) return -1; eynfs_superblock_t sb; if (eynfs_read_superblock(drive, EYNFS_SUPERBLOCK_LBA, &sb) != 0) return -1; uint32_t block_num = first_block; while (block_num != 0) { if (current_command_context) { scheduler_account(current_command_context->wo, SCHED_COST_FS); scheduler_yield_if_needed(current_command_context->wo); if (sched_det_is_enabled()) current_command_context->det_seq++; } uint8 buf[EYNFS_BLOCK_SIZE]; if (ata_read_sector(drive, eynfs_block_to_lba(block_num), buf) != 0) break; uint32_t next_block = *(uint32_t*)buf; eynfs_free_block(drive, &sb, block_num); block_num = next_block; } return 0; } // Write a directory table to disk (multi-block chain) - Simplified version int eynfs_write_dir_table(uint8 drive, uint32 lba, const eynfs_dir_entry_t *entries, size_t num_entries) { if (!entries && num_entries > 0) return -1; if (num_entries == 0) return 0; if (!eynfs_ctx_check(CAP_READ_FS | CAP_WRITE_FS, SCHED_COST_FS)) return -1; if (current_command_context) { fs_txn_touch(drive, FS_TXN_TAG_DIR, (uint32)(num_entries * sizeof(*entries))); } eynfs_superblock_t sb; if (eynfs_read_superblock(drive, EYNFS_SUPERBLOCK_LBA, &sb) != 0) return -1; // First, read the existing block chain to preserve it uint32_t original_blocks[32]; // Support up to 32 blocks int block_count = 0; uint32_t current_block = lba; while (current_block && block_count < 32) { if (current_command_context) { scheduler_account(current_command_context->wo, SCHED_COST_FS); scheduler_yield_if_needed(current_command_context->wo); if (sched_det_is_enabled()) current_command_context->det_seq++; } original_blocks[block_count++] = current_block; uint8 buf[EYNFS_BLOCK_SIZE]; if (ata_read_sector(drive, eynfs_block_to_lba(current_block), buf) != 0) break; current_block = *(uint32_t*)buf; } uint32_t prev_block = 0; size_t written = 0; size_t entries_per_block = (EYNFS_BLOCK_SIZE - 4) / sizeof(eynfs_dir_entry_t); // Write all entries using the preserved block chain uint32_t allocated_next_block = 0; // Store the allocated block for the second loop for (int block_idx = 0; block_idx < block_count && written < num_entries; block_idx++) { if (current_command_context) { scheduler_account(current_command_context->wo, SCHED_COST_FS); scheduler_yield_if_needed(current_command_context->wo); if (sched_det_is_enabled()) current_command_context->det_seq++; } uint32_t current_block = original_blocks[block_idx]; // Calculate how many entries to write in this block size_t to_write = num_entries - written; if (to_write > entries_per_block) to_write = entries_per_block; // Determine next block (0 if this is the last block or last entry) uint32_t next_block = 0; if (written + to_write < num_entries) { if (block_idx + 1 < block_count) { next_block = original_blocks[block_idx + 1]; } else { // Need to allocate a new block for remaining entries next_block = eynfs_alloc_block(drive, &sb); if (next_block < 0) { printf("%c[DEBUG] eynfs_write_dir_table: Failed to allocate new block\n", 0, 255, 0); return -1; } allocated_next_block = next_block; // Store for the second loop } } // Write the block if (eynfs_write_dir_block(drive, current_block, &entries[written], to_write, next_block) != 0) { return -1; } written += to_write; // If we just wrote to the last original block and there are more entries, // we need to continue with newly allocated blocks if (written < num_entries && block_idx == block_count - 1) { break; // Exit this loop and continue with new blocks } } // Continue writing to newly allocated blocks if needed while (written < num_entries) { if (current_command_context) { scheduler_account(current_command_context->wo, SCHED_COST_FS); scheduler_yield_if_needed(current_command_context->wo); if (sched_det_is_enabled()) current_command_context->det_seq++; } // Use the allocated_next_block that was allocated in the previous loop uint32_t new_block = allocated_next_block; if (new_block == 0) { // If no allocated_next_block was allocated, allocate one now new_block = eynfs_alloc_block(drive, &sb); if (new_block < 0) { printf("%c[DEBUG] eynfs_write_dir_table: Failed to allocate additional block\n", 0, 255, 0); return -1; } } size_t to_write = num_entries - written; if (to_write > entries_per_block) to_write = entries_per_block; uint32_t next_block = 0; if (written + to_write < num_entries) { next_block = eynfs_alloc_block(drive, &sb); if (next_block < 0) { printf("%c[DEBUG] eynfs_write_dir_table: Failed to allocate next block\n", 0, 255, 0); return -1; } } if (eynfs_write_dir_block(drive, new_block, &entries[written], to_write, next_block) != 0) { return -1; } written += to_write; } return (int)written; } // Helper: Validate superblock static int eynfs_validate_superblock(const eynfs_superblock_t *sb) { if (!sb) return -1; if (sb->magic != EYNFS_MAGIC) return -1; if (sb->version != EYNFS_VERSION) return -1; if (sb->block_size != EYNFS_BLOCK_SIZE) return -1; if (sb->total_blocks == 0) return -1; if (sb->root_dir_block >= sb->total_blocks) return -1; if (sb->free_block_map >= sb->total_blocks) return -1; return 0; } // Helper: Validate directory entry static int eynfs_validate_dir_entry(const eynfs_dir_entry_t *entry) { if (!entry) return -1; if (entry->name[0] == '\0') return -1; // Empty name if (entry->type != EYNFS_TYPE_FILE && entry->type != EYNFS_TYPE_DIR) return -1; return 0; } // Allocate a free block, mark it as used in the bitmap, and return its block number int eynfs_alloc_block(uint8 drive, eynfs_superblock_t *sb) { if (!eynfs_ctx_check(CAP_READ_FS | CAP_WRITE_FS, SCHED_COST_FS)) return -1; // Use optimized allocation with caching int block = eynfs_alloc_block_optimized(drive, sb); if (block >= 0) { if (eynfs_bitmap_set_used(drive, sb, (uint32_t)block, 1) != 0) { // Conservative: invalidate cache so we don't hand out the same block again. free_block_cache_valid = 0; free_block_cache_count = 0; return -1; } return block; } return -1; // No free block found } // Free a block (mark as unused in the bitmap) int eynfs_free_block(uint8 drive, eynfs_superblock_t *sb, uint32_t block) { if (!eynfs_ctx_check(CAP_WRITE_FS, SCHED_COST_FS)) return -1; if (block >= sb->total_blocks) return -1; if (eynfs_bitmap_set_used(drive, sb, block, 0) != 0) return -1; // The cache may now be stale. free_block_cache_valid = 0; free_block_cache_count = 0; return 0; } // Find an entry by name in a directory block // Returns 0 if found, -1 if not found int eynfs_find_in_dir(uint8 drive, const eynfs_superblock_t *sb, uint32_t dir_block, const char *name, eynfs_dir_entry_t *out_entry, uint32_t *out_index) { if (!eynfs_ctx_check(CAP_READ_FS, SCHED_COST_FS)) return -1; if (!sb || !name || !name[0]) return -1; if (current_command_context) { fs_txn_touch(drive, FS_TXN_TAG_DIR, (uint32)sizeof(eynfs_dir_entry_t)); } // Check directory cache first (best-effort). The cache may be partial (4KB), // so a cache miss must fall back to a full on-disk scan. eynfs_dir_cache_entry_t* cache_entry = eynfs_dir_cache_find(dir_block); if (cache_entry) { for (int i = 0; i < cache_entry->count; ++i) { if (cache_entry->entries[i].name[0] == '\0') continue; if (strncmp(cache_entry->entries[i].name, name, EYNFS_NAME_MAX) == 0) { if (out_entry) *out_entry = cache_entry->entries[i]; if (out_index) *out_index = (uint32_t)i; return 0; } } // fall through to full scan } // Stream-scan the directory table by walking the on-disk block chain. // This avoids large allocations and works for large directories. const uint32_t entries_per_block = (uint32_t)((EYNFS_BLOCK_SIZE - 4) / sizeof(eynfs_dir_entry_t)); uint32_t current_block = dir_block; uint32_t base_index = 0; int block_count = 0; const int max_blocks = 4096; // safety bound while (current_block && block_count < max_blocks) { if (current_command_context) { scheduler_account(current_command_context->wo, SCHED_COST_FS); scheduler_yield_if_needed(current_command_context->wo); if (sched_det_is_enabled()) current_command_context->det_seq++; } uint8 buf[EYNFS_BLOCK_SIZE]; if (eynfs_cache_get_block(drive, current_block, buf) != 0) return -1; uint32_t next_block = *(uint32_t*)buf; const eynfs_dir_entry_t* entries = (const eynfs_dir_entry_t*)(buf + 4); for (uint32_t i = 0; i < entries_per_block; ++i) { if (entries[i].name[0] == '\0') continue; if (strncmp(entries[i].name, name, EYNFS_NAME_MAX) == 0) { if (out_entry) *out_entry = entries[i]; if (out_index) *out_index = base_index + i; return 0; } } base_index += entries_per_block; current_block = next_block; block_count++; } return -1; } // Traverse a path from root, return the entry for the last component // Optionally returns parent directory block and entry index // Returns 0 if found, -1 if not found int eynfs_traverse_path(uint8 drive, const eynfs_superblock_t *sb, const char *path, eynfs_dir_entry_t *out_entry, uint32_t *parent_block, uint32_t *entry_index) { if (!path || path[0] != '/') return -1; // PATCH: handle root directory as a valid path if (strcmp(path, "/") == 0) { if (out_entry) { memset(out_entry, 0, sizeof(eynfs_dir_entry_t)); out_entry->type = EYNFS_TYPE_DIR; out_entry->first_block = sb->root_dir_block; } if (parent_block) *parent_block = 0; if (entry_index) *entry_index = 0; return 0; } char temp_path[256]; strncpy(temp_path, path, sizeof(temp_path)); temp_path[sizeof(temp_path)-1] = '\0'; char *token, *saveptr; uint32_t current_block = sb->root_dir_block; uint32_t last_parent = 0; uint32_t last_index = 0; eynfs_dir_entry_t entry; token = strtok_r(temp_path, "/", &saveptr); while (token) { if (eynfs_find_in_dir(drive, sb, current_block, token, &entry, &last_index) != 0) { return -1; } last_parent = current_block; current_block = entry.first_block; token = strtok_r(NULL, "/", &saveptr); if (token && entry.type != EYNFS_TYPE_DIR) { return -1; // Not a directory in the path } } if (out_entry) *out_entry = entry; if (parent_block) *parent_block = last_parent; if (entry_index) *entry_index = last_index; return 0; } // Create a file or directory entry in the given parent directory int eynfs_create_entry(uint8 drive, eynfs_superblock_t *sb, uint32_t parent_block, const char *name, uint8_t type) { if (!sb) return -1; if (!name || !name[0]) return -1; if (type != EYNFS_TYPE_FILE && type != EYNFS_TYPE_DIR) return -1; if (parent_block == 0) return -1; if (!eynfs_ctx_check(CAP_READ_FS | CAP_WRITE_FS, SCHED_COST_FS)) return -1; if (current_command_context) { fs_txn_touch(drive, FS_TXN_TAG_DIR, (uint32)sizeof(eynfs_dir_entry_t)); if (type == EYNFS_TYPE_FILE) { fs_txn_touch(drive, FS_TXN_TAG_FILE, 0u); } } // Create without heap allocations: scan the directory-table block chain looking // for an empty slot; if none, append a new directory-table block. const size_t entries_per_block = (EYNFS_BLOCK_SIZE - 4) / sizeof(eynfs_dir_entry_t); uint32_t cur = parent_block; uint32_t last = 0; while (cur != 0) { if (current_command_context) { scheduler_account(current_command_context->wo, SCHED_COST_FS); scheduler_yield_if_needed(current_command_context->wo); if (sched_det_is_enabled()) current_command_context->det_seq++; } uint8 buf[EYNFS_BLOCK_SIZE]; if (eynfs_cache_get_block(drive, cur, buf) != 0) return -1; uint32_t next = *(uint32_t*)buf; eynfs_dir_entry_t* entries = (eynfs_dir_entry_t*)(buf + 4); int free_idx = -1; for (size_t i = 0; i < entries_per_block; ++i) { // Empty slot if (entries[i].name[0] == '\0') { if (free_idx < 0) free_idx = (int)i; continue; } // Existing entry if (strncmp(entries[i].name, name, EYNFS_NAME_MAX) == 0) { return -1; // already exists } } if (free_idx >= 0) { int new_block = eynfs_alloc_block(drive, sb); if (new_block < 0) return -1; memset(&entries[free_idx], 0, sizeof(eynfs_dir_entry_t)); strncpy(entries[free_idx].name, name, EYNFS_NAME_MAX - 1); entries[free_idx].name[EYNFS_NAME_MAX - 1] = '\0'; entries[free_idx].type = type; entries[free_idx].first_block = (uint32_t)new_block; entries[free_idx].size = 0; if (type == EYNFS_TYPE_DIR) { uint8 zero_block[EYNFS_BLOCK_SIZE] = {0}; if (ata_write_sector(drive, eynfs_block_to_lba((uint32_t)new_block), zero_block) != 0) { eynfs_free_block(drive, sb, (uint32_t)new_block); return -1; } } if (eynfs_cache_write_block(drive, cur, buf) != 0) { eynfs_free_block(drive, sb, (uint32_t)new_block); return -1; } // Directory contents and path traversal results are cached. eynfs_dir_cache_invalidate(parent_block); eynfs_cache_flush(drive); return 0; } last = cur; cur = next; } // No free slots: append a new directory-table block to the chain. if (last == 0) return -1; int new_dir_block = eynfs_alloc_block(drive, sb); if (new_dir_block < 0) return -1; // Allocate the data/child block for the new entry. int new_entry_block = eynfs_alloc_block(drive, sb); if (new_entry_block < 0) { eynfs_free_block(drive, sb, (uint32_t)new_dir_block); return -1; } // Link the last directory-table block to the new directory-table block. { uint8 last_buf[EYNFS_BLOCK_SIZE]; if (eynfs_cache_get_block(drive, last, last_buf) != 0) { eynfs_free_block(drive, sb, (uint32_t)new_entry_block); eynfs_free_block(drive, sb, (uint32_t)new_dir_block); return -1; } *(uint32_t*)last_buf = (uint32_t)new_dir_block; if (eynfs_cache_write_block(drive, last, last_buf) != 0) { eynfs_free_block(drive, sb, (uint32_t)new_entry_block); eynfs_free_block(drive, sb, (uint32_t)new_dir_block); return -1; } } // Initialize the new directory-table block and install the entry at index 0. { uint8 new_buf[EYNFS_BLOCK_SIZE]; memset(new_buf, 0, sizeof(new_buf)); *(uint32_t*)new_buf = 0; // next = 0 eynfs_dir_entry_t* entries = (eynfs_dir_entry_t*)(new_buf + 4); memset(&entries[0], 0, sizeof(eynfs_dir_entry_t)); strncpy(entries[0].name, name, EYNFS_NAME_MAX - 1); entries[0].name[EYNFS_NAME_MAX - 1] = '\0'; entries[0].type = type; entries[0].first_block = (uint32_t)new_entry_block; entries[0].size = 0; if (type == EYNFS_TYPE_DIR) { uint8 zero_block[EYNFS_BLOCK_SIZE] = {0}; if (ata_write_sector(drive, eynfs_block_to_lba((uint32_t)new_entry_block), zero_block) != 0) { // Best-effort cleanup: unlinking is complex, just free blocks. eynfs_free_block(drive, sb, (uint32_t)new_entry_block); eynfs_free_block(drive, sb, (uint32_t)new_dir_block); return -1; } } // Ensure any stale cached version of this block can't be served. eynfs_cache_invalidate_block((uint32_t)new_dir_block); if (eynfs_cache_write_block(drive, (uint32_t)new_dir_block, new_buf) != 0) { eynfs_free_block(drive, sb, (uint32_t)new_entry_block); eynfs_free_block(drive, sb, (uint32_t)new_dir_block); return -1; } } eynfs_dir_cache_invalidate(parent_block); eynfs_cache_flush(drive); return 0; } // Delete a file or directory entry by name from the given parent directory int eynfs_delete_entry(uint8 drive, eynfs_superblock_t *sb, uint32_t parent_block, const char *name) { if (!name || !name[0]) return -1; if (!eynfs_ctx_check(CAP_READ_FS | CAP_WRITE_FS, SCHED_COST_FS)) return -1; if (current_command_context) { fs_txn_touch(drive, FS_TXN_TAG_DIR, (uint32)sizeof(eynfs_dir_entry_t)); fs_txn_touch(drive, FS_TXN_TAG_FILE, 0u); } eynfs_dir_entry_t victim; uint32_t entry_index = 0; if (eynfs_find_in_dir(drive, sb, parent_block, name, &victim, &entry_index) != 0) { return -1; } // Free all blocks in the target entry's chain. uint32_t block_num = victim.first_block; while (block_num != 0) { if (current_command_context) { scheduler_account(current_command_context->wo, SCHED_COST_FS); scheduler_yield_if_needed(current_command_context->wo); if (sched_det_is_enabled()) current_command_context->det_seq++; } uint8 tmp[EYNFS_BLOCK_SIZE]; if (ata_read_sector(drive, eynfs_block_to_lba(block_num), tmp) != 0) break; uint32_t next_block = *(uint32_t*)tmp; eynfs_free_block(drive, sb, block_num); block_num = next_block; } // Clear only the targeted directory slot in place. This avoids rewriting // the whole directory table and preserves entries past 64 items. const uint32_t entries_per_block = (uint32_t)((EYNFS_BLOCK_SIZE - 4) / sizeof(eynfs_dir_entry_t)); const uint32_t block_steps = entry_index / entries_per_block; const uint32_t slot = entry_index % entries_per_block; uint32_t cur = parent_block; const uint32_t max_blocks = 4096; for (uint32_t i = 0; i < block_steps; ++i) { if (cur == 0 || i >= max_blocks) return -1; uint8 dirblk[EYNFS_BLOCK_SIZE]; if (eynfs_cache_get_block(drive, cur, dirblk) != 0) return -1; cur = *(uint32_t*)dirblk; } if (cur == 0) return -1; uint8 dirblk[EYNFS_BLOCK_SIZE]; if (eynfs_cache_get_block(drive, cur, dirblk) != 0) return -1; eynfs_dir_entry_t* entries = (eynfs_dir_entry_t*)(dirblk + 4); memset(&entries[slot], 0, sizeof(eynfs_dir_entry_t)); eynfs_cache_invalidate_block(cur); if (eynfs_cache_write_block(drive, cur, dirblk) != 0) return -1; eynfs_dir_cache_invalidate(parent_block); return 0; } // Read up to bufsize bytes from a file's data block chain, starting at offset // Returns number of bytes read, or -1 on error int eynfs_read_file(uint8 drive, const eynfs_superblock_t *sb, const eynfs_dir_entry_t *entry, void *buf, size_t bufsize, size_t offset) { if (!eynfs_ctx_check(CAP_READ_FS, SCHED_COST_FS)) return -1; if (!entry || entry->type != EYNFS_TYPE_FILE) return -1; if (offset >= entry->size) return 0; if (current_command_context) { fs_txn_touch(drive, FS_TXN_TAG_FILE, (uint32)bufsize); } uint32_t block_num = entry->first_block; size_t bytes_left = entry->size - offset; if (bufsize < bytes_left) bytes_left = bufsize; size_t total_read = 0; uint8 block[EYNFS_BLOCK_SIZE]; size_t skip = offset; // Skip blocks and bytes up to offset uint32_t kick_ctr = 0; while (block_num && skip >= (EYNFS_BLOCK_SIZE-4)) { if (current_command_context) { scheduler_account(current_command_context->wo, SCHED_COST_FS); scheduler_yield_if_needed(current_command_context->wo); if (sched_det_is_enabled()) current_command_context->det_seq++; } if (((kick_ctr++) & 0x3u) == 0) watchdog_kick("eynfs-read"); if (eynfs_cache_get_block(drive, block_num, block) != 0) return -1; uint32_t next_block = *(uint32_t*)block; block_num = next_block; skip -= (EYNFS_BLOCK_SIZE-4); } // Now at the block containing the offset if (block_num && bytes_left > 0) { if (current_command_context) { scheduler_account(current_command_context->wo, SCHED_COST_FS); scheduler_yield_if_needed(current_command_context->wo); if (sched_det_is_enabled()) current_command_context->det_seq++; } if (((kick_ctr++) & 0x3u) == 0) watchdog_kick("eynfs-read"); if (eynfs_cache_get_block(drive, block_num, block) != 0) return -1; uint32_t next_block = *(uint32_t*)block; size_t block_offset = skip; size_t chunk = (EYNFS_BLOCK_SIZE-4) - block_offset; if (chunk > bytes_left) chunk = bytes_left; memcpy((uint8*)buf, block+4+block_offset, chunk); total_read += chunk; bytes_left -= chunk; block_num = next_block; } // Read remaining blocks while (block_num && bytes_left > 0) { if (current_command_context) { scheduler_account(current_command_context->wo, SCHED_COST_FS); scheduler_yield_if_needed(current_command_context->wo); if (sched_det_is_enabled()) current_command_context->det_seq++; } if (((kick_ctr++) & 0x3u) == 0) watchdog_kick("eynfs-read"); if (eynfs_cache_get_block(drive, block_num, block) != 0) return -1; uint32_t next_block = *(uint32_t*)block; size_t chunk = (EYNFS_BLOCK_SIZE-4) < bytes_left ? (EYNFS_BLOCK_SIZE-4) : bytes_left; memcpy((uint8*)buf + total_read, block+4, chunk); total_read += chunk; bytes_left -= chunk; block_num = next_block; } return (int)total_read; } /* * eynfs_read_file_fast -- cursor-aware file read for EYNFS. * * Like eynfs_read_file() but avoids re-traversing the full block chain from * the beginning on every call. A caller-supplied cursor (*p_cur_block, * *p_cur_block_off) records the last block reached and the file byte offset * where its data payload starts, so a subsequent read at a GREATER or EQUAL * offset begins traversal from the cached block rather than first_block. * * For a 14 MB WAD file, the old path required O(28 000) block reads per * 256-byte chunk; with the cursor a sequential read costs O(1) block reads * after the initial traversal. * * Cursor semantics: * *p_cur_block -- EYNFS block number. 0 = uninitialized; restart from * first_block at file offset 0. * *p_cur_block_off -- file byte offset at which *p_cur_block's data payload * begins (i.e., immediately after its 4-byte chain ptr). * * On return both fields are updated to the block reached after the skip * phase, so the next call benefits even for non-contiguous forward reads. * * ABI-INVARIANT: Not exported to user space. Internal kernel use only. * Performance-critical: called on every SYSCALL_READ for EYNFS file FDs. */ int eynfs_read_file_fast( uint8 drive, uint32_t first_block, uint32_t file_size, void *buf, size_t bufsize, size_t offset, uint32_t *p_cur_block, uint32_t *p_cur_block_off) { if (!eynfs_ctx_check(CAP_READ_FS, SCHED_COST_FS)) return -1; if (offset >= file_size) return 0; if (current_command_context) fs_txn_touch(drive, FS_TXN_TAG_FILE, (uint32)bufsize); size_t bytes_left = file_size - offset; if (bufsize < bytes_left) bytes_left = bufsize; size_t total_read = 0; uint8 block[EYNFS_BLOCK_SIZE]; uint32_t kick_ctr = 0; /* Choose starting point: cached cursor if it covers the target offset, * otherwise restart from the beginning of the chain. */ uint32_t block_num; size_t skip; if (p_cur_block && *p_cur_block != 0 && p_cur_block_off && offset >= (size_t)*p_cur_block_off) { block_num = *p_cur_block; skip = offset - (size_t)*p_cur_block_off; } else { block_num = first_block; skip = offset; } /* Advance through the chain until skip < one block's data payload. */ while (block_num && skip >= (EYNFS_BLOCK_SIZE - 4u)) { if (current_command_context) { scheduler_account(current_command_context->wo, SCHED_COST_FS); scheduler_yield_if_needed(current_command_context->wo); if (sched_det_is_enabled()) current_command_context->det_seq++; } if (((kick_ctr++) & 0x3u) == 0) watchdog_kick("eynfs-fast"); if (eynfs_cache_get_block(drive, block_num, block) != 0) return -1; block_num = *(uint32_t *)block; skip -= (EYNFS_BLOCK_SIZE - 4u); } /* Update the cursor to the block we just landed on. * *p_cur_block_off = the file byte offset at the START of this block. */ if (p_cur_block && p_cur_block_off) { *p_cur_block = block_num; *p_cur_block_off = (uint32_t)(offset - skip); } /* Read the partial leading portion of the first data block. */ if (block_num && bytes_left > 0) { if (((kick_ctr++) & 0x3u) == 0) watchdog_kick("eynfs-fast"); if (eynfs_cache_get_block(drive, block_num, block) != 0) return -1; uint32_t next_block = *(uint32_t *)block; size_t chunk = (EYNFS_BLOCK_SIZE - 4u) - skip; if (chunk > bytes_left) chunk = bytes_left; memcpy((uint8_t *)buf, block + 4 + skip, chunk); total_read += chunk; bytes_left -= chunk; if (p_cur_block && p_cur_block_off) { *p_cur_block = next_block; /* * Advance by a full block payload (508 bytes) regardless of skip. * *p_cur_block_off tracks the file offset at the START of a block, * so each block boundary is always +508, even if we started reading * mid-block on this first chunk. */ *p_cur_block_off += (uint32_t)(EYNFS_BLOCK_SIZE - 4u); } block_num = next_block; } /* Read remaining full blocks. */ while (block_num && bytes_left > 0) { if (current_command_context) { scheduler_account(current_command_context->wo, SCHED_COST_FS); scheduler_yield_if_needed(current_command_context->wo); if (sched_det_is_enabled()) current_command_context->det_seq++; } if (((kick_ctr++) & 0x3u) == 0) watchdog_kick("eynfs-fast"); if (eynfs_cache_get_block(drive, block_num, block) != 0) return -1; uint32_t next_block = *(uint32_t *)block; size_t chunk = (EYNFS_BLOCK_SIZE - 4u); if (chunk > bytes_left) chunk = bytes_left; memcpy((uint8_t *)buf + total_read, block + 4, chunk); total_read += chunk; bytes_left -= chunk; if (p_cur_block && p_cur_block_off) { *p_cur_block_off += (uint32_t)(EYNFS_BLOCK_SIZE - 4u); *p_cur_block = next_block; } block_num = next_block; } return (int)total_read; } // Write data to a file, creating a chain of blocks as needed // Returns number of bytes written, or -1 on error int eynfs_write_file(uint8 drive, eynfs_superblock_t *sb, eynfs_dir_entry_t *entry, const void *buf, size_t size, uint32_t parent_block, uint32_t entry_index) { if (!entry || entry->type != EYNFS_TYPE_FILE) return -1; if (!buf && size > 0) return -1; if (!eynfs_ctx_check(CAP_READ_FS | CAP_WRITE_FS, SCHED_COST_FS)) return -1; if (current_command_context) { fs_txn_touch(drive, FS_TXN_TAG_FILE, (uint32)size); } // Free existing blocks if this is a rewrite if (entry->first_block != 0) { uint32_t block_num = entry->first_block; while (block_num != 0) { uint8 tmp[EYNFS_BLOCK_SIZE]; if (ata_read_sector(drive, eynfs_block_to_lba(block_num), tmp) != 0) break; uint32_t next_block = *(uint32_t*)tmp; // Ensure any cached data for this block can't be served after it's freed/reused. eynfs_cache_invalidate_block(block_num); eynfs_free_block(drive, sb, block_num); block_num = next_block; } } uint32_t prev_block = 0; uint32_t first_block = 0; size_t bytes_left = size; size_t total_written = 0; const uint8* data = (const uint8*)buf; while (bytes_left > 0) { int new_block = eynfs_alloc_block(drive, sb); if (new_block < 0) return -1; // This block may have been previously cached under the same number. eynfs_cache_invalidate_block((uint32_t)new_block); if (!first_block) first_block = new_block; if (prev_block) { // Update previous block's next_block pointer uint8 tmp[EYNFS_BLOCK_SIZE]; if (ata_read_sector(drive, eynfs_block_to_lba(prev_block), tmp) != 0) return -1; *(uint32_t*)tmp = new_block; if (eynfs_cache_write_block(drive, prev_block, tmp) != 0) return -1; } uint8 block[EYNFS_BLOCK_SIZE] = {0}; size_t chunk = bytes_left < (EYNFS_BLOCK_SIZE-4) ? bytes_left : (EYNFS_BLOCK_SIZE-4); *(uint32_t*)block = 0; // next_block = 0 for now // Bounds check for memory copy if (chunk <= (EYNFS_BLOCK_SIZE-4) && (data+total_written) && (block+4)) { memcpy(block+4, data+total_written, chunk); } else { printf("%cWarning: Memory copy bounds check failed in write_file\n", 255, 165, 0); return -1; } if (eynfs_cache_write_block(drive, (uint32_t)new_block, block) != 0) return -1; total_written += chunk; bytes_left -= chunk; prev_block = new_block; } // Update entry with new first block and size entry->first_block = first_block; entry->size = size; // Update directory table - count entries first, then allocate exactly what we need int entry_count = eynfs_count_dir_entries(drive, parent_block); if (entry_count < 0) { printf("Error: Failed to count directory entries\n"); return -1; } size_t allocation_size = sizeof(eynfs_dir_entry_t) * entry_count; // Safety check: limit allocation to prevent memory exhaustion if (allocation_size > 4096) { // 4KB limit for directory operations printf("%cWarning: Directory allocation too large (%d bytes), limiting to 4KB\n", 255, 165, 0, allocation_size); entry_count = 4096 / sizeof(eynfs_dir_entry_t); allocation_size = 4096; } eynfs_dir_entry_t* entries = (eynfs_dir_entry_t*)malloc(allocation_size); if (!entries) { printf("Error: Out of memory for directory update\n"); return -1; } int count = eynfs_read_dir_table(drive, parent_block, entries, entry_count); if (count < 0) { free(entries); return -1; } // Validate entry_index if (entry_index >= (uint32_t)count) { printf("Warning: entry_index %d >= count %d, truncating\n", entry_index, count); entry_index = count - 1; } // Update the entry entries[entry_index] = *entry; // Write back the directory table with error checking int write_result = eynfs_write_dir_table(drive, parent_block, entries, count); free(entries); if (write_result < 0) { printf("Error: Failed to write directory table\n"); return -1; } // Directory contents and path traversal results are cached; invalidate so metadata updates are visible immediately. eynfs_dir_cache_invalidate(parent_block); return (int)size; } // Unix-like File Table and Open/Close Implementation --- #define EYNFS_MAX_OPEN_FILES 32 typedef struct { int used; uint8 drive; eynfs_superblock_t sb; eynfs_dir_entry_t entry; uint32_t offset; int mode; // 0 = read, 1 = write, 2 = append uint32_t parent_block; uint32_t entry_index; } eynfs_file_t; static eynfs_file_t eynfs_files[EYNFS_MAX_OPEN_FILES]; // EYNFS-specific file operations with full path support int eynfs_open(const char* path, int mode) { if (!path) return -1; // Find free file descriptor int fd = -1; for (int i = 0; i < EYNFS_MAX_OPEN_FILES; i++) { if (!eynfs_files[i].used) { fd = i; break; } } if (fd == -1) return -1; // Initialize file descriptor eynfs_files[fd].used = 1; const char* resolved_path = path; uint8 drive = eynfs_default_drive(); (void)eynfs_parse_drive_prefix(&resolved_path, &drive); eynfs_files[fd].drive = drive; eynfs_files[fd].offset = 0; eynfs_files[fd].mode = mode; uint8_t disk = drive; if (eynfs_read_superblock(disk, EYNFS_SUPERBLOCK_LBA, &eynfs_files[fd].sb) != 0 || eynfs_files[fd].sb.magic != EYNFS_MAGIC) { eynfs_files[fd].used = 0; return -1; } // Handle root directory specially if (strcmp(resolved_path, "/") == 0) { if (mode != 0) return -1; // Root can only be read memset(&eynfs_files[fd].entry, 0, sizeof(eynfs_dir_entry_t)); eynfs_files[fd].entry.type = EYNFS_TYPE_DIR; eynfs_files[fd].entry.first_block = eynfs_files[fd].sb.root_dir_block; eynfs_files[fd].parent_block = 0; eynfs_files[fd].entry_index = 0; return fd; } // Traverse path to find file/directory uint32_t parent_block, entry_idx; if (eynfs_traverse_path(disk, &eynfs_files[fd].sb, resolved_path, &eynfs_files[fd].entry, &parent_block, &entry_idx) != 0) { // File doesn't exist if (mode == 1 || mode == 2) { // Write or append mode // Create the file const char* filename = strrchr(resolved_path, '/'); if (!filename) filename = resolved_path; else filename++; // Skip the '/' // Get parent directory path char parent_path[256]; strncpy(parent_path, resolved_path, sizeof(parent_path)-1); parent_path[sizeof(parent_path)-1] = '\0'; char* last_slash = strrchr(parent_path, '/'); if (last_slash && last_slash != parent_path) { *last_slash = '\0'; } else { strcpy(parent_path, "/"); } // Get parent directory eynfs_dir_entry_t parent_entry; if (eynfs_traverse_path(disk, &eynfs_files[fd].sb, parent_path, &parent_entry, &parent_block, NULL) != 0) { eynfs_files[fd].used = 0; return -1; } // Create the file if (eynfs_create_entry(disk, &eynfs_files[fd].sb, parent_entry.first_block, filename, EYNFS_TYPE_FILE) != 0) { eynfs_files[fd].used = 0; return -1; } // Find the created entry if (eynfs_find_in_dir(disk, &eynfs_files[fd].sb, parent_entry.first_block, filename, &eynfs_files[fd].entry, &entry_idx) != 0) { eynfs_files[fd].used = 0; return -1; } eynfs_files[fd].parent_block = parent_entry.first_block; eynfs_files[fd].entry_index = entry_idx; if (mode == 2) { // Append mode eynfs_files[fd].offset = eynfs_files[fd].entry.size; } } else { eynfs_files[fd].used = 0; return -1; } } else { // File exists if (mode == 1) { // Write mode - truncate file eynfs_files[fd].entry.size = 0; eynfs_files[fd].entry.first_block = 0; } else if (mode == 2) { // Append mode eynfs_files[fd].offset = eynfs_files[fd].entry.size; } eynfs_files[fd].parent_block = parent_block; eynfs_files[fd].entry_index = entry_idx; } return fd; } // Legacy open function for compatibility int open(const char* path, int mode) { return eynfs_open(path, mode); } // Close a file descriptor int close(int fd) { if (fd < 0 || fd >= EYNFS_MAX_OPEN_FILES || !eynfs_files[fd].used) return -1; eynfs_files[fd].used = 0; return 0; } // Read from a file descriptor int read(int fd, void* buf, int size) { if (fd < 0 || fd >= EYNFS_MAX_OPEN_FILES || !eynfs_files[fd].used) return -1; eynfs_file_t* f = &eynfs_files[fd]; // Check if it's a directory if (f->entry.type == EYNFS_TYPE_DIR) { // For directories, return directory listing if (f->offset == 0) { // Read directory entries - use heap allocation (128*84=10752 bytes would overflow stack) const int max_entries = 48; // Limit to ~4KB heap allocation eynfs_dir_entry_t* entries = (eynfs_dir_entry_t*)malloc(max_entries * sizeof(eynfs_dir_entry_t)); if (!entries) return -1; int count = eynfs_read_dir_table(f->drive, f->entry.first_block, entries, max_entries); if (count < 0) { free(entries); return -1; } // Format as text listing char* text_buf = (char*)buf; int written = 0; for (int i = 0; i < count && written < size - 100; i++) { if (entries[i].name[0] != '\0') { int len = snprintf(text_buf + written, size - written, "%s%s\n", entries[i].name, entries[i].type == EYNFS_TYPE_DIR ? "/" : ""); if (len > 0) written += len; } } free(entries); f->offset = 1; // Mark as read return written; } return 0; // Directory already read } // Regular file read if (f->offset >= f->entry.size) return 0; int to_read = size; if (f->offset + to_read > f->entry.size) to_read = f->entry.size - f->offset; int n = eynfs_read_file(f->drive, &f->sb, &f->entry, buf, to_read, f->offset); if (n > 0) f->offset += n; return n; } // Write to a file descriptor int write(int fd, const void* buf, int size) { if (fd < 0 || fd >= EYNFS_MAX_OPEN_FILES || !eynfs_files[fd].used) return -1; eynfs_file_t* f = &eynfs_files[fd]; if (f->mode != 1 && f->mode != 2) return -1; // Can't write to directories if (f->entry.type == EYNFS_TYPE_DIR) return -1; // For append mode, we need to read existing data first if (f->mode == 2 && f->offset > 0) { // Read existing data uint8_t* existing_data = (uint8_t*)malloc(f->entry.size); if (!existing_data) return -1; int existing_read = eynfs_read_file(f->drive, &f->sb, &f->entry, existing_data, f->entry.size, 0); if (existing_read < 0) { free(existing_data); return -1; } // Combine existing data with new data uint8_t* combined_data = (uint8_t*)malloc(existing_read + size); if (!combined_data) { free(existing_data); return -1; } memcpy(combined_data, existing_data, existing_read); memcpy(combined_data + existing_read, buf, size); // Write combined data int n = eynfs_write_file(f->drive, &f->sb, &f->entry, combined_data, existing_read + size, f->parent_block, f->entry_index); free(existing_data); free(combined_data); if (n > 0) { f->offset = n; f->entry.size = n; } return n; } else { // Regular write (overwrite or new file) int n = eynfs_write_file(f->drive, &f->sb, &f->entry, buf, size, f->parent_block, f->entry_index); if (n > 0) { f->offset = n; f->entry.size = n; } return n; } } // Performance monitoring functions void eynfs_get_cache_stats(uint32_t* hits, uint32_t* misses) { if (hits) *hits = cache_hits; if (misses) *misses = cache_misses; } void eynfs_reset_cache_stats() { cache_hits = 0; cache_misses = 0; } void eynfs_cache_clear() { int can_write = eynfs_ctx_check(CAP_WRITE_FS, SCHED_COST_FS); if (can_write) { eynfs_cache_flush(0); // Flush all dirty blocks } if (eynfs_block_cache_is_enabled()) { for (uint32_t i = 0; i < g_block_cache_capacity; i++) { if (!can_write && g_block_cache[i].dirty) { // Refuse to drop dirty data if we cannot write it back. continue; } g_block_cache[i].valid = 0; g_block_cache[i].dirty = 0; g_block_cache[i].last_use = 0; } } // Clear directory cache for (int i = 0; i < EYNFS_DIR_CACHE_SIZE; i++) { if (dir_cache[i].entries) { free(dir_cache[i].entries); dir_cache[i].entries = NULL; } dir_cache[i].count = 0; dir_cache[i].sorted = 0; } // Clear free block cache free_block_cache_valid = 0; free_block_cache_count = 0; } // Enhanced block allocation with performance tracking int eynfs_alloc_block_fast(uint8 drive, eynfs_superblock_t *sb) { return eynfs_alloc_block_optimized(drive, sb); } // Streaming writer implementation --- #ifndef EYNFS_STREAM_DEBUG #define EYNFS_STREAM_DEBUG 0 #endif int eynfs_stream_begin(uint8 drive, const char* path, eynfs_stream_t* s) { if (!s || !path) return -1; memset(s, 0, sizeof(*s)); s->drive = drive; if (eynfs_read_superblock(drive, EYNFS_SUPERBLOCK_LBA, &s->sb) != 0) { if (EYNFS_STREAM_DEBUG) printf("[EYNFS:stream_begin] read_superblock failed\n"); return -101; } if (s->sb.magic != EYNFS_MAGIC || s->sb.block_size != EYNFS_BLOCK_SIZE) { if (EYNFS_STREAM_DEBUG) { printf("[EYNFS:stream_begin] invalid superblock magic=0x%X block=%u\n", (unsigned)s->sb.magic, (unsigned)s->sb.block_size); } return -102; } // Determine parent dir and file name char parent_path[256]; const char* filename = strrchr(path, '/'); if (filename) { size_t plen = filename - path; if (plen >= sizeof(parent_path)) { if (EYNFS_STREAM_DEBUG) printf("[EYNFS:stream_begin] parent path too long\n"); return -103; } memcpy(parent_path, path, plen); parent_path[plen] = '\0'; filename++; if (parent_path[0] == '\0') strcpy(parent_path, "/"); } else { strcpy(parent_path, "/"); filename = path; } eynfs_dir_entry_t parent_entry; uint32_t parent_block, entry_idx; if (eynfs_traverse_path(drive, &s->sb, parent_path, &parent_entry, &parent_block, NULL) != 0) { if (EYNFS_STREAM_DEBUG) printf("[EYNFS:stream_begin] traverse parent failed: '%s'\n", parent_path); return -104; } if (parent_entry.type != EYNFS_TYPE_DIR) { if (EYNFS_STREAM_DEBUG) printf("[EYNFS:stream_begin] parent not dir: '%s'\n", parent_path); return -105; } // If file exists, delete it to simplify streaming overwrite eynfs_dir_entry_t tmp; if (eynfs_find_in_dir(drive, &s->sb, parent_entry.first_block, filename, &tmp, &entry_idx) == 0) { int delrc = eynfs_delete_entry(drive, &s->sb, parent_entry.first_block, filename); if (EYNFS_STREAM_DEBUG) { printf("[EYNFS:stream_begin] overwriting '%s/%s' delrc=%d\n", parent_path, filename, delrc); } } int cr = eynfs_create_entry(drive, &s->sb, parent_entry.first_block, filename, EYNFS_TYPE_FILE); if (cr != 0) { if (EYNFS_STREAM_DEBUG) { printf("[EYNFS:stream_begin] create_entry failed parent='%s' name='%s' rc=%d\n", parent_path, filename, cr); } return -106; } if (eynfs_find_in_dir(drive, &s->sb, parent_entry.first_block, filename, &s->entry, &s->entry_index) != 0) { if (EYNFS_STREAM_DEBUG) { printf("[EYNFS:stream_begin] find-after-create failed parent='%s' name='%s'\n", parent_path, filename); } return -107; } s->parent_block = parent_entry.first_block; // `eynfs_create_entry()` allocates a data block for files. Reuse it as the // first streaming block to avoid leaking an unreferenced block. s->curr_block = s->entry.first_block; s->first_block = s->entry.first_block; s->size = 0; s->pos_in_block = 0; // Initialize the first block: next=0 and data cleared. if (s->curr_block != 0) { uint8 block[EYNFS_BLOCK_SIZE] = {0}; *(uint32_t*)block = 0; eynfs_cache_invalidate_block(s->curr_block); if (eynfs_cache_write_block(s->drive, s->curr_block, block) != 0) return -108; } return 0; } int eynfs_stream_write(eynfs_stream_t* s, const void* buf, size_t size) { if (!s || !buf || size==0) return 0; const uint8* p = (const uint8*)buf; size_t remaining = size; size_t total_written = 0; while (remaining > 0) { // Ensure we have a current block with space if (s->curr_block == 0 || s->pos_in_block >= (EYNFS_BLOCK_SIZE - 4)) { int new_block = eynfs_alloc_block(s->drive, &s->sb); if (new_block < 0) return -1; eynfs_cache_invalidate_block((uint32_t)new_block); // Initialize the new block's link to 0 and clear data area uint8 block[EYNFS_BLOCK_SIZE] = {0}; *(uint32_t*)block = 0; if (eynfs_cache_write_block(s->drive, (uint32_t)new_block, block) != 0) return -1; // Link from previous current block if it exists if (s->curr_block != 0) { uint8 prev[EYNFS_BLOCK_SIZE]; if (eynfs_cache_get_block(s->drive, s->curr_block, prev) != 0) return -1; *(uint32_t*)prev = (uint32_t)new_block; // store block number, not LBA eynfs_cache_invalidate_block(s->curr_block); if (eynfs_cache_write_block(s->drive, s->curr_block, prev) != 0) return -1; } s->curr_block = (uint32_t)new_block; if (s->first_block == 0) s->first_block = s->curr_block; s->pos_in_block = 0; } // Write into current block at current position uint8 block[EYNFS_BLOCK_SIZE]; if (eynfs_cache_get_block(s->drive, s->curr_block, block) != 0) return -1; size_t space = (EYNFS_BLOCK_SIZE - 4) - s->pos_in_block; size_t chunk = remaining < space ? remaining : space; memcpy(block + 4 + s->pos_in_block, p + total_written, chunk); eynfs_cache_invalidate_block(s->curr_block); if (eynfs_cache_write_block(s->drive, s->curr_block, block) != 0) return -1; s->pos_in_block += (uint16_t)chunk; s->size += chunk; total_written += chunk; remaining -= chunk; } return (int)total_written; } int eynfs_stream_end(eynfs_stream_t* s) { if (!s) return -1; // Update entry size/first_block. // NOTE: The directory table can span multiple blocks; do not read it into a // fixed 4KB buffer (that can miss the entry in large directories). s->entry.first_block = s->first_block; s->entry.size = s->size; const uint32_t entries_per_block = (uint32_t)((EYNFS_BLOCK_SIZE - 4) / sizeof(eynfs_dir_entry_t)); const uint32_t block_steps = s->entry_index / entries_per_block; const uint32_t slot = s->entry_index % entries_per_block; uint32_t cur = s->parent_block; const uint32_t max_blocks = 4096; // safety bound for (uint32_t i = 0; i < block_steps; ++i) { if (cur == 0 || i >= max_blocks) return -1; uint8 buf[EYNFS_BLOCK_SIZE]; if (eynfs_cache_get_block(s->drive, cur, buf) != 0) return -1; cur = *(uint32_t*)buf; } if (cur == 0) return -1; uint8 dirblk[EYNFS_BLOCK_SIZE]; if (eynfs_cache_get_block(s->drive, cur, dirblk) != 0) return -1; eynfs_dir_entry_t* entries = (eynfs_dir_entry_t*)(dirblk + 4); entries[slot] = s->entry; eynfs_cache_invalidate_block(cur); if (eynfs_cache_write_block(s->drive, cur, dirblk) != 0) return -1; eynfs_dir_cache_invalidate(s->parent_block); return 0; }