sysh · L5

The shell

Booting sysk gives you sysh, its shell (sysk 018), a part of the suite with a name of its own: its commands are a language programs can speak too. What you type decides which programs run, every command is checked before anything starts, and the shell can only ever hand out the pool the manifest set aside: no root, no devices, no other task's channels.

A command, from your keyboard to a job

Select a step, or play it. You type producer | filter | consumer at the shell's test system: the system you boot with three more programs in its catalogue, producer, filter and consumer, which pass numbers down a pipe (greeter and talker are the other two; sysk 073). No tool of the system you boot reads a channel yet, so this is where a pipe is tested. The picture lights up where the command is, and the cards show what it has become.

    On the screen
    The command
    The job table

    One language, and sugar in front of it

    The shell reads s-expressions and nothing else, so its grammar fits on a card and a program can drive it without pretending to be a person. What people type goes through a separate step first, sugar, which knows no words, only syntax. Anything that starts with ( passes through it untouched.

    The shell's commands

    (run (P A ... ARG ...) ...)
    programs P, each A a pool channel by name, then its arguments; out is the shell's output
    (pipe P1 P2 ...)
    P1's first sending parameter into P2's first receiving one, through a free pool channel of that size, and so on; every other sending parameter to out
    (P A ... ARG ...) · (P ARG ...)
    one program: wired by hand, or as a pipe of one; a pipe's element may be (P ARG ...) too
    (catalogue) · (pool)
    what you can run, and with what; each program's spec and doc are also files, ls /prg, cat /prg/wc (sysk 057, Files)
    (uptime) · (sleep ms)
    how long the machine has run; wait a while (the shell holds the clock)
    (cd PATH) · (cd) · (pwd)
    where a path that does not start at / starts, / if none is named; where that is. . and .. are the shell's to take away, in any tree: cd /sys/task/shell, then cat ../../time (sysk 056, Files)
    (ls PATH) · (ls) · (cat PATH)
    list a directory (the one paths start from if none is named), print a file. ls says a record for each name, (entry (name "addr") (type file) (format sexp) (rights read) (length 14)), and a host shows the names (sysk 066; Views); /sys is the kernel's tree (the shell holds a Sys; Files), /dev the devices' (Devices as files), everything else the disk's (Files on the disk). A stream, such as /dev/rng, is read once: its bytes in hex, one line
    (mkdir PATH) · (rm PATH) · (echo WORD ...)
    make a directory, remove a file or an empty directory, say the words, all of them however long (sysk 083)
    (P PATH ARG ...) · (P A.B.C.D ARG ...) · (P NAME!PORT ARG ...)
    program P, the words after its name read as its needs say (sysk 059): for (needs (read file)), with what PATH names lent to it to read, for as long as it runs: (wc /notes/shopping), (wc /net/eth/addr) (sysk 031; Files for jobs); for two file needs, two paths, each lent with its own need's rights, a path to write made or emptied first: (cp /notes/x /notes/y), (cmp /notes/x /notes/y) (sysk 078, Loans); or, for (needs (talk icmp host)), an address, a conversation dialed to it in the need's protocol and only its data lent, to read and write: (ping 10.0.2.2 (times 5)), sugar ping 10.0.2.2 --times 5 (sysk 058, The network); with a port, tell 10.0.2.2!8080 (sysn 012); or *!PORT, a port to answer one call on, its data lent the same way: tell *!7777 (sysn 013, Listening); for (needs (talk udp host)) the same, datagram 10.0.2.2!7777 (sysn 029, Datagrams); or a host's name in place of the address, looked up in /net/cs first: tell sysx.crops.no!80 (sysn 022, 025, By name); for (needs (talk dns name)), a host's name, dialed the same way: host sysx.crops.no (sysn 021, Names)
    (PART/NAME PATH)
    a library part's function as a command, on a file lent to read: (sysc/sha-256 /notes/letter) is its SHA-256, (sha-256 "…"). The part's runner is a catalogue program the build makes, and the name typed is the path under /prg; a runner's name alone names nothing (sysc 014, Files)
    ARG: (NAME N) · (NAME)
    a program's argument: a number, or a flag; each at most once, in any order, after its channels or path (sysk 036; Arguments)
    (> PATH (C ...)) · (>> PATH (C ...))
    command C's lines into a file on the disk, emptied first or added at its end; the file is made if it is not there, and stored whole when C is done
    (view V C)
    command C, shown in view V: long, every field; list, a field to a line; raw, the values as said. The view goes to the host with C's region, and changes nothing C does; (ls -l ...) is (view long (ls ...)), a habit from Unix (sysk 066; Views)
    (& JOB)
    a job in the background: the commands go on while it runs, and its lines come as they come (sysk 032)
    (explain JOB)
    what JOB would do, and nothing starts: a value for each task (its program, the pool channel each of its channels is bound to, its arguments as they would be, its need: sysk 059), the path it would be lent, or the address dialed, and with what rights, and the job check's verdict, the same that judges it when it runs; why, if refused (sysk 051)
    (help (C ...))
    what command C is, and C does not run: for a program, or a part's command (sysc/sha-256), its file in /prg, its spec and its doc comment, said as cat says it; for a part's runner (sysc), its directory there, a file for each command; for a path (it starts with / or .), its own entry record, as ls says a file's, a directory's too. (help ()) is the catalogue. The shell's own words have no file in /prg yet, and say so (sysk 075)
    (complete WORD ...)
    what could come next for the last word: a command (the shell's own, the catalogue's programs, and each part's directory, sysc/, then its commands, sysc/sha-256, sysc 014), a path in any tree (it starts with /), or a program's argument (it starts with --, with its range and default). Each choice is a value, (choice (word /sys/task/) (kind dir)), read from what the job check judges by: a program's arguments are its contract's, so what is offered and what is accepted cannot differ. A syst host asks on Tab (syst); anything driving the shell can ask the same way (sysk 050).
    (watch PATH) · (watch PATH MS) · (watch PATH MS TIMES)
    the file, and again each time it has changed, read every MS milliseconds (1,000 if not given, at least 100), TIMES reads in all if given; a line typed ends it, and is not run, and so does Ctrl-C (sysk 069). Any tree: watch /sys/task/shell/status, watch /net/eth0/stats once there is one. A change is a different count or checksum of the file's first payload: the kernel's tree keeps no versions and cannot answer a read that waits, so the shell reads again (sysk 049). A path with a * reads each file it names, as cat does, and shows them all when any changed, or one came or went; each showing ends with (frame N), so a host draws the next over it (sysk 060, syst). follow, a program, is for files that grow on the disk.
    (top) · (top MS) · (top MS TIMES)
    every task's status, watched: (watch /sys/task/*/status MS TIMES), which the shell rewrites it to before it reads it, so explain, the errors and the region are watch's (sysk 060). It does so under any wrapper too, top @raw or top ??, following each wrapper's command (its last element) down to the top (sysk 116). Each status says the ticks the task has run and what it waits for (Files); through a host it is one table, drawn again in place, each count that grew with by how much. Sugar knows no words, so top is the shell's; top 500 is (top 500) by sugar's rule for any line.

    Sugar, rewritten by svc/sugar.sysp

    producer | filter | consumer→(pipe producer filter consumer)
    uptime→(uptime)
    wc /notes/x ?→(explain (wc /notes/x)): a last ? asks what a command would do (sysk 051)
    wc /notes/x ??→(help (wc /notes/x)): a last word ?? asks what the command is: wc's spec and doc, and nothing runs; ?? alone is (help ()) (sysk 075)
    catalogue→(catalogue)
    ls /sys/task > /notes/tasks→(> /notes/tasks (ls /sys/task))
    wc /notes &→(& (wc /notes))
    wc /notes --lines→(wc /notes (lines))
    ls /net @long→(view long (ls /net)): a last word @V is how the host shows it (sysk 066)
    wc -l /notes→(wc -l /notes): a letter is the shell's to resolve
    follow /log --quiet 30000→(follow /log (quiet 30000))
    producer | then consumer→(pipe producer then consumer)
    (wc /notes)→unchanged

    A sugared line is a list without its outer parentheses, as in Tcl or rc; | makes it a pipe, and a > or >> outside parentheses on a command's first line sends it into a file (sysk 029); a last & runs it in the background (sysk 032); a last word @V before those is the view, (view V ...) outside them all (sysk 066); and --name N is (name N) when N is a number, --name alone (name) (sysk 036), each exactly as long as what it replaces, so sugar rewrites it in place. Sugar is deliberately small: it is the place to try new syntax without touching the shell. It knows no programs, so a pipe of programs no catalogue has rewrites as well as any; producer, filter and consumer are the test system's (sysk 073), since no tool of the system you boot reads a channel yet.

    Arguments

    A program may take arguments after its channel ends (sysk 036): a number or a flag, each with a name. They are named, never positional, so a command says what each value means wherever it is read: in a transcript, a refusal, or a command another program wrote. Each program says what it takes in its own code, and the catalogue fills in what you leave out:

    the program(fn follow (... (out Tx) (quiet i64)) (requires (... (<= 1000 quiet) (<= quiet 60000))) ...)

    an argument is a parameter after the channel ends; its contract is its range

    the manifest(programs ... wc (follow (quiet 5000)))

    each number's default; a flag (bool) is off unless named

    the shell(follow /log (quiet 30000))

    reads the names, fills the defaults, writes the values into the job's table

    the kerneljob-check

    refuses a number outside its range, reason 7, whatever sent it

    the slot(follow k h ... a0)

    reads it (job-arg), checks the range, calls: sysp proves the call meets the contract

    The shell refuses what it can see in the command: a name the program does not take, a name given twice, a flag with a value, a number without one. Whether a number is in range is the job check's alone, so there is one place that decides it. catalogue lists each program's arguments, a line each; a line that would pass the screen's 64 columns goes on below, indented by four (sysk 041):

    wc: ask -> stream x 576, answer <- stream x 576,
        out -> 1 x 64
      (lines): a flag
    follow: ask -> stream x 576, answer <- stream x 576,
        out -> stream x 64
      (quiet N): 1000 to 60000, else 5000

    Arguments are numbers from 0 to 999999 and flags. A flag may have a letter, for the fingers (sysk 066): (wc (letter l lines) ...) in the manifest makes wc -l /notes/x mean (wc /notes/x (lines)). Letters come right after the program's name, as Unix types them; the shell resolves them from the catalogue, so the program never sees one, and refuses a letter it does not know by saying what there are: wc takes -l (lines). A letter is only ever a program's own flag. How things look is never a letter but a view, after @, the host's (Views); ls -l, kept from Unix, is the one habit that means a view, and moves to the manifest with ls when it becomes a program. No bundles like -la until a program has two flags worth bundling. A program's letters are in its file in /prg, (letter l lines).

    The prompt

    The prompt is where paths start, its last name, a space and % (sysk 094), as in Plan 9's rc: / % at the root, eth % after cd /net/eth (sysk 068). % means nothing to sysh, so a line copied with its prompt, eth % ls /net @long, runs as it was typed: sugar drops a first word followed by % alone. Before the prompt the shell tells a host the whole directory, (dot "/net/eth"), which a host can lay out as the person likes (syst); a plain terminal shows the prompt alone. There is no mark for a command that failed: a job has no status yet, so a mark could only say what the shell itself refused, which the line above already says.

    Commands longer than a line

    A line is 64 bytes. While a command's parentheses are open, the shell joins the next line to it and shows how many are open and +, as wide as the prompt, 1+ instead of / % (sysk 068); once they balance, the command runs. A command may take four lines; Ctrl-C drops one left open (sysk 069). The squares are the parentheses still open after each line of a command typed at the test system:

    Both prompts are unfinished lines: a line whose length byte has its top bit set is written without its newline, so you type after it. A line longer than 64 bytes reaches the shell in pieces; a piece is exactly 63 bytes, and is joined to the next without a space, so no word is split. Sugar joins the pieces of a line the same way before it reads it, so a > path at the end of a long line is found, and it sends a word longer than its own lines as such pieces too (sysk 083).

    When a command cannot run

    The shell answers in your command's terms: the program, its argument, the channel by name, never an index. Its own refusals come first (what it cannot read, words it does not know); then the job check's, from Jobs. Nothing of a refused command starts, and the next one runs as if it had never been typed. These are a boot test, typed at the shell's test system, whose five test programs have the channels to get wrong (sysk 073):

    You typeWhyThe shell says

    Where its lines go

    Every line the shell says goes through one function, say: to the screen, or, while a command runs under >, as the next line of the file fid 1 names on the disk, written at the next offset. A job's output, a listing and an error all take the same road, so anything that can be printed can be stored. The first refusal on the way is kept, and said once the command is done: no space: /notes/x, say, and the file is not stored. A > inside a > is refused: one file at a time. The shell asks the disk's server over its own pair of streams, fsd-ask-shell and fsd-reply-shell, the devices' over another, rng-ask-shell and rng-reply-shell (sysk 046), and the network's over a third, netd-ask-shell and netd-reply-shell (sysk 052), so it waits for each reply as a client does (pairs). tools/wire.rkt makes each pair for a mount of its profile, observer, and names it after the server (sysk 062, The manifest); the profile is everything the shell holds but its heap, its commands and its answers. Which pair a path's requests take, the profile says: its mounts (sysk 053, Files), a table the shell is handed as text and keeps, and tree-of reads, the longest mount that starts the path. Since sysk 048 the shell holds its whole namespace as one value, Ns: the kernel's Sys, the table, and each server's two ends, so a function that reaches a file takes one argument for all four trees, and a server more is a field more (sysk 052 added /net as two fields); linked says each pair is a server's (served-over).

    Values, a *, and where

    What the shell says is not laid out by the shell (sysk 044, syst). say marks a line that is one s-expression as a value, and cat says a longer value whole, in pieces, rather than wrapping it for a screen of 64 columns; the terminal, or the host on the person's machine, lays it out. Each command's lines are a region of their own: the shell numbers commands, tells the terminal which region follows before each command and each line of a job in the background, which keeps its command's number (always, not only when it changes: a test of that would be a choice the deadlock model follows at every command). Two forms make values worth having:

    cat /sys/task/*/status                       ; each task's status: a * in place of each name
    (where (state live) (cat /sys/task/*/status))   ; only the values with that field

    A path may have one component that is *. The shell reads the directory before it, one entry at a time, and shows each path made with that name, in the job table's buffer, which is free while a file is shown. where takes a (FIELD VALUE) and a cat, and keeps the values that have that field among theirs; around anything else it says so. Through a host, the run of statuses is a table.

    A job's value longer than a line

    A job says lines of 63 bytes, and some values are longer: a SHA-256 in hex is 64 characters, (sha-256 "…") 76 bytes (sysc 014). A job sends such a value in pieces, each but the last an unfinished line. The shell, not the job, decides what passes for a value (sysk 044): it follows the pieces' shape as they pass, the parentheses outside strings, and sends them on as one value, answer-more then answer-value, only while it is well formed so far, at most the terminal's 512 bytes and 48 parentheses deep. A piece that breaks the shape, a line in the middle that is not marked and does not end it, the job's end with the value open, or a value past 512 bytes: the shell closes the value with what it needs, a quote and parentheses, says value cut: the shell closed it, and the line that broke it goes on alone. So whatever a job sends, a host reads balanced (out ...) messages. tests/values-test.rkt sends each case.

    / % shapes --shape 2          ; (shapes (open, and the job ends
    (shapes (open))
    value cut: the shell closed it
    job ended

    What the shell knows

    The shell is user code: it cannot read the kernel's tables. The manifest hands it a text instead, (catalogue), which tools/wire.rkt writes from the jobs clause and each program's contract, so it cannot disagree with them. The shell reads it with the same cursor it reads commands with (lib/sexp.sysp: the next word or parenthesis from a position, nothing built; what is there is a Token, end, open, close, word or other, sysk 041; sx-form finds a text's top-level list by number). Its mount table comes the same way, where its (profile observer) stands, read by the same functions (sysk 053, 062). Here is this system's catalogue, as the last make docs read it:

    
    

    What a command asks for is one of a fixed set of kinds, an Action (sysk 037, sysp 028): classify reads the command and answers one, (new Action reply) with the reason in a line when it cannot be run, and perform does what it says, ((is action uptime) …). Before, a kind was a number from 0 to 15 that one comment explained, and a contract bounded it; now sysp knows each by name, and no other value is one.

    A job in the background

    A job started with & runs while you go on typing (sysk 032). The shell then waits on two sources at once with alt, as the file server does: your next line, and the job's next line or its end. A command runs as it would without the job; one that would start a second job is refused, busy: a job is running, and the job goes on. When the job's output ends the shell says job ended and the prompt comes back. If your input ends first, the shell stops reading commands but still reads the job to its end, so the session finishes as before. One way a session can go, with the lines in the order they might come:

    you typecommands: source 0 the jobout: source 5 shell: alt over botha command runs, a line is said the screen
    / % echo started > /log
    / % follow /log &
    started
    / % echo more >> /log
    more
    / % wc /log
    busy: a job is running
    / % ls
    log
    …
    job ended
    youthe shellthe job

    follow /log & is the job to run this way (sysk 033): it prints the file, then each line added to it, as you add them with echo ... >> /log, and ends once the file has been quiet for five seconds, or as long as you tell it: follow /log --quiet 30000 & (Reads that wait; Arguments).

    Which comes first, a line of the job's or a command's reply, is the scheduler's to say; each stays whole, and each keeps its own order. A job in the background shows its lines: & around a > is refused, as is & around anything that is not a job. Both sources are the manifest's: since sysk 032 a launcher's (jobs out) gives it its jobs' output as one more alt source, after its others.

    One job at a time, by contract

    The shell holds one Jobs, the right to start jobs, and it keeps where it is in their protocol in its fields, as a channel handle keeps its count: idle, a job judged, a job running. job-check needs an idle right, job-start one whose job passed, job-end a running one and the job's output read to its end. So the shell's loop can hold the right across commands, and sysp proves it never judges a second job while one runs, never starts one the check refused, and never ends one it has not drained. With a job in the background the right is running while commands run: command is told so (busy), and its contract says the right comes back as it went in, so a command beside a job cannot touch it. The job's output end lives in background, the loop that reads it, and jobs-output (interface 0.6.2) says it is that right's.

    A job in the foreground ends at Ctrl-C (sysk 074), whatever was typed before it (sysk 084). While it runs the shell waits on two things: the job's output and interrupt, a channel from the terminal that carries only Ctrl-C. Any message there stops the job with job-stop, and the shell says job stopped. Lines typed meanwhile wait on commands, unread, and run in order once the job has ended, so a pasted script runs as before. The terminal takes Ctrl-C even while a line waits for room, and drops that line and the keys before the Ctrl-C, as a tty flushes its input at an interrupt; the lines already past sugar still run. At the prompt the shell takes interrupts by alt beside its commands, so one typed while no job runs stops nothing later. Plan 9 sends a note to the process group the same way, out of band, by the console. A job in the background is not stopped: the prompt is the foreground, and Ctrl-C there drops what is being typed.