feat: auto-mount boot drive to /boot; fix FAT32 deleted entry triple fault
continuous-integration/drone/push Build is passing

- Decode multiboot boot_device byte in storagemanager to identify boot device
- Add isBootDevice to TStorage_Device; propagate isBootDrive to volumes
- Create /boot VFS directory at init; mount boot volume there in auto_mount_volumes
- Fix getDirEntries skipping \-marked (deleted) FAT32 entries to prevent
  triple fault when navigating into a deleted directory via file picker
This commit is contained in:
2026-03-07 18:58:32 +00:00
parent deb513c925
commit 3a733b1559
14 changed files with 1063 additions and 310 deletions
+10 -9
View File
@@ -1,12 +1,13 @@
*.o
*.ppu
*.s
*.iso
*.elf
lib/
release/
doc/
bin/
/lib/*.ppu
/lib/*.a
/lib/*.o
/release/*.svg
/release/*.iso
/bin/*.bin
/iso/boot/asuro.bin
/*.iso
/*.md5
/*.sh~
/*.img
src/include/asuro.pas
localenv.json
+18 -18
View File
@@ -7,12 +7,12 @@ eff5f36e129bc22770d643173c426cbd src//gdt.pas
42d014445ddc0fd4d01c59e690795532 src//idt.pas
b40f0c2f3ec166a0bf2ebcf8f11f5614 src//irq.pas
160ca7f01eb779595a5c90174f8d545e src//isr.pas
f0d6b256c4c136d1538259cf11358665 src//kernel.pas
40ca8769ba09f7e2696f38a3b48687aa src//kernel.pas
c413d77a3264adb2d5337712c50e1925 src//lmemorymanager.pas
b290d919549d8b03a7568ff3323ccbca src//pmemorymanager.pas
4a5f5bf0f2ca7fff89074ad02813091c src//processloader.pas
303a93d2dce68d91d454bcd7e883c5d5 src//processmanager.pas
99c3d17476d2fe34b04a848514a880f2 src//progmanager.pas
ab28dadd75652c6f3c655c7941fdb823 src//processmanager.pas
ba78e851b043241d0099c0a67d385362 src//progmanager.pas
81d4b5c4a25a1a855d75a88c725b77fe src//scheduler.pas
1be263e53e4838f6a06d2ece86400ef2 src//stdio.pas
940a7ed5808841c6ba344b67965ecba7 src//syslog.pas
@@ -26,7 +26,6 @@ fe1dba288d3a3ab427a9dea0a9f747ae src//tracer.pas
1a107558af58e8d4d689ae67582913e8 src/driver/bus/usb/UHCI.pas
6edd4a1e9c92e092aea1368d9aa2724f src/driver/bus/usb/USB.pas
33d50f2daa362859d80a1a684e69f75b src/driver/bus/usb/XHCI.pas
442d27dfbf472cf94bb51ae08f203675 src/driver/bus/usb/usb_storage.pas
7694d4aeb0878b22792996a59efa7607 src/driver/bus/usb/usbcore.pas
e8aabf9e1ba0c09ede43ed2844802aa0 src/driver/bus/usb/usbhub.pas
ea992f0cedb8a760d2b6d3f23ff88a86 src/driver/bus/usb/usbtypes.pas
@@ -57,18 +56,19 @@ a2b9bf4fe7d878b73fe1af51e7d050b1 src/driver/storage/fdtable.pas
c305190af3b74ae62992653d4c1a5cf8 src/driver/storage/iobuffer.pas
82766a71b0f186f1be6832493fb84bdc src/driver/storage/iorequest.pas
08a8280503120f3a90a9a041bc8b67d3 src/driver/storage/partitiontable.pas
7b2c0fddec72e7ed25f7aee080e24465 src/driver/storage/storagemanager.pas
3285573a47389c43a7e47f872440964c src/driver/storage/storagetest.pas
91c530b1e023160a79cf942ba386805d src/driver/storage/storagetypes.pas
b085ff2efcb7c812524ad840b7cdf315 src/driver/storage/vfs.pas
bac08992d201dc839edcc29818b55c75 src/driver/storage/volumemanager.pas
d67ae26b98a12080f5ae3065b4352c9b src/driver/storage/storagemanager.pas
260d1bc1716be4347d14f3cb2d92ccde src/driver/storage/storagetest.pas
27ea69c2f0bdfb642a5beafdf71ec3d4 src/driver/storage/storagetypes.pas
430a17272f1a2995ba6803d575ef9d3c src/driver/storage/usb_storage.pas
a03e87379ad290860ccbad1d822b37df src/driver/storage/vfs.pas
60fde551085ffa111ee913bea8791b44 src/driver/storage/volumemanager.pas
71ad146cd66801b5bc7992d57a7ecf31 src/driver/storage/AHCI/AHCI.pas
16304358a6278c23a751e31bb464eae5 src/driver/storage/AHCI/AHCITypes.pas
b5e3bcb914b585a95ac646cfea7d016e src/driver/storage/Filesystems/asfs.pas
a4a0def26868069ed7b734fa78efef12 src/driver/storage/Filesystems/fat32.pas
df16abe38aafcb4bb02130baa6dc1607 src/driver/storage/Filesystems/fat32.pas
9bceba956d3eeffe97e07bdd45a9476f src/driver/storage/Filesystems/filesystemmanager.pas
a0ea5cc8bdc152492312d444f249f3c1 src/driver/storage/Filesystems/flatfs.pas
b16930bae1c7b49281582b0c6a601c4f src/driver/storage/Filesystems/iso9660.pas
9ef2de4df40c4b57af2deb297abfd664 src/driver/storage/Filesystems/iso9660.pas
ed26491124206842b40c219d2af4d70d src/driver/storage/IDE/IDE.pas
0030f888faa8ef6c89963be0b6841289 src/driver/storage/IDE/ata.pas
68665eec07c019f5fdb874b52d3be03b src/driver/storage/IDE/atapi.pas
@@ -78,9 +78,9 @@ c025ffabb5bba2272578d04903637d11 src/driver/timers/TMR_0_ISR.pas
4a7434e5567f738b8545d56558902d9f src/driver/timers/TMR_1_ISR.pas
11aad5f2638a14546abe30ce19c0a807 src/driver/timers/graphicsrefresh.pas
4503465b3f62464f25e17e1d1a67e54d src/driver/timers/usbhotplug.pas
0434cc7b77429f971d372e89a8a6972d src/driver/video/desktop.pas
8d5f544ab27e5057d95f0f8069b8c2f6 src/driver/video/desktop.pas
fef57dc0269caad191652df07da04466 src/driver/video/doublebuffer.pas
f6038a53763be5d9c1f66fb8baacd83e src/driver/video/lvgl.pas
357cca32ea92028137551608dd5b0e82 src/driver/video/lvgl.pas
dce8755865885e41c08b062c162b06c9 src/driver/video/uidebug.pas
20dc95321989d632fa4c39a5faf74733 src/driver/video/vesa.pas
19c988cfcc36bcc78e73747662672d8f src/driver/video/vesa16.pas
@@ -114,18 +114,18 @@ dee8388dac0ca79ee6aef73469d75f6b src/include/bios_data_area.pas
717b8c0a42983f6b8864d5deda4dd532 src/include/color.pas
1987f262a6053e02080023554d942ae1 src/include/crc.pas
7f6c812b5d7ae1a8af5ef00d42a4eb1d src/include/fonts.pas
bf9fe2883789147e08980ab4c43220f7 src/include/hashmap.pas
9cc7742497cf9a1940b9d400140348a6 src/include/hashmap.pas
513d59ae08ba6d753a871e1a82b46ed4 src/include/lists.pas
c5ef14ac60dad5a32ebfa43da153d05c src/include/md5.pas
124f12d75de7ade6993ccd2f02b53ad4 src/include/multiboot.pas
1bb33ff2af8c360774d9c260f8036d2e src/include/proctypes.pas
c3cb83dddcf53d87c14f67f5ab4bbe5e src/include/proctypes.pas
d8beadde6ebb05c8a3e74aae57d61f34 src/include/rand.pas
cfa50e59e4bd9ea3a5b05c6b1275858d src/include/strings.pas
cb254f95c5e09595c90db690bf98794f src/include/system.pas
5df11e0052010580d13f1a5ce3acd60f src/include/targa.pas
833967cf14697db6da421a4be58e9044 src/include/texture.pas
635e5a8936a088a244ddfe48cd658a3f src/include/types.pas
cde48ecd2a80195da906f0d1e2658714 src/include/util.pas
ebf13bf342b063a79345613639c9b114 src/include/util.pas
d6a64b1434e1b528d1ba57171a9918ce src/include/data_structures/bheap.pas
54bc51933c982219ea4445e158a7c4ee src/include/data_structures/cfifo.pas
51628ff135cf306cb9b0a0fac24f8c73 src/include/data_structures/cfifols.pas
@@ -144,11 +144,11 @@ e23b0bef333262860e1f68034331e529 src/isr/ioapic.pas
f90540cb40f741dc70b1ae7435cf966b src/prog/diskcmd.pas
cf729bd7af2f1bbb6319b3466ecb173a src/prog/diskutil.pas
93837d26fcacbcc6e5d339de51a666ff src/prog/edit.pas
99c3cf044d474264f7af27c1dcbeb1ca src/prog/filepicker.pas
b2dd736b9c8b93dfae3962422a597663 src/prog/filepicker.pas
190f559362d0ea3b0ebfdde710364fbb src/prog/inio.pas
1ff556cd50088e3ee3efa3103fdc6ba0 src/prog/md5sum.pas
1161e238b85f6b59debbc6a507dd9478 src/prog/meminfo.pas
3df29f500456aa285b10cc7fd7f05cda src/prog/notepad.pas
80c1abd52c36a13114cd952704b6f455 src/prog/notepad.pas
cd03ccb60a3a3f0708fad6e70e2559c2 src/prog/partcmd.pas
f47e06c6989dfb9a497b6efd024e5863 src/prog/ping.pas
24289df8b6d60736a48633a927cfeb06 src/prog/testcmd.pas
Binary file not shown.
+14
View File
@@ -24,3 +24,17 @@ The correct way to block a process on an async I/O result is: (1) set up a compl
## 9. VFS GetDirectoryListing Must Return a Snapshot, Not a Live Tree Reference
`GetDirectoryListing` for `otVDIRECTORY` nodes previously returned `PHashMap(Obj^.Reference)` — a direct pointer into the live VFS tree. When callers called `FreeDirectoryListing` on the returned map, it freed the live VFSObjects and their name strings, destroying the VFS tree. Any subsequent `GetObjectFromPath` call would then dereference the trashed memory and page fault. **Fix**: always return a heap-allocated snapshot copy of the directory (new `PHashMap`, new `PVFSObject` entries with `stringCopy`'d keys). `FreeDirectoryListing` can then safely own and free the snapshot without touching the live tree. The rule: any function that returns a `PHashMap` to a caller who will call `FreeDirectoryListing` must allocate a fresh map — never return a raw interior pointer to a live data structure.
## 10. Never Call Synchronous Disk I/O From an LVGL Timer or Event Callback
LVGL timer callbacks (`lv_timer_create`) and event callbacks run synchronously inside the LVGL render loop. Calling any function that parks the current process waiting for disk I/O (e.g. `vfs.FileSize`, `vfs.OpenFile`, `vfs.ReadFile`) from inside a timer or event callback will hang the entire UI — the process blocks mid-callback and the LVGL loop never resumes. All disk I/O that must occur in a UI context must either: (a) use the async VFS API (`OpenFileAsync`, `WriteFileAsync`) with a completion callback that triggers a deferred LVGL update, or (b) be deferred to a dedicated worker process that signals back via a flag the UI can poll. `GetDirectoryListingFrom` is the one bulk exception — it was already present in the original picker and works because it loads into a snapshot synchronously as a single operation; per-file calls like `FileSize` in a loop are the problem.
## 11. Never Allocate Large Arrays as Local Variables in Deep Kernel Call Chains
Local variables are stack-allocated in Pascal. Kernel processes have a fixed, limited stack (≈8 KB). In deep LVGL callback chains (kernel → LVGL render loop → timer callback → app code), there may only be a few KB of usable stack remaining by the time app code executes. Declaring local arrays larger than ~200400 bytes (e.g. `array[0..255] of pchar` = 1024 bytes) inside any procedure that runs in this context will silently overflow the stack, corruptung adjacent memory and causing a page fault with a garbled call trace. **Rule**: any local variable array larger than ~128 bytes in a UI callback or procedure called from one must be moved to the heap with `kalloc`/`kfree`. The corruption pattern is a page fault with a call stack full of completely unrelated function names.
## 12. Pascal Silently Ignores Everything After `end.` — Dead Code Masquerades as Live Code
FPC compiles everything up to and including the first `end.` and silently discards the rest of the file. If a file is partially rewritten by prepending a new implementation but the old implementation is not deleted, the old code survives after `end.`. Forward declarations resolve to the **last** implementation before `end.` — so if the new code was added before the old `end.` but the old procedures were in the "dead" zone they have no effect. However the reverse is dangerous: if the old `end.` was replaced/removed and the new code placed before it, the old procedures in the dead zone still compile to nothing but pollute the source. **Rule**: after any large-scale replacement of a Pascal unit, explicitly check where `end.` falls with a text search and PowerShell-truncate the file at the first `end.`. Never assume a multi-step replacement tool correctly removed all old code — always verify with `Select-String -Pattern "^end\.$"`.
## 13. `for i := 0 to uint32_count - 1` Wraps to 4 Billion When Count is Zero
In Free Pascal, `uint32` arithmetic wraps: if `dir_count` is `uint32` and equals `0`, then `dir_count - 1 = 0xFFFFFFFF`. A `for i := 0 to dir_count - 1 do` loop with zero count does NOT skip — it runs 4 billion iterations, immediately accessing invalid memory and page faulting. FPC does not emit a warning for this. **Rule**: always guard `uint32`-bounded for loops: `if count > 0 then for i := 0 to count - 1 do`. Alternatively, use `sint32` for loop counters that might legitimately be zero.
## 14. FAT32 Deleted Entries ($E5) Must Be Filtered in getDirEntries
When a FAT32 directory entry is deleted, `fileName[0]` is overwritten with `$E5` but the rest of the entry (including the cluster pointer) remains intact on disk. `getDirEntries` must explicitly skip entries where `fileName[0] = char($E5)` — stopping only at `char(0)` (end-of-directory) is not enough. If `$E5` entries are returned to callers, they appear in directory listings with a garbage first character, and navigating into them follows a stale/freed cluster chain, which causes reads into arbitrary memory or FAT data, leading to a triple fault. Fix location: the `while i < maxEntries` loop in `fat32.getDirEntries`.
+9
View File
@@ -490,6 +490,12 @@ begin
while i < maxEntries do begin
if PDirectory(buffer)[i].fileName[0] = char(0) then break;
{ Skip deleted entries ($E5 = FAT32 deleted marker) }
if PDirectory(buffer)[i].fileName[0] = char($E5) then begin
i+=1;
continue;
end;
dirElm:= LL_Add(directories);
PDirectory(dirElm)^:= PDirectory(buffer)[i];
i+=1;
@@ -535,6 +541,9 @@ begin
for i:= 0 to LL_Size(list) - 1 do begin
dir := PDirectory(LL_get(list, i));
{ Skip deleted entries (should already be filtered by getDirEntries, but guard here too) }
if dir^.fileName[0] = char($E5) then continue;
{ Trim trailing spaces from fileName (8 bytes) }
nameLen := 8;
while (nameLen > 0) and (dir^.fileName[nameLen - 1] = ' ') do
+1 -1
View File
@@ -448,7 +448,7 @@ begin
volume^.sectorCount := pvd^.volumeSpaceSizeLSB;
volume^.freeSectors := 0;
volume^.filesystem := @filesystem;
volume^.isBootDrive := false;
volume^.isBootDrive := disk^.isBootDevice;
volumemanager.register_volume(disk, volume);
+50 -1
View File
@@ -52,7 +52,15 @@ var
storageDevices : PDList;
nextDeviceId : uint32;
{ BIOS drive byte from multiboot — set once during kernel init before any
device drivers run. $80=first HDD, $81=second HDD, $9F/other=CD-ROM. }
bootDriveByte : uint8;
hdCount : uint32; { non-ATAPI devices registered so far }
atapiCount : uint32; { ATAPI devices registered so far }
procedure init();
procedure set_boot_drive_byte(driveByte : uint8);
function get_boot_drive_byte() : uint8;
procedure register_device(device : PStorage_Device);
function get_device_list() : PDList;
function get_device_count() : uint32;
@@ -147,7 +155,22 @@ procedure init();
begin
push_trace('StorageManager.init');
storageDevices := DL_New(sizeof(TStorage_Device));
nextDeviceId := 0;
nextDeviceId := 0;
bootDriveByte := $FF; { $FF = unknown/uninitialised }
hdCount := 0;
atapiCount := 0;
end;
procedure set_boot_drive_byte(driveByte : uint8);
begin
bootDriveByte := driveByte;
syslog.log('STRMGR', 'Boot drive BIOS byte = 0x');
syslog.writehexln(driveByte);
end;
function get_boot_drive_byte() : uint8;
begin
get_boot_drive_byte := bootDriveByte;
end;
procedure register_device(device : PStorage_Device);
@@ -163,6 +186,7 @@ begin
device^.requestQueue := CFIFO_New(SizeOf(TIORequest), 32);
device^.activeRequest := nil;
device^.cachedMBR := nil;
device^.isBootDevice := false;
{ dispatchRead/dispatchWrite: left as-is — driver sets them before
calling register_device. memset(0) guarantees nil for unmigrated drivers. }
@@ -172,6 +196,31 @@ begin
{ Get pointer to the DList copy so volumes reference the canonical device }
storedDev := PStorage_Device(DL_Get(storageDevices, DL_Size(storageDevices) - 1));
{ Identify whether this is the boot device using the BIOS drive byte.
BIOS numbers HDDs from $80 upward; the nth HDD is $80 + (n-1).
ATAPI/CD-ROM devices are NOT in the $80-range HDD sequence, so if
bootDriveByte does not match any HDD we have seen, the first ATAPI
device is marked as the boot device (covers standard ISO boot). }
if (storedDev^.controller = ControllerATAPI) or
(storedDev^.controller = ControllerAHCI_ATAPI) then begin
{ ATAPI device: boot if the drive byte is outside the known HDD range }
if bootDriveByte < ($80 + hdCount) then begin
{ drive byte refers to an HDD — this ATAPI is not the boot device }
end else if atapiCount = 0 then begin
{ first ATAPI and boot byte is not a known HDD → this is the boot drive }
storedDev^.isBootDevice := true;
syslog.logln('STRMGR', 'Boot device identified (ATAPI/ISO).');
end;
atapiCount := atapiCount + 1;
end else begin
{ Non-ATAPI (HDD): boot if BIOS drive byte matches $80 + index }
if bootDriveByte = ($80 + hdCount) then begin
storedDev^.isBootDevice := true;
syslog.logln('STRMGR', 'Boot device identified (HDD).');
end;
hdCount := hdCount + 1;
end;
{ Discover partitions and register volumes automatically }
if storedDev^.maxSectorCount > 0 then
volumemanager.discover_volumes(storedDev);
+1
View File
@@ -209,6 +209,7 @@ type
sectorSize : uint32; //in bytes
start : uint32; //start of device in sectors
cachedMBR : pointer; { cached MBR data, nil if not yet read }
isBootDevice : boolean; { true if this is the device GRUB booted from }
end;
{ Filesystem driver descriptor }
+8
View File
@@ -430,6 +430,7 @@ begin
if (dirList <> nil) and (status^ = 0) and (LL_Size(dirList) > 0) then begin
for i := 0 to LL_Size(dirList) - 1 do begin
entry := PDirectory_Entry(LL_Get(dirList, i));
if (entry = nil) or (entry^.fileName = nil) then continue;
newObj := PVFSObject(kalloc(sizeof(TVFSObject)));
newObj^.Parent := Parent;
newObj^.Reference := nil;
@@ -2062,6 +2063,12 @@ begin
syslog.writestring(' volume at ');
syslog.writestringln(mountPath);
{ If this is the boot volume, also mount it at /boot }
if vol^.isBootDrive then begin
mountVolume('/boot', vol);
syslog.writestringln('VFS: Boot volume mounted at /boot');
end;
kfree(void(volName));
kfree(void(prefix));
kfree(void(mountPath));
@@ -2135,6 +2142,7 @@ begin
newVirtualDirectory('/disk');
newVirtualDirectory('/mnt');
newVirtualDirectory('/cfg');
newVirtualDirectory('/boot');
{ Register Terminal Commands }
stdio.registerCommand('LS', @VFS_COMMAND_LS, 'List directory contents.');
+1 -1
View File
@@ -81,7 +81,7 @@ begin
volume^.sectorSize := device^.sectorSize;
volume^.freeSectors := 0;
volume^.filesystem := nil;
volume^.isBootDrive := false;
volume^.isBootDrive := device^.isBootDevice;
filesystemmanager.probe_volume(volume);
register_volume(device, volume);
end;
+27 -1
View File
@@ -22,7 +22,12 @@ unit hashmap;
interface
uses
md5, util, strings, lmemorymanager, tracer, syslog;
lmemorymanager,
md5,
strings,
syslog,
tracer,
util;
type
DPHashItem = ^PHashItem;
@@ -45,12 +50,16 @@ const
HASHMAP_DEFAULT_SIZE = 16;
HASHMAP_DEFAULT_LOADFACTOR = 0.75;
type
THashForEachCb = procedure(key: pchar; data: void; ud: void);
function new : PHashMap;
function newEx(size : uint32; loadFactor : Single) : PHashMap;
procedure add(map : PHashMap; key : pchar; value : void);
function get(map : PHashMap; key : pchar) : void;
procedure delete(map : PHashMap; key : pchar; freeItem : boolean);
procedure printMap(map : PHashMap);
procedure forEach(map : PHashMap; cb : THashForEachCb; ud : void);
implementation
@@ -257,6 +266,23 @@ begin
end;
end;
procedure forEach(map : PHashMap; cb : THashForEachCb; ud : void);
var
i : uint32;
item : PHashItem;
begin
if (map = nil) or (map^.Size = 0) or (map^.Table = nil) then exit;
if cb = nil then exit;
for i := 0 to map^.Size - 1 do begin
item := map^.Table[i];
while item <> nil do begin
if item^.Key <> nil then
cb(item^.Key, item^.Data, ud);
item := item^.Next;
end;
end;
end;
procedure printMap(map : PHashMap);
var
i,c : uint32;
+1
View File
@@ -230,6 +230,7 @@ begin
drivermanagement.init();
tracer.push_trace('kmain.STRMGMT');
storagemanager.init();
storagemanager.set_boot_drive_byte((multibootinfo^.boot_device shr 24) and $FF);
volumemanager.init();
filesystemmanager.init();
+902 -213
View File
File diff suppressed because it is too large Load Diff
+21 -66
View File
File diff suppressed because it is too large Load Diff