feat(shell): job-aware kill, disown, jobs/set flags, fallback REPL history
- Document interactive shell vs /bin/sh in bare-os-shell man, handbook §9, kill(1), and shell-completion guide - Resolve kill %n and %% to synthetic PIDs via shellBackgroundJobs - Add disown builtin; jobs -p (pgid-only) and -l (pid column); set -o/+o to print errexit/nounset/pipefail/noglob - Expand completion-engine fallback flags for common utilities - Persist lines to /.bare/repl_history_<USER> when BARE_OS_REPL_HISTORY=1 and Fish REPL is off (repl-session + cli-readline) - Tests: kill job specs, set -o output, jobs -p
This commit is contained in:
@@ -35,7 +35,7 @@ Or `node packages/bare-os-coreutils/build.mjs`.
|
||||
|
||||
## Commands (authoritative list)
|
||||
|
||||
**Source of truth:** **`lib/commands.mjs`** — **`COREUTILS_COMMANDS`** (imported by **`build.mjs`** and **`scripts/build-man-db.mjs`**). Each name must have **`man/pages/<name>.json`**. **150** Tier-1 commands in the current tree (**`sshd`**’s `/bin` body is emitted by **`bare-os-openssh`**); root **`pretest`** runs **`verify-man-coverage.mjs`** against this list (do not hand-maintain a duplicate comma-separated inventory here—use **`lib/commands.mjs`**, **`ls /bin`** in the guest, or **`man -k`**).
|
||||
**Source of truth:** **`lib/commands.mjs`** — **`COREUTILS_COMMANDS`** (imported by **`build.mjs`** and **`scripts/build-man-db.mjs`**). Each name must have **`man/pages/<name>.json`**. **153** Tier-1 commands in the current tree (**`sshd`**’s `/bin` body is emitted by **`bare-os-openssh`**); root **`pretest`** runs **`verify-man-coverage.mjs`** against this list (do not hand-maintain a duplicate comma-separated inventory here—use **`lib/commands.mjs`**, **`ls /bin`** in the guest, or **`man -k`**).
|
||||
|
||||
**`edit`** is a full-screen TTY buffer editor (syntax highlighting, search, save). **`nano`** is built from the same **`src/edit.js`** with the same **`lib/edit-*.js`** preamble; **`/bin/nano`** exists for familiarity, and the stock shell alias **`nano` → `edit`** routes **`nano`** to that utility (see **`packages/bare-os-booter/lib/shell.js`**). Both require a real TTY (**`stdout.isTTY`**).
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@ const seederKernelLibBare = join(
|
||||
/** Commands whose /bin script is preceded by extra library sources (no import in src). */
|
||||
const preamble = {
|
||||
md5sum: ['md5.js'],
|
||||
sha224sum: ['sha224.js'],
|
||||
sed: ['sed-engine.js'],
|
||||
awk: ['awk-engine.js'],
|
||||
jq: ['jq-engine.js'],
|
||||
|
||||
@@ -111,7 +111,9 @@ export const COREUTILS_COMMANDS = [
|
||||
'seq',
|
||||
'setfacl',
|
||||
'sha1sum',
|
||||
'sha224sum',
|
||||
'sha256sum',
|
||||
'sha384sum',
|
||||
'sha512sum',
|
||||
'sh',
|
||||
'shuf',
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
/**
|
||||
* SHA-224 (FIPS 180-4) — same compression as SHA-256, distinct IV, output first 224 bits.
|
||||
* Bundled because many runtimes (incl. Node Web Crypto) omit SHA-224 in subtle.digest.
|
||||
*/
|
||||
function bareSha224DigestBytes(u8) {
|
||||
const K = new Uint32Array([
|
||||
0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4,
|
||||
0xab1c5ed5, 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe,
|
||||
0x9bdc06a7, 0xc19bf174, 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f,
|
||||
0x4a7484aa, 0x5cb0a9dc, 0x76f988da, 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7,
|
||||
0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967, 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc,
|
||||
0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, 0xa2bfe8a1, 0xa81a664b,
|
||||
0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, 0x19a4c116,
|
||||
0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
|
||||
0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7,
|
||||
0xc67178f2
|
||||
])
|
||||
const W = new Uint32Array(64)
|
||||
const n = u8.length
|
||||
const bits = BigInt(n) * 8n
|
||||
const bitLenHi = Number((bits >> 32n) & 0xffffffffn)
|
||||
const bitLenLo = Number(bits & 0xffffffffn)
|
||||
const padLen = (56 - ((n + 1) % 64) + 64) % 64
|
||||
const total = n + 1 + padLen + 8
|
||||
const buf = new Uint8Array(total)
|
||||
buf.set(u8)
|
||||
buf[n] = 0x80
|
||||
const view = new DataView(buf.buffer)
|
||||
view.setUint32(total - 8, bitLenHi, false)
|
||||
view.setUint32(total - 4, bitLenLo, false)
|
||||
|
||||
let h0 = 0xc1059ed8
|
||||
let h1 = 0x367cd507
|
||||
let h2 = 0x3070dd17
|
||||
let h3 = 0xf70e5939
|
||||
let h4 = 0xffc00b31
|
||||
let h5 = 0x68581511
|
||||
let h6 = 0x64f98fa7
|
||||
let h7 = 0xbefa4fa4
|
||||
|
||||
const rotr = (x, c) => ((x >>> c) | (x << (32 - c))) >>> 0
|
||||
for (let off = 0; off < total; off += 64) {
|
||||
for (let i = 0; i < 16; i++) W[i] = view.getUint32(off + i * 4, false)
|
||||
for (let i = 16; i < 64; i++) {
|
||||
const s0 = rotr(W[i - 15], 7) ^ rotr(W[i - 15], 18) ^ (W[i - 15] >>> 3)
|
||||
const s1 = rotr(W[i - 2], 17) ^ rotr(W[i - 2], 19) ^ (W[i - 2] >>> 10)
|
||||
W[i] = (W[i - 16] + s0 + W[i - 7] + s1) >>> 0
|
||||
}
|
||||
let a = h0
|
||||
let b = h1
|
||||
let c = h2
|
||||
let d = h3
|
||||
let e = h4
|
||||
let f = h5
|
||||
let g = h6
|
||||
let h = h7
|
||||
for (let i = 0; i < 64; i++) {
|
||||
const S1 = rotr(e, 6) ^ rotr(e, 11) ^ rotr(e, 25)
|
||||
const ch = (e & f) ^ (~e & g)
|
||||
const t1 = (h + S1 + ch + K[i] + W[i]) >>> 0
|
||||
const S0 = rotr(a, 2) ^ rotr(a, 13) ^ rotr(a, 22)
|
||||
const maj = (a & b) ^ (a & c) ^ (b & c)
|
||||
const t2 = (S0 + maj) >>> 0
|
||||
h = g
|
||||
g = f
|
||||
f = e
|
||||
e = (d + t1) >>> 0
|
||||
d = c
|
||||
c = b
|
||||
b = a
|
||||
a = (t1 + t2) >>> 0
|
||||
}
|
||||
h0 = (h0 + a) >>> 0
|
||||
h1 = (h1 + b) >>> 0
|
||||
h2 = (h2 + c) >>> 0
|
||||
h3 = (h3 + d) >>> 0
|
||||
h4 = (h4 + e) >>> 0
|
||||
h5 = (h5 + f) >>> 0
|
||||
h6 = (h6 + g) >>> 0
|
||||
h7 = (h7 + h) >>> 0
|
||||
}
|
||||
|
||||
const out = new Uint8Array(28)
|
||||
const ov = new DataView(out.buffer)
|
||||
ov.setUint32(0, h0, false)
|
||||
ov.setUint32(4, h1, false)
|
||||
ov.setUint32(8, h2, false)
|
||||
ov.setUint32(12, h3, false)
|
||||
ov.setUint32(16, h4, false)
|
||||
ov.setUint32(20, h5, false)
|
||||
ov.setUint32(24, h6, false)
|
||||
return [...out].map((b) => b.toString(16).padStart(2, '0')).join('')
|
||||
}
|
||||
@@ -1,11 +1,12 @@
|
||||
{
|
||||
"name": "bare-os-shell",
|
||||
"section": 1,
|
||||
"title": "Bare OS interactive shell builtins",
|
||||
"title": "Bare OS interactive shell (Issue 7–inspired subset)",
|
||||
"synopsis": [
|
||||
"# builtins only — no full POSIX sh grammar"
|
||||
"# Interactive session: builtins + /bin via ctx.execLine (see handbook §9)",
|
||||
"# /bin/sh is a separate script runner — see man sh"
|
||||
],
|
||||
"description": "The line-at-a-time shell supports aliases, simple pipelines (simulated), redirection, and the builtins below. Compound commands (if, for, while) are not available.",
|
||||
"description": "The interactive shell runs in the booter: tokenization, pipelines (simulated capture), redirection, AND-OR lists (; && ||), bounded compound commands (if/fi, while/for/case), background jobs (&), optional command substitution ($(…)) when BARE_OS_SHELL_CMDSUBST=1, globbing, and subsets of errexit (-e), nounset (-u), and pipefail. There are no forked subshells and no full POSIX sh grammar. Canonical narrative: handbook ch.9; completion/REPL: docs/reference/shell-completion-and-repl-editor.md.",
|
||||
"options": [],
|
||||
"aliases": [
|
||||
"sh-builtins"
|
||||
@@ -13,9 +14,9 @@
|
||||
"keywords": [
|
||||
"shell",
|
||||
"builtin",
|
||||
"cd",
|
||||
"export",
|
||||
"alias",
|
||||
"pipeline",
|
||||
"jobs",
|
||||
"execLine",
|
||||
"bare-os-shell",
|
||||
"sh-builtins"
|
||||
],
|
||||
@@ -27,7 +28,7 @@
|
||||
"alias name=value ...",
|
||||
"unalias name ..."
|
||||
],
|
||||
"description": "Define or list command aliases. unalias removes definitions."
|
||||
"description": "Define or list command aliases; unalias removes definitions."
|
||||
},
|
||||
{
|
||||
"name": "cd",
|
||||
@@ -64,13 +65,24 @@
|
||||
],
|
||||
"description": "Show or set shell file creation mask (stored in env UMASK)."
|
||||
},
|
||||
{
|
||||
"name": "set",
|
||||
"synopsis": [
|
||||
"set -o",
|
||||
"set +o",
|
||||
"set -e | +e | -u | +u | -f | +f",
|
||||
"set -o errexit|nounset|pipefail",
|
||||
"set +o errexit|nounset|pipefail"
|
||||
],
|
||||
"description": "Toggle errexit (BARE_OS_SHELL_ERREXIT), nounset (BARE_OS_SHELL_NOUNSET), pipefail (BARE_OS_SHELL_PIPEFAIL), noglob (BARE_OS_SHELL_NOGLOB). Use set -o / set +o alone to print current shell options (subset)."
|
||||
},
|
||||
{
|
||||
"name": "command",
|
||||
"synopsis": [
|
||||
"command -v|-V NAME",
|
||||
"command ARGV..."
|
||||
],
|
||||
"description": "Resolve or run a command without using shell functions (none) or aliases for -v/-V."
|
||||
"description": "Resolve or run a command; -v/-V skip aliases."
|
||||
},
|
||||
{
|
||||
"name": "type",
|
||||
@@ -79,6 +91,46 @@
|
||||
],
|
||||
"description": "Report whether NAME is a builtin or a path under PATH."
|
||||
},
|
||||
{
|
||||
"name": "jobs",
|
||||
"synopsis": [
|
||||
"jobs [-l] [-p]"
|
||||
],
|
||||
"description": "List logical background jobs. -p prints pgid only; -l includes pgid/sid in the listing."
|
||||
},
|
||||
{
|
||||
"name": "fg / bg / wait",
|
||||
"synopsis": [
|
||||
"fg [%job]",
|
||||
"bg [%job]",
|
||||
"wait [n | %n]",
|
||||
"wait -n (with BARE_OS_SHELL_POSIX_MODE=1)"
|
||||
],
|
||||
"description": "Cooperative job control: fg awaits a job; bg resumes stopped jobs; wait waits for jobs by id or all."
|
||||
},
|
||||
{
|
||||
"name": "suspend-job",
|
||||
"synopsis": [
|
||||
"suspend-job [%job]"
|
||||
],
|
||||
"description": "Mark a running background job stopped (logical); resume with fg or bg."
|
||||
},
|
||||
{
|
||||
"name": "disown",
|
||||
"synopsis": [
|
||||
"disown [%job]"
|
||||
],
|
||||
"description": "Remove a job from the jobs table without cancelling its async work (still runs to completion)."
|
||||
},
|
||||
{
|
||||
"name": "trap",
|
||||
"synopsis": [
|
||||
"trap -l",
|
||||
"trap -p",
|
||||
"trap CMD SIGNAL"
|
||||
],
|
||||
"description": "List signals, print handlers, or register synthetic trap handlers (ctx.shellTrapHandlers)."
|
||||
},
|
||||
{
|
||||
"name": "login / logout",
|
||||
"synopsis": [
|
||||
@@ -100,9 +152,20 @@
|
||||
"exit [n]"
|
||||
],
|
||||
"description": "Request booter exit with status n (builtin path)."
|
||||
},
|
||||
{
|
||||
"name": "read",
|
||||
"synopsis": [
|
||||
"read [-r] [NAME ...]"
|
||||
],
|
||||
"description": "Optional when BARE_OS_SHELL_READ_BUILTIN=1; bounded line from shell stdin or readLine."
|
||||
}
|
||||
],
|
||||
"seeAlso": [
|
||||
{
|
||||
"name": "sh",
|
||||
"section": 1
|
||||
},
|
||||
{
|
||||
"name": "help",
|
||||
"section": 1
|
||||
@@ -112,31 +175,27 @@
|
||||
"section": 1
|
||||
}
|
||||
],
|
||||
"bareOsNotes": "Pipelines do not use OS pipes; see handbook ch.4 and ch.9.",
|
||||
"bareOsNotes": "Pipelines use simulated capture (not OS pipes). kill and wait accept %n job specs when shellBackgroundJobs is populated. Full UX (history file, tab menu, Ctrl+R) uses BARE_OS_FISH≠0 on a TTY; see shell-completion-and-repl-editor.md.",
|
||||
"examples": [
|
||||
{
|
||||
"caption": "pipeline (simulated)",
|
||||
"code": "ls -1 /bin | grep man"
|
||||
},
|
||||
{
|
||||
"caption": "errexit + compound",
|
||||
"code": "set -e\nif true; then echo ok; fi"
|
||||
},
|
||||
{
|
||||
"caption": "background job",
|
||||
"code": "sleep 1 &\njobs"
|
||||
},
|
||||
{
|
||||
"caption": "redirect out",
|
||||
"code": "echo hi > ~/hello.txt"
|
||||
},
|
||||
{
|
||||
"caption": "append",
|
||||
"code": "date >> ~/log.txt"
|
||||
},
|
||||
{
|
||||
"caption": "alias + use",
|
||||
"code": "alias ll='ls -la'\nll ~"
|
||||
},
|
||||
{
|
||||
"caption": "export for children",
|
||||
"code": "export EDITOR=ed\nman ls"
|
||||
},
|
||||
{
|
||||
"caption": "temp var for one command",
|
||||
"code": "PATH=/bin man which"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -5,11 +5,19 @@
|
||||
"synopsis": [
|
||||
"kill [OPTION]... [OPERAND]..."
|
||||
],
|
||||
"description": "Bare OS implementation of kill. Full behavior is defined in packages/bare-os-coreutils/src/kill.js.",
|
||||
"description": "Send a signal to synthetic processes. Targets may be numeric PIDs, kernel/booter/shell names, negative values for logical process groups, or job specs when the interactive shell has background jobs: %n (job id n) or %% (current job), resolved to synthetic PIDs 4100+job.id for ctx.bareOsSendSignal. See handbook ch.9 and bare-os-process-table jobControlSemantics.",
|
||||
"options": [],
|
||||
"keywords": [
|
||||
"kill",
|
||||
"signal",
|
||||
"job",
|
||||
"bare-os",
|
||||
"coreutils"
|
||||
],
|
||||
"seeAlso": [
|
||||
{
|
||||
"name": "bare-os-shell",
|
||||
"section": 1
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
"synopsis": [
|
||||
"sh [OPTION]... [OPERAND]..."
|
||||
],
|
||||
"description": "Bare OS implementation of sh. Full behavior is defined in packages/bare-os-coreutils/src/sh.js.",
|
||||
"description": "Runs a shell script file line-by-line via ctx.execLine (same language as the interactive booter shell). This is not the interactive REPL; see man bare-os-shell for session builtins, pipelines, and jobs.",
|
||||
"options": [],
|
||||
"keywords": [
|
||||
"sh",
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"name": "sha224sum",
|
||||
"section": 1,
|
||||
"title": "sha224sum",
|
||||
"synopsis": [
|
||||
"sha224sum [OPTION]... [OPERAND]..."
|
||||
],
|
||||
"description": "Bare OS implementation of sha224sum. Computes SHA-224 via a bundled digest (many runtimes omit SHA-224 in crypto.subtle.digest). Full behavior is defined in packages/bare-os-coreutils/src/sha224sum.js.",
|
||||
"options": [],
|
||||
"keywords": [
|
||||
"sha224sum",
|
||||
"bare-os",
|
||||
"coreutils"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"name": "sha384sum",
|
||||
"section": 1,
|
||||
"title": "sha384sum",
|
||||
"synopsis": [
|
||||
"sha384sum [OPTION]... [OPERAND]..."
|
||||
],
|
||||
"description": "Bare OS implementation of sha384sum. Full behavior is defined in packages/bare-os-coreutils/src/sha384sum.js.",
|
||||
"options": [],
|
||||
"keywords": [
|
||||
"sha384sum",
|
||||
"bare-os",
|
||||
"coreutils"
|
||||
]
|
||||
}
|
||||
@@ -6,6 +6,6 @@
|
||||
"description": "Build JS /bin utilities for bare-operating-system (concat + stage to kernel/)",
|
||||
"scripts": {
|
||||
"build": "node ./scripts/ensure-man-pages.mjs && node ./build.mjs",
|
||||
"test": "node ./test/help-bin-list.test.mjs && node ./test/edit-key-parse.test.mjs && node ./test/edit-teardown.test.mjs && node ./test/baretop-bundle.test.mjs && node ./test/baretop-fixture.test.mjs && node ./test/baretop-compose.test.mjs && node ./test/baretop-incremental.test.mjs && node ./test/baretop-ui-helpers.test.mjs && node ./test/posix-test-int-compare.test.mjs && node ./test/posix-utils-edge.test.mjs && node ./test/posix-golden-issue7.test.mjs && node ./test/getconf-pathconf-union-mirror.test.mjs && node ./test/getconf-posix-shm.test.mjs && node ./test/awk-sed-posix-smoke.test.mjs && node ./test/xattr-acl-utils.test.mjs && node ./test/xcu-issue7-sweep.test.mjs && node ./test/expand-tab-stops.test.mjs && node ./test/pkg-swarm-index-pathcap.test.mjs"
|
||||
"test": "node ./test/help-bin-list.test.mjs && node ./test/edit-key-parse.test.mjs && node ./test/edit-teardown.test.mjs && node ./test/baretop-bundle.test.mjs && node ./test/baretop-fixture.test.mjs && node ./test/baretop-compose.test.mjs && node ./test/baretop-incremental.test.mjs && node ./test/baretop-ui-helpers.test.mjs && node ./test/posix-test-int-compare.test.mjs && node ./test/posix-utils-edge.test.mjs && node ./test/posix-golden-issue7.test.mjs && node ./test/getconf-pathconf-union-mirror.test.mjs && node ./test/getconf-posix-shm.test.mjs && node ./test/awk-sed-posix-smoke.test.mjs && node ./test/xattr-acl-utils.test.mjs && node ./test/xcu-issue7-sweep.test.mjs && node ./test/expand-tab-stops.test.mjs && node ./test/pkg-swarm-index-pathcap.test.mjs && node ./test/sha224-sha384-sum.test.mjs"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -933,18 +933,21 @@ function shellPage() {
|
||||
return {
|
||||
name: 'bare-os-shell',
|
||||
section: 1,
|
||||
title: 'Bare OS interactive shell builtins',
|
||||
synopsis: ['# builtins only — no full POSIX sh grammar'],
|
||||
title: 'Bare OS interactive shell (Issue 7–inspired subset)',
|
||||
synopsis: [
|
||||
'# Interactive session: builtins + /bin via ctx.execLine (see handbook §9)',
|
||||
'# /bin/sh is a separate script runner — see man sh'
|
||||
],
|
||||
description:
|
||||
'The line-at-a-time shell supports aliases, simple pipelines (simulated), redirection, and the builtins below. Compound commands (if, for, while) are not available.',
|
||||
'The interactive shell runs in the booter: tokenization, pipelines (simulated capture), redirection, AND-OR lists (; && ||), bounded compound commands (if/fi, while/for/case), background jobs (&), optional command substitution ($(…)) when BARE_OS_SHELL_CMDSUBST=1, globbing, and subsets of errexit (-e), nounset (-u), and pipefail. There are no forked subshells and no full POSIX sh grammar. Canonical narrative: handbook ch.9; completion/REPL: docs/reference/shell-completion-and-repl-editor.md.',
|
||||
options: [],
|
||||
aliases: ['sh-builtins'],
|
||||
keywords: [
|
||||
'shell',
|
||||
'builtin',
|
||||
'cd',
|
||||
'export',
|
||||
'alias',
|
||||
'pipeline',
|
||||
'jobs',
|
||||
'execLine',
|
||||
'bare-os-shell',
|
||||
'sh-builtins'
|
||||
],
|
||||
@@ -953,7 +956,7 @@ function shellPage() {
|
||||
name: 'alias',
|
||||
synopsis: ['alias', 'alias name=value ...', 'unalias name ...'],
|
||||
description:
|
||||
'Define or list command aliases. unalias removes definitions.'
|
||||
'Define or list command aliases; unalias removes definitions.'
|
||||
},
|
||||
{
|
||||
name: 'cd',
|
||||
@@ -982,17 +985,63 @@ function shellPage() {
|
||||
description:
|
||||
'Show or set shell file creation mask (stored in env UMASK).'
|
||||
},
|
||||
{
|
||||
name: 'set',
|
||||
synopsis: [
|
||||
'set -o',
|
||||
'set +o',
|
||||
'set -e | +e | -u | +u | -f | +f',
|
||||
'set -o errexit|nounset|pipefail',
|
||||
'set +o errexit|nounset|pipefail'
|
||||
],
|
||||
description:
|
||||
'Toggle errexit (BARE_OS_SHELL_ERREXIT), nounset (BARE_OS_SHELL_NOUNSET), pipefail (BARE_OS_SHELL_PIPEFAIL), noglob (BARE_OS_SHELL_NOGLOB). Use set -o / set +o alone to print current shell options (subset).'
|
||||
},
|
||||
{
|
||||
name: 'command',
|
||||
synopsis: ['command -v|-V NAME', 'command ARGV...'],
|
||||
description:
|
||||
'Resolve or run a command without using shell functions (none) or aliases for -v/-V.'
|
||||
description: 'Resolve or run a command; -v/-V skip aliases.'
|
||||
},
|
||||
{
|
||||
name: 'type',
|
||||
synopsis: ['type NAME'],
|
||||
description: 'Report whether NAME is a builtin or a path under PATH.'
|
||||
},
|
||||
{
|
||||
name: 'jobs',
|
||||
synopsis: ['jobs [-l] [-p]'],
|
||||
description:
|
||||
'List logical background jobs. -p prints pgid only; -l includes pgid/sid in the listing.'
|
||||
},
|
||||
{
|
||||
name: 'fg / bg / wait',
|
||||
synopsis: [
|
||||
'fg [%job]',
|
||||
'bg [%job]',
|
||||
'wait [n | %n]',
|
||||
'wait -n (with BARE_OS_SHELL_POSIX_MODE=1)'
|
||||
],
|
||||
description:
|
||||
'Cooperative job control: fg awaits a job; bg resumes stopped jobs; wait waits for jobs by id or all.'
|
||||
},
|
||||
{
|
||||
name: 'suspend-job',
|
||||
synopsis: ['suspend-job [%job]'],
|
||||
description:
|
||||
'Mark a running background job stopped (logical); resume with fg or bg.'
|
||||
},
|
||||
{
|
||||
name: 'disown',
|
||||
synopsis: ['disown [%job]'],
|
||||
description:
|
||||
'Remove a job from the jobs table without cancelling its async work (still runs to completion).'
|
||||
},
|
||||
{
|
||||
name: 'trap',
|
||||
synopsis: ['trap -l', 'trap -p', 'trap CMD SIGNAL'],
|
||||
description:
|
||||
'List signals, print handlers, or register synthetic trap handlers (ctx.shellTrapHandlers).'
|
||||
},
|
||||
{
|
||||
name: 'login / logout',
|
||||
synopsis: ['login [--new] passphrase...', 'logout [--save]'],
|
||||
@@ -1008,20 +1057,30 @@ function shellPage() {
|
||||
name: 'exit',
|
||||
synopsis: ['exit [n]'],
|
||||
description: 'Request booter exit with status n (builtin path).'
|
||||
},
|
||||
{
|
||||
name: 'read',
|
||||
synopsis: ['read [-r] [NAME ...]'],
|
||||
description:
|
||||
'Optional when BARE_OS_SHELL_READ_BUILTIN=1; bounded line from shell stdin or readLine.'
|
||||
}
|
||||
],
|
||||
seeAlso: [
|
||||
{ name: 'sh', section: 1 },
|
||||
{ name: 'help', section: 1 },
|
||||
{ name: 'man', section: 1 }
|
||||
],
|
||||
bareOsNotes: 'Pipelines do not use OS pipes; see handbook ch.4 and ch.9.',
|
||||
bareOsNotes:
|
||||
'Pipelines use simulated capture (not OS pipes). kill and wait accept %n job specs when shellBackgroundJobs is populated. Full UX (history file, tab menu, Ctrl+R) uses BARE_OS_FISH≠0 on a TTY; see shell-completion-and-repl-editor.md.',
|
||||
examples: [
|
||||
{ caption: 'pipeline (simulated)', code: 'ls -1 /bin | grep man' },
|
||||
{
|
||||
caption: 'errexit + compound',
|
||||
code: 'set -e\nif true; then echo ok; fi'
|
||||
},
|
||||
{ caption: 'background job', code: 'sleep 1 &\njobs' },
|
||||
{ caption: 'redirect out', code: 'echo hi > ~/hello.txt' },
|
||||
{ caption: 'append', code: 'date >> ~/log.txt' },
|
||||
{ caption: 'alias + use', code: "alias ll='ls -la'\nll ~" },
|
||||
{ caption: 'export for children', code: 'export EDITOR=ed\nman ls' },
|
||||
{ caption: 'temp var for one command', code: 'PATH=/bin man which' }
|
||||
{ caption: 'alias + use', code: "alias ll='ls -la'\nll ~" }
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,34 @@
|
||||
/**
|
||||
* Resolve job spec %n / %% to synthetic PID (4100+job.id) for ctx.bareOsSendSignal.
|
||||
* Matches fg/wait job selection: %% is latest non-done job.
|
||||
* @param {string} raw
|
||||
* @param {Record<string, unknown>} ctx
|
||||
* @returns {string} numeric pid, pgid negative form, or passthrough token
|
||||
*/
|
||||
function bareOsKillResolveTarget(raw, ctx) {
|
||||
const s = String(raw == null ? '' : raw).trim()
|
||||
if (!s.startsWith('%')) return s
|
||||
const list =
|
||||
ctx.shellBackgroundJobs &&
|
||||
typeof ctx.shellBackgroundJobs === 'object' &&
|
||||
Array.isArray(ctx.shellBackgroundJobs.list)
|
||||
? ctx.shellBackgroundJobs.list
|
||||
: []
|
||||
const spec = s.slice(1)
|
||||
if (spec === '%' || spec === '') {
|
||||
const running = list.filter((j) => j && !j.done)
|
||||
const j = running.length ? running[running.length - 1] : null
|
||||
if (!j) throw new Error('kill: no current job')
|
||||
return String(4100 + j.id)
|
||||
}
|
||||
const id = Number.parseInt(spec, 10)
|
||||
if (!Number.isFinite(id))
|
||||
throw new Error('kill: invalid job specification: ' + raw)
|
||||
const j = list.find((x) => x && x.id === id)
|
||||
if (!j) throw new Error('kill: %' + id + ': no such job')
|
||||
return String(4100 + j.id)
|
||||
}
|
||||
|
||||
async function run(ctx, argv) {
|
||||
const args = argv.slice(1)
|
||||
let signal = 'TERM'
|
||||
@@ -33,7 +64,9 @@ async function run(ctx, argv) {
|
||||
}
|
||||
|
||||
if (targets.length === 0) {
|
||||
ctx.console.error('usage: kill [-s SIGNAL | -SIGNAL] <pid|name>...')
|
||||
ctx.console.error(
|
||||
'usage: kill [-s SIGNAL | -SIGNAL] <pid|name|%job|%%>...'
|
||||
)
|
||||
ctx.exitCode = 1
|
||||
return
|
||||
}
|
||||
@@ -47,7 +80,8 @@ async function run(ctx, argv) {
|
||||
let failed = 0
|
||||
for (const t of targets) {
|
||||
try {
|
||||
send.call(ctx, t, signal)
|
||||
const resolved = bareOsKillResolveTarget(t, ctx)
|
||||
send.call(ctx, resolved, signal)
|
||||
} catch (e) {
|
||||
failed++
|
||||
ctx.console.error('kill: ' + t + ': ' + (e?.message || String(e)))
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
/**
|
||||
* sha224sum — compute SHA-224 checksums (hex), GNU-like output line.
|
||||
* Uses bundled SHA-224 (many runtimes omit SHA-224 in crypto.subtle.digest).
|
||||
*/
|
||||
async function run(ctx, argv) {
|
||||
const paths = []
|
||||
for (let i = 1; i < argv.length; i++) {
|
||||
const a = argv[i]
|
||||
if (a === '-h' || a === '--help') {
|
||||
ctx.console.log(
|
||||
'usage: sha224sum [FILE]...\n' +
|
||||
'With no FILE, or when FILE is -, read standard input.'
|
||||
)
|
||||
ctx.exitCode = 0
|
||||
return
|
||||
}
|
||||
if (a.startsWith('-') && a !== '-') {
|
||||
ctx.console.error('sha224sum: unsupported option ' + a)
|
||||
ctx.exitCode = 1
|
||||
return
|
||||
}
|
||||
paths.push(a)
|
||||
}
|
||||
const b4 = ctx.b4a
|
||||
function one(name, buf) {
|
||||
const u8 = buf instanceof Uint8Array ? buf : new Uint8Array(buf)
|
||||
try {
|
||||
const hex = bareSha224DigestBytes(u8)
|
||||
ctx.console.log(hex + ' ' + name)
|
||||
} catch (e) {
|
||||
ctx.console.error('sha224sum: ' + (e.message || e))
|
||||
ctx.exitCode = 1
|
||||
}
|
||||
}
|
||||
if (!paths.length || (paths.length === 1 && paths[0] === '-')) {
|
||||
one('-', b4.from(bareStdin(ctx)))
|
||||
return
|
||||
}
|
||||
for (const p of paths) {
|
||||
if (p === '-') {
|
||||
one('-', b4.from(bareStdin(ctx)))
|
||||
continue
|
||||
}
|
||||
const b = await ctx.vfs.readFile(p)
|
||||
if (!b) {
|
||||
ctx.console.error('sha224sum: ' + p + ': No such file')
|
||||
ctx.exitCode = 1
|
||||
continue
|
||||
}
|
||||
one(p, b)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
async function sha384Hex(u8) {
|
||||
const subtle = globalThis.crypto?.subtle
|
||||
if (!subtle || typeof subtle.digest !== 'function') {
|
||||
throw new Error('crypto.subtle.digest (SHA-384) is not available')
|
||||
}
|
||||
const hash = await subtle.digest('SHA-384', u8)
|
||||
return [...new Uint8Array(hash)]
|
||||
.map((b) => b.toString(16).padStart(2, '0'))
|
||||
.join('')
|
||||
}
|
||||
|
||||
async function run(ctx, argv) {
|
||||
const paths = []
|
||||
for (let i = 1; i < argv.length; i++) {
|
||||
const a = argv[i]
|
||||
if (a === '-h' || a === '--help') {
|
||||
ctx.console.log(
|
||||
'usage: sha384sum [FILE]...\n' +
|
||||
'With no FILE, or when FILE is -, read standard input.'
|
||||
)
|
||||
ctx.exitCode = 0
|
||||
return
|
||||
}
|
||||
if (a.startsWith('-') && a !== '-') {
|
||||
ctx.console.error('sha384sum: unsupported option ' + a)
|
||||
ctx.exitCode = 1
|
||||
return
|
||||
}
|
||||
paths.push(a)
|
||||
}
|
||||
const b4 = ctx.b4a
|
||||
async function one(name, buf) {
|
||||
const u8 = buf instanceof Uint8Array ? buf : new Uint8Array(buf)
|
||||
try {
|
||||
const hex = await sha384Hex(u8)
|
||||
ctx.console.log(hex + ' ' + name)
|
||||
} catch (e) {
|
||||
ctx.console.error('sha384sum: ' + (e.message || e))
|
||||
ctx.exitCode = 1
|
||||
}
|
||||
}
|
||||
if (!paths.length || (paths.length === 1 && paths[0] === '-')) {
|
||||
await one('-', b4.from(bareStdin(ctx)))
|
||||
return
|
||||
}
|
||||
for (const p of paths) {
|
||||
if (p === '-') {
|
||||
await one('-', b4.from(bareStdin(ctx)))
|
||||
continue
|
||||
}
|
||||
const b = await ctx.vfs.readFile(p)
|
||||
if (!b) {
|
||||
ctx.console.error('sha384sum: ' + p + ': No such file')
|
||||
ctx.exitCode = 1
|
||||
continue
|
||||
}
|
||||
await one(p, b)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
/**
|
||||
* SHA-224 bundled digest vectors; SHA-384 via Web Crypto (same as sha384sum command).
|
||||
*/
|
||||
import { readFileSync } from 'node:fs'
|
||||
import path from 'node:path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
import vm from 'node:vm'
|
||||
import test from 'brittle'
|
||||
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
||||
const sha224Lib = path.join(__dirname, '../lib/sha224.js')
|
||||
|
||||
function loadBareSha224() {
|
||||
const src = readFileSync(sha224Lib, 'utf8')
|
||||
const ctx = {}
|
||||
vm.createContext(ctx)
|
||||
vm.runInContext(src + '\nthis.__fn = bareSha224DigestBytes;', ctx)
|
||||
return ctx.__fn
|
||||
}
|
||||
|
||||
test('bareSha224DigestBytes matches known vectors', async (t) => {
|
||||
const bareSha224DigestBytes = loadBareSha224()
|
||||
t.is(
|
||||
bareSha224DigestBytes(new Uint8Array(0)),
|
||||
'd14a028c2a3a2bc9476102bb288234c415a2b01f828ea62ac5b3e42f'
|
||||
)
|
||||
t.is(
|
||||
bareSha224DigestBytes(new TextEncoder().encode('abc')),
|
||||
'23097d223405d8228642a477bda255b32aadbce4bda0b3f7e36c9da7'
|
||||
)
|
||||
})
|
||||
|
||||
test('SHA-384 empty and abc via subtle.digest', async (t) => {
|
||||
const subtle = globalThis.crypto?.subtle
|
||||
if (!subtle?.digest) {
|
||||
t.pass('skip: no Web Crypto subtle')
|
||||
return
|
||||
}
|
||||
async function hex384(u8) {
|
||||
const hash = await subtle.digest('SHA-384', u8)
|
||||
return [...new Uint8Array(hash)]
|
||||
.map((b) => b.toString(16).padStart(2, '0'))
|
||||
.join('')
|
||||
}
|
||||
t.is(
|
||||
await hex384(new Uint8Array(0)),
|
||||
'38b060a751ac96384cd9327eb1b1e36a21fdb71114be07434c0cc7bf63f6e1da274edebfe76f65fbd51ad2f14898b95b'
|
||||
)
|
||||
const abc = new TextEncoder().encode('abc')
|
||||
t.is(
|
||||
await hex384(abc),
|
||||
'cb00753f45a35e8bb5a03d699ac65007272c32ab0eded1631a8b605a43ff5bed8086072ba1e7cc2358baeca134c825a7'
|
||||
)
|
||||
})
|
||||
Reference in New Issue
Block a user