From 9e1f907da52c8dd8894c7ed6ed7d9ed09bd27daa Mon Sep 17 00:00:00 2001 From: DualFroz Date: Tue, 8 Sep 2026 12:23:52 +0200 Subject: [PATCH] feat: extend shell with printLsEntry and listDir --- src/shell.c | 187 +++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 141 insertions(+), 46 deletions(-) diff --git a/src/shell.c b/src/shell.c index e0ea5e0..e680d7a 100644 --- a/src/shell.c +++ b/src/shell.c @@ -956,58 +956,148 @@ static void printThreads(void) static void buildPath(const char* argument, char* out) { - if (argument[0] == '/') { - uint64 length = strlen(argument); - if (length >= pathCapacity) - length = pathCapacity - 1; - memcpy(out, argument, length); - out[length] = 0; - return; - } - - uint64 pos = strlen(cwd); - if (pos >= pathCapacity) - pos = pathCapacity - 1; - memcpy(out, cwd, pos); - if (pos == 0 || out[pos - 1] != '/') { - if (pos < pathCapacity - 1) - out[pos++] = '/'; - } - for (uint64 i = 0; argument[i] && pos < pathCapacity - 1; i++) - out[pos++] = argument[i]; - out[pos] = 0; + vfsResolvePath(argument, out, pathCapacity); } -static void listDir(const char* argument) +/* One line of `ls` output for a single entry, real stat data (fs.h's + FbStat) already resolved by the caller -- shared by both the plain and + `-l` formats and by the "argument names a file, not a directory" case + below, so all three ever only format an FbStat one way. */ +static void printLsEntry(bool longFormat, const FbStat* stat, const char* name) { - char path[pathCapacity]; - if (argument[0] == 0) - memcpy(path, cwd, strlen(cwd) + 1); - else - buildPath(argument, path); + char typeChar = stat->type == fsTypeDir ? 'd' : (stat->type == fsTypeFifo ? 'p' : 'f'); - VfsNode* node = vfsLookup(path); - if (!node) { - kprintf("ls: no such path: %s\n", path); - return; - } - if (!vfsIsDir(node)) { - kprintf("%s %lu\n", vfsName(node), vfsSize(node)); - return; - } - for (VfsNode* child = vfsChild(node); child; child = vfsNext(child)) { - if (vfsIsDir(child)) - kprintf("%s/\n", vfsName(child)); + if (!longFormat) { + if (stat->type == fsTypeDir) + kprintf("%s/\n", name); else - kprintf("%s %lu\n", vfsName(child), vfsSize(child)); + kprintf("%s %lu\n", name, stat->size); + return; + } + + /* Deliberately prints type/size/name BEFORE the date: a needle that + greps for a known type+size+name substring (tools/ci-test.sh) then + never has to also match today's wall-clock date, which is real and + therefore different on every run. The real owner/group/other rwx + permission string (fs.h's fsModeOwner*, fsModeGroup*, fsModeOther*) is + appended AFTER the date for the same reason, in the other direction + -- every existing needle that greps a type+size+name+date-prefix + substring keeps matching unchanged, since nothing before it in the + line moved (only perm itself grew longer, from 3 chars to the full + 9-char rwxrwxrwx real `ls -l` prints, now that a real gid exists to + make the group triad meaningful). */ + char perm[10]; + perm[0] = (stat->mode & fsModeOwnerRead) ? 'r' : '-'; + perm[1] = (stat->mode & fsModeOwnerWrite) ? 'w' : '-'; + perm[2] = (stat->mode & fsModeOwnerExec) ? 'x' : '-'; + perm[3] = (stat->mode & fsModeGroupRead) ? 'r' : '-'; + perm[4] = (stat->mode & fsModeGroupWrite) ? 'w' : '-'; + perm[5] = (stat->mode & fsModeGroupExec) ? 'x' : '-'; + perm[6] = (stat->mode & fsModeOtherRead) ? 'r' : '-'; + perm[7] = (stat->mode & fsModeOtherWrite) ? 'w' : '-'; + perm[8] = (stat->mode & fsModeOtherExec) ? 'x' : '-'; + perm[9] = 0; + + kprintf("%c %lu %s ", typeChar, stat->size, name); + if (stat->mtime == 0) { + /* tmpfs/dev entries never track time (fs.h's FbStat comment) -- + an honest "no timestamp" marker instead of a fake 1970 date. */ + kprintf("- %s\n", perm); + } else { + RtcDateTime dt; + rtcCivilFromEpoch((int64)stat->mtime, &dt); + kprintf("%04ld-%02ld-%02ld %02ld:%02ld:%02ld %s\n", + dt.year, dt.month, dt.day, dt.hour, dt.minute, dt.second, perm); } } -static void catFile(const char* argument) +/* `ls [-l] [PATH]` -- generalized over vfsOpen/vfsGetdents/vfsStat + (fs.h/vfs.h) instead of the old tmpfs-only vfsLookup/vfsChild/vfsNext + VfsNode-tree walk, so a directory under `/mnt` (SFS) can finally be + listed from the interactive shell at all (previously vfsLookup, which + only ever understood the in-memory tmpfs tree, silently returned "no + such path" for every SFS directory -- fstest.c/linktest.c could only + ever prove SFS directory listing through a raw userGetdents syscall, + never through this builtin). `-l` adds real stat data per entry (type, + size, mtime -- sfs.h's on-disk timestamps, FALA 39) the same way a real + `ls -l` would. */ +/* Returns 0 on success, 1 on failure (real POSIX `ls` exit-status shape) -- + feeds shell.c's new `&&`/`||` conditional chaining (execute()) the same + way a spawned program's own real exit code already does, see + shellSplitAndOr's own doc comment in shell.h. */ +static int listDir(const char* argument) +{ + bool longFormat = false; + if (argument[0] == '-' && argument[1] == 'l' && (argument[2] == 0 || argument[2] == ' ')) { + longFormat = true; + argument += 2; + while (*argument == ' ') + argument++; + } + + char path[pathCapacity]; + buildPath(argument, path); + + FbStat stat; + if (vfsStat(path, &stat) != 0) { + kprintf("ls: no such path: %s\n", path); + return 1; + } + + if (stat.type != fsTypeDir) { + const char* base = path; + for (const char* p = path; *p; p++) { + if (*p == '/') + base = p + 1; + } + printLsEntry(longFormat, &stat, base); + return 0; + } + + int fd = vfsOpen(path, oReadOnly); + if (fd < 0) { + kprintf("ls: cannot open %s\n", path); + return 1; + } + + uint64 pathLen = strlen(path); + if (pathLen >= pathCapacity) + pathLen = pathCapacity - 1; + bool needSlash = pathLen == 0 || path[pathLen - 1] != '/'; + + FbDirent entries[16]; + int64 bytes; + while ((bytes = vfsGetdents(fd, entries, sizeof(entries))) > 0) { + int64 count = bytes / (int64)sizeof(FbDirent); + for (int64 i = 0; i < count; i++) { + char childPath[pathCapacity]; + uint64 childLen = pathLen; + memcpy(childPath, path, childLen); + if (needSlash && childLen < pathCapacity - 1) + childPath[childLen++] = '/'; + uint64 nameLen = strlen(entries[i].name); + if (childLen + nameLen >= pathCapacity) + nameLen = pathCapacity - 1 - childLen; + memcpy(childPath + childLen, entries[i].name, nameLen); + childPath[childLen + nameLen] = 0; + + FbStat childStat; + if (vfsStat(childPath, &childStat) == 0) + printLsEntry(longFormat, &childStat, entries[i].name); + else + kprintf("%s ?\n", entries[i].name); + } + } + vfsClose(fd); + return 0; +} + +/* Same "0 success, 1 failure" shape listDir above uses. */ +static int catFile(const char* argument) { if (argument[0] == 0) { kprintf("cat: missing file\n"); - return; + return 1; } char path[pathCapacity]; buildPath(argument, path); @@ -1015,16 +1105,17 @@ static void catFile(const char* argument) VfsNode* node = vfsLookup(path); if (!node) { kprintf("cat: no such file: %s\n", path); - return; + return 1; } if (vfsIsDir(node)) { kprintf("cat: %s is a directory\n", path); - return; + return 1; } const char* data = (const char*)vfsFileData(node); uint64 size = vfsSize(node); for (uint64 i = 0; i < size; i++) kputchar(data[i]); + return 0; } static void echoWrite(char* text, const char* target) @@ -1043,16 +1134,20 @@ static void echoWrite(char* text, const char* target) vfsClose(fd); } -static void makeDir(const char* argument) +/* Same "0 success, 1 failure" shape listDir/catFile above use. */ +static int makeDir(const char* argument) { if (argument[0] == 0) { kprintf("mkdir: missing path\n"); - return; + return 1; } char path[pathCapacity]; buildPath(argument, path); - if (vfsMkdir(path) != 0) + if (vfsMkdir(path) != 0) { kprintf("mkdir: cannot create %s\n", path); + return 1; + } + return 0; } static void changeDir(const char* argument)