feat: extend shell with printLsEntry and listDir

This commit is contained in:
dualfroz committed 2026-09-08 12:23:52 +02:00
1 parent ff36cc0275
commit 9e1f907da5
1 file changed
+141 -46
+141 -46
View File
@@ -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)