Expand bounded awk/expr/test toward Issue 7; refresh man, profile 1.0.18,
posix matrix/dashboard, and syscalls/process_table schema alignment (v8). Booter: replication_operator_sketch/corestore hints, HRPC allowlist tests, Protomux cap channel 65536-byte bound + export, Wasm posix_profile_peek, swarm-disk and security_posture docs. Coreutils/kernel: pkg-swarm-index pathCapabilityEnvelopeVerify on get; pathcap-verify --trusted failure hint; rebuild bins and sync seeder. Docs: KERNEL_CONTRACT, kernel-extensions, capabilities index, environment appendix (warm-cache tuning, cap channel, Wasm env), handbook observability, vault threat model (multisig), developer-guide ctx/HRPC/Wasm, DOCUMENTATION release-checklist note, release-checklist optional tier1 drift. Changelog maintenance in bare-os-booter and bare-os-protocol.
This commit is contained in:
@@ -14,6 +14,8 @@ Run from repo root:
|
||||
npm run build -w bare-os-bare-libs
|
||||
```
|
||||
|
||||
**Catalog bump pipeline (maintainers):** refresh Holepunch package metadata with **`npm run gen:bare-catalog`** at the repo root (updates **`docs/bare-holepunch-catalog.json`**), then rebuild this workspace, run **`npm test`** ( **`verify-bundle-health`**, **`verify-bundle-markers`**, **`verify-bundle-throws`**), and sync **`kernel/lib/bare/`** → seeder per **`verify-kernel-seeder-parity`**. Intentional large bundle size shifts should add a row under **Bundle health log** below.
|
||||
|
||||
The booter loads these only when `BARE_OS_BARE_MODULES` is enabled and `BARE_OS_BARE_DRIVE_BUNDLES` is not disabled; see the developer guide.
|
||||
|
||||
Manifest rows may include optional **`tier`** (**`core`** default when omitted) and **`risk`** (**`low`** / **`medium`** / **`high`**) so distributors can filter bundles without reading upstream READMEs. The catalog sync scripts preserve unknown fields when merging from **`docs/bare-holepunch-catalog.json`**.
|
||||
|
||||
@@ -7,7 +7,9 @@ Authoritative **version alignment** with protocol and telemetry schema numbers l
|
||||
- **`baretop` snapshot**: **`ctx.bareOsReadBareTopSnapshot({ lite: true })`** returns a reduced **`files`** map (see **`BARE_TOP_SNAPSHOT_LITE_ENTRIES`** in **`packages/bare-os-coreutils/lib/baretop-snapshot.js`**). Full batch includes **`securityPosture`** → **`/proc/bare_os/security_posture.json`**. **`metrics_live`** JSON uses **`schema: 4`** with embedded **`processTable`** when present; kernel counters include optional **`vfs.readfile.samples`** ( **`BARE_OS_VFS_READ_METRICS`** ), **`initd.unit_failed_final`**, and **`shell.pipeline_last_stages`** gauge from the shell.
|
||||
- **`/proc/bare_os/net_summary.json`**: **`schemaVersion` 2** adds scalar **`replicationQueueDepth`**, firewall session totals, optional **`BARE_OS_BSD_SOCKET_BRIDGE_STATS_JSON`** merge into **`bsdSocketGuestBridge`**; **`peer_firewall_stats`** field alignment documented in **`bare-os-protocol`** channel handler.
|
||||
- **Host filesystem**: first-party booter and seeder sources use **`import … from '#host-fs'`** / **`#host-path`** (and seeder **`#host-fs-promises`**) so Bare resolves **`bare-fs`** / **`bare-path`** and Node resolves the built-in **`fs`** / **`path`** modules via the default **`imports`** condition. **`bare-fs`** is a direct **`dependencies`** entry of **`bare-os-booter`**. CI: **`scripts/verify-pear-no-static-node-import.mjs`** rejects bare **`fs`** / **`path`** specifiers.
|
||||
- **POSIX / proc**: **`/proc/bare_os/syscalls.json`** schema **11** with **`susv4Refs`** on **`opsDetail`**, **`posixXsh` schema 3**, **`socketMsgSurface` schema 5** (`logicalScmRightsOnSend`); includes **`posix_fadvise`** simulated, **`fdModel`**, **`errnoHints`**; JSON text **cached** until **`vfs.bareOsClearWarmReadCaches`**. **`/proc/bare_os/process_table.json`** schema **7**; **`/proc/bare_os/protomux.json`** wire text schema **2**; **`/dev/shm`** in-memory named segments. **`/proc/bare_os/security_posture.json`** — **`blindRelayPosture`**, **`blindPeeringOperatorSketch`** env gate pointers; **`vaultCryptoPrimitives`**, **`bareCryptoReportedVersion`**, **`peerAdmission`**. **`/proc/bare_os/boot_budget_summary.json`** mirrors kernel **`/run/bare-os/boot-budget-summary.json`**. **`/proc/bare_os_features`**: **`protomuxAppChannel`**, optional **`protomuxCapChannel`**, **`pearIpcConservativeAdvertisement`**. Protocol export **`BARE_OS_POSIX_PROFILE_*`**.
|
||||
- **POSIX / proc**: **`/proc/bare_os/syscalls.json`** schema **11** with **`susv4Refs`** on **`opsDetail`**, **`posixXsh` schema 3**, **`socketMsgSurface` schema 5** (`logicalScmRightsOnSend`); includes **`posix_fadvise`** simulated, **`fdModel.processTableSchema` 8**, **`errnoHints`**; JSON text **cached** until **`vfs.bareOsClearWarmReadCaches`**. **`/proc/bare_os/process_table.json`** schema **8**; **`/proc/bare_os/protomux.json`** wire text schema **2**; **`/dev/shm`** in-memory named segments. **`/proc/bare_os/security_posture.json`** — **`blindRelayPosture`**, **`blindPeeringOperatorSketch`** env gate pointers; **`vaultCryptoPrimitives`**, **`bareCryptoReportedVersion`**, **`peerAdmission`**. **`/proc/bare_os/boot_budget_summary.json`** mirrors kernel **`/run/bare-os/boot-budget-summary.json`**. **`/proc/bare_os_features`**: **`protomuxAppChannel`**, optional **`protomuxCapChannel`**, **`pearIpcConservativeAdvertisement`**. Protocol export **`BARE_OS_POSIX_PROFILE_*`**.
|
||||
- **Protomux cap channel**: inbound **`bare-os-cap-v1`** messages are accepted only up to **65536** bytes (**`BARE_OS_PROTOMUX_CAP_CHANNEL_MESSAGE_MAX_BYTES`** in **`lib/swarm-disk.js`**); larger payloads log **`protomux_cap_payload_oversized`** and do not increment **`protomuxCapChannelRxTotal`**.
|
||||
- **Wasm (sync imports)**: optional **`BARE_OS_WASM_KERNEL_POSIX_PROFILE_PEEK`** adds **`env.bare_os_posix_profile_peek`** (declared **`BARE_OS_POSIX_PROFILE_VERSION`** string, bounded).
|
||||
- **Blind-relay / pairing / geo proc** (default): **`bareOsProcBlindPeerRelayHintsMinimal`** returns **schema 1** with **`operatorRedacted: true`**. The legacy export **`bareOsProcBlindPeerRelayHintsStub`** was removed; use **`bareOsProcBlindPeerRelayHintsMinimal`** only.
|
||||
- **Vendored bundles**: post-esbuild **`scripts/sanitize-bare-bundles.mjs`** (from **`bare-os-bare-libs/build.mjs`**) strips CI-forbidden markers and replaces **`node:`** requires in **`bareDev.js`** with **`bare-*`** modules.
|
||||
- **Boot hooks (naming)**: canonical **`bareOsRegisterBootStepHook`**, **`bareOsInvokeBootStepHooks`**, **`bareOsEmitBareBootStepHint`**; legacy **`*BootPhase*`** methods remain thin wrappers (**no `bareOsCtxApiVersion` bump**). See [docs/reference/naming-alias-matrix.md](../../docs/reference/naming-alias-matrix.md).
|
||||
|
||||
@@ -657,6 +657,7 @@ async function executeKernel(disk, store, swarm, initSource) {
|
||||
'BARE_OS_WASM_KERNEL_MONOTONIC_MS',
|
||||
'BARE_OS_WASM_KERNEL_HOSTNAME_IMPORT',
|
||||
'BARE_OS_WASM_KERNEL_CTX_API_PEEK',
|
||||
'BARE_OS_WASM_KERNEL_POSIX_PROFILE_PEEK',
|
||||
'BARE_OS_POSIX_FD_SIM',
|
||||
'BARE_OS_POSIX_FD_SIM_MAX_BYTES',
|
||||
'BARE_OS_POSIX_SOCKET_FD_BRIDGE',
|
||||
@@ -1686,6 +1687,15 @@ async function executeKernel(disk, store, swarm, initSource) {
|
||||
shellEnv.BARE_OS_BLIND_PEER_TOPOLOGY_V3_JSON &&
|
||||
String(shellEnv.BARE_OS_BLIND_PEER_TOPOLOGY_V3_JSON).trim()
|
||||
),
|
||||
upstreamHolepunchPackages: {
|
||||
schema: 1,
|
||||
blindPeer: 'blind-peer',
|
||||
blindPeerRouter: 'blind-peer-router',
|
||||
blindPeering: 'blind-peering',
|
||||
blindRelayService: 'blind-relay-service',
|
||||
note:
|
||||
'npm package names on the Holepunch org; host operators merge these with BARE_OS_BLIND_* env JSON — proc never echoes secret topology payloads.'
|
||||
},
|
||||
note:
|
||||
'Non-secret env gates only; JSON bodies are not echoed here. Wire blind-peering / blind-peer-router operator configs on the host, then read sketches via disk.os replication_operator_sketch.'
|
||||
},
|
||||
|
||||
@@ -450,6 +450,13 @@ export function createBareOsDiskOsBridge(opts) {
|
||||
corestoreSnapshotUxHint: bareOsCorestoreSnapshotOperatorHint(
|
||||
opts.corestore ?? null
|
||||
),
|
||||
corestoreSnapshotPackageRef: {
|
||||
schema: 1,
|
||||
npmPackage: 'corestore-snapshot',
|
||||
upstreamOrg: 'holepunchto',
|
||||
role:
|
||||
'Host-side optional workflow; pair BARE_OS_CORESTORE_SNAPSHOT_JSON with BARE_OS_CORESTORE_STATS_JSON — corestoreSnapshotUxHint compares non-secret counts for operator rollback UX.'
|
||||
},
|
||||
corestoreSnapshotPaused:
|
||||
String(env?.BARE_OS_CORESTORE_SNAPSHOT_PAUSED || '') === '1' ||
|
||||
String(env?.BARE_OS_CORESTORE_SNAPSHOT_PAUSED || '') === 'true',
|
||||
|
||||
@@ -78,7 +78,7 @@ export function buildBareOsSyscallsProcJson(p) {
|
||||
stdio: [0, 1, 2],
|
||||
register: 'ctx.bareOsRegisterLogicalFd',
|
||||
processTablePath: '/proc/bare_os/process_table.json',
|
||||
processTableSchema: 7,
|
||||
processTableSchema: 8,
|
||||
posixLike: {
|
||||
schema: 1,
|
||||
note: 'POSIX open/read/write/close/pipe/dup/fcntl analogs; see docs/architecture/POSIX_DECLARED_PROFILE.md.',
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
* Optional WebAssembly compile / instantiate for feature-gated kernel extensions.
|
||||
* Uses global WebAssembly when present (Bare/V8); no Node built-ins.
|
||||
*/
|
||||
import { BARE_OS_POSIX_PROFILE_VERSION } from 'bare-os-protocol/bare-os-posix-profile.js'
|
||||
import { bareOsMonotonicNowMs } from './bare-os-monotonic-time.js'
|
||||
|
||||
const DEFAULT_MAX = 512 * 1024
|
||||
@@ -83,6 +84,7 @@ function writeCStrToMemory(memory, ptr, outCap, text) {
|
||||
* With **`BARE_OS_WASM_KERNEL_MONOTONIC_MS=1`**, adds **`env.bare_os_monotonic_ms`**: floored monotonic milliseconds (`performance.now` when available), i32-shaped.
|
||||
* With **`BARE_OS_WASM_KERNEL_HOSTNAME_IMPORT=1`** (requires syscall imports), adds **`env.bare_os_hostname_peek`**: writes session **`HOSTNAME`** (or **`bare-os`**) NUL-terminated into Wasm memory (**bounded**).
|
||||
* With **`BARE_OS_WASM_KERNEL_CTX_API_PEEK=1`** (requires syscall imports), adds **`env.bare_os_ctx_api_version_peek`**: writes **`ctx.bareOsCtxApiVersion`** NUL-terminated (**bounded**).
|
||||
* With **`BARE_OS_WASM_KERNEL_POSIX_PROFILE_PEEK=1`** (requires syscall imports), adds **`env.bare_os_posix_profile_peek`**: writes declared **`BARE_OS_POSIX_PROFILE_VERSION`** NUL-terminated (**bounded**).
|
||||
*
|
||||
* @param {Uint8Array} source
|
||||
* @param {Record<string, unknown>} ctx
|
||||
@@ -154,6 +156,10 @@ export async function bareOsWasmKernelInstantiate(source, ctx, opts = {}) {
|
||||
wantSyscall &&
|
||||
(shellEnv.BARE_OS_WASM_KERNEL_POSIX_BRIDGE_PEEK === '1' ||
|
||||
shellEnv.BARE_OS_WASM_KERNEL_POSIX_BRIDGE_PEEK === 'true')
|
||||
const wantPosixProfilePeek =
|
||||
wantSyscall &&
|
||||
(shellEnv.BARE_OS_WASM_KERNEL_POSIX_PROFILE_PEEK === '1' ||
|
||||
shellEnv.BARE_OS_WASM_KERNEL_POSIX_PROFILE_PEEK === 'true')
|
||||
const pathconfFn = ctx && typeof ctx.bareOsPathconf === 'function'
|
||||
? ctx.bareOsPathconf.bind(ctx)
|
||||
: null
|
||||
@@ -245,6 +251,12 @@ export async function bareOsWasmKernelInstantiate(source, ctx, opts = {}) {
|
||||
return flags | 0
|
||||
}
|
||||
}
|
||||
if (wantPosixProfilePeek) {
|
||||
envImports.bare_os_posix_profile_peek = (outPtr, outCap) => {
|
||||
const v = String(BARE_OS_POSIX_PROFILE_VERSION || '').slice(0, 32)
|
||||
return writeCStrToMemory(memory, Number(outPtr), Number(outCap), v)
|
||||
}
|
||||
}
|
||||
}
|
||||
const importObject = {
|
||||
env: envImports
|
||||
@@ -274,7 +286,8 @@ export async function bareOsWasmKernelInstantiate(source, ctx, opts = {}) {
|
||||
wasmMonotonicMsImport: !!wantMonotonicMs,
|
||||
wasmHostnamePeekImport: !!(wantSyscall && wantHostname),
|
||||
wasmCtxApiVersionPeekImport: !!(wantSyscall && wantCtxApiPeek),
|
||||
wasmPosixBridgePeekImport: !!(wantSyscall && wantPosixBridgePeek)
|
||||
wasmPosixBridgePeekImport: !!(wantSyscall && wantPosixBridgePeek),
|
||||
wasmPosixProfilePeekImport: !!(wantSyscall && wantPosixProfilePeek)
|
||||
}
|
||||
} catch (e) {
|
||||
return { ok: false, reason: String(e && e.message ? e.message : e) }
|
||||
|
||||
@@ -49,6 +49,9 @@ function emitSwarmDiskHostLog(level, message, detail = null) {
|
||||
/** Default max wait for block 0 (MBR) before failing boot (override **`BARE_OS_MBR_READ_TIMEOUT_MS`**). */
|
||||
const MBR_READ_TIMEOUT_MS_DEFAULT = 60_000
|
||||
|
||||
/** Cap-channel (`bare-os-cap-v1`) message size bound when **`BARE_OS_PROTOMUX_CAP_CHANNEL`** is enabled. */
|
||||
export const BARE_OS_PROTOMUX_CAP_CHANNEL_MESSAGE_MAX_BYTES = 65536
|
||||
|
||||
/**
|
||||
* Max wait (ms) for block 0 / MBR replication before boot failure.
|
||||
* Exported for microbench / unit introspection (same rules as booter).
|
||||
@@ -444,8 +447,16 @@ export class SwarmDisk {
|
||||
mux.pair({ protocol: PROTOCOL_CAP_CHANNEL_NAME }, (cchan) => {
|
||||
cchan.addMessage({
|
||||
encoding: c.buffer,
|
||||
onmessage: () => {
|
||||
onmessage: (buf) => {
|
||||
try {
|
||||
const n = buf ? buf.byteLength : 0
|
||||
if (n > BARE_OS_PROTOMUX_CAP_CHANNEL_MESSAGE_MAX_BYTES) {
|
||||
emitSwarmDiskHostLog('warn', 'protomux_cap_payload_oversized', {
|
||||
byteLength: n,
|
||||
maxBytes: BARE_OS_PROTOMUX_CAP_CHANNEL_MESSAGE_MAX_BYTES
|
||||
})
|
||||
return
|
||||
}
|
||||
this.protomuxCapChannelRxTotal++
|
||||
} catch {
|
||||
/* ignore */
|
||||
|
||||
@@ -34,3 +34,15 @@ test('bareOsHrpcAllowlistDeniesRoute wildcard edges', (t) => {
|
||||
t.absent(bareOsHrpcAllowlistDeniesRoute(exact, 'vfs', 'readText'))
|
||||
t.ok(bareOsHrpcAllowlistDeniesRoute(exact, 'vfs', 'other'))
|
||||
})
|
||||
|
||||
test('bareOsHrpcAllowlistDeniesRoute bare_os service wildcard', (t) => {
|
||||
const s = new Set(['bare_os.*'])
|
||||
t.absent(bareOsHrpcAllowlistDeniesRoute(s, 'bare_os', 'pkg_index_get'))
|
||||
t.ok(bareOsHrpcAllowlistDeniesRoute(s, 'kernel', 'ping'))
|
||||
})
|
||||
|
||||
test('parseBareOsHrpcAllowlistJson empty array is null allow', (t) => {
|
||||
const r = parseBareOsHrpcAllowlistJson('[]')
|
||||
t.absent(r.allow)
|
||||
t.absent(r.parseError)
|
||||
})
|
||||
|
||||
@@ -1586,6 +1586,7 @@ test('disk.os replication_snapshot and replication_operator_sketch RPCs', async
|
||||
t.is(oj.schema, 7)
|
||||
t.is(oj.protomuxOperatorSketch?.schema, 2)
|
||||
t.is(oj.corestoreSnapshotUxHint?.schema, 3)
|
||||
t.is(oj.corestoreSnapshotPackageRef?.npmPackage, 'corestore-snapshot')
|
||||
t.is(oj.seedReplicationStatus?.depth, 1)
|
||||
t.is(oj.swarmPeerCount, null)
|
||||
const badIntent = await bridge.execRpc(
|
||||
@@ -7014,6 +7015,37 @@ test('bareOsWasmKernelInstantiate optional bare_os_hostname_peek import flag', a
|
||||
t.ok(r.wasmHostnamePeekImport)
|
||||
})
|
||||
|
||||
test('bareOsWasmKernelInstantiate optional bare_os_posix_profile_peek import flag', async (t) => {
|
||||
const { bareOsWasmKernelInstantiate } = await import('./lib/bare-os-wasm-kernel.js')
|
||||
if (typeof WebAssembly === 'undefined' || !WebAssembly.compile) {
|
||||
t.pass('skip: no WebAssembly')
|
||||
return
|
||||
}
|
||||
const wasmMinimal = new Uint8Array([
|
||||
0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00
|
||||
])
|
||||
const ctx = { bareOsPathconf: () => '4096' }
|
||||
const r = await bareOsWasmKernelInstantiate(wasmMinimal, ctx, {
|
||||
wasmSyscallImports: true,
|
||||
shellEnv: {
|
||||
BARE_OS_WASM_KERNEL_SYSCALL: '1',
|
||||
BARE_OS_WASM_KERNEL_POSIX_PROFILE_PEEK: '1'
|
||||
}
|
||||
})
|
||||
if (!r.ok) {
|
||||
t.pass('instantiate may fail on minimal module')
|
||||
return
|
||||
}
|
||||
t.ok(r.wasmPosixProfilePeekImport)
|
||||
})
|
||||
|
||||
test('Protomux cap channel inbound message size bound is 65536 bytes', async (t) => {
|
||||
const { BARE_OS_PROTOMUX_CAP_CHANNEL_MESSAGE_MAX_BYTES } = await import(
|
||||
'./lib/swarm-disk.js'
|
||||
)
|
||||
t.is(BARE_OS_PROTOMUX_CAP_CHANNEL_MESSAGE_MAX_BYTES, 65536)
|
||||
})
|
||||
|
||||
test('bareOsAppendVaultRotationCheckpoint appends vault_save row', async (t) => {
|
||||
const { bareOsAppendVaultRotationCheckpoint } = await import(
|
||||
'./lib/bare-os-vault-rotation-audit.js'
|
||||
|
||||
@@ -2,7 +2,8 @@
|
||||
* POSIX-oriented awk interpreter for Bare OS (no import; concatenated before src/awk.js).
|
||||
* Supports: BEGIN/END, /re/, line patterns, {}, print/printf, if/else, while, for(;;), for(x in a),
|
||||
* delete arr[idx], next, exit, break, continue, ++/--, arrays, strings, regex ~ !~, builtins (length, substr, index,
|
||||
* split, sprintf, sub, gsub, match, int, tolower, toupper, rand, srand), getline from stdin/files,
|
||||
* split, sprintf, sub, gsub, match, int, atan2, cos, sin, exp, log, sqrt, tolower, toupper, rand, srand),
|
||||
* getline from stdin, files (getline x < \"path\"), unary minus,
|
||||
* optional fixed-width fields via FIELDWIDTHS when BARE_OS_AWK_FIELDWIDTHS is enabled,
|
||||
* -F FS, NF, NR, FNR, $0..$n, OFS, ORS, RS, ARGC, ARGV, FILENAME, ENVIRON (read-only mirror).
|
||||
* GNU-style empty fields: consecutive FS delimiters still advance $1..$NF where the grammar allows.
|
||||
@@ -581,7 +582,11 @@ class BareAwkParser {
|
||||
}
|
||||
|
||||
parseUnary() {
|
||||
if (this.peek().value === '!' || this.peek().value === '+') {
|
||||
if (
|
||||
this.peek().value === '!' ||
|
||||
this.peek().value === '+' ||
|
||||
this.peek().value === '-'
|
||||
) {
|
||||
const op = String(this.peek().value)
|
||||
this.p++
|
||||
return { t: 'unop', op, a: this.parseUnary() }
|
||||
@@ -730,6 +735,8 @@ class BareAwkRuntime {
|
||||
this._stdinIx = 0
|
||||
/** @type {{ path: string, data: string, append: boolean }[]} */
|
||||
this.pendingWrites = []
|
||||
/** @type {Map<string, { lines: string[], i: number }>} */
|
||||
this._getlineFileState = new Map()
|
||||
}
|
||||
|
||||
splitFields() {
|
||||
@@ -800,14 +807,14 @@ class BareAwkRuntime {
|
||||
/**
|
||||
* @param {BareAwkNode | null} pat
|
||||
*/
|
||||
patternMatch(pat) {
|
||||
async patternMatch(pat) {
|
||||
if (pat == null) return true
|
||||
if (pat.t === 'pat') return false
|
||||
if (pat.t === 'patRe') {
|
||||
pat.rx.lastIndex = 0
|
||||
return pat.rx.test(this.$0)
|
||||
}
|
||||
if (pat.t === 'patExpr') return this.truthy(this.evalExpr(pat.e))
|
||||
if (pat.t === 'patExpr') return this.truthy(await this.evalExpr(pat.e))
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -821,7 +828,7 @@ class BareAwkRuntime {
|
||||
/**
|
||||
* @param {BareAwkNode} n
|
||||
*/
|
||||
evalExpr(n) {
|
||||
async evalExpr(n) {
|
||||
if (!n) return ''
|
||||
switch (n.t) {
|
||||
case 'num':
|
||||
@@ -843,9 +850,9 @@ class BareAwkRuntime {
|
||||
return this.vars[name] != null ? this.vars[name] : ''
|
||||
}
|
||||
case 'field':
|
||||
return this.fieldNum(this.evalExpr(n.e))
|
||||
return this.fieldNum(await this.evalExpr(n.e))
|
||||
case 'index': {
|
||||
const k = String(this.evalExpr(n.idx))
|
||||
const k = String(await this.evalExpr(n.idx))
|
||||
if (n.name === 'ENVIRON') {
|
||||
const v = this.environ[k]
|
||||
return v != null ? v : ''
|
||||
@@ -855,8 +862,8 @@ class BareAwkRuntime {
|
||||
return arr[k] != null ? arr[k] : ''
|
||||
}
|
||||
case 'binop': {
|
||||
const a = this.evalExpr(n.a)
|
||||
const b = this.evalExpr(n.b)
|
||||
const a = await this.evalExpr(n.a)
|
||||
const b = await this.evalExpr(n.b)
|
||||
const an = Number(a)
|
||||
const bn = Number(b)
|
||||
switch (n.op) {
|
||||
@@ -889,77 +896,78 @@ class BareAwkRuntime {
|
||||
}
|
||||
}
|
||||
case 'unop': {
|
||||
const a = this.evalExpr(n.a)
|
||||
const a = await this.evalExpr(n.a)
|
||||
if (n.op === '!') return this.truthy(a) ? 0 : 1
|
||||
if (n.op === '+') return Number(a) || 0
|
||||
if (n.op === '-') return -(Number(a) || 0)
|
||||
return 0
|
||||
}
|
||||
case '||':
|
||||
return this.truthy(this.evalExpr(n.a))
|
||||
return this.truthy(await this.evalExpr(n.a))
|
||||
? 1
|
||||
: this.truthy(this.evalExpr(n.b))
|
||||
: this.truthy(await this.evalExpr(n.b))
|
||||
? 1
|
||||
: 0
|
||||
case '&&':
|
||||
return this.truthy(this.evalExpr(n.a)) &&
|
||||
this.truthy(this.evalExpr(n.b))
|
||||
return this.truthy(await this.evalExpr(n.a)) &&
|
||||
this.truthy(await this.evalExpr(n.b))
|
||||
? 1
|
||||
: 0
|
||||
case 'match': {
|
||||
const s = String(this.evalExpr(n.a))
|
||||
const s = String(await this.evalExpr(n.a))
|
||||
const rhs = n.b
|
||||
let rx
|
||||
if (rhs.t === 'rxLit') rx = rhs.rx
|
||||
else {
|
||||
const t = String(this.evalExpr(rhs))
|
||||
const t = String(await this.evalExpr(rhs))
|
||||
rx = bareAwkMakeRx(t, '')
|
||||
}
|
||||
rx.lastIndex = 0
|
||||
return rx.test(s) ? 1 : 0
|
||||
}
|
||||
case 'nmatch': {
|
||||
const s = String(this.evalExpr(n.a))
|
||||
const s = String(await this.evalExpr(n.a))
|
||||
const rhs = n.b
|
||||
let rx
|
||||
if (rhs.t === 'rxLit') rx = rhs.rx
|
||||
else {
|
||||
const t = String(this.evalExpr(rhs))
|
||||
const t = String(await this.evalExpr(rhs))
|
||||
rx = bareAwkMakeRx(t, '')
|
||||
}
|
||||
rx.lastIndex = 0
|
||||
return rx.test(s) ? 0 : 1
|
||||
}
|
||||
case '?:':
|
||||
return this.truthy(this.evalExpr(n.n))
|
||||
? this.evalExpr(n.a)
|
||||
: this.evalExpr(n.b)
|
||||
return this.truthy(await this.evalExpr(n.n))
|
||||
? await this.evalExpr(n.a)
|
||||
: await this.evalExpr(n.b)
|
||||
case 'assign': {
|
||||
const v = this.evalExpr(n.right)
|
||||
this.assign(n.left, n.op, v)
|
||||
const v = await this.evalExpr(n.right)
|
||||
await this.assign(n.left, n.op, v)
|
||||
return v
|
||||
}
|
||||
case 'pre': {
|
||||
const cur = Number(this.evalExpr(n.x))
|
||||
const cur = Number(await this.evalExpr(n.x))
|
||||
const next = n.op === '++' ? cur + 1 : cur - 1
|
||||
this.assignScalar(n.x, next)
|
||||
await this.assignScalar(n.x, next)
|
||||
return next
|
||||
}
|
||||
case 'post': {
|
||||
const cur = Number(this.evalExpr(n.x))
|
||||
const cur = Number(await this.evalExpr(n.x))
|
||||
const next = n.op === '++' ? cur + 1 : cur - 1
|
||||
this.assignScalar(n.x, next)
|
||||
await this.assignScalar(n.x, next)
|
||||
return cur
|
||||
}
|
||||
case 'call':
|
||||
return this.callBuiltin(n.name, n.args)
|
||||
return await this.callBuiltin(n.name, n.args)
|
||||
case 'getline':
|
||||
return this.doGetline(n)
|
||||
return await this.doGetline(n)
|
||||
default:
|
||||
return ''
|
||||
}
|
||||
}
|
||||
|
||||
assignScalar(x, v) {
|
||||
async assignScalar(x, v) {
|
||||
if (x.t === 'var') {
|
||||
const name = x.name
|
||||
if (name === 'FS') this.FS = String(v)
|
||||
@@ -968,7 +976,7 @@ class BareAwkRuntime {
|
||||
else if (name === 'RS') this.RS = String(v)
|
||||
else this.vars[name] = v
|
||||
} else if (x.t === 'field') {
|
||||
this.setField(this.evalExpr(x.e), v)
|
||||
this.setField(await this.evalExpr(x.e), v)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -977,10 +985,10 @@ class BareAwkRuntime {
|
||||
* @param {string} op
|
||||
* @param {unknown} v
|
||||
*/
|
||||
assign(left, op, v) {
|
||||
async assign(left, op, v) {
|
||||
let base = v
|
||||
if (op !== '=') {
|
||||
const cur = Number(this.evalExpr(left))
|
||||
const cur = Number(await this.evalExpr(left))
|
||||
const nv = Number(v)
|
||||
if (op === '+=') base = cur + nv
|
||||
else if (op === '-=') base = cur - nv
|
||||
@@ -991,32 +999,52 @@ class BareAwkRuntime {
|
||||
if (left.t === 'index') {
|
||||
const arr =
|
||||
this.arrays[left.name] || (this.arrays[left.name] = Object.create(null))
|
||||
arr[String(this.evalExpr(left.idx))] = base
|
||||
arr[String(await this.evalExpr(left.idx))] = base
|
||||
return
|
||||
}
|
||||
this.assignScalar(left, base)
|
||||
await this.assignScalar(left, base)
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} path
|
||||
*/
|
||||
async _ensureGetlineFile(path) {
|
||||
let st = this._getlineFileState.get(path)
|
||||
if (st) return st
|
||||
const buf = await this.io.readFile(path)
|
||||
const text =
|
||||
buf && this.io.bytesToString ? this.io.bytesToString(buf) : ''
|
||||
const lines = text.split(/\r?\n/)
|
||||
if (lines.length && lines[lines.length - 1] === '') lines.pop()
|
||||
st = { lines, i: 0 }
|
||||
this._getlineFileState.set(path, st)
|
||||
return st
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} name
|
||||
* @param {BareAwkNode[]} args
|
||||
*/
|
||||
callBuiltin(name, args) {
|
||||
const a = (i) => (args[i] ? this.evalExpr(args[i]) : '')
|
||||
async callBuiltin(name, args) {
|
||||
const a = async (i) =>
|
||||
args[i] != null ? await this.evalExpr(args[i]) : ''
|
||||
switch (name) {
|
||||
case 'length':
|
||||
if (!args.length) return this.$0.length
|
||||
return String(a(0)).length
|
||||
case 'substr':
|
||||
return String(a(0)).slice(
|
||||
Math.max(0, Math.trunc(Number(a(1))) - 1),
|
||||
Math.max(0, Math.trunc(Number(a(1))) - 1 + Math.trunc(Number(a(2))))
|
||||
)
|
||||
return String(await a(0)).length
|
||||
case 'substr': {
|
||||
const s = String(await a(0))
|
||||
const start = Math.max(0, Math.trunc(Number(await a(1))) - 1)
|
||||
if (args.length < 3) return s.slice(start)
|
||||
const ln = Math.trunc(Number(await a(2)))
|
||||
if (!Number.isFinite(ln) || ln < 0) return ''
|
||||
return s.slice(start, start + ln)
|
||||
}
|
||||
case 'index':
|
||||
return String(a(0)).indexOf(String(a(1))) + 1 || 0
|
||||
return String(await a(0)).indexOf(String(await a(1))) + 1 || 0
|
||||
case 'split': {
|
||||
const s = String(a(0))
|
||||
const fs = args[1] ? String(this.evalExpr(args[1])) : this.FS
|
||||
const s = String(await a(0))
|
||||
const fs = args[1] ? String(await this.evalExpr(args[1])) : this.FS
|
||||
let rx = fs.length === 1 ? null : bareAwkMakeRx(fs, '')
|
||||
const parts = rx ? s.split(rx) : s.split(fs)
|
||||
const aname = /** @type {BareAwkNode} */ (args[2])
|
||||
@@ -1027,17 +1055,35 @@ class BareAwkRuntime {
|
||||
}
|
||||
return parts.length
|
||||
}
|
||||
case 'sprintf':
|
||||
return bareAwkSprintf(
|
||||
String(a(0)),
|
||||
args.slice(1).map((x) => this.evalExpr(x))
|
||||
)
|
||||
case 'sprintf': {
|
||||
const fmt = String(await a(0))
|
||||
const rest = []
|
||||
for (let j = 1; j < args.length; j++)
|
||||
rest.push(await this.evalExpr(args[j]))
|
||||
return bareAwkSprintf(fmt, rest)
|
||||
}
|
||||
case 'int':
|
||||
return Math.trunc(Number(a(0)))
|
||||
return Math.trunc(Number(await a(0)))
|
||||
case 'log': {
|
||||
const x = Number(await a(0))
|
||||
return x > 0 ? Math.log(x) : Number.NaN
|
||||
}
|
||||
case 'sqrt': {
|
||||
const x = Number(await a(0))
|
||||
return x >= 0 ? Math.sqrt(x) : Number.NaN
|
||||
}
|
||||
case 'sin':
|
||||
return Math.sin(Number(await a(0)))
|
||||
case 'cos':
|
||||
return Math.cos(Number(await a(0)))
|
||||
case 'exp':
|
||||
return Math.exp(Number(await a(0)))
|
||||
case 'atan2':
|
||||
return Math.atan2(Number(await a(0)), Number(await a(1)))
|
||||
case 'tolower':
|
||||
return String(a(0)).toLowerCase()
|
||||
return String(await a(0)).toLowerCase()
|
||||
case 'toupper':
|
||||
return String(a(0)).toUpperCase()
|
||||
return String(await a(0)).toUpperCase()
|
||||
case 'rand': {
|
||||
this.randSeed = (this.randSeed * 1103515245 + 12345) & 0x7fffffff
|
||||
return this.randSeed / 0x7fffffff
|
||||
@@ -1051,9 +1097,11 @@ class BareAwkRuntime {
|
||||
case 'sub':
|
||||
case 'gsub': {
|
||||
const rx =
|
||||
args[0].t === 'rxLit' ? args[0].rx : bareAwkMakeRx(String(a(0)), '')
|
||||
const rep = String(a(1))
|
||||
let target = args[2] ? String(a(2)) : this.$0
|
||||
args[0].t === 'rxLit'
|
||||
? args[0].rx
|
||||
: bareAwkMakeRx(String(await a(0)), '')
|
||||
const rep = String(await a(1))
|
||||
let target = args[2] ? String(await a(2)) : this.$0
|
||||
let n = 0
|
||||
if (name === 'sub') {
|
||||
rx.lastIndex = 0
|
||||
@@ -1078,9 +1126,11 @@ class BareAwkRuntime {
|
||||
return n
|
||||
}
|
||||
case 'match': {
|
||||
const s = String(a(0))
|
||||
const s = String(await a(0))
|
||||
const rx =
|
||||
args[1].t === 'rxLit' ? args[1].rx : bareAwkMakeRx(String(a(1)), '')
|
||||
args[1].t === 'rxLit'
|
||||
? args[1].rx
|
||||
: bareAwkMakeRx(String(await a(1)), '')
|
||||
rx.lastIndex = 0
|
||||
const m = rx.exec(s)
|
||||
if (!m) {
|
||||
@@ -1094,7 +1144,7 @@ class BareAwkRuntime {
|
||||
}
|
||||
default:
|
||||
if (this.funcs.has(name)) {
|
||||
return this.callUser(name, args)
|
||||
return await this.callUser(name, args)
|
||||
}
|
||||
bareAwkError('unknown function ' + name)
|
||||
}
|
||||
@@ -1104,19 +1154,19 @@ class BareAwkRuntime {
|
||||
* @param {string} name
|
||||
* @param {BareAwkNode[]} args
|
||||
*/
|
||||
callUser(name, args) {
|
||||
async callUser(name, args) {
|
||||
const f = this.funcs.get(name)
|
||||
if (!f) return ''
|
||||
const frame = { ...this.vars }
|
||||
for (let i = 0; i < f.params.length; i++) {
|
||||
frame[f.params[i]] = args[i] ? this.evalExpr(args[i]) : ''
|
||||
frame[f.params[i]] = args[i] ? await this.evalExpr(args[i]) : ''
|
||||
}
|
||||
const prev = this.vars
|
||||
this.vars = frame
|
||||
let ret = ''
|
||||
try {
|
||||
for (const st of f.body) {
|
||||
const r = this.execStmt(st)
|
||||
const r = await this.execStmt(st)
|
||||
if (r && r.t === 'return') {
|
||||
ret = r.v != null ? r.v : ''
|
||||
break
|
||||
@@ -1131,12 +1181,27 @@ class BareAwkRuntime {
|
||||
/**
|
||||
* @param {BareAwkNode} n
|
||||
*/
|
||||
doGetline(n) {
|
||||
async doGetline(n) {
|
||||
let line = null
|
||||
if (n.from) {
|
||||
const path = String(this.evalExpr(n.from))
|
||||
/* sync read — awk.js should preload or use async wrapper; runtime uses promise in run loop */
|
||||
return 0
|
||||
const path = String(await this.evalExpr(n.from))
|
||||
const st = await this._ensureGetlineFile(path)
|
||||
if (st.i < st.lines.length) line = st.lines[st.i++]
|
||||
else line = null
|
||||
if (line == null) {
|
||||
if (n.var) this.vars[n.var] = ''
|
||||
else {
|
||||
this.$0 = ''
|
||||
this.splitFields()
|
||||
}
|
||||
return 0
|
||||
}
|
||||
if (n.var) this.vars[n.var] = line
|
||||
else {
|
||||
this.$0 = line
|
||||
this.splitFields()
|
||||
}
|
||||
return 1
|
||||
}
|
||||
if (this._stdinIx < this.io.stdinLines.length) {
|
||||
line = this.io.stdinLines[this._stdinIx++]
|
||||
@@ -1156,23 +1221,24 @@ class BareAwkRuntime {
|
||||
|
||||
/**
|
||||
* @param {BareAwkNode} st
|
||||
* @returns {{ t: string, v?: unknown } | void}
|
||||
* @returns {Promise<{ t: string, v?: unknown } | void>}
|
||||
*/
|
||||
execStmt(st) {
|
||||
async execStmt(st) {
|
||||
switch (st.t) {
|
||||
case 'block':
|
||||
for (const s of st.stmts) {
|
||||
const r = this.execStmt(s)
|
||||
const r = await this.execStmt(s)
|
||||
if (r) return r
|
||||
}
|
||||
break
|
||||
case 'if':
|
||||
if (this.truthy(this.evalExpr(st.cond))) return this.execStmt(st.then)
|
||||
if (st.else) return this.execStmt(st.else)
|
||||
if (this.truthy(await this.evalExpr(st.cond)))
|
||||
return await this.execStmt(st.then)
|
||||
if (st.else) return await this.execStmt(st.else)
|
||||
break
|
||||
case 'while':
|
||||
while (this.truthy(this.evalExpr(st.cond))) {
|
||||
const r = this.execStmt(st.body)
|
||||
while (this.truthy(await this.evalExpr(st.cond))) {
|
||||
const r = await this.execStmt(st.body)
|
||||
if (r?.t === 'break') break
|
||||
if (r?.t === 'continue') continue
|
||||
if (r?.t === 'next' || r?.t === 'nextfile' || r?.t === 'exit')
|
||||
@@ -1180,25 +1246,25 @@ class BareAwkRuntime {
|
||||
}
|
||||
break
|
||||
case 'for':
|
||||
if (st.init) this.evalExpr(st.init)
|
||||
if (st.init) await this.evalExpr(st.init)
|
||||
while (true) {
|
||||
if (st.cond && !this.truthy(this.evalExpr(st.cond))) break
|
||||
const r = this.execStmt(st.body)
|
||||
if (st.cond && !this.truthy(await this.evalExpr(st.cond))) break
|
||||
const r = await this.execStmt(st.body)
|
||||
if (r?.t === 'break') break
|
||||
if (r?.t === 'continue') {
|
||||
if (st.step) this.evalExpr(st.step)
|
||||
if (st.step) await this.evalExpr(st.step)
|
||||
continue
|
||||
}
|
||||
if (r?.t === 'next' || r?.t === 'nextfile' || r?.t === 'exit')
|
||||
return r
|
||||
if (st.step) this.evalExpr(st.step)
|
||||
if (st.step) await this.evalExpr(st.step)
|
||||
}
|
||||
break
|
||||
case 'forin': {
|
||||
const arr = this.arrays[st.arr] || {}
|
||||
for (const k of Object.keys(arr)) {
|
||||
this.vars[st.iv] = k
|
||||
const r = this.execStmt(st.body)
|
||||
const r = await this.execStmt(st.body)
|
||||
if (r?.t === 'break') break
|
||||
if (r?.t === 'continue') continue
|
||||
if (r?.t === 'next' || r?.t === 'nextfile' || r?.t === 'exit')
|
||||
@@ -1209,7 +1275,7 @@ class BareAwkRuntime {
|
||||
case 'delete': {
|
||||
const tbl =
|
||||
this.arrays[st.arr] || (this.arrays[st.arr] = Object.create(null))
|
||||
const ky = String(this.evalExpr(st.key))
|
||||
const ky = String(await this.evalExpr(st.key))
|
||||
delete tbl[ky]
|
||||
break
|
||||
}
|
||||
@@ -1222,19 +1288,23 @@ class BareAwkRuntime {
|
||||
case 'continue':
|
||||
return { t: 'continue' }
|
||||
case 'exit':
|
||||
this.exitCode = Math.trunc(Number(this.evalExpr(st.code))) || 0
|
||||
this.exitCode =
|
||||
Math.trunc(Number(await this.evalExpr(st.code))) || 0
|
||||
this.exitPending = true
|
||||
return { t: 'exit' }
|
||||
case 'return':
|
||||
return { t: 'return', v: st.e ? this.evalExpr(st.e) : '' }
|
||||
return {
|
||||
t: 'return',
|
||||
v: st.e ? await this.evalExpr(st.e) : ''
|
||||
}
|
||||
case 'print':
|
||||
this.queuePrint(st.args, st.redir, false)
|
||||
await this.queuePrint(st.args, st.redir, false)
|
||||
break
|
||||
case 'printf':
|
||||
this.queuePrint([st.fmt, ...st.args], st.redir, true)
|
||||
await this.queuePrint([st.fmt, ...st.args], st.redir, true)
|
||||
break
|
||||
case 'expr':
|
||||
this.evalExpr(st.e)
|
||||
await this.evalExpr(st.e)
|
||||
break
|
||||
}
|
||||
}
|
||||
@@ -1244,17 +1314,23 @@ class BareAwkRuntime {
|
||||
* @param {{ op: string, file: BareAwkNode } | null} redir
|
||||
* @param {boolean} isPrintf
|
||||
*/
|
||||
queuePrint(args, redir, isPrintf) {
|
||||
async queuePrint(args, redir, isPrintf) {
|
||||
let s = ''
|
||||
if (isPrintf) {
|
||||
const fmt = String(this.evalExpr(args[0]))
|
||||
const rest = args.slice(1).map((x) => this.evalExpr(x))
|
||||
const fmt = String(await this.evalExpr(args[0]))
|
||||
const rest = []
|
||||
for (let j = 1; j < args.length; j++)
|
||||
rest.push(await this.evalExpr(args[j]))
|
||||
s = bareAwkSprintf(fmt, rest)
|
||||
} else if (!args.length) s = this.$0
|
||||
else s = args.map((x) => String(this.evalExpr(x))).join(this.OFS)
|
||||
else {
|
||||
const parts = []
|
||||
for (const x of args) parts.push(String(await this.evalExpr(x)))
|
||||
s = parts.join(this.OFS)
|
||||
}
|
||||
s += this.ORS
|
||||
if (redir) {
|
||||
const path = String(this.evalExpr(redir.file))
|
||||
const path = String(await this.evalExpr(redir.file))
|
||||
this.pendingWrites.push({
|
||||
path,
|
||||
data: s,
|
||||
@@ -1317,7 +1393,7 @@ export async function bareAwkRun(program, opts, io) {
|
||||
rule.pattern.k === 'BEGIN'
|
||||
) {
|
||||
for (const st of rule.stmts) {
|
||||
const r = rt.execStmt(st)
|
||||
const r = await rt.execStmt(st)
|
||||
await rt.flushWrites()
|
||||
if (r?.t === 'exit') return rt.exitCode
|
||||
}
|
||||
@@ -1343,9 +1419,9 @@ export async function bareAwkRun(program, opts, io) {
|
||||
rt.splitFields()
|
||||
for (const rule of ast.rules) {
|
||||
if (rule.pattern && rule.pattern.t === 'pat') continue
|
||||
if (!rt.patternMatch(rule.pattern)) continue
|
||||
if (!(await rt.patternMatch(rule.pattern))) continue
|
||||
for (const st of rule.stmts) {
|
||||
const r = rt.execStmt(st)
|
||||
const r = await rt.execStmt(st)
|
||||
await rt.flushWrites()
|
||||
if (r?.t === 'next') break
|
||||
if (r?.t === 'nextfile') break lines
|
||||
@@ -1371,7 +1447,7 @@ export async function bareAwkRun(program, opts, io) {
|
||||
for (const rule of ast.rules) {
|
||||
if (rule.pattern && rule.pattern.t === 'pat' && rule.pattern.k === 'END') {
|
||||
for (const st of rule.stmts) {
|
||||
rt.execStmt(st)
|
||||
await rt.execStmt(st)
|
||||
await rt.flushWrites()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
"synopsis": [
|
||||
"awk [OPTION]... [OPERAND]..."
|
||||
],
|
||||
"description": "Pattern-directed scanning and processing. Engine in lib/awk-engine.js; not full POSIX awk.",
|
||||
"description": "Pattern-directed scanning and processing. Engine in lib/awk-engine.js; Issue 7–oriented subset (BEGIN/END, patterns, arrays, printf, POSIX math builtins atan2/cos/sin/exp/log/sqrt, getline from stdin or file via getline var < path, next/nextfile, delete). See handbook ch.9 §7 for lexer and timing divergences.",
|
||||
"options": [],
|
||||
"keywords": [
|
||||
"awk",
|
||||
|
||||
@@ -5,7 +5,8 @@
|
||||
"synopsis": [
|
||||
"expr [OPTION]... [OPERAND]..."
|
||||
],
|
||||
"description": "Bare OS implementation of expr. Full behavior is defined in packages/bare-os-coreutils/src/expr.js.",
|
||||
"description": "Integer and string expressions: + - * / % (truncating division), comparisons, = and != for strings or numbers, and POSIX : (match length) with ECMA RegExp body after ^(?:…).",
|
||||
"bareOsNotes": "Exit 2 on errors; : pattern is not full BRE — ECMA syntax inside (?:).",
|
||||
"options": [],
|
||||
"keywords": [
|
||||
"expr",
|
||||
|
||||
@@ -5,7 +5,8 @@
|
||||
"synopsis": [
|
||||
"test [OPTION]... [OPERAND]..."
|
||||
],
|
||||
"description": "Bare OS implementation of evaluate a condition. Full behavior is defined in packages/bare-os-coreutils/src/test.js.",
|
||||
"description": "File and string predicates: -e -f -d -r -w -x -s -h/-L, -z -n, string = !=, integer -eq -ne -lt -le -gt -ge (decimal integers only), file mtime FILE1 -nt FILE2 / -ot and same-inode -ef when stat exposes dev+ino.",
|
||||
"bareOsNotes": "-ef falls back to path string compare if dev/ino missing.",
|
||||
"options": [],
|
||||
"keywords": [
|
||||
"test",
|
||||
|
||||
@@ -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"
|
||||
"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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ async function run(ctx, argv) {
|
||||
const tokens = argv.slice(1)
|
||||
if (!tokens.length || tokens[0] === '--help' || tokens[0] === '-h') {
|
||||
ctx.console.log(
|
||||
'usage: expr EXPRESSION\nInteger arithmetic (+ - * / %), comparisons, and string = / !=.'
|
||||
'usage: expr EXPRESSION\nInteger arithmetic (+ - * / %), comparisons, string = / !=, and POSIX : (regex match length; pattern is ECMA ^(?:…))'
|
||||
)
|
||||
return
|
||||
}
|
||||
@@ -19,11 +19,31 @@ async function run(ctx, argv) {
|
||||
if (/^-?\d+$/.test(t)) return { kind: 'n', v: parseInt(t, 10) }
|
||||
return { kind: 's', v: t }
|
||||
}
|
||||
function parseMul() {
|
||||
/** POSIX-style `:` regex match length (pattern is ECMA RegExp body after ^). */
|
||||
function parseColon() {
|
||||
let left = parsePrimary()
|
||||
while (peek() === ':') {
|
||||
take()
|
||||
const right = parsePrimary()
|
||||
const s = left.kind === 'n' ? String(left.v) : String(left.v)
|
||||
const pat = right.kind === 'n' ? String(right.v) : String(right.v)
|
||||
let n = 0
|
||||
try {
|
||||
const re = new RegExp('^(?:' + pat + ')')
|
||||
const m = re.exec(s)
|
||||
if (m) n = m[0].length
|
||||
} catch (_) {
|
||||
n = 0
|
||||
}
|
||||
left = { kind: 'n', v: n }
|
||||
}
|
||||
return left
|
||||
}
|
||||
function parseMul() {
|
||||
let left = parseColon()
|
||||
while (peek() === '*' || peek() === '/' || peek() === '%') {
|
||||
const op = take()
|
||||
const right = parsePrimary()
|
||||
const right = parseColon()
|
||||
if (left.kind !== 'n' || right.kind !== 'n') throw new Error('non-numeric')
|
||||
if (op === '*') left = { kind: 'n', v: left.v * right.v }
|
||||
else if (op === '/') {
|
||||
|
||||
@@ -83,5 +83,10 @@ async function run(ctx, argv) {
|
||||
return
|
||||
}
|
||||
ctx.console.error('pathcap-verify: FAIL ' + r.reason)
|
||||
if (trusted) {
|
||||
ctx.console.error(
|
||||
'pathcap-verify: hint: extend BARE_OS_PATH_CAPABILITY_TRUSTED_PUBKEYS_HEX with the issuer Ed25519 pubkey (64 hex) when the envelope is otherwise well-formed.'
|
||||
)
|
||||
}
|
||||
ctx.exitCode = 1
|
||||
}
|
||||
|
||||
@@ -102,6 +102,27 @@ ${topic ? 'Topic pin prefix: ' + topic.slice(0, 16) + '…' : 'Topic pin unset.'
|
||||
return
|
||||
}
|
||||
const r = await pkgIndexLookup(ctx, keyArg)
|
||||
const ent =
|
||||
r && typeof r === 'object' && r.entry && typeof r.entry === 'object'
|
||||
? r.entry
|
||||
: null
|
||||
if (
|
||||
ent &&
|
||||
ent.pathCapabilityEnvelope != null &&
|
||||
typeof ctx.bareOsVerifyPathCapabilityEnvelope === 'function'
|
||||
) {
|
||||
try {
|
||||
r.pathCapabilityEnvelopeVerify =
|
||||
ctx.bareOsVerifyPathCapabilityEnvelope(
|
||||
ent.pathCapabilityEnvelope
|
||||
)
|
||||
} catch (e) {
|
||||
r.pathCapabilityEnvelopeVerify = {
|
||||
ok: false,
|
||||
error: (e && e.message) || String(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
ctx.console.log(JSON.stringify(r, null, 2))
|
||||
if (!r.ok) ctx.exitCode = 1
|
||||
return
|
||||
|
||||
@@ -67,6 +67,25 @@ async function evalTest(ctx, args) {
|
||||
}
|
||||
if (op === '=') return a === b
|
||||
if (op === '!=') return a !== b
|
||||
if (op === '-nt' || op === '-ot' || op === '-ef') {
|
||||
const s1 = await ctx.vfs.stat(a)
|
||||
const s2 = await ctx.vfs.stat(b)
|
||||
if (!s1 || !s2) return false
|
||||
if (op === '-ef') {
|
||||
const i1 = s1.ino != null ? String(s1.ino) : ''
|
||||
const i2 = s2.ino != null ? String(s2.ino) : ''
|
||||
const d1 = s1.dev != null ? String(s1.dev) : ''
|
||||
const d2 = s2.dev != null ? String(s2.dev) : ''
|
||||
if (i1 && i2 && d1 && d2) return i1 === i2 && d1 === d2
|
||||
return a === b
|
||||
}
|
||||
const t1 = Number(s1.mtimeMs)
|
||||
const t2 = Number(s2.mtimeMs)
|
||||
const m1 = Number.isFinite(t1) ? t1 : 0
|
||||
const m2 = Number.isFinite(t2) ? t2 : 0
|
||||
if (op === '-nt') return m1 > m2
|
||||
if (op === '-ot') return m1 < m2
|
||||
}
|
||||
return false
|
||||
}
|
||||
if (args.length === 2) {
|
||||
|
||||
@@ -59,3 +59,78 @@ test('awk nextfile skips remainder of first file', async (t) => {
|
||||
)
|
||||
t.is(lines.join('\n'), 'A1\nB1\nB2')
|
||||
})
|
||||
|
||||
test('awk getline from file reads lines', async (t) => {
|
||||
/** @type {string[]} */
|
||||
const lines = []
|
||||
const enc = new TextEncoder()
|
||||
const io = {
|
||||
stdinLines: [],
|
||||
bytesToString(buf) {
|
||||
return buf ? new TextDecoder().decode(buf) : ''
|
||||
},
|
||||
print(s) {
|
||||
lines.push(String(s).replace(/\n$/, ''))
|
||||
},
|
||||
async writeFile() {},
|
||||
readFile(p) {
|
||||
if (p === 'data.txt') return enc.encode('one\ntwo\n')
|
||||
return null
|
||||
}
|
||||
}
|
||||
await bareAwkRun(
|
||||
'BEGIN { while ((getline x < "data.txt") > 0) print x }',
|
||||
{ fs: ' ', argc: 1, argv: ['awk'], environ: {} },
|
||||
io
|
||||
)
|
||||
t.is(lines.join('\n'), 'one\ntwo')
|
||||
})
|
||||
|
||||
test('awk atan2 and unary minus', async (t) => {
|
||||
/** @type {string[]} */
|
||||
const lines = []
|
||||
const io = {
|
||||
stdinLines: [''],
|
||||
bytesToString() {
|
||||
return ''
|
||||
},
|
||||
print(s) {
|
||||
lines.push(String(s).replace(/\n$/, ''))
|
||||
},
|
||||
async writeFile() {},
|
||||
async readFile() {
|
||||
return null
|
||||
}
|
||||
}
|
||||
await bareAwkRun(
|
||||
'BEGIN { print int(atan2(0,-1)*1000); print -3+1 }',
|
||||
{ fs: ' ', argc: 1, argv: ['awk'], environ: {} },
|
||||
io
|
||||
)
|
||||
t.ok(lines[0].includes('3141') || lines[0] === '3141' || Number(lines[0]) > 3000)
|
||||
t.is(lines[1], '-2')
|
||||
})
|
||||
|
||||
test('awk substr two-arg is suffix from start', async (t) => {
|
||||
/** @type {string[]} */
|
||||
const lines = []
|
||||
const io = {
|
||||
stdinLines: [''],
|
||||
bytesToString() {
|
||||
return ''
|
||||
},
|
||||
print(s) {
|
||||
lines.push(String(s).replace(/\n$/, ''))
|
||||
},
|
||||
async writeFile() {},
|
||||
async readFile() {
|
||||
return null
|
||||
}
|
||||
}
|
||||
await bareAwkRun(
|
||||
'BEGIN { print substr("abcdef", 3) }',
|
||||
{ fs: ' ', argc: 1, argv: ['awk'], environ: {} },
|
||||
io
|
||||
)
|
||||
t.is(lines[0], 'cdef')
|
||||
})
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
/**
|
||||
* pkg-swarm-index path-capability envelope verify on `get`.
|
||||
*/
|
||||
import { readFile } from 'node:fs/promises'
|
||||
import path from 'node:path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
import test from 'brittle'
|
||||
import b4a from 'b4a'
|
||||
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
||||
|
||||
const AsyncFunction = Object.getPrototypeOf(async function () {}).constructor
|
||||
|
||||
async function loadBin() {
|
||||
const runtime = await readFile(
|
||||
path.join(__dirname, '../lib/runtime.js'),
|
||||
'utf8'
|
||||
)
|
||||
let body = await readFile(
|
||||
path.join(__dirname, '../src/pkg-swarm-index.js'),
|
||||
'utf8'
|
||||
)
|
||||
body = body.replace(/\nexport \{ run \}\s*$/m, '\n')
|
||||
return new AsyncFunction(
|
||||
'ctx',
|
||||
'argv',
|
||||
`${runtime}\n${body}\nif (typeof run === 'function') return await run(ctx, argv)\n`
|
||||
)
|
||||
}
|
||||
|
||||
const sampleIndex = {
|
||||
packages: {
|
||||
'[email protected]': {
|
||||
driveKey: 'a'.repeat(64),
|
||||
manifestHash: 'b'.repeat(64),
|
||||
pathCapabilityEnvelope: { stub: true }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
test('pkg-swarm-index get adds pathCapabilityEnvelopeVerify when ctx hook exists', async (t) => {
|
||||
const run = await loadBin()
|
||||
const logs = []
|
||||
const ctx = {
|
||||
b4a,
|
||||
exitCode: 0,
|
||||
env: {},
|
||||
vfs: {
|
||||
async readFile(p) {
|
||||
t.is(p, '/etc/bare-os/pkg-index.json')
|
||||
return b4a.from(JSON.stringify(sampleIndex))
|
||||
}
|
||||
},
|
||||
bareOsVerifyPathCapabilityEnvelope(env) {
|
||||
return { ok: true, envelope: env }
|
||||
},
|
||||
console: {
|
||||
log: (m) => logs.push(String(m)),
|
||||
error: (m) => logs.push(String(m))
|
||||
}
|
||||
}
|
||||
await run(ctx, ['pkg-swarm-index', 'get', '[email protected]'])
|
||||
t.is(ctx.exitCode, 0)
|
||||
const out = JSON.parse(logs[0])
|
||||
t.ok(out.pathCapabilityEnvelopeVerify)
|
||||
t.is(out.pathCapabilityEnvelopeVerify.ok, true)
|
||||
})
|
||||
|
||||
test('pkg-swarm-index get captures verify errors in pathCapabilityEnvelopeVerify', async (t) => {
|
||||
const run = await loadBin()
|
||||
const logs = []
|
||||
const ctx = {
|
||||
b4a,
|
||||
exitCode: 0,
|
||||
env: {},
|
||||
vfs: {
|
||||
async readFile() {
|
||||
return b4a.from(JSON.stringify(sampleIndex))
|
||||
}
|
||||
},
|
||||
bareOsVerifyPathCapabilityEnvelope() {
|
||||
throw new Error('bad envelope')
|
||||
},
|
||||
console: {
|
||||
log: (m) => logs.push(String(m)),
|
||||
error: (m) => logs.push(String(m))
|
||||
}
|
||||
}
|
||||
await run(ctx, ['pkg-swarm-index', 'get', '[email protected]'])
|
||||
t.is(ctx.exitCode, 0)
|
||||
const out = JSON.parse(logs[0])
|
||||
t.ok(out.pathCapabilityEnvelopeVerify)
|
||||
t.is(out.pathCapabilityEnvelopeVerify.ok, false)
|
||||
t.ok(String(out.pathCapabilityEnvelopeVerify.error).includes('bad envelope'))
|
||||
})
|
||||
@@ -71,3 +71,41 @@ test('/bin/printf %c uses first character of string arg', async (t) => {
|
||||
await run(ctx, ['printf', '%c', 'xy'])
|
||||
t.is(ctx._logs.join(''), 'x')
|
||||
})
|
||||
|
||||
test('/bin/expr : returns match length (POSIX-shaped)', async (t) => {
|
||||
const run = await loadBin('expr')
|
||||
const ctx = createCtx()
|
||||
await run(ctx, ['expr', 'foobar', ':', 'foo'])
|
||||
t.is(ctx._logs.join(''), '3')
|
||||
const ctx2 = createCtx()
|
||||
await run(ctx2, ['expr', 'foo', ':', 'bar'])
|
||||
t.is(ctx2._logs.join(''), '0')
|
||||
})
|
||||
|
||||
test('/bin/test -nt and -ot use mtimeMs', async (t) => {
|
||||
const run = await loadBin('test')
|
||||
const ctx = {
|
||||
b4a,
|
||||
exitCode: 0,
|
||||
vfs: {
|
||||
async stat(p) {
|
||||
if (p === 'older')
|
||||
return { type: 'file', mtimeMs: 1000, ino: 1, dev: 0 }
|
||||
if (p === 'newer')
|
||||
return { type: 'file', mtimeMs: 2000, ino: 2, dev: 0 }
|
||||
return null
|
||||
}
|
||||
},
|
||||
console: { log() {}, error() {} }
|
||||
}
|
||||
await run(ctx, ['test', 'newer', '-nt', 'older'])
|
||||
t.is(ctx.exitCode, 0)
|
||||
const ctx2 = {
|
||||
b4a,
|
||||
exitCode: 0,
|
||||
vfs: ctx.vfs,
|
||||
console: { log() {}, error() {} }
|
||||
}
|
||||
await run(ctx2, ['test', 'older', '-ot', 'newer'])
|
||||
t.is(ctx2.exitCode, 0)
|
||||
})
|
||||
|
||||
@@ -4,6 +4,7 @@ Cross-package **version alignment** (ctx API, feature-bits doc, lifecycle schema
|
||||
|
||||
## Documentation (rolling)
|
||||
|
||||
- **Protomux cap channel** — With **`BARE_OS_PROTOMUX_CAP_CHANNEL`**, the booter accepts **`bare-os-cap-v1`** messages up to **65536** bytes; larger payloads are dropped (**`protomux_cap_payload_oversized`**) without incrementing **`protomuxCapChannelRxTotal`** ([`packages/bare-os-booter/lib/swarm-disk.js`](../packages/bare-os-booter/lib/swarm-disk.js)).
|
||||
- **`peer_firewall_stats` / `net_summary`** — [`lib/channel.js`](lib/channel.js) comment ties default firewall counters to **`/proc/bare_os/net_summary.json`** scalars (**`peerFirewallBlockedSessions`**, **`peerFirewallAllowedSessions`**) and the **`peerFirewallStats`** object when present.
|
||||
- **`0.9.1` / `BARE_OS_KERNEL_FEATURE_BITS_DOC` `16`** — Protomux cap-channel constant (**`PROTOCOL_CAP_CHANNEL_NAME`**) + **`BARE_OS_FEATURE8_PROTOMUX_CAP_CHANNEL`** documentation bump (stock **`bare-os-booter`** surfaces); **`stock-booter-capability-contract.json`** **`expectedFeatureBitsDoc`** aligned.
|
||||
- **Wire stack pins** — Re-verified **2026-04-05**: root **`package-lock.json`** hoisted **`protomux`** / **`hyperswarm`** versions still match [`packages/bare-os-booter/fixtures/protomux-hyperswarm-lock.json`](../packages/bare-os-booter/fixtures/protomux-hyperswarm-lock.json) (**`BARE_OS_PROTOMUX_CHANNEL_SCHEMA_VERSION`** unchanged).
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
*/
|
||||
|
||||
/** Semver for the documented POSIX-like surface (handbook ch.9 + environment appendix). */
|
||||
export const BARE_OS_POSIX_PROFILE_VERSION = '1.0.17'
|
||||
export const BARE_OS_POSIX_PROFILE_VERSION = '1.0.18'
|
||||
|
||||
/** Short identifier for telemetry and `/proc` mirrors. */
|
||||
export const BARE_OS_POSIX_PROFILE_ID = 'bare-os-posix-like'
|
||||
|
||||
@@ -91,7 +91,8 @@ function bareOsEmitRaw(ctx, chunk) {
|
||||
* POSIX-oriented awk interpreter for Bare OS (no import; concatenated before src/awk.js).
|
||||
* Supports: BEGIN/END, /re/, line patterns, {}, print/printf, if/else, while, for(;;), for(x in a),
|
||||
* delete arr[idx], next, exit, break, continue, ++/--, arrays, strings, regex ~ !~, builtins (length, substr, index,
|
||||
* split, sprintf, sub, gsub, match, int, tolower, toupper, rand, srand), getline from stdin/files,
|
||||
* split, sprintf, sub, gsub, match, int, atan2, cos, sin, exp, log, sqrt, tolower, toupper, rand, srand),
|
||||
* getline from stdin, files (getline x < \"path\"), unary minus,
|
||||
* optional fixed-width fields via FIELDWIDTHS when BARE_OS_AWK_FIELDWIDTHS is enabled,
|
||||
* -F FS, NF, NR, FNR, $0..$n, OFS, ORS, RS, ARGC, ARGV, FILENAME, ENVIRON (read-only mirror).
|
||||
* GNU-style empty fields: consecutive FS delimiters still advance $1..$NF where the grammar allows.
|
||||
@@ -670,7 +671,11 @@ class BareAwkParser {
|
||||
}
|
||||
|
||||
parseUnary() {
|
||||
if (this.peek().value === '!' || this.peek().value === '+') {
|
||||
if (
|
||||
this.peek().value === '!' ||
|
||||
this.peek().value === '+' ||
|
||||
this.peek().value === '-'
|
||||
) {
|
||||
const op = String(this.peek().value)
|
||||
this.p++
|
||||
return { t: 'unop', op, a: this.parseUnary() }
|
||||
@@ -819,6 +824,8 @@ class BareAwkRuntime {
|
||||
this._stdinIx = 0
|
||||
/** @type {{ path: string, data: string, append: boolean }[]} */
|
||||
this.pendingWrites = []
|
||||
/** @type {Map<string, { lines: string[], i: number }>} */
|
||||
this._getlineFileState = new Map()
|
||||
}
|
||||
|
||||
splitFields() {
|
||||
@@ -889,14 +896,14 @@ class BareAwkRuntime {
|
||||
/**
|
||||
* @param {BareAwkNode | null} pat
|
||||
*/
|
||||
patternMatch(pat) {
|
||||
async patternMatch(pat) {
|
||||
if (pat == null) return true
|
||||
if (pat.t === 'pat') return false
|
||||
if (pat.t === 'patRe') {
|
||||
pat.rx.lastIndex = 0
|
||||
return pat.rx.test(this.$0)
|
||||
}
|
||||
if (pat.t === 'patExpr') return this.truthy(this.evalExpr(pat.e))
|
||||
if (pat.t === 'patExpr') return this.truthy(await this.evalExpr(pat.e))
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -910,7 +917,7 @@ class BareAwkRuntime {
|
||||
/**
|
||||
* @param {BareAwkNode} n
|
||||
*/
|
||||
evalExpr(n) {
|
||||
async evalExpr(n) {
|
||||
if (!n) return ''
|
||||
switch (n.t) {
|
||||
case 'num':
|
||||
@@ -932,9 +939,9 @@ class BareAwkRuntime {
|
||||
return this.vars[name] != null ? this.vars[name] : ''
|
||||
}
|
||||
case 'field':
|
||||
return this.fieldNum(this.evalExpr(n.e))
|
||||
return this.fieldNum(await this.evalExpr(n.e))
|
||||
case 'index': {
|
||||
const k = String(this.evalExpr(n.idx))
|
||||
const k = String(await this.evalExpr(n.idx))
|
||||
if (n.name === 'ENVIRON') {
|
||||
const v = this.environ[k]
|
||||
return v != null ? v : ''
|
||||
@@ -944,8 +951,8 @@ class BareAwkRuntime {
|
||||
return arr[k] != null ? arr[k] : ''
|
||||
}
|
||||
case 'binop': {
|
||||
const a = this.evalExpr(n.a)
|
||||
const b = this.evalExpr(n.b)
|
||||
const a = await this.evalExpr(n.a)
|
||||
const b = await this.evalExpr(n.b)
|
||||
const an = Number(a)
|
||||
const bn = Number(b)
|
||||
switch (n.op) {
|
||||
@@ -978,77 +985,78 @@ class BareAwkRuntime {
|
||||
}
|
||||
}
|
||||
case 'unop': {
|
||||
const a = this.evalExpr(n.a)
|
||||
const a = await this.evalExpr(n.a)
|
||||
if (n.op === '!') return this.truthy(a) ? 0 : 1
|
||||
if (n.op === '+') return Number(a) || 0
|
||||
if (n.op === '-') return -(Number(a) || 0)
|
||||
return 0
|
||||
}
|
||||
case '||':
|
||||
return this.truthy(this.evalExpr(n.a))
|
||||
return this.truthy(await this.evalExpr(n.a))
|
||||
? 1
|
||||
: this.truthy(this.evalExpr(n.b))
|
||||
: this.truthy(await this.evalExpr(n.b))
|
||||
? 1
|
||||
: 0
|
||||
case '&&':
|
||||
return this.truthy(this.evalExpr(n.a)) &&
|
||||
this.truthy(this.evalExpr(n.b))
|
||||
return this.truthy(await this.evalExpr(n.a)) &&
|
||||
this.truthy(await this.evalExpr(n.b))
|
||||
? 1
|
||||
: 0
|
||||
case 'match': {
|
||||
const s = String(this.evalExpr(n.a))
|
||||
const s = String(await this.evalExpr(n.a))
|
||||
const rhs = n.b
|
||||
let rx
|
||||
if (rhs.t === 'rxLit') rx = rhs.rx
|
||||
else {
|
||||
const t = String(this.evalExpr(rhs))
|
||||
const t = String(await this.evalExpr(rhs))
|
||||
rx = bareAwkMakeRx(t, '')
|
||||
}
|
||||
rx.lastIndex = 0
|
||||
return rx.test(s) ? 1 : 0
|
||||
}
|
||||
case 'nmatch': {
|
||||
const s = String(this.evalExpr(n.a))
|
||||
const s = String(await this.evalExpr(n.a))
|
||||
const rhs = n.b
|
||||
let rx
|
||||
if (rhs.t === 'rxLit') rx = rhs.rx
|
||||
else {
|
||||
const t = String(this.evalExpr(rhs))
|
||||
const t = String(await this.evalExpr(rhs))
|
||||
rx = bareAwkMakeRx(t, '')
|
||||
}
|
||||
rx.lastIndex = 0
|
||||
return rx.test(s) ? 0 : 1
|
||||
}
|
||||
case '?:':
|
||||
return this.truthy(this.evalExpr(n.n))
|
||||
? this.evalExpr(n.a)
|
||||
: this.evalExpr(n.b)
|
||||
return this.truthy(await this.evalExpr(n.n))
|
||||
? await this.evalExpr(n.a)
|
||||
: await this.evalExpr(n.b)
|
||||
case 'assign': {
|
||||
const v = this.evalExpr(n.right)
|
||||
this.assign(n.left, n.op, v)
|
||||
const v = await this.evalExpr(n.right)
|
||||
await this.assign(n.left, n.op, v)
|
||||
return v
|
||||
}
|
||||
case 'pre': {
|
||||
const cur = Number(this.evalExpr(n.x))
|
||||
const cur = Number(await this.evalExpr(n.x))
|
||||
const next = n.op === '++' ? cur + 1 : cur - 1
|
||||
this.assignScalar(n.x, next)
|
||||
await this.assignScalar(n.x, next)
|
||||
return next
|
||||
}
|
||||
case 'post': {
|
||||
const cur = Number(this.evalExpr(n.x))
|
||||
const cur = Number(await this.evalExpr(n.x))
|
||||
const next = n.op === '++' ? cur + 1 : cur - 1
|
||||
this.assignScalar(n.x, next)
|
||||
await this.assignScalar(n.x, next)
|
||||
return cur
|
||||
}
|
||||
case 'call':
|
||||
return this.callBuiltin(n.name, n.args)
|
||||
return await this.callBuiltin(n.name, n.args)
|
||||
case 'getline':
|
||||
return this.doGetline(n)
|
||||
return await this.doGetline(n)
|
||||
default:
|
||||
return ''
|
||||
}
|
||||
}
|
||||
|
||||
assignScalar(x, v) {
|
||||
async assignScalar(x, v) {
|
||||
if (x.t === 'var') {
|
||||
const name = x.name
|
||||
if (name === 'FS') this.FS = String(v)
|
||||
@@ -1057,7 +1065,7 @@ class BareAwkRuntime {
|
||||
else if (name === 'RS') this.RS = String(v)
|
||||
else this.vars[name] = v
|
||||
} else if (x.t === 'field') {
|
||||
this.setField(this.evalExpr(x.e), v)
|
||||
this.setField(await this.evalExpr(x.e), v)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1066,10 +1074,10 @@ class BareAwkRuntime {
|
||||
* @param {string} op
|
||||
* @param {unknown} v
|
||||
*/
|
||||
assign(left, op, v) {
|
||||
async assign(left, op, v) {
|
||||
let base = v
|
||||
if (op !== '=') {
|
||||
const cur = Number(this.evalExpr(left))
|
||||
const cur = Number(await this.evalExpr(left))
|
||||
const nv = Number(v)
|
||||
if (op === '+=') base = cur + nv
|
||||
else if (op === '-=') base = cur - nv
|
||||
@@ -1080,32 +1088,52 @@ class BareAwkRuntime {
|
||||
if (left.t === 'index') {
|
||||
const arr =
|
||||
this.arrays[left.name] || (this.arrays[left.name] = Object.create(null))
|
||||
arr[String(this.evalExpr(left.idx))] = base
|
||||
arr[String(await this.evalExpr(left.idx))] = base
|
||||
return
|
||||
}
|
||||
this.assignScalar(left, base)
|
||||
await this.assignScalar(left, base)
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} path
|
||||
*/
|
||||
async _ensureGetlineFile(path) {
|
||||
let st = this._getlineFileState.get(path)
|
||||
if (st) return st
|
||||
const buf = await this.io.readFile(path)
|
||||
const text =
|
||||
buf && this.io.bytesToString ? this.io.bytesToString(buf) : ''
|
||||
const lines = text.split(/\r?\n/)
|
||||
if (lines.length && lines[lines.length - 1] === '') lines.pop()
|
||||
st = { lines, i: 0 }
|
||||
this._getlineFileState.set(path, st)
|
||||
return st
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} name
|
||||
* @param {BareAwkNode[]} args
|
||||
*/
|
||||
callBuiltin(name, args) {
|
||||
const a = (i) => (args[i] ? this.evalExpr(args[i]) : '')
|
||||
async callBuiltin(name, args) {
|
||||
const a = async (i) =>
|
||||
args[i] != null ? await this.evalExpr(args[i]) : ''
|
||||
switch (name) {
|
||||
case 'length':
|
||||
if (!args.length) return this.$0.length
|
||||
return String(a(0)).length
|
||||
case 'substr':
|
||||
return String(a(0)).slice(
|
||||
Math.max(0, Math.trunc(Number(a(1))) - 1),
|
||||
Math.max(0, Math.trunc(Number(a(1))) - 1 + Math.trunc(Number(a(2))))
|
||||
)
|
||||
return String(await a(0)).length
|
||||
case 'substr': {
|
||||
const s = String(await a(0))
|
||||
const start = Math.max(0, Math.trunc(Number(await a(1))) - 1)
|
||||
if (args.length < 3) return s.slice(start)
|
||||
const ln = Math.trunc(Number(await a(2)))
|
||||
if (!Number.isFinite(ln) || ln < 0) return ''
|
||||
return s.slice(start, start + ln)
|
||||
}
|
||||
case 'index':
|
||||
return String(a(0)).indexOf(String(a(1))) + 1 || 0
|
||||
return String(await a(0)).indexOf(String(await a(1))) + 1 || 0
|
||||
case 'split': {
|
||||
const s = String(a(0))
|
||||
const fs = args[1] ? String(this.evalExpr(args[1])) : this.FS
|
||||
const s = String(await a(0))
|
||||
const fs = args[1] ? String(await this.evalExpr(args[1])) : this.FS
|
||||
let rx = fs.length === 1 ? null : bareAwkMakeRx(fs, '')
|
||||
const parts = rx ? s.split(rx) : s.split(fs)
|
||||
const aname = /** @type {BareAwkNode} */ (args[2])
|
||||
@@ -1116,17 +1144,35 @@ class BareAwkRuntime {
|
||||
}
|
||||
return parts.length
|
||||
}
|
||||
case 'sprintf':
|
||||
return bareAwkSprintf(
|
||||
String(a(0)),
|
||||
args.slice(1).map((x) => this.evalExpr(x))
|
||||
)
|
||||
case 'sprintf': {
|
||||
const fmt = String(await a(0))
|
||||
const rest = []
|
||||
for (let j = 1; j < args.length; j++)
|
||||
rest.push(await this.evalExpr(args[j]))
|
||||
return bareAwkSprintf(fmt, rest)
|
||||
}
|
||||
case 'int':
|
||||
return Math.trunc(Number(a(0)))
|
||||
return Math.trunc(Number(await a(0)))
|
||||
case 'log': {
|
||||
const x = Number(await a(0))
|
||||
return x > 0 ? Math.log(x) : Number.NaN
|
||||
}
|
||||
case 'sqrt': {
|
||||
const x = Number(await a(0))
|
||||
return x >= 0 ? Math.sqrt(x) : Number.NaN
|
||||
}
|
||||
case 'sin':
|
||||
return Math.sin(Number(await a(0)))
|
||||
case 'cos':
|
||||
return Math.cos(Number(await a(0)))
|
||||
case 'exp':
|
||||
return Math.exp(Number(await a(0)))
|
||||
case 'atan2':
|
||||
return Math.atan2(Number(await a(0)), Number(await a(1)))
|
||||
case 'tolower':
|
||||
return String(a(0)).toLowerCase()
|
||||
return String(await a(0)).toLowerCase()
|
||||
case 'toupper':
|
||||
return String(a(0)).toUpperCase()
|
||||
return String(await a(0)).toUpperCase()
|
||||
case 'rand': {
|
||||
this.randSeed = (this.randSeed * 1103515245 + 12345) & 0x7fffffff
|
||||
return this.randSeed / 0x7fffffff
|
||||
@@ -1140,9 +1186,11 @@ class BareAwkRuntime {
|
||||
case 'sub':
|
||||
case 'gsub': {
|
||||
const rx =
|
||||
args[0].t === 'rxLit' ? args[0].rx : bareAwkMakeRx(String(a(0)), '')
|
||||
const rep = String(a(1))
|
||||
let target = args[2] ? String(a(2)) : this.$0
|
||||
args[0].t === 'rxLit'
|
||||
? args[0].rx
|
||||
: bareAwkMakeRx(String(await a(0)), '')
|
||||
const rep = String(await a(1))
|
||||
let target = args[2] ? String(await a(2)) : this.$0
|
||||
let n = 0
|
||||
if (name === 'sub') {
|
||||
rx.lastIndex = 0
|
||||
@@ -1167,9 +1215,11 @@ class BareAwkRuntime {
|
||||
return n
|
||||
}
|
||||
case 'match': {
|
||||
const s = String(a(0))
|
||||
const s = String(await a(0))
|
||||
const rx =
|
||||
args[1].t === 'rxLit' ? args[1].rx : bareAwkMakeRx(String(a(1)), '')
|
||||
args[1].t === 'rxLit'
|
||||
? args[1].rx
|
||||
: bareAwkMakeRx(String(await a(1)), '')
|
||||
rx.lastIndex = 0
|
||||
const m = rx.exec(s)
|
||||
if (!m) {
|
||||
@@ -1183,7 +1233,7 @@ class BareAwkRuntime {
|
||||
}
|
||||
default:
|
||||
if (this.funcs.has(name)) {
|
||||
return this.callUser(name, args)
|
||||
return await this.callUser(name, args)
|
||||
}
|
||||
bareAwkError('unknown function ' + name)
|
||||
}
|
||||
@@ -1193,19 +1243,19 @@ class BareAwkRuntime {
|
||||
* @param {string} name
|
||||
* @param {BareAwkNode[]} args
|
||||
*/
|
||||
callUser(name, args) {
|
||||
async callUser(name, args) {
|
||||
const f = this.funcs.get(name)
|
||||
if (!f) return ''
|
||||
const frame = { ...this.vars }
|
||||
for (let i = 0; i < f.params.length; i++) {
|
||||
frame[f.params[i]] = args[i] ? this.evalExpr(args[i]) : ''
|
||||
frame[f.params[i]] = args[i] ? await this.evalExpr(args[i]) : ''
|
||||
}
|
||||
const prev = this.vars
|
||||
this.vars = frame
|
||||
let ret = ''
|
||||
try {
|
||||
for (const st of f.body) {
|
||||
const r = this.execStmt(st)
|
||||
const r = await this.execStmt(st)
|
||||
if (r && r.t === 'return') {
|
||||
ret = r.v != null ? r.v : ''
|
||||
break
|
||||
@@ -1220,12 +1270,27 @@ class BareAwkRuntime {
|
||||
/**
|
||||
* @param {BareAwkNode} n
|
||||
*/
|
||||
doGetline(n) {
|
||||
async doGetline(n) {
|
||||
let line = null
|
||||
if (n.from) {
|
||||
const path = String(this.evalExpr(n.from))
|
||||
/* sync read — awk.js should preload or use async wrapper; runtime uses promise in run loop */
|
||||
return 0
|
||||
const path = String(await this.evalExpr(n.from))
|
||||
const st = await this._ensureGetlineFile(path)
|
||||
if (st.i < st.lines.length) line = st.lines[st.i++]
|
||||
else line = null
|
||||
if (line == null) {
|
||||
if (n.var) this.vars[n.var] = ''
|
||||
else {
|
||||
this.$0 = ''
|
||||
this.splitFields()
|
||||
}
|
||||
return 0
|
||||
}
|
||||
if (n.var) this.vars[n.var] = line
|
||||
else {
|
||||
this.$0 = line
|
||||
this.splitFields()
|
||||
}
|
||||
return 1
|
||||
}
|
||||
if (this._stdinIx < this.io.stdinLines.length) {
|
||||
line = this.io.stdinLines[this._stdinIx++]
|
||||
@@ -1245,23 +1310,24 @@ class BareAwkRuntime {
|
||||
|
||||
/**
|
||||
* @param {BareAwkNode} st
|
||||
* @returns {{ t: string, v?: unknown } | void}
|
||||
* @returns {Promise<{ t: string, v?: unknown } | void>}
|
||||
*/
|
||||
execStmt(st) {
|
||||
async execStmt(st) {
|
||||
switch (st.t) {
|
||||
case 'block':
|
||||
for (const s of st.stmts) {
|
||||
const r = this.execStmt(s)
|
||||
const r = await this.execStmt(s)
|
||||
if (r) return r
|
||||
}
|
||||
break
|
||||
case 'if':
|
||||
if (this.truthy(this.evalExpr(st.cond))) return this.execStmt(st.then)
|
||||
if (st.else) return this.execStmt(st.else)
|
||||
if (this.truthy(await this.evalExpr(st.cond)))
|
||||
return await this.execStmt(st.then)
|
||||
if (st.else) return await this.execStmt(st.else)
|
||||
break
|
||||
case 'while':
|
||||
while (this.truthy(this.evalExpr(st.cond))) {
|
||||
const r = this.execStmt(st.body)
|
||||
while (this.truthy(await this.evalExpr(st.cond))) {
|
||||
const r = await this.execStmt(st.body)
|
||||
if (r?.t === 'break') break
|
||||
if (r?.t === 'continue') continue
|
||||
if (r?.t === 'next' || r?.t === 'nextfile' || r?.t === 'exit')
|
||||
@@ -1269,25 +1335,25 @@ class BareAwkRuntime {
|
||||
}
|
||||
break
|
||||
case 'for':
|
||||
if (st.init) this.evalExpr(st.init)
|
||||
if (st.init) await this.evalExpr(st.init)
|
||||
while (true) {
|
||||
if (st.cond && !this.truthy(this.evalExpr(st.cond))) break
|
||||
const r = this.execStmt(st.body)
|
||||
if (st.cond && !this.truthy(await this.evalExpr(st.cond))) break
|
||||
const r = await this.execStmt(st.body)
|
||||
if (r?.t === 'break') break
|
||||
if (r?.t === 'continue') {
|
||||
if (st.step) this.evalExpr(st.step)
|
||||
if (st.step) await this.evalExpr(st.step)
|
||||
continue
|
||||
}
|
||||
if (r?.t === 'next' || r?.t === 'nextfile' || r?.t === 'exit')
|
||||
return r
|
||||
if (st.step) this.evalExpr(st.step)
|
||||
if (st.step) await this.evalExpr(st.step)
|
||||
}
|
||||
break
|
||||
case 'forin': {
|
||||
const arr = this.arrays[st.arr] || {}
|
||||
for (const k of Object.keys(arr)) {
|
||||
this.vars[st.iv] = k
|
||||
const r = this.execStmt(st.body)
|
||||
const r = await this.execStmt(st.body)
|
||||
if (r?.t === 'break') break
|
||||
if (r?.t === 'continue') continue
|
||||
if (r?.t === 'next' || r?.t === 'nextfile' || r?.t === 'exit')
|
||||
@@ -1298,7 +1364,7 @@ class BareAwkRuntime {
|
||||
case 'delete': {
|
||||
const tbl =
|
||||
this.arrays[st.arr] || (this.arrays[st.arr] = Object.create(null))
|
||||
const ky = String(this.evalExpr(st.key))
|
||||
const ky = String(await this.evalExpr(st.key))
|
||||
delete tbl[ky]
|
||||
break
|
||||
}
|
||||
@@ -1311,19 +1377,23 @@ class BareAwkRuntime {
|
||||
case 'continue':
|
||||
return { t: 'continue' }
|
||||
case 'exit':
|
||||
this.exitCode = Math.trunc(Number(this.evalExpr(st.code))) || 0
|
||||
this.exitCode =
|
||||
Math.trunc(Number(await this.evalExpr(st.code))) || 0
|
||||
this.exitPending = true
|
||||
return { t: 'exit' }
|
||||
case 'return':
|
||||
return { t: 'return', v: st.e ? this.evalExpr(st.e) : '' }
|
||||
return {
|
||||
t: 'return',
|
||||
v: st.e ? await this.evalExpr(st.e) : ''
|
||||
}
|
||||
case 'print':
|
||||
this.queuePrint(st.args, st.redir, false)
|
||||
await this.queuePrint(st.args, st.redir, false)
|
||||
break
|
||||
case 'printf':
|
||||
this.queuePrint([st.fmt, ...st.args], st.redir, true)
|
||||
await this.queuePrint([st.fmt, ...st.args], st.redir, true)
|
||||
break
|
||||
case 'expr':
|
||||
this.evalExpr(st.e)
|
||||
await this.evalExpr(st.e)
|
||||
break
|
||||
}
|
||||
}
|
||||
@@ -1333,17 +1403,23 @@ class BareAwkRuntime {
|
||||
* @param {{ op: string, file: BareAwkNode } | null} redir
|
||||
* @param {boolean} isPrintf
|
||||
*/
|
||||
queuePrint(args, redir, isPrintf) {
|
||||
async queuePrint(args, redir, isPrintf) {
|
||||
let s = ''
|
||||
if (isPrintf) {
|
||||
const fmt = String(this.evalExpr(args[0]))
|
||||
const rest = args.slice(1).map((x) => this.evalExpr(x))
|
||||
const fmt = String(await this.evalExpr(args[0]))
|
||||
const rest = []
|
||||
for (let j = 1; j < args.length; j++)
|
||||
rest.push(await this.evalExpr(args[j]))
|
||||
s = bareAwkSprintf(fmt, rest)
|
||||
} else if (!args.length) s = this.$0
|
||||
else s = args.map((x) => String(this.evalExpr(x))).join(this.OFS)
|
||||
else {
|
||||
const parts = []
|
||||
for (const x of args) parts.push(String(await this.evalExpr(x)))
|
||||
s = parts.join(this.OFS)
|
||||
}
|
||||
s += this.ORS
|
||||
if (redir) {
|
||||
const path = String(this.evalExpr(redir.file))
|
||||
const path = String(await this.evalExpr(redir.file))
|
||||
this.pendingWrites.push({
|
||||
path,
|
||||
data: s,
|
||||
@@ -1406,7 +1482,7 @@ async function bareAwkRun(program, opts, io) {
|
||||
rule.pattern.k === 'BEGIN'
|
||||
) {
|
||||
for (const st of rule.stmts) {
|
||||
const r = rt.execStmt(st)
|
||||
const r = await rt.execStmt(st)
|
||||
await rt.flushWrites()
|
||||
if (r?.t === 'exit') return rt.exitCode
|
||||
}
|
||||
@@ -1432,9 +1508,9 @@ async function bareAwkRun(program, opts, io) {
|
||||
rt.splitFields()
|
||||
for (const rule of ast.rules) {
|
||||
if (rule.pattern && rule.pattern.t === 'pat') continue
|
||||
if (!rt.patternMatch(rule.pattern)) continue
|
||||
if (!(await rt.patternMatch(rule.pattern))) continue
|
||||
for (const st of rule.stmts) {
|
||||
const r = rt.execStmt(st)
|
||||
const r = await rt.execStmt(st)
|
||||
await rt.flushWrites()
|
||||
if (r?.t === 'next') break
|
||||
if (r?.t === 'nextfile') break lines
|
||||
@@ -1460,7 +1536,7 @@ async function bareAwkRun(program, opts, io) {
|
||||
for (const rule of ast.rules) {
|
||||
if (rule.pattern && rule.pattern.t === 'pat' && rule.pattern.k === 'END') {
|
||||
for (const st of rule.stmts) {
|
||||
rt.execStmt(st)
|
||||
await rt.execStmt(st)
|
||||
await rt.flushWrites()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -91,7 +91,7 @@ async function run(ctx, argv) {
|
||||
const tokens = argv.slice(1)
|
||||
if (!tokens.length || tokens[0] === '--help' || tokens[0] === '-h') {
|
||||
ctx.console.log(
|
||||
'usage: expr EXPRESSION\nInteger arithmetic (+ - * / %), comparisons, and string = / !=.'
|
||||
'usage: expr EXPRESSION\nInteger arithmetic (+ - * / %), comparisons, string = / !=, and POSIX : (regex match length; pattern is ECMA ^(?:…))'
|
||||
)
|
||||
return
|
||||
}
|
||||
@@ -108,11 +108,31 @@ async function run(ctx, argv) {
|
||||
if (/^-?\d+$/.test(t)) return { kind: 'n', v: parseInt(t, 10) }
|
||||
return { kind: 's', v: t }
|
||||
}
|
||||
function parseMul() {
|
||||
/** POSIX-style `:` regex match length (pattern is ECMA RegExp body after ^). */
|
||||
function parseColon() {
|
||||
let left = parsePrimary()
|
||||
while (peek() === ':') {
|
||||
take()
|
||||
const right = parsePrimary()
|
||||
const s = left.kind === 'n' ? String(left.v) : String(left.v)
|
||||
const pat = right.kind === 'n' ? String(right.v) : String(right.v)
|
||||
let n = 0
|
||||
try {
|
||||
const re = new RegExp('^(?:' + pat + ')')
|
||||
const m = re.exec(s)
|
||||
if (m) n = m[0].length
|
||||
} catch (_) {
|
||||
n = 0
|
||||
}
|
||||
left = { kind: 'n', v: n }
|
||||
}
|
||||
return left
|
||||
}
|
||||
function parseMul() {
|
||||
let left = parseColon()
|
||||
while (peek() === '*' || peek() === '/' || peek() === '%') {
|
||||
const op = take()
|
||||
const right = parsePrimary()
|
||||
const right = parseColon()
|
||||
if (left.kind !== 'n' || right.kind !== 'n') throw new Error('non-numeric')
|
||||
if (op === '*') left = { kind: 'n', v: left.v * right.v }
|
||||
else if (op === '/') {
|
||||
|
||||
@@ -172,5 +172,10 @@ async function run(ctx, argv) {
|
||||
return
|
||||
}
|
||||
ctx.console.error('pathcap-verify: FAIL ' + r.reason)
|
||||
if (trusted) {
|
||||
ctx.console.error(
|
||||
'pathcap-verify: hint: extend BARE_OS_PATH_CAPABILITY_TRUSTED_PUBKEYS_HEX with the issuer Ed25519 pubkey (64 hex) when the envelope is otherwise well-formed.'
|
||||
)
|
||||
}
|
||||
ctx.exitCode = 1
|
||||
}
|
||||
|
||||
@@ -191,6 +191,27 @@ ${topic ? 'Topic pin prefix: ' + topic.slice(0, 16) + '…' : 'Topic pin unset.'
|
||||
return
|
||||
}
|
||||
const r = await pkgIndexLookup(ctx, keyArg)
|
||||
const ent =
|
||||
r && typeof r === 'object' && r.entry && typeof r.entry === 'object'
|
||||
? r.entry
|
||||
: null
|
||||
if (
|
||||
ent &&
|
||||
ent.pathCapabilityEnvelope != null &&
|
||||
typeof ctx.bareOsVerifyPathCapabilityEnvelope === 'function'
|
||||
) {
|
||||
try {
|
||||
r.pathCapabilityEnvelopeVerify =
|
||||
ctx.bareOsVerifyPathCapabilityEnvelope(
|
||||
ent.pathCapabilityEnvelope
|
||||
)
|
||||
} catch (e) {
|
||||
r.pathCapabilityEnvelopeVerify = {
|
||||
ok: false,
|
||||
error: (e && e.message) || String(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
ctx.console.log(JSON.stringify(r, null, 2))
|
||||
if (!r.ok) ctx.exitCode = 1
|
||||
return
|
||||
|
||||
@@ -156,6 +156,25 @@ async function evalTest(ctx, args) {
|
||||
}
|
||||
if (op === '=') return a === b
|
||||
if (op === '!=') return a !== b
|
||||
if (op === '-nt' || op === '-ot' || op === '-ef') {
|
||||
const s1 = await ctx.vfs.stat(a)
|
||||
const s2 = await ctx.vfs.stat(b)
|
||||
if (!s1 || !s2) return false
|
||||
if (op === '-ef') {
|
||||
const i1 = s1.ino != null ? String(s1.ino) : ''
|
||||
const i2 = s2.ino != null ? String(s2.ino) : ''
|
||||
const d1 = s1.dev != null ? String(s1.dev) : ''
|
||||
const d2 = s2.dev != null ? String(s2.dev) : ''
|
||||
if (i1 && i2 && d1 && d2) return i1 === i2 && d1 === d2
|
||||
return a === b
|
||||
}
|
||||
const t1 = Number(s1.mtimeMs)
|
||||
const t2 = Number(s2.mtimeMs)
|
||||
const m1 = Number.isFinite(t1) ? t1 : 0
|
||||
const m2 = Number.isFinite(t2) ? t2 : 0
|
||||
if (op === '-nt') return m1 > m2
|
||||
if (op === '-ot') return m1 < m2
|
||||
}
|
||||
return false
|
||||
}
|
||||
if (args.length === 2) {
|
||||
|
||||
@@ -7,6 +7,20 @@
|
||||
"driveKeyHex": "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
|
||||
"manifestSha256Hex": "optional content hash of package root",
|
||||
"publisherPubkeyHex": "optional ed25519 pubkey that signed this row"
|
||||
},
|
||||
"[email protected]": {
|
||||
"driveKeyHex": "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
|
||||
"pathCapabilityEnvelope": {
|
||||
"schema": 1,
|
||||
"note": "Illustrative only; replace pubkeyHex/signatureHex for real issuance. When present, pkg-swarm-index get calls ctx.bareOsVerifyPathCapabilityEnvelope and prints pathCapabilityEnvelopeVerify.",
|
||||
"pubkeyHex": "0000000000000000000000000000000000000000000000000000000000000000",
|
||||
"signatureHex": "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
|
||||
"payload": {
|
||||
"expMs": 1893456000000,
|
||||
"ops": ["read"],
|
||||
"prefix": "/tmp/"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"schema": 2,
|
||||
"profileId": "bare-os-posix-like",
|
||||
"generatedAt": "2026-04-06T01:43:58.480Z",
|
||||
"generatedAt": "2026-04-06T03:01:17.010Z",
|
||||
"note": "Sparse POSIX Issue 7 coverage hints for /bin utilities. Omitted command names are not yet profiled here.",
|
||||
"commandIndex": [
|
||||
{
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
"ctxApiVersion": "1.53.0",
|
||||
"posixProfile": {
|
||||
"id": "bare-os-posix-like",
|
||||
"version": "1.0.17"
|
||||
"version": "1.0.18"
|
||||
},
|
||||
"ops": ["readFile", "writeFile", "socket"],
|
||||
"opsDetail": [
|
||||
@@ -60,7 +60,7 @@
|
||||
"fdModel": {
|
||||
"schema": 1,
|
||||
"stdio": [0, 1, 2],
|
||||
"processTableSchema": 7
|
||||
"processTableSchema": 8
|
||||
},
|
||||
"signalModel": {
|
||||
"schema": 1,
|
||||
|
||||
@@ -1322,7 +1322,7 @@ async function applyBootPolicyFile(ctx) {
|
||||
|
||||
/**
|
||||
* @param {{ file: string, extId: string, scripts: string[], dependsOn: string[], signaturePointer?: string }[]} entries
|
||||
* @returns {{ ok: true, ordered: typeof entries } | { ok: false, cycleExtIds: string[] }}
|
||||
* @returns {{ ok: true, ordered: typeof entries } | { ok: false, cycleExtIds: string[], cycleEdges: string[] }}
|
||||
*/
|
||||
function topologicalOrderKernelExtEntries(entries) {
|
||||
const sorted = [...entries].sort((a, b) => a.file.localeCompare(b.file))
|
||||
@@ -1364,7 +1364,21 @@ function topologicalOrderKernelExtEntries(entries) {
|
||||
}
|
||||
if (out.length !== sorted.length) {
|
||||
const stuck = sorted.filter((e) => !out.includes(e.file))
|
||||
return { ok: false, cycleExtIds: stuck.map((e) => e.extId) }
|
||||
const stuckIds = new Set(stuck.map((e) => e.extId))
|
||||
/** @type {string[]} */
|
||||
const cycleEdges = []
|
||||
for (const e of stuck) {
|
||||
for (const dep of e.dependsOn) {
|
||||
if (stuckIds.has(dep))
|
||||
cycleEdges.push(String(e.extId) + ' -> dependsOn:' + String(dep))
|
||||
}
|
||||
}
|
||||
cycleEdges.sort()
|
||||
return {
|
||||
ok: false,
|
||||
cycleExtIds: stuck.map((e) => e.extId),
|
||||
cycleEdges: cycleEdges.slice(0, 48)
|
||||
}
|
||||
}
|
||||
const byFile = new Map(sorted.map((e) => [e.file, e]))
|
||||
return {
|
||||
@@ -1936,17 +1950,23 @@ async function runKernelExtDropins(ctx, opts = {}) {
|
||||
/** @type {typeof collected} */
|
||||
let ordered
|
||||
if (!topo.ok) {
|
||||
const edgeHint =
|
||||
topo.cycleEdges && topo.cycleEdges.length
|
||||
? '; cycle edges (stuck->dep): ' + topo.cycleEdges.join('; ')
|
||||
: ''
|
||||
bootStructuredLog(
|
||||
ctx,
|
||||
'error',
|
||||
'kernelExt.cycle',
|
||||
'[kernel.ext.d] dependency cycle in extension drop-ins; ext ids: ' +
|
||||
topo.cycleExtIds.join(', ')
|
||||
topo.cycleExtIds.join(', ') +
|
||||
edgeHint
|
||||
)
|
||||
if (strictPol) {
|
||||
await appendKernelExtAuditNdjson(ctx, {
|
||||
event: 'dependency_cycle',
|
||||
cycleExtIds: [...topo.cycleExtIds].sort()
|
||||
cycleExtIds: [...topo.cycleExtIds].sort(),
|
||||
cycleEdges: topo.cycleEdges || []
|
||||
})
|
||||
return false
|
||||
}
|
||||
@@ -1987,6 +2007,7 @@ async function runKernelExtDropins(ctx, opts = {}) {
|
||||
? {
|
||||
kind: 'dependency_cycle',
|
||||
cycleExtIds: cycleSorted,
|
||||
cycleEdges: topo.cycleEdges || [],
|
||||
provenance: 'kernel.ext.d topological sort (init-main)'
|
||||
}
|
||||
: null,
|
||||
@@ -3747,24 +3768,21 @@ async function runKernelSelftest(ctx) {
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Record<string, unknown>} ctx
|
||||
* @param {number} bootT0
|
||||
* @param {string[]} stageLog
|
||||
*/
|
||||
/**
|
||||
* @param {Record<string, unknown>} ctx
|
||||
* @param {number} bootT0
|
||||
* @param {{ kind: string, code: string, wallMs: number, limitMs: number }[]} violations
|
||||
* @param {boolean} coldExceeded
|
||||
* @param {boolean} stdlibExceeded
|
||||
* @param {string[]} [stageLog]
|
||||
*/
|
||||
async function maybeWriteBootBudgetSummaryJson(
|
||||
ctx,
|
||||
bootT0,
|
||||
violations,
|
||||
coldExceeded,
|
||||
stdlibExceeded
|
||||
stdlibExceeded,
|
||||
stageLog
|
||||
) {
|
||||
const vfs = ctx.vfs
|
||||
const b4 = ctx.b4a
|
||||
@@ -3772,15 +3790,21 @@ async function maybeWriteBootBudgetSummaryJson(
|
||||
const wall = Date.now() - bootT0
|
||||
const row =
|
||||
JSON.stringify({
|
||||
schema: 1,
|
||||
schema: 2,
|
||||
atMs: Date.now(),
|
||||
coldWallMs: wall,
|
||||
coldExceeded,
|
||||
bareStdlibExceeded: stdlibExceeded,
|
||||
violationCodes: violations.map((v) => v.code).filter(Boolean),
|
||||
violations,
|
||||
bootStageCount:
|
||||
Array.isArray(stageLog) && stageLog.length ? stageLog.length : null,
|
||||
bootStageTail:
|
||||
Array.isArray(stageLog) && stageLog.length
|
||||
? stageLog.slice(-16)
|
||||
: undefined,
|
||||
procHint: '/proc/bare_os/boot_budget_summary.json',
|
||||
note: 'Written every boot; operators mirror into proc via booter VFS provider.'
|
||||
note: 'Written every boot; operators mirror into proc via booter VFS provider. Schema 2 adds bootStageCount/bootStageTail from kernel stage log.'
|
||||
}) + '\n'
|
||||
try {
|
||||
await vfs.writeFile('/run/bare-os/boot-budget-summary.json', b4.from(row))
|
||||
@@ -4306,7 +4330,8 @@ async function start(ctx) {
|
||||
bootT0,
|
||||
bootBudgetViolationsForSummary,
|
||||
coldExceeded,
|
||||
stdlibExceeded
|
||||
stdlibExceeded,
|
||||
stageLog
|
||||
)
|
||||
if (budgetStrict && polStrict && (coldExceeded || stdlibExceeded)) {
|
||||
bootStructuredLog(
|
||||
|
||||
@@ -1,18 +1,6 @@
|
||||
{
|
||||
"version": 1,
|
||||
"bundles": [
|
||||
{
|
||||
"path": "/lib/bare/bundles/hypercoreIdEncoding.js",
|
||||
"keys": [
|
||||
"hypercoreIdEncoding"
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/lib/bare/bundles/safetyCatch.js",
|
||||
"keys": [
|
||||
"safetyCatch"
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/lib/bare/bundles/b4a.js",
|
||||
"keys": [
|
||||
@@ -25,6 +13,18 @@
|
||||
"compactEncoding"
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/lib/bare/bundles/safetyCatch.js",
|
||||
"keys": [
|
||||
"safetyCatch"
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/lib/bare/bundles/hypercoreIdEncoding.js",
|
||||
"keys": [
|
||||
"hypercoreIdEncoding"
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/lib/bare/bundles/protomux.js",
|
||||
"keys": [
|
||||
@@ -43,18 +43,18 @@
|
||||
"barePath"
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/lib/bare/bundles/bareEvents.js",
|
||||
"keys": [
|
||||
"bareEvents"
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/lib/bare/bundles/bareEncoding.js",
|
||||
"keys": [
|
||||
"bareEncoding"
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/lib/bare/bundles/bareEvents.js",
|
||||
"keys": [
|
||||
"bareEvents"
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/lib/bare/bundles/bareAbort.js",
|
||||
"keys": [
|
||||
@@ -91,30 +91,18 @@
|
||||
"bareCrypto"
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/lib/bare/bundles/bareAsyncHooks.js",
|
||||
"keys": [
|
||||
"bareAsyncHooks"
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/lib/bare/bundles/bareAtomics.js",
|
||||
"keys": [
|
||||
"bareAtomics"
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/lib/bare/bundles/bareApk.js",
|
||||
"keys": [
|
||||
"bareApk"
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/lib/bare/bundles/bareAppKit.js",
|
||||
"keys": [
|
||||
"bareAppKit"
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/lib/bare/bundles/bareAsyncHooks.js",
|
||||
"keys": [
|
||||
"bareAsyncHooks"
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/lib/bare/bundles/bareAssert.js",
|
||||
"keys": [
|
||||
@@ -122,9 +110,15 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/lib/bare/bundles/fetch.js",
|
||||
"path": "/lib/bare/bundles/bareApk.js",
|
||||
"keys": [
|
||||
"fetch"
|
||||
"bareApk"
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/lib/bare/bundles/bareAtomics.js",
|
||||
"keys": [
|
||||
"bareAtomics"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -134,15 +128,9 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/lib/bare/bundles/bareBundleCompile.js",
|
||||
"path": "/lib/bare/bundles/fetch.js",
|
||||
"keys": [
|
||||
"bareBundleCompile"
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/lib/bare/bundles/bareBundle.js",
|
||||
"keys": [
|
||||
"bareBundle"
|
||||
"fetch"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -152,9 +140,9 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/lib/bare/bundles/bareBundleEvaluate.js",
|
||||
"path": "/lib/bare/bundles/bareBundleCompile.js",
|
||||
"keys": [
|
||||
"bareBundleEvaluate"
|
||||
"bareBundleCompile"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -163,6 +151,24 @@
|
||||
"bareBluetoothApple"
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/lib/bare/bundles/bareBundle.js",
|
||||
"keys": [
|
||||
"bareBundle"
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/lib/bare/bundles/bareBundleEvaluate.js",
|
||||
"keys": [
|
||||
"bareBundleEvaluate"
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/lib/bare/bundles/bareConsole.js",
|
||||
"keys": [
|
||||
"bareConsole"
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/lib/bare/bundles/bareBoot.js",
|
||||
"keys": [
|
||||
@@ -175,12 +181,6 @@
|
||||
"bareBundleId"
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/lib/bare/bundles/bareConsole.js",
|
||||
"keys": [
|
||||
"bareConsole"
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/lib/bare/bundles/bareChannel.js",
|
||||
"keys": [
|
||||
@@ -193,12 +193,6 @@
|
||||
"bareDebugLog"
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/lib/bare/bundles/bareDaemon.js",
|
||||
"keys": [
|
||||
"bareDaemon"
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/lib/bare/bundles/bareDelta.js",
|
||||
"keys": [
|
||||
@@ -206,9 +200,9 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/lib/bare/bundles/bareDns.js",
|
||||
"path": "/lib/bare/bundles/bareDaemon.js",
|
||||
"keys": [
|
||||
"bareDns"
|
||||
"bareDaemon"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -217,6 +211,12 @@
|
||||
"bareDiagnosticsChannel"
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/lib/bare/bundles/bareDns.js",
|
||||
"keys": [
|
||||
"bareDns"
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/lib/bare/bundles/bareEnv.js",
|
||||
"keys": [
|
||||
@@ -229,18 +229,18 @@
|
||||
"bareExif"
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/lib/bare/bundles/bareDgram.js",
|
||||
"keys": [
|
||||
"bareDgram"
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/lib/bare/bundles/bareFfmpeg.js",
|
||||
"keys": [
|
||||
"bareFfmpeg"
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/lib/bare/bundles/bareDgram.js",
|
||||
"keys": [
|
||||
"bareDgram"
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/lib/bare/bundles/bareCov.js",
|
||||
"keys": [
|
||||
@@ -254,9 +254,9 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/lib/bare/bundles/bareFileLogger.js",
|
||||
"path": "/lib/bare/bundles/bareFormat.js",
|
||||
"keys": [
|
||||
"bareFileLogger"
|
||||
"bareFormat"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -266,33 +266,9 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/lib/bare/bundles/bareFormat.js",
|
||||
"path": "/lib/bare/bundles/bareFileLogger.js",
|
||||
"keys": [
|
||||
"bareFormat"
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/lib/bare/bundles/bareGif.js",
|
||||
"keys": [
|
||||
"bareGif"
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/lib/bare/bundles/bareHeif.js",
|
||||
"keys": [
|
||||
"bareHeif"
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/lib/bare/bundles/bareGtk.js",
|
||||
"keys": [
|
||||
"bareGtk"
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/lib/bare/bundles/bareHrtime.js",
|
||||
"keys": [
|
||||
"bareHrtime"
|
||||
"bareFileLogger"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -302,15 +278,33 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/lib/bare/bundles/bareHttpParser.js",
|
||||
"path": "/lib/bare/bundles/bareGif.js",
|
||||
"keys": [
|
||||
"bareHttpParser"
|
||||
"bareGif"
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/lib/bare/bundles/bareIco.js",
|
||||
"path": "/lib/bare/bundles/bareHrtime.js",
|
||||
"keys": [
|
||||
"bareIco"
|
||||
"bareHrtime"
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/lib/bare/bundles/bareGtk.js",
|
||||
"keys": [
|
||||
"bareGtk"
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/lib/bare/bundles/bareHeif.js",
|
||||
"keys": [
|
||||
"bareHeif"
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/lib/bare/bundles/bareHttpParser.js",
|
||||
"keys": [
|
||||
"bareHttpParser"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -320,9 +314,9 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/lib/bare/bundles/bareInspect.js",
|
||||
"path": "/lib/bare/bundles/bareIco.js",
|
||||
"keys": [
|
||||
"bareInspect"
|
||||
"bareIco"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -331,6 +325,12 @@
|
||||
"bareHttp1"
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/lib/bare/bundles/bareInspect.js",
|
||||
"keys": [
|
||||
"bareInspect"
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/lib/bare/bundles/bareHttps.js",
|
||||
"keys": [
|
||||
@@ -355,12 +355,6 @@
|
||||
"bareIntl"
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/lib/bare/bundles/bareInspector.js",
|
||||
"keys": [
|
||||
"bareInspector"
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/lib/bare/bundles/bareLief.js",
|
||||
"keys": [
|
||||
@@ -374,15 +368,15 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/lib/bare/bundles/bareLink.js",
|
||||
"path": "/lib/bare/bundles/bareInspector.js",
|
||||
"keys": [
|
||||
"bareLink"
|
||||
"bareInspector"
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/lib/bare/bundles/bareModuleLexer.js",
|
||||
"path": "/lib/bare/bundles/bareLink.js",
|
||||
"keys": [
|
||||
"bareModuleLexer"
|
||||
"bareLink"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -391,6 +385,12 @@
|
||||
"bareMake"
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/lib/bare/bundles/bareModule.js",
|
||||
"keys": [
|
||||
"bareModule"
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/lib/bare/bundles/bareModuleResolve.js",
|
||||
"keys": [
|
||||
@@ -398,9 +398,9 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/lib/bare/bundles/bareModule.js",
|
||||
"path": "/lib/bare/bundles/bareModuleLexer.js",
|
||||
"keys": [
|
||||
"bareModule"
|
||||
"bareModuleLexer"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -415,18 +415,6 @@
|
||||
"bareModuleTraverse"
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/lib/bare/bundles/bareMedia.js",
|
||||
"keys": [
|
||||
"bareMedia"
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/lib/bare/bundles/bareNodeFetch.js",
|
||||
"keys": [
|
||||
"bareNodeFetch"
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/lib/bare/bundles/bareNative.js",
|
||||
"keys": [
|
||||
@@ -434,15 +422,9 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/lib/bare/bundles/bareOpen.js",
|
||||
"path": "/lib/bare/bundles/bareMedia.js",
|
||||
"keys": [
|
||||
"bareOpen"
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/lib/bare/bundles/bareOs.js",
|
||||
"keys": [
|
||||
"bareOs"
|
||||
"bareMedia"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -452,9 +434,21 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/lib/bare/bundles/barePerformance.js",
|
||||
"path": "/lib/bare/bundles/bareNodeFetch.js",
|
||||
"keys": [
|
||||
"barePerformance"
|
||||
"bareNodeFetch"
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/lib/bare/bundles/bareOs.js",
|
||||
"keys": [
|
||||
"bareOs"
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/lib/bare/bundles/bareOpen.js",
|
||||
"keys": [
|
||||
"bareOpen"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -470,9 +464,9 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/lib/bare/bundles/barePipe.js",
|
||||
"path": "/lib/bare/bundles/barePerformance.js",
|
||||
"keys": [
|
||||
"barePipe"
|
||||
"barePerformance"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -482,15 +476,9 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/lib/bare/bundles/bareNodeRuntime.js",
|
||||
"path": "/lib/bare/bundles/barePipe.js",
|
||||
"keys": [
|
||||
"bareNodeRuntime"
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/lib/bare/bundles/barePunycode.js",
|
||||
"keys": [
|
||||
"barePunycode"
|
||||
"barePipe"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -500,15 +488,9 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/lib/bare/bundles/bareDev.js",
|
||||
"path": "/lib/bare/bundles/bareNodeRuntime.js",
|
||||
"keys": [
|
||||
"bareDev"
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/lib/bare/bundles/bareQuerystring.js",
|
||||
"keys": [
|
||||
"bareQuerystring"
|
||||
"bareNodeRuntime"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -518,9 +500,21 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/lib/bare/bundles/bareQueueMicrotask.js",
|
||||
"path": "/lib/bare/bundles/bareQuerystring.js",
|
||||
"keys": [
|
||||
"bareQueueMicrotask"
|
||||
"bareQuerystring"
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/lib/bare/bundles/bareDev.js",
|
||||
"keys": [
|
||||
"bareDev"
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/lib/bare/bundles/barePunycode.js",
|
||||
"keys": [
|
||||
"barePunycode"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -529,6 +523,12 @@
|
||||
"bareRealm"
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/lib/bare/bundles/bareQueueMicrotask.js",
|
||||
"keys": [
|
||||
"bareQueueMicrotask"
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/lib/bare/bundles/bareRuntime.js",
|
||||
"keys": [
|
||||
@@ -553,6 +553,12 @@
|
||||
"bareRepl"
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/lib/bare/bundles/bareSdl.js",
|
||||
"keys": [
|
||||
"bareSdl"
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/lib/bare/bundles/bareSemver.js",
|
||||
"keys": [
|
||||
@@ -565,30 +571,12 @@
|
||||
"bareRun"
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/lib/bare/bundles/bareSdl.js",
|
||||
"keys": [
|
||||
"bareSdl"
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/lib/bare/bundles/bareSignals.js",
|
||||
"keys": [
|
||||
"bareSignals"
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/lib/bare/bundles/bareSidecar.js",
|
||||
"keys": [
|
||||
"bareSidecar"
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/lib/bare/bundles/bareStdio.js",
|
||||
"keys": [
|
||||
"bareStdio"
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/lib/bare/bundles/bareStringDecoder.js",
|
||||
"keys": [
|
||||
@@ -596,9 +584,9 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/lib/bare/bundles/bareStorage.js",
|
||||
"path": "/lib/bare/bundles/bareSidecar.js",
|
||||
"keys": [
|
||||
"bareStorage"
|
||||
"bareSidecar"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -608,9 +596,15 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/lib/bare/bundles/bareSvg.js",
|
||||
"path": "/lib/bare/bundles/bareStorage.js",
|
||||
"keys": [
|
||||
"bareSvg"
|
||||
"bareStorage"
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/lib/bare/bundles/bareStdio.js",
|
||||
"keys": [
|
||||
"bareStdio"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -620,21 +614,9 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/lib/bare/bundles/bareSubprocess.js",
|
||||
"path": "/lib/bare/bundles/bareSvg.js",
|
||||
"keys": [
|
||||
"bareSubprocess"
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/lib/bare/bundles/bareSystemLogger.js",
|
||||
"keys": [
|
||||
"bareSystemLogger"
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/lib/bare/bundles/bareTiff.js",
|
||||
"keys": [
|
||||
"bareTiff"
|
||||
"bareSvg"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -644,9 +626,21 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/lib/bare/bundles/bareTcp.js",
|
||||
"path": "/lib/bare/bundles/bareSystemLogger.js",
|
||||
"keys": [
|
||||
"bareTcp"
|
||||
"bareSystemLogger"
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/lib/bare/bundles/bareSubprocess.js",
|
||||
"keys": [
|
||||
"bareSubprocess"
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/lib/bare/bundles/bareTiff.js",
|
||||
"keys": [
|
||||
"bareTiff"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -667,6 +661,12 @@
|
||||
"bareTpl"
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/lib/bare/bundles/bareTcp.js",
|
||||
"keys": [
|
||||
"bareTcp"
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/lib/bare/bundles/bareType.js",
|
||||
"keys": [
|
||||
@@ -686,9 +686,9 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/lib/bare/bundles/bareV8.js",
|
||||
"path": "/lib/bare/bundles/bareTty.js",
|
||||
"keys": [
|
||||
"bareV8"
|
||||
"bareTty"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -698,15 +698,9 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/lib/bare/bundles/bareTty.js",
|
||||
"path": "/lib/bare/bundles/bareV8.js",
|
||||
"keys": [
|
||||
"bareTty"
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/lib/bare/bundles/bareWalkHandles.js",
|
||||
"keys": [
|
||||
"bareWalkHandles"
|
||||
"bareV8"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -715,6 +709,12 @@
|
||||
"bareVm"
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/lib/bare/bundles/bareWalkHandles.js",
|
||||
"keys": [
|
||||
"bareWalkHandles"
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/lib/bare/bundles/bareUnionBundle.js",
|
||||
"keys": [
|
||||
@@ -734,9 +734,9 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/lib/bare/bundles/bareUtils.js",
|
||||
"path": "/lib/bare/bundles/bareV8ToIstanbul.js",
|
||||
"keys": [
|
||||
"bareUtils"
|
||||
"bareV8ToIstanbul"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -745,24 +745,18 @@
|
||||
"bareWebKitGtk"
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/lib/bare/bundles/bareUtils.js",
|
||||
"keys": [
|
||||
"bareUtils"
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/lib/bare/bundles/bareWhich.js",
|
||||
"keys": [
|
||||
"bareWhich"
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/lib/bare/bundles/bareV8ToIstanbul.js",
|
||||
"keys": [
|
||||
"bareV8ToIstanbul"
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/lib/bare/bundles/bareXdiff.js",
|
||||
"keys": [
|
||||
"bareXdiff"
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/lib/bare/bundles/bareWinUi.js",
|
||||
"keys": [
|
||||
@@ -770,9 +764,15 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/lib/bare/bundles/bareWs.js",
|
||||
"path": "/lib/bare/bundles/bareXdiff.js",
|
||||
"keys": [
|
||||
"bareWs"
|
||||
"bareXdiff"
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/lib/bare/bundles/bareZmq.js",
|
||||
"keys": [
|
||||
"bareZmq"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -782,9 +782,9 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/lib/bare/bundles/bareZmq.js",
|
||||
"path": "/lib/bare/bundles/bareWs.js",
|
||||
"keys": [
|
||||
"bareZmq"
|
||||
"bareWs"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -1595,8 +1595,8 @@
|
||||
],
|
||||
"bundleProvenance": {
|
||||
"schemaVersion": 1,
|
||||
"generatedAt": "2026-04-05T21:35:39.414Z",
|
||||
"gitCommit": "f3c8ef1fc50e9c895629cfae96cdcfd644903132",
|
||||
"generatedAt": "2026-04-06T03:01:17.869Z",
|
||||
"gitCommit": "8f61d0bea8efc674a1063df176d7383a09f85b46",
|
||||
"nodeVersion": "v22.22.0",
|
||||
"bundleTier": "all",
|
||||
"normativeManifest": "packages/bare-os-booter/lib/bare-module-manifest.json",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"schema": 1,
|
||||
"atMs": 1775439838480,
|
||||
"atMs": 1775444477009,
|
||||
"commands": [
|
||||
"arch",
|
||||
"awk",
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
/**
|
||||
* @param {{ file: string, extId: string, scripts: string[], dependsOn: string[], signaturePointer?: string }[]} entries
|
||||
* @returns {{ ok: true, ordered: typeof entries } | { ok: false, cycleExtIds: string[] }}
|
||||
* @returns {{ ok: true, ordered: typeof entries } | { ok: false, cycleExtIds: string[], cycleEdges: string[] }}
|
||||
*/
|
||||
function topologicalOrderKernelExtEntries(entries) {
|
||||
const sorted = [...entries].sort((a, b) => a.file.localeCompare(b.file))
|
||||
@@ -50,7 +50,21 @@ function topologicalOrderKernelExtEntries(entries) {
|
||||
}
|
||||
if (out.length !== sorted.length) {
|
||||
const stuck = sorted.filter((e) => !out.includes(e.file))
|
||||
return { ok: false, cycleExtIds: stuck.map((e) => e.extId) }
|
||||
const stuckIds = new Set(stuck.map((e) => e.extId))
|
||||
/** @type {string[]} */
|
||||
const cycleEdges = []
|
||||
for (const e of stuck) {
|
||||
for (const dep of e.dependsOn) {
|
||||
if (stuckIds.has(dep))
|
||||
cycleEdges.push(String(e.extId) + ' -> dependsOn:' + String(dep))
|
||||
}
|
||||
}
|
||||
cycleEdges.sort()
|
||||
return {
|
||||
ok: false,
|
||||
cycleExtIds: stuck.map((e) => e.extId),
|
||||
cycleEdges: cycleEdges.slice(0, 48)
|
||||
}
|
||||
}
|
||||
const byFile = new Map(sorted.map((e) => [e.file, e]))
|
||||
return {
|
||||
@@ -622,17 +636,23 @@ async function runKernelExtDropins(ctx, opts = {}) {
|
||||
/** @type {typeof collected} */
|
||||
let ordered
|
||||
if (!topo.ok) {
|
||||
const edgeHint =
|
||||
topo.cycleEdges && topo.cycleEdges.length
|
||||
? '; cycle edges (stuck->dep): ' + topo.cycleEdges.join('; ')
|
||||
: ''
|
||||
bootStructuredLog(
|
||||
ctx,
|
||||
'error',
|
||||
'kernelExt.cycle',
|
||||
'[kernel.ext.d] dependency cycle in extension drop-ins; ext ids: ' +
|
||||
topo.cycleExtIds.join(', ')
|
||||
topo.cycleExtIds.join(', ') +
|
||||
edgeHint
|
||||
)
|
||||
if (strictPol) {
|
||||
await appendKernelExtAuditNdjson(ctx, {
|
||||
event: 'dependency_cycle',
|
||||
cycleExtIds: [...topo.cycleExtIds].sort()
|
||||
cycleExtIds: [...topo.cycleExtIds].sort(),
|
||||
cycleEdges: topo.cycleEdges || []
|
||||
})
|
||||
return false
|
||||
}
|
||||
@@ -673,6 +693,7 @@ async function runKernelExtDropins(ctx, opts = {}) {
|
||||
? {
|
||||
kind: 'dependency_cycle',
|
||||
cycleExtIds: cycleSorted,
|
||||
cycleEdges: topo.cycleEdges || [],
|
||||
provenance: 'kernel.ext.d topological sort (init-main)'
|
||||
}
|
||||
: null,
|
||||
|
||||
@@ -1622,24 +1622,21 @@ async function runKernelSelftest(ctx) {
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Record<string, unknown>} ctx
|
||||
* @param {number} bootT0
|
||||
* @param {string[]} stageLog
|
||||
*/
|
||||
/**
|
||||
* @param {Record<string, unknown>} ctx
|
||||
* @param {number} bootT0
|
||||
* @param {{ kind: string, code: string, wallMs: number, limitMs: number }[]} violations
|
||||
* @param {boolean} coldExceeded
|
||||
* @param {boolean} stdlibExceeded
|
||||
* @param {string[]} [stageLog]
|
||||
*/
|
||||
async function maybeWriteBootBudgetSummaryJson(
|
||||
ctx,
|
||||
bootT0,
|
||||
violations,
|
||||
coldExceeded,
|
||||
stdlibExceeded
|
||||
stdlibExceeded,
|
||||
stageLog
|
||||
) {
|
||||
const vfs = ctx.vfs
|
||||
const b4 = ctx.b4a
|
||||
@@ -1647,15 +1644,21 @@ async function maybeWriteBootBudgetSummaryJson(
|
||||
const wall = Date.now() - bootT0
|
||||
const row =
|
||||
JSON.stringify({
|
||||
schema: 1,
|
||||
schema: 2,
|
||||
atMs: Date.now(),
|
||||
coldWallMs: wall,
|
||||
coldExceeded,
|
||||
bareStdlibExceeded: stdlibExceeded,
|
||||
violationCodes: violations.map((v) => v.code).filter(Boolean),
|
||||
violations,
|
||||
bootStageCount:
|
||||
Array.isArray(stageLog) && stageLog.length ? stageLog.length : null,
|
||||
bootStageTail:
|
||||
Array.isArray(stageLog) && stageLog.length
|
||||
? stageLog.slice(-16)
|
||||
: undefined,
|
||||
procHint: '/proc/bare_os/boot_budget_summary.json',
|
||||
note: 'Written every boot; operators mirror into proc via booter VFS provider.'
|
||||
note: 'Written every boot; operators mirror into proc via booter VFS provider. Schema 2 adds bootStageCount/bootStageTail from kernel stage log.'
|
||||
}) + '\n'
|
||||
try {
|
||||
await vfs.writeFile('/run/bare-os/boot-budget-summary.json', b4.from(row))
|
||||
@@ -2181,7 +2184,8 @@ async function start(ctx) {
|
||||
bootT0,
|
||||
bootBudgetViolationsForSummary,
|
||||
coldExceeded,
|
||||
stdlibExceeded
|
||||
stdlibExceeded,
|
||||
stageLog
|
||||
)
|
||||
if (budgetStrict && polStrict && (coldExceeded || stdlibExceeded)) {
|
||||
bootStructuredLog(
|
||||
|
||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user