diff --git a/src/kmain.c b/src/kmain.c index cf9841e..1d81545 100644 --- a/src/kmain.c +++ b/src/kmain.c @@ -1542,6 +1542,179 @@ static void globTypeEchoSkipCommand(void) injectKeyPress(scancodeMakeP); injectKeyPress(scancodeMakeSlash); injectKeyPress(scancodeMakeG); + injectKeyPress(scancodeMakeD); + injectKeyPress(scancodeMakeSlash); + injectKeyPress(scancodeCPressMake); + injectKeyPress(scancodeMakeDot); + injectKeyPress(scancodeMakeL); + injectKeyPress(scancodeMakeO); + injectKeyPress(scancodeMakeG); + injectKeyPress(scancodeMakeEnter); +} + +static void globTypeGlobcatCommand(void) +{ + injectKeyPress(scancodeMakeG); + injectKeyPress(scancodeMakeL); + injectKeyPress(scancodeMakeO); + injectKeyPress(scancodeMakeB); + injectKeyPress(scancodeCPressMake); + injectKeyPress(scancodeMakeA); + injectKeyPress(scancodeMakeT); + injectKeyPress(scancodeMakeSpace); + injectKeyPress(scancodeMakeSlash); + injectKeyPress(scancodeMakeT); + injectKeyPress(scancodeMakeM); + injectKeyPress(scancodeMakeP); + injectKeyPress(scancodeMakeSlash); + injectKeyPress(scancodeMakeG); + injectKeyPress(scancodeMakeD); + injectKeyPress(scancodeMakeSlash); + injectShiftedKeyPress(scancodeMakeEight); + injectKeyPress(scancodeMakeDot); + injectKeyPress(scancodeMakeT); + injectKeyPress(scancodeMakeX); + injectKeyPress(scancodeMakeT); + injectKeyPress(scancodeMakeEnter); +} + +/* 15000ms clears envDriverThread's own total budget (12500 initial + + 3*400 settle = 13700ms, above its own real spawn/run/reap time), the same + "next demo's initial delay clears the previous demo's total budget" + convention every driver thread above already establishes. Settle is + 300ms per step, matching redirDriverThread's own choice for similarly + trivial commands (a single mkdir or a single short file write, none of + which ever sleeps or blocks on a timer) -- comfortable headroom to let + the shell finish running each FOREGROUND command before the next one's + keystrokes are typed into the same shared 32-byte console-reader ring + buffer (keyboard.c). */ +#define globDriverInitialDelayMs 15000 +#define globDriverStepSettleMs 300 + +/* + * Live ring-3 proof for shell.c's new wildcard glob expansion + * (shellExpandGlobs, shellGlobMatch/shellTokenHasGlob/shellSplitGlobToken): + * types four setup commands at the real interactive shell (mkdir, then + * three "echo X > /tmp/gd/NAME" writes -- two matching *.txt, one + * deliberately a *.log that must NOT match), then "globcat /tmp/gd/ *.txt". + * shell.c resolves that last argv token against the real tmpfs directory + * /tmp/gd (vfsChild/vfsNext, the exact traversal the `ls` builtin already + * uses) and replaces it with however many real paths matched BEFORE + * globcat.c (loaded on demand, like envprint/hcat/pipecat above) ever runs + * a single instruction -- so globcat.c's own argc/argv and each file's + * content are the actual proof: exactly 2 files (never 3 -- c.log must be + * filtered out by pattern, not just "everything in the directory"), each + * one's real resolved path and its own distinct content. The actual + * pass/fail proof is entirely globcat.c's own doing; this thread's only job + * is to type the five commands and give the scheduler real time between + * them. + */ +static void globDriverThread(void) +{ + threadSleep(globDriverInitialDelayMs); + while (currentThread()->state == threadSleeping) + halt(); + + kprintf("[kbd] typing shell commands for the wildcard glob demo\n"); + jobctlTypeFlushLine(); + + globTypeMkdirCommand(); + threadSleep(globDriverStepSettleMs); + while (currentThread()->state == threadSleeping) + halt(); + + globTypeEchoACommand(); + threadSleep(globDriverStepSettleMs); + while (currentThread()->state == threadSleeping) + halt(); + + globTypeEchoBCommand(); + threadSleep(globDriverStepSettleMs); + while (currentThread()->state == threadSleeping) + halt(); + + globTypeEchoSkipCommand(); + threadSleep(globDriverStepSettleMs); + while (currentThread()->state == threadSleeping) + halt(); + + globTypeGlobcatCommand(); + threadSleep(globDriverStepSettleMs); + while (currentThread()->state == threadSleeping) + halt(); + + for (;;) + halt(); +} + +/* 19500ms clears globDriverThread's own total budget (15000 initial + + 5*300 settle = 16500ms, plus the real time globcat.c itself needs to + spawn/run/read two files/print/exit after that last keystroke) -- the + same "next demo's initial delay clears the previous demo's total budget + with margin" convention every driver thread above establishes. A single + settle step is enough -- see the file comment on lsDriverThread below + for why this demo types only ONE command, unlike every prior driver + thread's multi-command setup sequence: tools/ci-test.sh's QEMU timeout + is a fixed 25 REAL (host wall-clock) seconds shared by the entire boot, + and this is already the last of well over a dozen shell-driven demos + stacked at increasing absolute timestamps in that same fixed window, so + minimizing this demo's own keystroke count directly buys back margin + for the whole chain instead of spending it. */ +#define lsDriverInitialDelayMs 19500 +#define lsDriverStepSettleMs 300 + +static void lsTypeLongListCommand(void) +{ + injectKeyPress(scancodeMakeL); + injectKeyPress(scancodeMakeS); + injectKeyPress(scancodeMakeSpace); + injectKeyPress(scancodeMakeHyphen); + injectKeyPress(scancodeMakeL); + injectKeyPress(scancodeMakeSpace); + injectKeyPress(scancodeMakeSlash); + injectKeyPress(scancodeMakeM); + injectKeyPress(scancodeMakeN); + injectKeyPress(scancodeMakeT); + injectKeyPress(scancodeMakeSlash); + injectKeyPress(scancodeMakeT); + injectKeyPress(scancodeMakeI); + injectKeyPress(scancodeMakeM); + injectKeyPress(scancodeMakeE); + injectKeyPress(scancodeMakeT); + injectKeyPress(scancodeMakeE); + injectKeyPress(scancodeMakeS); + injectKeyPress(scancodeMakeT); + injectKeyPress(scancodeMakeEnter); +} + +/* + * Live proof for shell.c's new generalized `ls`/`ls -l` (listDir/ + * printLsEntry): types `ls -l /mnt/timetest` at the real interactive + * shell. `/mnt/timetest` is deliberately NOT created by this demo -- + * timetest.c (FALA 39, boot-time synchronous, long finished by the time + * any shell-driven demo runs) already left it behind with two real files + * (data.txt, and alias.txt, a hardlink sharing the same inode/mtime) once + * `timetest: PASS` prints, so this demo needs exactly ONE typed command + * instead of the usual "mkdir + write setup, then the command under test" + * shape every earlier shell demo used -- both a deliberate budget saving + * (see lsDriverInitialDelayMs's own comment) and, incidentally, a second + * independent proof that `ls -l`'s real stat data (size, mtime) matches + * what timetest.c's own syscall-level userStat already established for + * the exact same on-disk inode, through a completely different code path + * (the interactive shell builtin instead of a ring-3 program's syscalls). + * `/mnt`, not `/tmp`, is the whole point -- the OLD `ls` only ever + * understood the tmpfs VfsNode tree via vfsLookup/vfsChild/vfsNext, so any + * `/mnt` directory silently reported "no such path" no matter what it + * actually held (fstest.c/linktest.c could only ever prove SFS directory + * listing through a raw userGetdents syscall, never through this + * builtin). The needles tools/ci-test.sh checks for are the deterministic + * type+size+name prefix of each `-l` output line ("f data.txt " and + * "f alias.txt ", both files' real on-disk size) plus a "20" year- + * prefix right after one of them, proving a real mtime got formatted and + * printed (sfs.h's on-disk timestamp, rtc.c's new rtcCivilFromEpoch) + * rather than silently falling back to the "no timestamp" dash this same + * code path prints for tmpfs/dev entries. + */ } void kmain(uint32 magic, uint64 multibootInfo)