Repository navigation
v0.6.0
The host API gains an argv entry for the engine's Node, per-fd standard
streams (kind, terminal, bytes) and a shared-memory stdin, and a guest's
processes and files carry bytes and modes as Linux does. Released as v0.6.0
(the exported surface changed); the workflow dates this section when it
publishes.
New API
container.runNode(argv, options): the engine's Node started from an
execve-style vector (argv[0]isprocess.argv0), withrun's options
and answer, through the same launch the shell'snodeuses; no command line
is built and no shell runs.envis the whole environment;cwddefaults
to/.RunOptions(forrunandrunNode):stdioKind: ['tty'|'pipe'|'file'|'char', x3]says what each of fds 0, 1,
2 is ('char' is a character device that is not a terminal,/dev/null);
stdioIsTTY: [b, b, b]says only which is a terminal (used where
stdioKindis absent). Each fd is a terminal or not on its own.onStdoutBytes/onStderrBytes(Uint8Array)take fd 1 / fd 2 as the
bytes the program wrote, before any decode.stdinShared: SharedArrayBuffergives fd 0 as a ring a host writes from
its own thread;STDIN_RING(the layout),createStdinRing,
stdinRingProblemand the producerStdinRingWriter(write(bytes)
waits withAtomics.waitAsyncwhen the ring is full;close()is EOF)
are exported. The header is eight Int32 words (32 bytes):WRITE_INDEX,
READ_INDEX,CLOSED,CAPACITYandSIGNAL, which the producer bumps
and notifies after every write and at close, and on which readers wait,
so a close between a reader's look and its wait still wakes it; three
words are reserved. A host writes the ring only throughStdinRingWriter.stdinaccepts bytes.
@volter/tabnode/stdin-ring: the ring's layout, producer and helpers alone,
a module that imports nothing, for a page that writes a run's stdin without
loading the engine. The engine's reader is the same module.- Types
StdioKindexported. The host-executor request a page publishes
(Symbol.for('@volter/browser-runtime/child-process-executor')) is offered
onStdoutBytes/onStderrBytes;NodeProcessLaunch.stdinmay be bytes. - Declarations say what the implementation already took: the engine's process
streams'writeand a WASI host process'swriteacceptUint8Arrayas
well as strings, and the guest stdin'sread()answers
string | Uint8Array | null, asReadable's does. NodeProcessLaunch.argv0: the process's own name,argv[0]of the vector
it was started with. A Node process host starts the process with it as
argv[0];argvstays the words after it. A host that rebuilt the vector
as["node", ...argv]gave every hosted processprocess.argv0 === 'node'
(a kernel's exec of a renamed executable reportednode); it must use
argv0.
Behaviour changes a consumer must know
VirtualFSemits an event for every mutation:changefor an entry created
or changed (bytes, mode, times, a directory, a link; a file's carries its
text, others none) anddeletefor one that went.chmodSync,utimesSync,
symlinkSync,mkdirSync(and the directories a write or a rename makes),
rmdirSyncandrenameSyncemitted nothing; a rename now reports every
path of the moved subtree gone at the old name and made at the new.- File modes are real. The shell's filesystem reports each file's own mode and
itschmodsets it; the/node_modules/.bin/rule that made only those
files executable is gone. A host that writes an executable must write it
0755 (orchmodSyncit); a 0644 file is not found on PATH or run by path.
The engine's own writers set modes: npm install keeps each archive entry's
mode ((mode | 0666) & ~022, directories| 0777), makes each bin target
and its.binstub executable. - The engine writes nothing into a tree a host supplied (
createContainer({ vfs })):
the/usr/local/bin/node(process.execPath) file it used to create in any
tree that lacked one is created, 0755, only in a tree the engine made itself.
A host's tree carries its own program atprocess.execPath(any file there
namednoderuns the engine'snodefrom the shell); without one, a shell
line naming the path gets "No such file or directory". - A guest's
nodechild (spawn/execFile/forkofnodeor of
process.execPath, andspawnSync/execFileSyncof them) starts from its
argv through the Node launch, not as a line for the engine's shell. It is one
process whose pid is the one its parent's handle holds (there is no shell
process between), and its environment is exactly the one it was spawned with
(the shell's own HOME/USER/PATH defaults are not added). A shell line
(exec,execSync,{ shell: true }) is unchanged. - Child stdio carries bytes: a Node child's fd 0/1/2 reach the parent's pipe,
inherited stdio or descriptor as bytes, and Node'schild_processreturns a
Buffer or a string by its encoding option, as on Linux. Output that was
invalid UTF-8 used to arrive with replacement characters. - A run given
onStdoutBytes/onStderrBytesgets that fd only as bytes:
onStdout/onStderrare not called for it andRunResult.stdout/stderr
is empty for it. Text callbacks now decode with one streaming decoder per fd,
so a character split across two writes arrives whole. process.stdout.write(string, encoding)writes the string in that encoding
('ff', 'hex'is one byte); the encoding was ignored.fs.writeSync(fd, string, pos, encoding)likewise (every non-UTF-8 encoding was latin1).- fd 0, 1 and 2 are descriptors of every run.
fs.writeSync(1|2)and writes to
/dev/stdout,/dev/stderr,/dev/fd/1|2,/proc/self/fd/1|2go to the
run's stdout/stderr in order withprocess.stdout(they were EBADF);
fs.readSync(0)and/dev/stdinread the run's stdin in order with
process.stdin(EBADF before). A blocking read with the writer open and
nothing buffered waits onstdinSharedwhere the run has one, and is
otherwise refused asERR_STDIN_BLOCKING_READ; anO_NONBLOCKdescriptor
gets EAGAIN.fs.fstat(0|1|2)is a terminal (pts), a FIFO, a regular file
or a non-terminal character device (0666, device 1,3) by the fd's kind.
tty.isatty(fd)andguessHandleType(fd)answer from the asking run's own
streams, as libuv does: a terminal fd is'TTY'(sonew net.Socket({ fd })
on it fails as in Node), a file or character device'FILE', a pipe
'PIPE'; it was'PIPE'for all three. - A run's life and its stdin, as in Node. A program that reads fd 0 with
fs.readSync(0)or/dev/stdinand returns ends when its loop is empty,
whether or not the stdin writer has closed: those reads take what
process.stdinalready holds and then read the ring, without starting the
stream (the run used to live until the ring closed, and forever if it never
did). A run whose program consumesprocess.stdin(a'data'or
'readable'listener, flowing mode, or its ownread()call) is kept alive
until the stream ends; a bareprocess.stdin.read()used to let it end early. - A WASI guest's stdout/stderr are written as its bytes, not decoded text.
- A background job (
cmd &) writes its parent's byte sinks where the parent
has them; its output was dropped when the parent took bytes only. - A host's totals: an fd a host executor or a Node process host streamed
through its byte sink has an empty total, and is never replayed through the
text callbacks. A host that streamed no bytes is read as before. The Node
process host is handed a view of the run's streams (its fields read through
to the run's own object), not the object itself. - A Node entry whose top-level
awaitfails prints its error on stderr (it was
only appended to the result); a run's idle detection counts what was
printed, by length, in either form.