From a28b8ad0b2932203b812e967f35f029f060fd6b5 Mon Sep 17 00:00:00 2001 From: Daniel Hammer Date: Wed, 12 Aug 2026 18:35:14 +0200 Subject: [PATCH] fix: fix ramdisk readdir inconsistency. --- kernel/src/Api/BuildNo.hpp | 2 +- kernel/src/Fs/Ramdisk.cpp | 18 +++++-- .../desktop/apps/filemanager/filesystem.cpp | 6 +-- programs/src/desktop/main.cpp | 7 ++- programs/src/desktop/wallpaper.hpp | 2 +- programs/src/installer/actions.cpp | 51 ++++++++++++++++--- programs/src/installer/installer.h | 8 +-- programs/src/wordprocessor/document.cpp | 4 +- 8 files changed, 71 insertions(+), 27 deletions(-) diff --git a/kernel/src/Api/BuildNo.hpp b/kernel/src/Api/BuildNo.hpp index 4e12ae4..6ed5512 100644 --- a/kernel/src/Api/BuildNo.hpp +++ b/kernel/src/Api/BuildNo.hpp @@ -12,4 +12,4 @@ #pragma once -#define MONTAUK_BUILD_NUMBER 108 +#define MONTAUK_BUILD_NUMBER 109 diff --git a/kernel/src/Fs/Ramdisk.cpp b/kernel/src/Fs/Ramdisk.cpp index e86d35c..aea7111 100644 --- a/kernel/src/Fs/Ramdisk.cpp +++ b/kernel/src/Fs/Ramdisk.cpp @@ -262,6 +262,10 @@ namespace Fs::Ramdisk { } int ReadDirAt(const char* path, const char** outNames, int maxEntries, int startIndex) { + if (path == nullptr || outNames == nullptr || maxEntries < 0 || startIndex < 0) { + return -1; + } + // Reject outright if path names an existing regular file: without this, // a file with no matching "path/..." children below falls through to // the child-scan loop and returns 0 (empty), which callers that use @@ -269,10 +273,10 @@ namespace Fs::Ramdisk { int selfIdx = FindEntryByPath(path); if (selfIdx >= 0 && !fileTable[selfIdx].isDirectory) return -1; - // Normalize path: skip leading '/' - if (path[0] == '/') path++; - - int pathLen = StrLen(path); + char normalized[MaxNameLen]; + int pathLen = NormalizePath(path, normalized, MaxNameLen); + if (pathLen < 0) return -1; + path = normalized; int count = 0; // entries written to outNames int seen = 0; // matching direct children scanned so far (for startIndex skip) @@ -280,6 +284,7 @@ namespace Fs::Ramdisk { const char* entryName = fileTable[i].name; bool isChild = false; + const char* childName = entryName; if (pathLen == 0) { // Root directory: find entries without '/' in them (or only trailing '/') bool hasSlash = false; @@ -311,11 +316,14 @@ namespace Fs::Ramdisk { } } isChild = (!hasDeepSlash && restLen > 0); + childName = rest; } if (!isChild) continue; if (seen++ < startIndex) continue; // skip entries before the requested page - outNames[count++] = entryName; + // ReadDir follows the same contract as ext2/FAT32: return the + // direct child's name, not its archive path relative to root. + outNames[count++] = childName; } // A path with no directory entry and no children does not exist. diff --git a/programs/src/desktop/apps/filemanager/filesystem.cpp b/programs/src/desktop/apps/filemanager/filesystem.cpp index 607796e..66af7f8 100644 --- a/programs/src/desktop/apps/filemanager/filesystem.cpp +++ b/programs/src/desktop/apps/filemanager/filesystem.cpp @@ -269,8 +269,8 @@ void filemanager_read_dir(FileManagerState* fm) { fm->virtual_view = FM_VIRTUAL_VIEW_NONE; fm->entry_count = 0; - // readdir returns full paths from the VFS (e.g. "man/fetch.1" instead - // of just "fetch.1"). Compute the prefix to strip so we get basenames. + // Current drivers return direct-child names. Retain prefix stripping for + // compatibility with images built against the older ramdisk behavior. const char* after_drive = fm->current_path; for (int k = 0; after_drive[k]; k++) { if (after_drive[k] == ':' && after_drive[k + 1] == '/') { @@ -433,7 +433,7 @@ void filemanager_read_system_configuration(FileManagerState* fm) { static int filemanager_read_all_children(const char* dir, char (**out)[256]) { *out = nullptr; - // Compute the prefix to strip (readdir returns drive-root-relative paths). + // Retain support for older ramdisks that returned drive-root-relative paths. const char* after_drive = dir; for (int k = 0; after_drive[k]; k++) { if (after_drive[k] == ':' && after_drive[k + 1] == '/') { diff --git a/programs/src/desktop/main.cpp b/programs/src/desktop/main.cpp index 98fb6c5..ce0c808 100644 --- a/programs/src/desktop/main.cpp +++ b/programs/src/desktop/main.cpp @@ -14,9 +14,8 @@ // App Manifest Scanning // ============================================================================ -// Extract the basename from a readdir entry. -// readdir returns full paths from drive root (e.g. "apps/doom/") — -// strip the directory prefix and any trailing slash to get just "doom". +// Extract the basename from a readdir entry, tolerating both current +// direct-child names and paths returned by older ramdisk kernels. static void extract_basename(char* out, int outSz, const char* entry) { // Strip trailing slash int len = montauk::slen(entry); @@ -89,7 +88,7 @@ void desktop_scan_apps(DesktopState* ds) { scanned += got; for (int i = 0; i < got; i++) { - // readdir returns paths like "apps/doom/" — extract just "doom" + // Strip the directory marker and tolerate old root-relative entries. char dirname[64]; extract_basename(dirname, sizeof(dirname), entries[i]); if (dirname[0] == '\0') continue; diff --git a/programs/src/desktop/wallpaper.hpp b/programs/src/desktop/wallpaper.hpp index 83bd6a6..1fd7dea 100644 --- a/programs/src/desktop/wallpaper.hpp +++ b/programs/src/desktop/wallpaper.hpp @@ -258,7 +258,7 @@ inline void wallpaper_scan_dir(const char* dir_path, WallpaperFileList* list, int total = montauk::readdir(dir_path, raw_names, 64); if (total <= 0) return; - // Compute prefix to strip (readdir returns full paths from VFS root) + // Retain support for older ramdisks that returned paths from the VFS root. const char* after_drive = dir_path; for (int k = 0; after_drive[k]; k++) { if (after_drive[k] == ':' && after_drive[k + 1] == '/') { diff --git a/programs/src/installer/actions.cpp b/programs/src/installer/actions.cpp index c189ee0..10efb0d 100644 --- a/programs/src/installer/actions.cpp +++ b/programs/src/installer/actions.cpp @@ -124,23 +124,54 @@ bool installer_list_dir(const char* abs_dir, DirList* out) { int scanned = 0; int capacity = 0; + // The filesystem API returns direct-child names. Keep this installer's + // historical root-relative representation because component filtering + // and recursive source paths operate on paths such as "sdk/tcc". + const char* dir_rel = abs_dir; + for (int i = 0; dir_rel[i]; i++) { + if (dir_rel[i] == ':') { + dir_rel += i + 1; + if (dir_rel[0] == '/') dir_rel++; + break; + } + } + int dir_rel_len = slen(dir_rel); + while (dir_rel_len > 0 && dir_rel[dir_rel_len - 1] == '/') dir_rel_len--; + int got = montauk::readdir_at(abs_dir, batch, BATCH, 0); if (got < 0) return false; while (got > 0) { for (int i = 0; i < got; i++) { const char* raw = batch[i]; - int len = slen(raw); + int raw_len = slen(raw); - bool is_dir = (len > 0 && raw[len - 1] == '/'); - if (is_dir) len--; - if (len <= 0) continue; + bool is_dir = (raw_len > 0 && raw[raw_len - 1] == '/'); + if (is_dir) raw_len--; + if (raw_len <= 0) continue; + + bool already_root_relative = false; + if (dir_rel_len > 0 && raw_len > dir_rel_len && + raw[dir_rel_len] == '/') { + already_root_relative = true; + for (int k = 0; k < dir_rel_len; k++) { + if (raw[k] != dir_rel[k]) { + already_root_relative = false; + break; + } + } + } + + int len = raw_len; + if (dir_rel_len > 0 && !already_root_relative) { + len += dir_rel_len + 1; + } // Skip "." and ".." const char* base = raw; - for (int k = 0; k < len; k++) + for (int k = 0; k < raw_len; k++) if (raw[k] == '/') base = raw + k + 1; - int base_len = (int)(len - (base - raw)); + int base_len = (int)(raw_len - (base - raw)); if (base_len == 1 && base[0] == '.') continue; if (base_len == 2 && base[0] == '.' && base[1] == '.') continue; @@ -156,7 +187,13 @@ bool installer_list_dir(const char* abs_dir, DirList* out) { char* rel = (char*)montauk::malloc(len + 1); if (!rel) { installer_free_dir_list(out); return false; } - montauk::memcpy(rel, raw, len); + int pos = 0; + if (dir_rel_len > 0 && !already_root_relative) { + montauk::memcpy(rel, dir_rel, dir_rel_len); + pos = dir_rel_len; + rel[pos++] = '/'; + } + montauk::memcpy(rel + pos, raw, raw_len); rel[len] = '\0'; out->entries[out->count].rel = rel; diff --git a/programs/src/installer/installer.h b/programs/src/installer/installer.h index 5e193ef..2ad2529 100644 --- a/programs/src/installer/installer.h +++ b/programs/src/installer/installer.h @@ -284,10 +284,10 @@ void do_install(); void do_update(); bool installer_path_exists(const char* abs_path); -// Heap-backed directory listing. Entries are full paths relative to the -// drive root (e.g. "apps/doom"), with is_dir derived from the trailing '/' -// the VFS appends to directory names. Survives nested listings, unlike the -// kernel's rotating readdir scratch pages. +// Heap-backed directory listing. Direct-child names from readdir are expanded +// to paths relative to the drive root (e.g. "apps/doom"), with is_dir derived +// from the trailing '/' the VFS appends to directory names. Survives nested +// listings, unlike the kernel's rotating readdir scratch pages. struct DirEntry { char* rel; // path relative to drive root, no trailing '/' bool is_dir; diff --git a/programs/src/wordprocessor/document.cpp b/programs/src/wordprocessor/document.cpp index c7aff35..24032c1 100644 --- a/programs/src/wordprocessor/document.cpp +++ b/programs/src/wordprocessor/document.cpp @@ -622,8 +622,8 @@ void wp_load_fonts() { if (!raw) continue; int rl = montauk::slen(raw); if (rl == 0 || raw[rl - 1] == '/') continue; // skip directories - // readdir may return a full internal path (ramdisk) or a bare name; - // take everything after the last '/'. + // Current drivers return a bare name; accepting an older ramdisk's + // internal path here is harmless and keeps mixed images compatible. const char* base = raw; for (int k = 0; raw[k]; k++) if (raw[k] == '/') base = raw + k + 1; wp_register_font_file(base);