diff --git a/kernel/src/Api/BuildNo.hpp b/kernel/src/Api/BuildNo.hpp index 95c263b..2acf12d 100644 --- a/kernel/src/Api/BuildNo.hpp +++ b/kernel/src/Api/BuildNo.hpp @@ -12,4 +12,4 @@ #pragma once -#define MONTAUK_BUILD_NUMBER 221 +#define MONTAUK_BUILD_NUMBER 223 diff --git a/kernel/src/Api/Storage.hpp b/kernel/src/Api/Storage.hpp index 9ec194c..9cdc89c 100644 --- a/kernel/src/Api/Storage.hpp +++ b/kernel/src/Api/Storage.hpp @@ -57,6 +57,48 @@ namespace montauk::abi { return count; } + // Fill a user buffer with one entry per mounted volume. Returns the number + // of entries written, or -1 for a bad buffer. + static int Sys_VolInfo(VolInfo* buf, int maxCount) { + if (buf == nullptr || maxCount <= 0) return 0; + if (!UserMemory::Range((uint64_t)buf, (uint64_t)maxCount * sizeof(VolInfo), true)) return -1; + + int drives[Fs::Vfs::MaxDrives]; + int n = Fs::Vfs::VfsDriveList(drives, Fs::Vfs::MaxDrives); + if (n > maxCount) n = maxCount; + + for (int i = 0; i < n; i++) { + VolInfo& v = buf[i]; + v = VolInfo{}; + v.drive = drives[i]; + v.blockDev = -1; + v.partIndex = -1; + + Fs::Vfs::VfsDriveLabel(v.drive, v.label, sizeof(v.label)); + Fs::Vfs::VfsDriveFsType(v.drive, v.fsType, sizeof(v.fsType)); + + v.partIndex = Fs::FsProbe::GetPartitionForDrive(v.drive); + if (v.partIndex < 0) continue; // ramdisk: no backing partition + + auto* part = Drivers::Storage::Gpt::GetPartition(v.partIndex); + if (part == nullptr) continue; + + v.blockDev = part->BlockDevIndex; + v.startLba = part->StartLba; + v.sectorCount = part->SectorCount; + dl_strcpy_s(v.partName, part->Name, sizeof(v.partName)); + + auto* dev = Drivers::Storage::GetBlockDevice(v.blockDev); + if (dev == nullptr) continue; + + v.kind = dev->Kind; + v.sectorSize = dev->SectorSize; + dl_strcpy_s(v.model, dev->Model, sizeof(v.model)); + } + + return n; + } + // Read sectors from a block device. Returns bytes read, or -1 on error. static int64_t Sys_DiskRead(int blockDev, uint64_t lba, uint32_t count, void* buffer) { if (buffer == nullptr || count == 0) return -1; diff --git a/kernel/src/Api/Syscall.cpp b/kernel/src/Api/Syscall.cpp index 45e568d..4d26c2c 100644 --- a/kernel/src/Api/Syscall.cpp +++ b/kernel/src/Api/Syscall.cpp @@ -29,7 +29,7 @@ #include "MemInfo.hpp" // SYS_MEMSTATS #include "Device.hpp" // SYS_DEVLIST, SYS_DISKINFO #include "Input.hpp" // SYS_INPUT_WAIT -#include "Storage.hpp" // SYS_PARTLIST, SYS_DISKREAD, SYS_DISKWRITE +#include "Storage.hpp" // SYS_PARTLIST, SYS_VOLINFO, SYS_DISKREAD, SYS_DISKWRITE #include "Window.hpp" // SYS_WINCREATE, SYS_WINDESTROY, SYS_WINPRESENT, SYS_WINPOLL, SYS_WINENUM, SYS_WINMAP, SYS_WINSENDEVENT, SYS_WINRESIZE, SYS_WINSETCURSOR, SYS_WINSETFLAGS, SYS_WINSETSCALE, SYS_WINGETSCALE #include "Audio.hpp" // SYS_AUDIOOPEN, SYS_AUDIOCLOSE, SYS_AUDIOWRITE, SYS_AUDIOCTL #include "BluetoothSyscall.hpp" // SYS_BTSCAN, SYS_BTCONNECT, SYS_BTDISCONNECT, SYS_BTLIST, SYS_BTINFO @@ -425,6 +425,10 @@ namespace montauk::abi { if ((int64_t)frame->arg2 < 0) return -1; if (!UserMemory::Range(frame->arg1, (uint64_t)frame->arg2 * sizeof(PartInfo), true)) return -1; return (int64_t)Sys_PartList((PartInfo*)frame->arg1, (int)frame->arg2); + case SYS_VOLINFO: + if ((int64_t)frame->arg2 < 0) return -1; + if (!UserMemory::Range(frame->arg1, (uint64_t)frame->arg2 * sizeof(VolInfo), true)) return -1; + return (int64_t)Sys_VolInfo((VolInfo*)frame->arg1, (int)frame->arg2); case SYS_DISKREAD: if (!Sched::HasCapability(CAP_RAW_STORAGE)) return SYS_ERR_PERMISSION; return (int64_t)Sys_DiskRead((int)frame->arg1, frame->arg2, diff --git a/kernel/src/Api/Syscall.hpp b/kernel/src/Api/Syscall.hpp index 565c2aa..cb4c713 100644 --- a/kernel/src/Api/Syscall.hpp +++ b/kernel/src/Api/Syscall.hpp @@ -349,6 +349,9 @@ namespace montauk::abi { /* Process.hpp -- the caller's own capability masks. */ static constexpr uint64_t SYS_GETCAPS = 189; // (SpawnCapabilities*) -> 0, -1 bad pointer + /* Storage.hpp */ + static constexpr uint64_t SYS_VOLINFO = 190; // (VolInfo*, maxCount) -> count, -1 bad pointer + /* Kernel-owned process capabilities. User identities may namespace per-user resources, but never participate in authorization decisions. */ static constexpr uint64_t CAP_PROCESS_ADMIN = 1ULL << 0; @@ -679,6 +682,28 @@ namespace montauk::abi { char typeName[24]; // human-readable type name }; + // One mounted volume (returned by SYS_VOLINFO). A volume is a single + // mounted filesystem, which is what a drive number names: one physical + // disk with three partitions occupies three drive numbers. + // + // A volume with no backing partition -- the boot ramdisk -- reports + // blockDev and partIndex as -1 and leaves the geometry fields zero. + struct VolInfo { + int32_t drive; // VFS drive number ("N:/") + int32_t blockDev; // backing block device index, -1 if none + int32_t partIndex; // global GPT partition index, -1 if none + uint32_t kind; // BlockDeviceKind: 0=none/ramdisk, 1=SATA, 2=SATAPI, 3=NVMe, 4=USB MSC + uint64_t startLba; // partition start LBA, 0 if none + uint64_t sectorCount; // partition length in sectors, 0 if none + uint32_t sectorSize; // logical sector size in bytes, 0 if none + uint32_t _pad0; + char label[36]; // filesystem volume label, "" if unset + char fsType[12]; // "ext2", "FAT32", "ramdisk", "" if unknown + char partName[72]; // GPT partition name, "" if none + char model[41]; // backing disk model, "" if none + char _pad1[3]; + }; + struct GptAddParams { int32_t blockDev; uint32_t _pad0; diff --git a/kernel/src/Fs/Boot.cpp b/kernel/src/Fs/Boot.cpp index 06a53f2..9874fc9 100644 --- a/kernel/src/Fs/Boot.cpp +++ b/kernel/src/Fs/Boot.cpp @@ -67,6 +67,7 @@ namespace Fs { int RdMkdir(void*, const char* p) { return Ramdisk::Mkdir(p); } int RdRename(void*, const char* o, const char* n) { return Ramdisk::Rename(o, n); } const char* RdGetLabel(void*) { return Ramdisk::GetLabel(); } + const char* RdGetFsType(void*) { return "ramdisk"; } int RdReadDirAt(void*, const char* p, const char** o, int m, int s) { return Ramdisk::ReadDirAt(p, o, m, s); } int RdStat(void*, const char* p, Vfs::StatInfo* out) { @@ -99,6 +100,7 @@ namespace Fs { .Mkdir = RdMkdir, .Rename = RdRename, .GetLabel = RdGetLabel, + .GetFsType = RdGetFsType, .ReadDirAt = RdReadDirAt, .Stat = RdStat, .SetTimes = nullptr, diff --git a/kernel/src/Fs/Ext2.cpp b/kernel/src/Fs/Ext2.cpp index 0bf7db4..3f21539 100644 --- a/kernel/src/Fs/Ext2.cpp +++ b/kernel/src/Fs/Ext2.cpp @@ -1861,6 +1861,7 @@ namespace Fs::Ext2 { static int DrvMkdir(void* c, const char* p) { return MkdirImpl(CtxToInst(c), p); } static int DrvRename(void* c, const char* o, const char* n) { return RenameImpl(CtxToInst(c), o, n); } static const char* DrvGetLabel(void* c) { return GetLabelImpl(CtxToInst(c)); } + static const char* DrvGetFsType(void*) { return "ext2"; } static int DrvStat(void* c, const char* p, Vfs::StatInfo* o) { return StatImpl(CtxToInst(c), p, o); } static int DrvSetTimes(void* c, const char* p, int64_t a, int64_t m) { return SetTimesImpl(CtxToInst(c), p, a, m); } @@ -2071,6 +2072,7 @@ namespace Fs::Ext2 { .Mkdir = DrvMkdir, .Rename = DrvRename, .GetLabel = DrvGetLabel, + .GetFsType = DrvGetFsType, .ReadDirAt = DrvReadDirAt, .Stat = DrvStat, .SetTimes = DrvSetTimes, diff --git a/kernel/src/Fs/Fat32.cpp b/kernel/src/Fs/Fat32.cpp index 514135f..d66b565 100644 --- a/kernel/src/Fs/Fat32.cpp +++ b/kernel/src/Fs/Fat32.cpp @@ -1894,6 +1894,7 @@ namespace Fs::Fat32 { static int DrvMkdir(void* c, const char* p) { return MkdirImpl(CtxToInst(c), p); } static int DrvRename(void* c, const char* o, const char* n) { return RenameImpl(CtxToInst(c), o, n); } static const char* DrvGetLabel(void* c) { return GetLabelImpl(CtxToInst(c)); } + static const char* DrvGetFsType(void*) { return "FAT32"; } static int DrvStat(void* c, const char* p, Vfs::StatInfo* o) { return StatImpl(CtxToInst(c), p, o); } // Release everything the mount owns. The driver is either not registered @@ -2101,6 +2102,7 @@ namespace Fs::Fat32 { .Mkdir = DrvMkdir, .Rename = DrvRename, .GetLabel = DrvGetLabel, + .GetFsType = DrvGetFsType, .ReadDirAt = DrvReadDirAt, .Stat = DrvStat, .SetTimes = nullptr, diff --git a/kernel/src/Fs/FsProbe.cpp b/kernel/src/Fs/FsProbe.cpp index 01b3dd6..7b5f996 100644 --- a/kernel/src/Fs/FsProbe.cpp +++ b/kernel/src/Fs/FsProbe.cpp @@ -154,18 +154,22 @@ namespace Fs::FsProbe { return -1; } - int GetBlockDeviceForDrive(int driveNum) { + int GetPartitionForDrive(int driveNum) { if (driveNum < 0) return -1; int partCount = Drivers::Storage::Gpt::GetPartitionCount(); for (int i = 0; i < partCount && i < Drivers::Storage::Gpt::MaxPartitions; i++) { - if (g_mounted[i] && g_driveForPart[i] == driveNum) { - auto* part = Drivers::Storage::Gpt::GetPartition(i); - if (part) return part->BlockDevIndex; - } + if (g_mounted[i] && g_driveForPart[i] == driveNum) return i; } return -1; } + int GetBlockDeviceForDrive(int driveNum) { + int partIndex = GetPartitionForDrive(driveNum); + if (partIndex < 0) return -1; + auto* part = Drivers::Storage::Gpt::GetPartition(partIndex); + return part ? part->BlockDevIndex : -1; + } + int UnmountPartitionsForBlockDevice(int blockDevIndex) { int partCount = Drivers::Storage::Gpt::GetPartitionCount(); int unmounted = 0; diff --git a/kernel/src/Fs/FsProbe.hpp b/kernel/src/Fs/FsProbe.hpp index 89f579b..541aac9 100644 --- a/kernel/src/Fs/FsProbe.hpp +++ b/kernel/src/Fs/FsProbe.hpp @@ -38,6 +38,10 @@ namespace Fs::FsProbe { // drive isn't backed by a partition (e.g. ramdisk). int GetBlockDeviceForDrive(int driveNum); + // Return the global GPT partition index backing a mounted VFS drive, or -1 + // if the drive isn't backed by a partition (e.g. ramdisk). + int GetPartitionForDrive(int driveNum); + // Prepare persistent storage for power-off: flush every block device's // write cache to media, then cleanly unmount all disk-backed VFS drives. // The ramdisk (which has no backing block device) is left mounted. Returns diff --git a/kernel/src/Fs/Vfs.cpp b/kernel/src/Fs/Vfs.cpp index e925806..1e29d19 100644 --- a/kernel/src/Fs/Vfs.cpp +++ b/kernel/src/Fs/Vfs.cpp @@ -350,10 +350,10 @@ namespace Fs::Vfs { vfsLock.Release(); return count; } - - int VfsDriveLabel(int driveNumber, char* outLabel, int maxLen) { - if (outLabel == nullptr || maxLen <= 0) return -1; - outLabel[0] = '\0'; + static int CopyDriveString(int driveNumber, const char* (*FsDriver::*hook)(void*), + char* out, int maxLen) { + if (out == nullptr || maxLen <= 0) return -1; + out[0] = '\0'; vfsLock.Acquire(); FsDriver* driver = DriverForLocked(driveNumber); @@ -362,28 +362,36 @@ namespace Fs::Vfs { return -1; } - if (driver->GetLabel == nullptr) { + if (driver->*hook == nullptr) { vfsLock.Release(); return 0; } - const char* label = driver->GetLabel(driver->ctx); - if (label == nullptr || label[0] == '\0') { + const char* value = (driver->*hook)(driver->ctx); + if (value == nullptr || value[0] == '\0') { vfsLock.Release(); return 0; } int copied = 0; - while (label[copied] != '\0' && copied < maxLen - 1) { - outLabel[copied] = label[copied]; + while (value[copied] != '\0' && copied < maxLen - 1) { + out[copied] = value[copied]; copied++; } - outLabel[copied] = '\0'; + out[copied] = '\0'; vfsLock.Release(); return copied; } + int VfsDriveLabel(int driveNumber, char* outLabel, int maxLen) { + return CopyDriveString(driveNumber, &FsDriver::GetLabel, outLabel, maxLen); + } + + int VfsDriveFsType(int driveNumber, char* outType, int maxLen) { + return CopyDriveString(driveNumber, &FsDriver::GetFsType, outType, maxLen); + } + int VfsRename(const char* oldPath, const char* newPath) { int oldDrive, newDrive; const char* oldLocal; diff --git a/kernel/src/Fs/Vfs.hpp b/kernel/src/Fs/Vfs.hpp index 2fded13..42dbead 100644 --- a/kernel/src/Fs/Vfs.hpp +++ b/kernel/src/Fs/Vfs.hpp @@ -59,6 +59,9 @@ namespace Fs::Vfs { int (*Mkdir)(void* ctx, const char* path); int (*Rename)(void* ctx, const char* oldPath, const char* newPath); const char* (*GetLabel)(void* ctx); + // Optional: short name of the on-disk format ("ext2", "FAT32", ...). + // Drivers that leave this null report an empty type string. + const char* (*GetFsType)(void* ctx); // Optional: paginated directory read returning entries [startIndex, startIndex+maxEntries). // Drivers that leave this null are read via ReadDir at startIndex 0 only. int (*ReadDirAt)(void* ctx, const char* path, const char** outNames, int maxEntries, int startIndex); @@ -103,5 +106,6 @@ namespace Fs::Vfs { // Returns number of registered drives, fills outDrives[] with their indices int VfsDriveList(int* outDrives, int maxEntries); int VfsDriveLabel(int driveNumber, char* outLabel, int maxLen); + int VfsDriveFsType(int driveNumber, char* outType, int maxLen); } diff --git a/programs/include/Api/Syscall.hpp b/programs/include/Api/Syscall.hpp index 34dd643..753396f 100644 --- a/programs/include/Api/Syscall.hpp +++ b/programs/include/Api/Syscall.hpp @@ -262,6 +262,9 @@ namespace montauk::abi { // The caller's own capability masks. static constexpr uint64_t SYS_GETCAPS = 189; // (SpawnCapabilities*) -> 0, -1 bad pointer + /* Storage.hpp */ + static constexpr uint64_t SYS_VOLINFO = 190; // (VolInfo*, maxCount) -> count, -1 bad pointer + /* Kernel-owned process capabilities. User identities may namespace per-user resources, but never participate in authorization decisions. */ static constexpr uint64_t CAP_PROCESS_ADMIN = 1ULL << 0; @@ -603,6 +606,28 @@ namespace montauk::abi { char typeName[24]; // human-readable type name }; + // One mounted volume (returned by SYS_VOLINFO). A volume is a single + // mounted filesystem, which is what a drive number names: one physical + // disk with three partitions occupies three drive numbers. + // + // A volume with no backing partition -- the boot ramdisk -- reports + // blockDev and partIndex as -1 and leaves the geometry fields zero. + struct VolInfo { + int32_t drive; // VFS drive number ("N:/") + int32_t blockDev; // backing block device index, -1 if none + int32_t partIndex; // global GPT partition index, -1 if none + uint32_t kind; // BlockDeviceKind: 0=none/ramdisk, 1=SATA, 2=SATAPI, 3=NVMe, 4=USB MSC + uint64_t startLba; // partition start LBA, 0 if none + uint64_t sectorCount; // partition length in sectors, 0 if none + uint32_t sectorSize; // logical sector size in bytes, 0 if none + uint32_t _pad0; + char label[36]; // filesystem volume label, "" if unset + char fsType[12]; // "ext2", "FAT32", "ramdisk", "" if unknown + char partName[72]; // GPT partition name, "" if none + char model[41]; // backing disk model, "" if none + char _pad1[3]; + }; + struct GptAddParams { int32_t blockDev; uint32_t _pad0; diff --git a/programs/include/libc/montauk.h b/programs/include/libc/montauk.h index fcbecf1..d0187fa 100644 --- a/programs/include/libc/montauk.h +++ b/programs/include/libc/montauk.h @@ -213,6 +213,7 @@ extern "C" { #define MTK_SYS_ALLOC_EAGER 187 #define MTK_SYS_BATTERYINFO 188 #define MTK_SYS_GETCAPS 189 +#define MTK_SYS_VOLINFO 190 /* @SYSCALLS-END */ #define MTK_SOCK_TCP 1 diff --git a/programs/include/montauk/syscall.h b/programs/include/montauk/syscall.h index 943f5ce..a019dfe 100644 --- a/programs/include/montauk/syscall.h +++ b/programs/include/montauk/syscall.h @@ -514,6 +514,10 @@ namespace montauk { inline int partlist(montauk::abi::PartInfo* buf, int max) { return (int)syscall2(montauk::abi::SYS_PARTLIST, (uint64_t)buf, (uint64_t)max); } + // One entry per mounted volume (one filesystem, i.e. one drive number). + inline int volinfo(montauk::abi::VolInfo* buf, int max) { + return (int)syscall2(montauk::abi::SYS_VOLINFO, (uint64_t)buf, (uint64_t)max); + } // Raw block device I/O (driver-agnostic) inline int64_t disk_read(int blockDev, uint64_t lba, uint32_t sectorCount, void* buf) { diff --git a/programs/man/vol.1 b/programs/man/vol.1 new file mode 100644 index 0000000..561bfdd --- /dev/null +++ b/programs/man/vol.1 @@ -0,0 +1,34 @@ +.TH VOL 1 +.SH NAME + vol - list mounted volumes + +.SH SYNOPSIS + vol + +.SH DESCRIPTION + Prints information about volumes present on the system. + + Drive numbers (column "Vol") represent volumes rather than physical disk devices. + + The boot ramdisk, if present, reports "-" for its size, disk, and partition. + +.SH OUTPUT + Vol Type Label Size Disk Part Partition + 0:/ ramdisk MontaukOS - - - - + 1:/ ext2 Montauk 118.4 GiB 0 0 Montauk + 2:/ FAT32 EFI 512.0 MiB 0 1 EFI System + + Disks: + 0 NVMe Samsung SSD 990 PRO 1TB + +.SH COLUMNS + Vol Drive number, as written in paths ("1:/file.txt"). + Type Filesystem type (ext2, FAT32, ramdisk). + Label Filesystem volume label, or "-" if unset. + Size Partition size, binary prefixes. + Disk Block device index (see disk legend below the table). + Part Partition index on that disk. + Partition GPT partition name, or "-" if unset. + +.SH SEE ALSO + disks(1), shell(1), syscalls(2) diff --git a/programs/src/shell/builtins.cpp b/programs/src/shell/builtins.cpp index 9170d64..9739f73 100644 --- a/programs/src/shell/builtins.cpp +++ b/programs/src/shell/builtins.cpp @@ -62,6 +62,7 @@ void cmd_help() { montauk::print(" edit [file] Text editor\n"); montauk::print(" whoami Print current username\n"); montauk::print(" info Show system information\n"); + montauk::print(" vol List mounted volumes\n"); montauk::print(" date Show current date and time\n"); montauk::print(" uptime Show uptime\n"); montauk::print(" clear Clear the screen\n"); diff --git a/programs/src/vol/main.cpp b/programs/src/vol/main.cpp new file mode 100644 index 0000000..48f81a5 --- /dev/null +++ b/programs/src/vol/main.cpp @@ -0,0 +1,105 @@ +/* + * main.cpp + * vol - List mounted volumes and the disks they live on + * Copyright (c) 2026 Daniel Hammer +*/ + +#include +#include + +static constexpr int MaxVolumes = 64; + +static const char* kind_name(unsigned kind) { + switch (kind) { + case 1: return "SATA"; + case 2: return "SATAPI"; + case 3: return "NVMe"; + case 4: return "USB"; + default: return "-"; + } +} + +static void format_size(uint64_t bytes, char* out, int outMax) { + static const char* units[] = { "B", "KiB", "MiB", "GiB", "TiB", "PiB" }; + int unit = 0; + uint64_t whole = bytes; + uint64_t rem = 0; + + while (whole >= 1024 && unit < 5) { + rem = whole % 1024; + whole /= 1024; + unit++; + } + + if (unit == 0) { + snprintf(out, outMax, "%llu B", (unsigned long long)whole); + return; + } + + unsigned tenths = (unsigned)((rem * 10) / 1024); + snprintf(out, outMax, "%llu.%u %s", (unsigned long long)whole, tenths, units[unit]); +} + +extern "C" void _start() { + montauk::abi::VolInfo vols[MaxVolumes] = {}; + int n = montauk::volinfo(vols, MaxVolumes); + + if (n <= 0) { + montauk::print("No volumes are mounted.\n"); + montauk::exit(n < 0 ? 1 : 0); + } + + printf("%-5s %-8s %-18s %10s %5s %5s %s\n", + "Vol", "Type", "Label", "Size", "Disk", "Part", "Partition"); + + for (int i = 0; i < n; i++) { + const montauk::abi::VolInfo& v = vols[i]; + + char drive[8]; + snprintf(drive, sizeof(drive), "%d:/", v.drive); + + char size[16]; + if (v.sectorCount != 0 && v.sectorSize != 0) { + format_size(v.sectorCount * (uint64_t)v.sectorSize, size, sizeof(size)); + } else { + snprintf(size, sizeof(size), "-"); + } + + char disk[8]; + char part[8]; + if (v.blockDev >= 0) snprintf(disk, sizeof(disk), "%d", v.blockDev); + else snprintf(disk, sizeof(disk), "-"); + if (v.partIndex >= 0) snprintf(part, sizeof(part), "%d", v.partIndex); + else snprintf(part, sizeof(part), "-"); + + printf("%-5s %-8s %-18s %10s %5s %5s %s\n", + drive, + v.fsType[0] ? v.fsType : "-", + v.label[0] ? v.label : "-", + size, disk, part, + v.partName[0] ? v.partName : "-"); + } + + bool printedHeader = false; + for (int i = 0; i < n; i++) { + if (vols[i].blockDev < 0) continue; + + bool seen = false; + for (int j = 0; j < i; j++) { + if (vols[j].blockDev == vols[i].blockDev) { seen = true; break; } + } + if (seen) continue; + + if (!printedHeader) { + montauk::print("\nDisks:\n"); + printedHeader = true; + } + + printf(" %d %-6s %s\n", + vols[i].blockDev, + kind_name(vols[i].kind), + vols[i].model[0] ? vols[i].model : "(unknown)"); + } + + montauk::exit(0); +}