fix: fix ramdisk readdir inconsistency.

This commit is contained in:
2026-08-12 18:35:14 +02:00
parent 7f2d4b693f
commit a28b8ad0b2
8 changed files with 71 additions and 27 deletions
+1 -1
View File
@@ -12,4 +12,4 @@
#pragma once #pragma once
#define MONTAUK_BUILD_NUMBER 108 #define MONTAUK_BUILD_NUMBER 109
+13 -5
View File
@@ -262,6 +262,10 @@ namespace Fs::Ramdisk {
} }
int ReadDirAt(const char* path, const char** outNames, int maxEntries, int startIndex) { 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, // Reject outright if path names an existing regular file: without this,
// a file with no matching "path/..." children below falls through to // a file with no matching "path/..." children below falls through to
// the child-scan loop and returns 0 (empty), which callers that use // the child-scan loop and returns 0 (empty), which callers that use
@@ -269,10 +273,10 @@ namespace Fs::Ramdisk {
int selfIdx = FindEntryByPath(path); int selfIdx = FindEntryByPath(path);
if (selfIdx >= 0 && !fileTable[selfIdx].isDirectory) return -1; if (selfIdx >= 0 && !fileTable[selfIdx].isDirectory) return -1;
// Normalize path: skip leading '/' char normalized[MaxNameLen];
if (path[0] == '/') path++; int pathLen = NormalizePath(path, normalized, MaxNameLen);
if (pathLen < 0) return -1;
int pathLen = StrLen(path); path = normalized;
int count = 0; // entries written to outNames int count = 0; // entries written to outNames
int seen = 0; // matching direct children scanned so far (for startIndex skip) 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; const char* entryName = fileTable[i].name;
bool isChild = false; bool isChild = false;
const char* childName = entryName;
if (pathLen == 0) { if (pathLen == 0) {
// Root directory: find entries without '/' in them (or only trailing '/') // Root directory: find entries without '/' in them (or only trailing '/')
bool hasSlash = false; bool hasSlash = false;
@@ -311,11 +316,14 @@ namespace Fs::Ramdisk {
} }
} }
isChild = (!hasDeepSlash && restLen > 0); isChild = (!hasDeepSlash && restLen > 0);
childName = rest;
} }
if (!isChild) continue; if (!isChild) continue;
if (seen++ < startIndex) continue; // skip entries before the requested page 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. // A path with no directory entry and no children does not exist.
@@ -269,8 +269,8 @@ void filemanager_read_dir(FileManagerState* fm) {
fm->virtual_view = FM_VIRTUAL_VIEW_NONE; fm->virtual_view = FM_VIRTUAL_VIEW_NONE;
fm->entry_count = 0; fm->entry_count = 0;
// readdir returns full paths from the VFS (e.g. "man/fetch.1" instead // Current drivers return direct-child names. Retain prefix stripping for
// of just "fetch.1"). Compute the prefix to strip so we get basenames. // compatibility with images built against the older ramdisk behavior.
const char* after_drive = fm->current_path; const char* after_drive = fm->current_path;
for (int k = 0; after_drive[k]; k++) { for (int k = 0; after_drive[k]; k++) {
if (after_drive[k] == ':' && after_drive[k + 1] == '/') { 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]) { static int filemanager_read_all_children(const char* dir, char (**out)[256]) {
*out = nullptr; *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; const char* after_drive = dir;
for (int k = 0; after_drive[k]; k++) { for (int k = 0; after_drive[k]; k++) {
if (after_drive[k] == ':' && after_drive[k + 1] == '/') { if (after_drive[k] == ':' && after_drive[k + 1] == '/') {
+3 -4
View File
@@ -14,9 +14,8 @@
// App Manifest Scanning // App Manifest Scanning
// ============================================================================ // ============================================================================
// Extract the basename from a readdir entry. // Extract the basename from a readdir entry, tolerating both current
// readdir returns full paths from drive root (e.g. "apps/doom/") — // direct-child names and paths returned by older ramdisk kernels.
// strip the directory prefix and any trailing slash to get just "doom".
static void extract_basename(char* out, int outSz, const char* entry) { static void extract_basename(char* out, int outSz, const char* entry) {
// Strip trailing slash // Strip trailing slash
int len = montauk::slen(entry); int len = montauk::slen(entry);
@@ -89,7 +88,7 @@ void desktop_scan_apps(DesktopState* ds) {
scanned += got; scanned += got;
for (int i = 0; i < got; i++) { 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]; char dirname[64];
extract_basename(dirname, sizeof(dirname), entries[i]); extract_basename(dirname, sizeof(dirname), entries[i]);
if (dirname[0] == '\0') continue; if (dirname[0] == '\0') continue;
+1 -1
View File
@@ -258,7 +258,7 @@ inline void wallpaper_scan_dir(const char* dir_path, WallpaperFileList* list,
int total = montauk::readdir(dir_path, raw_names, 64); int total = montauk::readdir(dir_path, raw_names, 64);
if (total <= 0) return; 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; const char* after_drive = dir_path;
for (int k = 0; after_drive[k]; k++) { for (int k = 0; after_drive[k]; k++) {
if (after_drive[k] == ':' && after_drive[k + 1] == '/') { if (after_drive[k] == ':' && after_drive[k + 1] == '/') {
+44 -7
View File
@@ -124,23 +124,54 @@ bool installer_list_dir(const char* abs_dir, DirList* out) {
int scanned = 0; int scanned = 0;
int capacity = 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); int got = montauk::readdir_at(abs_dir, batch, BATCH, 0);
if (got < 0) return false; if (got < 0) return false;
while (got > 0) { while (got > 0) {
for (int i = 0; i < got; i++) { for (int i = 0; i < got; i++) {
const char* raw = batch[i]; const char* raw = batch[i];
int len = slen(raw); int raw_len = slen(raw);
bool is_dir = (len > 0 && raw[len - 1] == '/'); bool is_dir = (raw_len > 0 && raw[raw_len - 1] == '/');
if (is_dir) len--; if (is_dir) raw_len--;
if (len <= 0) continue; 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 ".." // Skip "." and ".."
const char* base = raw; 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; 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 == 1 && base[0] == '.') continue;
if (base_len == 2 && base[0] == '.' && base[1] == '.') 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); char* rel = (char*)montauk::malloc(len + 1);
if (!rel) { installer_free_dir_list(out); return false; } 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'; rel[len] = '\0';
out->entries[out->count].rel = rel; out->entries[out->count].rel = rel;
+4 -4
View File
@@ -284,10 +284,10 @@ void do_install();
void do_update(); void do_update();
bool installer_path_exists(const char* abs_path); bool installer_path_exists(const char* abs_path);
// Heap-backed directory listing. Entries are full paths relative to the // Heap-backed directory listing. Direct-child names from readdir are expanded
// drive root (e.g. "apps/doom"), with is_dir derived from the trailing '/' // to paths relative to the drive root (e.g. "apps/doom"), with is_dir derived
// the VFS appends to directory names. Survives nested listings, unlike the // from the trailing '/' the VFS appends to directory names. Survives nested
// kernel's rotating readdir scratch pages. // listings, unlike the kernel's rotating readdir scratch pages.
struct DirEntry { struct DirEntry {
char* rel; // path relative to drive root, no trailing '/' char* rel; // path relative to drive root, no trailing '/'
bool is_dir; bool is_dir;
+2 -2
View File
@@ -622,8 +622,8 @@ void wp_load_fonts() {
if (!raw) continue; if (!raw) continue;
int rl = montauk::slen(raw); int rl = montauk::slen(raw);
if (rl == 0 || raw[rl - 1] == '/') continue; // skip directories if (rl == 0 || raw[rl - 1] == '/') continue; // skip directories
// readdir may return a full internal path (ramdisk) or a bare name; // Current drivers return a bare name; accepting an older ramdisk's
// take everything after the last '/'. // internal path here is harmless and keeps mixed images compatible.
const char* base = raw; const char* base = raw;
for (int k = 0; raw[k]; k++) if (raw[k] == '/') base = raw + k + 1; for (int k = 0; raw[k]; k++) if (raw[k] == '/') base = raw + k + 1;
wp_register_font_file(base); wp_register_font_file(base);