Pass 8
This commit is contained in:
@@ -167,6 +167,8 @@ import { createBooterProcHelpers } from './lib/bare-os-booter-proc-helpers.js'
|
||||
import { createBareOsVirtualSignalDeliverer } from './lib/bare-os-virtual-signal.js'
|
||||
import { createKernelLoaderAuditAppend } from './lib/bare-os-loader-audit.js'
|
||||
import { createBareOsPosixFdSimMethods } from './lib/bare-os-posix-fd-sim.js'
|
||||
import { createBareOsPathconfMethods } from './lib/bare-os-pathconf.js'
|
||||
import { createBareOsLogicalFdMethods } from './lib/bare-os-logical-fd.js'
|
||||
import { createBareOsQvacBridge } from './lib/bare-os-qvac-host.mjs'
|
||||
import { bareOsQvacEnsureModelsHdms } from './lib/bare-os-qvac-models-store.mjs'
|
||||
import { buildPearIpcRegistryJson } from './lib/bare-os-pear-ipc-registry.js'
|
||||
@@ -4462,224 +4464,18 @@ async function executeKernel(disk, store, swarm, initSource) {
|
||||
errorCode: 'host_subprocess_unavailable'
|
||||
}
|
||||
},
|
||||
/**
|
||||
* @param {number} fd
|
||||
* @param {string} target
|
||||
*/
|
||||
bareOsRegisterLogicalFd(fd, target) {
|
||||
const n = Number(fd)
|
||||
if (!Number.isFinite(n) || n < 0 || n > 65535) return
|
||||
const k = String(n >>> 0)
|
||||
if (k === '0' || k === '1' || k === '2') return
|
||||
this.bareOsLogicalFds[k] = String(target || '')
|
||||
},
|
||||
/** @param {number} fd */
|
||||
bareOsUnregisterLogicalFd(fd) {
|
||||
const k = String(Number(fd) >>> 0)
|
||||
if (this.bareOsLogicalFds && typeof this.bareOsLogicalFds === 'object') {
|
||||
delete this.bareOsLogicalFds[k]
|
||||
}
|
||||
if (
|
||||
this.bareOsLogicalFdFlags &&
|
||||
typeof this.bareOsLogicalFdFlags === 'object'
|
||||
) {
|
||||
delete this.bareOsLogicalFdFlags[k]
|
||||
}
|
||||
},
|
||||
...createBareOsLogicalFdMethods(),
|
||||
...createBareOsPosixFdSimMethods({
|
||||
env: shellEnv,
|
||||
bootHrtimeNowNs
|
||||
}),
|
||||
/**
|
||||
* Logical `sigaction`: `IGNORE` suppresses synthetic delivery for this signal.
|
||||
* @param {string} signal
|
||||
* @param {'IGNORE' | 'DEFAULT' | 'IGN' | 'DEF'} mode
|
||||
*/
|
||||
bareOsSigaction(signal, mode) {
|
||||
const s = bareOsNormalizeSignalName(signal)
|
||||
if (!bareOsIsPosixSignalName(signal) || s === '0') {
|
||||
throw new Error('bareOsSigaction: unsupported signal')
|
||||
}
|
||||
const m = String(mode || 'DEFAULT')
|
||||
.toUpperCase()
|
||||
.replace(/^SIG/i, '')
|
||||
if (m === 'DEFAULT' || m === 'DEF') {
|
||||
if (this.bareOsLogicalSigaction) delete this.bareOsLogicalSigaction[s]
|
||||
return { ok: true, signal: s, mode: 'DEFAULT' }
|
||||
}
|
||||
if (m === 'IGNORE' || m === 'IGN') {
|
||||
if (!this.bareOsLogicalSigaction) {
|
||||
throw new Error('bareOsSigaction: internal state missing')
|
||||
}
|
||||
this.bareOsLogicalSigaction[s] = 'IGNORE'
|
||||
return { ok: true, signal: s, mode: 'IGNORE' }
|
||||
}
|
||||
throw new Error('bareOsSigaction: mode must be IGNORE or DEFAULT')
|
||||
},
|
||||
/** @type {Record<string, unknown>[]} */
|
||||
bareOsSnapshotHandles: [],
|
||||
/** @param {Record<string, unknown>} desc */
|
||||
bareOsRegisterSnapshotHandle(desc) {
|
||||
if (!desc || typeof desc !== 'object') return
|
||||
if (!Array.isArray(this.bareOsSnapshotHandles)) {
|
||||
this.bareOsSnapshotHandles = []
|
||||
}
|
||||
const id =
|
||||
typeof desc.id === 'string' && desc.id.trim()
|
||||
? desc.id.trim().slice(0, 128)
|
||||
: `snap-${this.bareOsSnapshotHandles.length}`
|
||||
this.bareOsSnapshotHandles.push({ ...desc, id, atMs: Date.now() })
|
||||
},
|
||||
/**
|
||||
* Fixed pathconf/nameconf-style limits (no live host query).
|
||||
* @param {string} [_path]
|
||||
* @param {string} name
|
||||
*/
|
||||
bareOsPathconf(pathArg, name) {
|
||||
const k = String(name || '').trim()
|
||||
const p = vfs.resolveLogical(String(pathArg || '/').trim() || '/')
|
||||
const rawUnion = String(shellEnv.BARE_OS_VFS_UNION_PREFIXES || '')
|
||||
const underUnion = rawUnion
|
||||
.split(',')
|
||||
.map((s) => s.trim())
|
||||
.filter((x) => x.startsWith('/'))
|
||||
.some((pre) => p === pre || p.startsWith(pre + '/'))
|
||||
const underMirror = p === '/mirror' || p.startsWith('/mirror/')
|
||||
const acctOn =
|
||||
shellEnv.BARE_OS_PERSONAL_ACCT_PREFIX === '1' ||
|
||||
shellEnv.BARE_OS_PERSONAL_ACCT_PREFIX === 'true'
|
||||
const underPersonalAcct =
|
||||
acctOn && (p === '/.bare-os/acct' || p.startsWith('/.bare-os/acct/'))
|
||||
const underDevShm = p === '/dev/shm' || p.startsWith('/dev/shm/')
|
||||
/** @type {Record<string, number>} */
|
||||
const table = {
|
||||
PATH_MAX: 4096,
|
||||
NAME_MAX: 255,
|
||||
_PC_PATH_MAX: 4096,
|
||||
_PC_NAME_MAX: 255,
|
||||
_PC_SYMLINK_MAX: 1024,
|
||||
_PC_CHOWN_RESTRICTED: 1,
|
||||
_PC_NO_TRUNC: 1,
|
||||
_PC_FILESIZEBITS: 64,
|
||||
_PC_LINK_MAX: 1,
|
||||
_PC_MAX_CANON: 255,
|
||||
_PC_MAX_INPUT: 512,
|
||||
_PC_PIPE_BUF: 4096,
|
||||
_PC_2_SYMLINKS: 1,
|
||||
_POSIX_VERSION: 200809,
|
||||
_POSIX_THREAD_ATTR_STACKSIZE: 65536
|
||||
}
|
||||
if (k === '_PC_CHOWN_RESTRICTED') {
|
||||
if (underMirror) return 0
|
||||
return 1
|
||||
}
|
||||
if (
|
||||
k === '_PC_NO_TRUNC' &&
|
||||
(underMirror || (underUnion && !underPersonalAcct))
|
||||
) {
|
||||
return 0
|
||||
}
|
||||
if (underDevShm && k === '_PC_NAME_MAX') {
|
||||
return 128
|
||||
}
|
||||
if (!k || table[k] === undefined) {
|
||||
throw new Error('bareOsPathconf: unknown name: ' + k)
|
||||
}
|
||||
return table[k]
|
||||
},
|
||||
/**
|
||||
* Dynamic **`sysconf`**-style values for **`getconf _SC_*`** names (not path-specific).
|
||||
* @param {string} name
|
||||
* @returns {string | number | null}
|
||||
*/
|
||||
bareOsGetconfSysconf(name) {
|
||||
const n = String(name || '').trim()
|
||||
if (n === '_SC_NPROCESSORS_ONLN') {
|
||||
const raw = String(shellEnv.BARE_OS_NPROC || '').trim()
|
||||
const p = parseInt(raw, 10)
|
||||
if (Number.isFinite(p) && p > 0) return String(Math.min(4096, p))
|
||||
return '4'
|
||||
}
|
||||
if (n === '_SC_PAGESIZE' || n === '_SC_PAGE_SIZE') return '4096'
|
||||
if (n === '_SC_PHYS_PAGES') {
|
||||
const raw = String(shellEnv.BARE_OS_PHYS_PAGES_HINT || '').trim()
|
||||
const p = parseInt(raw, 10)
|
||||
if (Number.isFinite(p) && p > 0) return String(p)
|
||||
return '524288'
|
||||
}
|
||||
if (n === '_SC_AVPHYS_PAGES') {
|
||||
const raw = String(shellEnv.BARE_OS_AVPHYS_PAGES_HINT || '').trim()
|
||||
const p = parseInt(raw, 10)
|
||||
if (Number.isFinite(p) && p > 0) return String(p)
|
||||
return '262144'
|
||||
}
|
||||
if (n === '_SC_OPEN_MAX') return '256'
|
||||
if (n === '_SC_STREAM_MAX') return '256'
|
||||
if (n === '_SC_CHILD_MAX') return '0'
|
||||
if (n === '_SC_CLK_TCK') return '100'
|
||||
if (n === '_SC_ARG_MAX') return '262144'
|
||||
if (n === '_SC_NPROCESSORS_CONF') {
|
||||
const raw = String(shellEnv.BARE_OS_NPROC || '').trim()
|
||||
const p = parseInt(raw, 10)
|
||||
if (Number.isFinite(p) && p > 0) return String(Math.min(4096, p))
|
||||
return '4'
|
||||
}
|
||||
if (n === '_SC_HOST_NAME_MAX' || n === '_POSIX_HOST_NAME_MAX')
|
||||
return '255'
|
||||
if (n === '_SC_SYMLOOP_MAX') return '32'
|
||||
if (n === '_SC_IOV_MAX' || n === '_SC_UIO_MAXIOV') return '1024'
|
||||
if (n === '_SC_THREAD_DESTRUCTOR_ITERATIONS') return '4'
|
||||
if (n === '_SC_TTY_NAME_MAX') return '32'
|
||||
if (n === '_SC_LOGIN_NAME_MAX') return '256'
|
||||
if (n === '_SC_MONOTONIC_CLOCK') return '1'
|
||||
if (n === '_SC_MONOTONIC_CLOCK_RES') {
|
||||
return bootHrtimeNowNs ? '1' : '0'
|
||||
}
|
||||
if (n === '_SC_TRACE' || n === '_SC_TRACE_VERSION') return '-1'
|
||||
if (n === '_SC_RAW_SOCKETS') return '-1'
|
||||
if (n === '_SC_ADVISORY_INFO') return '0'
|
||||
if (n === '_SC_BARRIERS') return '-1'
|
||||
if (n === '_SC_SPIN_LOCKS') return '1'
|
||||
if (n === '_SC_TIMER_MAX') return '32'
|
||||
if (n === '_SC_DELAYTIMER_MAX') return '0'
|
||||
if (n === '_SC_ATEXIT_MAX') return '32'
|
||||
if (n === '_SC_LINE_MAX') return '2048'
|
||||
if (n === '_SC_BC_BASE_MAX') return '99'
|
||||
if (n === '_SC_BC_DIM_MAX') return '2048'
|
||||
if (n === '_SC_BC_SCALE_MAX') return '99'
|
||||
const pl = getBareOsPipelineLimits(shellEnv)
|
||||
if (n === '_SC_BARE_OS_PIPELINE_MAX_BYTES') return String(pl.maxBytes)
|
||||
if (n === '_SC_BARE_OS_PIPELINE_MAX_LINES') return String(pl.maxLines)
|
||||
if (n === '_SC_BARE_OS_PIPELINE_MAX_STAGES') return String(pl.maxStages)
|
||||
const parseCap = (key, def, cap) => {
|
||||
const raw = String(shellEnv[key] ?? '').trim()
|
||||
if (!raw) return String(def)
|
||||
const p = parseInt(raw, 10)
|
||||
if (!Number.isFinite(p) || p < 0) return String(def)
|
||||
return String(cap != null ? Math.min(cap, p) : p)
|
||||
}
|
||||
if (n === '_SC_BARE_OS_GLOB_MAX_MATCHES')
|
||||
return parseCap('BARE_OS_GLOB_MAX_MATCHES', 4096, null)
|
||||
if (n === '_SC_BARE_OS_SHELL_LOOP_MAX')
|
||||
return parseCap('BARE_OS_SHELL_LOOP_MAX', 10000, null)
|
||||
if (n === '_SC_BARE_OS_EXEC_MAX_DEPTH')
|
||||
return parseCap('BARE_OS_EXEC_MAX_DEPTH', 64, null)
|
||||
if (n === '_SC_BARE_OS_XARGS_MAX_PROCS')
|
||||
return parseCap('BARE_OS_XARGS_MAX_PROCS', 8, 32)
|
||||
if (n === '_SC_BARE_OS_SWARM_MAX_PEERS')
|
||||
return parseCap('BARE_OS_SWARM_MAX_PEERS', 512, null)
|
||||
if (n === '_SC_BARE_OS_SWARM_MAX_CLIENT_CONNECTIONS')
|
||||
return parseCap('BARE_OS_SWARM_MAX_CLIENT_CONNECTIONS', 256, null)
|
||||
if (n === '_SC_BARE_OS_SWARM_MAX_SERVER_CONNECTIONS')
|
||||
return parseCap('BARE_OS_SWARM_MAX_SERVER_CONNECTIONS', 256, null)
|
||||
if (n === '_SC_BARE_OS_SWARM_MAX_PARALLEL')
|
||||
return parseCap('BARE_OS_SWARM_MAX_PARALLEL', 64, null)
|
||||
if (n === '_SC_BARE_OS_DGRAM_RECVQ_MAX')
|
||||
return parseCap('BARE_OS_POSIX_DGRAM_RECVQ_MAX', 256, null)
|
||||
if (n === '_SC_BARE_OS_ACCEPT_QUEUE_MAX')
|
||||
return parseCap('BARE_OS_POSIX_ACCEPT_QUEUE_MAX', 64, null)
|
||||
return null
|
||||
},
|
||||
...createBareOsPathconfMethods({
|
||||
env: shellEnv,
|
||||
resolveLogical: (p) => vfs.resolveLogical(p),
|
||||
bootHrtimeNowNs
|
||||
}),
|
||||
bareOsSyscall: runBareOsSyscall,
|
||||
/**
|
||||
* Peer admission against optional **`BARE_OS_PEER_DENYLIST_HEX`**,
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
/**
|
||||
* Logical FD registry, sigaction, and snapshot-handle methods on kernel `ctx`.
|
||||
*/
|
||||
import {
|
||||
bareOsIsPosixSignalName,
|
||||
bareOsNormalizeSignalName
|
||||
} from './bare-os-posix-signals.js'
|
||||
|
||||
export function createBareOsLogicalFdMethods() {
|
||||
return {
|
||||
/**
|
||||
* @param {number} fd
|
||||
* @param {string} target
|
||||
*/
|
||||
bareOsRegisterLogicalFd(fd, target) {
|
||||
const n = Number(fd)
|
||||
if (!Number.isFinite(n) || n < 0 || n > 65535) return
|
||||
const k = String(n >>> 0)
|
||||
if (k === '0' || k === '1' || k === '2') return
|
||||
this.bareOsLogicalFds[k] = String(target || '')
|
||||
},
|
||||
/** @param {number} fd */
|
||||
bareOsUnregisterLogicalFd(fd) {
|
||||
const k = String(Number(fd) >>> 0)
|
||||
if (this.bareOsLogicalFds && typeof this.bareOsLogicalFds === 'object') {
|
||||
delete this.bareOsLogicalFds[k]
|
||||
}
|
||||
if (
|
||||
this.bareOsLogicalFdFlags &&
|
||||
typeof this.bareOsLogicalFdFlags === 'object'
|
||||
) {
|
||||
delete this.bareOsLogicalFdFlags[k]
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Logical `sigaction`: `IGNORE` suppresses synthetic delivery for this signal.
|
||||
* @param {string} signal
|
||||
* @param {'IGNORE' | 'DEFAULT' | 'IGN' | 'DEF'} mode
|
||||
*/
|
||||
bareOsSigaction(signal, mode) {
|
||||
const s = bareOsNormalizeSignalName(signal)
|
||||
if (!bareOsIsPosixSignalName(signal) || s === '0') {
|
||||
throw new Error('bareOsSigaction: unsupported signal')
|
||||
}
|
||||
const m = String(mode || 'DEFAULT')
|
||||
.toUpperCase()
|
||||
.replace(/^SIG/i, '')
|
||||
if (m === 'DEFAULT' || m === 'DEF') {
|
||||
if (this.bareOsLogicalSigaction) delete this.bareOsLogicalSigaction[s]
|
||||
return { ok: true, signal: s, mode: 'DEFAULT' }
|
||||
}
|
||||
if (m === 'IGNORE' || m === 'IGN') {
|
||||
if (!this.bareOsLogicalSigaction) {
|
||||
throw new Error('bareOsSigaction: internal state missing')
|
||||
}
|
||||
this.bareOsLogicalSigaction[s] = 'IGNORE'
|
||||
return { ok: true, signal: s, mode: 'IGNORE' }
|
||||
}
|
||||
throw new Error('bareOsSigaction: mode must be IGNORE or DEFAULT')
|
||||
},
|
||||
/** @param {Record<string, unknown>} desc */
|
||||
bareOsRegisterSnapshotHandle(desc) {
|
||||
if (!desc || typeof desc !== 'object') return
|
||||
if (!Array.isArray(this.bareOsSnapshotHandles)) {
|
||||
this.bareOsSnapshotHandles = []
|
||||
}
|
||||
const id =
|
||||
typeof desc.id === 'string' && desc.id.trim()
|
||||
? desc.id.trim().slice(0, 128)
|
||||
: `snap-${this.bareOsSnapshotHandles.length}`
|
||||
this.bareOsSnapshotHandles.push({ ...desc, id, atMs: Date.now() })
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,168 @@
|
||||
/**
|
||||
* ctx.bareOsPathconf / ctx.bareOsGetconfSysconf implementations.
|
||||
*/
|
||||
import { getBareOsPipelineLimits } from './shell-runtime.js'
|
||||
|
||||
/**
|
||||
* @param {{
|
||||
* env: Record<string, string | undefined>,
|
||||
* resolveLogical: (pathArg: string) => string,
|
||||
* bootHrtimeNowNs?: (() => bigint) | null
|
||||
* }} deps
|
||||
*/
|
||||
export function createBareOsPathconfMethods(deps) {
|
||||
const { env, resolveLogical, bootHrtimeNowNs = null } = deps
|
||||
|
||||
return {
|
||||
/**
|
||||
* Fixed pathconf/nameconf-style limits (no live host query).
|
||||
* @param {string} [_path]
|
||||
* @param {string} name
|
||||
*/
|
||||
bareOsPathconf(pathArg, name) {
|
||||
const k = String(name || '').trim()
|
||||
const p = resolveLogical(String(pathArg || '/').trim() || '/')
|
||||
const rawUnion = String(env.BARE_OS_VFS_UNION_PREFIXES || '')
|
||||
const underUnion = rawUnion
|
||||
.split(',')
|
||||
.map((s) => s.trim())
|
||||
.filter((x) => x.startsWith('/'))
|
||||
.some((pre) => p === pre || p.startsWith(pre + '/'))
|
||||
const underMirror = p === '/mirror' || p.startsWith('/mirror/')
|
||||
const acctOn =
|
||||
env.BARE_OS_PERSONAL_ACCT_PREFIX === '1' ||
|
||||
env.BARE_OS_PERSONAL_ACCT_PREFIX === 'true'
|
||||
const underPersonalAcct =
|
||||
acctOn && (p === '/.bare-os/acct' || p.startsWith('/.bare-os/acct/'))
|
||||
const underDevShm = p === '/dev/shm' || p.startsWith('/dev/shm/')
|
||||
/** @type {Record<string, number>} */
|
||||
const table = {
|
||||
PATH_MAX: 4096,
|
||||
NAME_MAX: 255,
|
||||
_PC_PATH_MAX: 4096,
|
||||
_PC_NAME_MAX: 255,
|
||||
_PC_SYMLINK_MAX: 1024,
|
||||
_PC_CHOWN_RESTRICTED: 1,
|
||||
_PC_NO_TRUNC: 1,
|
||||
_PC_FILESIZEBITS: 64,
|
||||
_PC_LINK_MAX: 1,
|
||||
_PC_MAX_CANON: 255,
|
||||
_PC_MAX_INPUT: 512,
|
||||
_PC_PIPE_BUF: 4096,
|
||||
_PC_2_SYMLINKS: 1,
|
||||
_POSIX_VERSION: 200809,
|
||||
_POSIX_THREAD_ATTR_STACKSIZE: 65536
|
||||
}
|
||||
if (k === '_PC_CHOWN_RESTRICTED') {
|
||||
if (underMirror) return 0
|
||||
return 1
|
||||
}
|
||||
if (
|
||||
k === '_PC_NO_TRUNC' &&
|
||||
(underMirror || (underUnion && !underPersonalAcct))
|
||||
) {
|
||||
return 0
|
||||
}
|
||||
if (underDevShm && k === '_PC_NAME_MAX') {
|
||||
return 128
|
||||
}
|
||||
if (!k || table[k] === undefined) {
|
||||
throw new Error('bareOsPathconf: unknown name: ' + k)
|
||||
}
|
||||
return table[k]
|
||||
},
|
||||
/**
|
||||
* Dynamic **`sysconf`**-style values for **`getconf _SC_*`** names (not path-specific).
|
||||
* @param {string} name
|
||||
* @returns {string | number | null}
|
||||
*/
|
||||
bareOsGetconfSysconf(name) {
|
||||
const n = String(name || '').trim()
|
||||
if (n === '_SC_NPROCESSORS_ONLN') {
|
||||
const raw = String(env.BARE_OS_NPROC || '').trim()
|
||||
const p = parseInt(raw, 10)
|
||||
if (Number.isFinite(p) && p > 0) return String(Math.min(4096, p))
|
||||
return '4'
|
||||
}
|
||||
if (n === '_SC_PAGESIZE' || n === '_SC_PAGE_SIZE') return '4096'
|
||||
if (n === '_SC_PHYS_PAGES') {
|
||||
const raw = String(env.BARE_OS_PHYS_PAGES_HINT || '').trim()
|
||||
const p = parseInt(raw, 10)
|
||||
if (Number.isFinite(p) && p > 0) return String(p)
|
||||
return '524288'
|
||||
}
|
||||
if (n === '_SC_AVPHYS_PAGES') {
|
||||
const raw = String(env.BARE_OS_AVPHYS_PAGES_HINT || '').trim()
|
||||
const p = parseInt(raw, 10)
|
||||
if (Number.isFinite(p) && p > 0) return String(p)
|
||||
return '262144'
|
||||
}
|
||||
if (n === '_SC_OPEN_MAX') return '256'
|
||||
if (n === '_SC_STREAM_MAX') return '256'
|
||||
if (n === '_SC_CHILD_MAX') return '0'
|
||||
if (n === '_SC_CLK_TCK') return '100'
|
||||
if (n === '_SC_ARG_MAX') return '262144'
|
||||
if (n === '_SC_NPROCESSORS_CONF') {
|
||||
const raw = String(env.BARE_OS_NPROC || '').trim()
|
||||
const p = parseInt(raw, 10)
|
||||
if (Number.isFinite(p) && p > 0) return String(Math.min(4096, p))
|
||||
return '4'
|
||||
}
|
||||
if (n === '_SC_HOST_NAME_MAX' || n === '_POSIX_HOST_NAME_MAX')
|
||||
return '255'
|
||||
if (n === '_SC_SYMLOOP_MAX') return '32'
|
||||
if (n === '_SC_IOV_MAX' || n === '_SC_UIO_MAXIOV') return '1024'
|
||||
if (n === '_SC_THREAD_DESTRUCTOR_ITERATIONS') return '4'
|
||||
if (n === '_SC_TTY_NAME_MAX') return '32'
|
||||
if (n === '_SC_LOGIN_NAME_MAX') return '256'
|
||||
if (n === '_SC_MONOTONIC_CLOCK') return '1'
|
||||
if (n === '_SC_MONOTONIC_CLOCK_RES') {
|
||||
return bootHrtimeNowNs ? '1' : '0'
|
||||
}
|
||||
if (n === '_SC_TRACE' || n === '_SC_TRACE_VERSION') return '-1'
|
||||
if (n === '_SC_RAW_SOCKETS') return '-1'
|
||||
if (n === '_SC_ADVISORY_INFO') return '0'
|
||||
if (n === '_SC_BARRIERS') return '-1'
|
||||
if (n === '_SC_SPIN_LOCKS') return '1'
|
||||
if (n === '_SC_TIMER_MAX') return '32'
|
||||
if (n === '_SC_DELAYTIMER_MAX') return '0'
|
||||
if (n === '_SC_ATEXIT_MAX') return '32'
|
||||
if (n === '_SC_LINE_MAX') return '2048'
|
||||
if (n === '_SC_BC_BASE_MAX') return '99'
|
||||
if (n === '_SC_BC_DIM_MAX') return '2048'
|
||||
if (n === '_SC_BC_SCALE_MAX') return '99'
|
||||
const pl = getBareOsPipelineLimits(env)
|
||||
if (n === '_SC_BARE_OS_PIPELINE_MAX_BYTES') return String(pl.maxBytes)
|
||||
if (n === '_SC_BARE_OS_PIPELINE_MAX_LINES') return String(pl.maxLines)
|
||||
if (n === '_SC_BARE_OS_PIPELINE_MAX_STAGES') return String(pl.maxStages)
|
||||
const parseCap = (key, def, cap) => {
|
||||
const raw = String(env[key] ?? '').trim()
|
||||
if (!raw) return String(def)
|
||||
const p = parseInt(raw, 10)
|
||||
if (!Number.isFinite(p) || p < 0) return String(def)
|
||||
return String(cap != null ? Math.min(cap, p) : p)
|
||||
}
|
||||
if (n === '_SC_BARE_OS_GLOB_MAX_MATCHES')
|
||||
return parseCap('BARE_OS_GLOB_MAX_MATCHES', 4096, null)
|
||||
if (n === '_SC_BARE_OS_SHELL_LOOP_MAX')
|
||||
return parseCap('BARE_OS_SHELL_LOOP_MAX', 10000, null)
|
||||
if (n === '_SC_BARE_OS_EXEC_MAX_DEPTH')
|
||||
return parseCap('BARE_OS_EXEC_MAX_DEPTH', 64, null)
|
||||
if (n === '_SC_BARE_OS_XARGS_MAX_PROCS')
|
||||
return parseCap('BARE_OS_XARGS_MAX_PROCS', 8, 32)
|
||||
if (n === '_SC_BARE_OS_SWARM_MAX_PEERS')
|
||||
return parseCap('BARE_OS_SWARM_MAX_PEERS', 512, null)
|
||||
if (n === '_SC_BARE_OS_SWARM_MAX_CLIENT_CONNECTIONS')
|
||||
return parseCap('BARE_OS_SWARM_MAX_CLIENT_CONNECTIONS', 256, null)
|
||||
if (n === '_SC_BARE_OS_SWARM_MAX_SERVER_CONNECTIONS')
|
||||
return parseCap('BARE_OS_SWARM_MAX_SERVER_CONNECTIONS', 256, null)
|
||||
if (n === '_SC_BARE_OS_SWARM_MAX_PARALLEL')
|
||||
return parseCap('BARE_OS_SWARM_MAX_PARALLEL', 64, null)
|
||||
if (n === '_SC_BARE_OS_DGRAM_RECVQ_MAX')
|
||||
return parseCap('BARE_OS_POSIX_DGRAM_RECVQ_MAX', 256, null)
|
||||
if (n === '_SC_BARE_OS_ACCEPT_QUEUE_MAX')
|
||||
return parseCap('BARE_OS_POSIX_ACCEPT_QUEUE_MAX', 64, null)
|
||||
return null
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -179,3 +179,71 @@ export function bareOsResetShellIdentityState(ctx) {
|
||||
ctx.shellBackgroundJobs.nextId = 1
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Consume an interactive here-document after `cmd <<DELIM`.
|
||||
* @param {Record<string, unknown>} ctx
|
||||
* @param {string} execLine
|
||||
* @param {(ctx: Record<string, unknown>) => void} syncExit
|
||||
* @returns {Promise<{ ok: true, execLine: string } | { ok: false }>}
|
||||
*/
|
||||
export async function consumeInteractiveHeredoc(ctx, execLine, syncExit) {
|
||||
const readL = ctx.readLine
|
||||
if (typeof readL !== 'function') return { ok: true, execLine }
|
||||
const hm = execLine.match(/^(.*?)<<-?\s*(?:'([^']+)'|"([^"]+)"|(\S+))\s*$/)
|
||||
if (!hm) return { ok: true, execLine }
|
||||
const prefix = hm[1].trimEnd()
|
||||
if (!prefix) {
|
||||
ctx.console.error(
|
||||
'shell: here-document requires a command before << on the same line'
|
||||
)
|
||||
ctx.exitCode = 2
|
||||
syncExit(ctx)
|
||||
return { ok: false }
|
||||
}
|
||||
const delim = hm[2] ?? hm[3] ?? hm[4]
|
||||
const singleQuoted = hm[2] != null
|
||||
const posixHeredocCap = (() => {
|
||||
if (
|
||||
ctx.env?.BARE_OS_SHELL_POSIX_MODE !== '1' &&
|
||||
ctx.env?.BARE_OS_SHELL_POSIX_MODE !== 'true'
|
||||
) {
|
||||
return null
|
||||
}
|
||||
const raw = String(ctx.env.BARE_OS_SHELL_HEREDOC_MAX_BYTES || '').trim()
|
||||
const n = parseInt(raw, 10)
|
||||
if (Number.isFinite(n) && n > 0) return Math.min(2_000_000, n)
|
||||
return 262144
|
||||
})()
|
||||
/** @type {string[]} */
|
||||
const bodyLines = []
|
||||
let heredocAcc = 0
|
||||
for (;;) {
|
||||
const ln = await readL('> ')
|
||||
if (ln == null) break
|
||||
if (ln === delim) break
|
||||
if (posixHeredocCap != null) {
|
||||
heredocAcc += ln.length + 1
|
||||
if (heredocAcc > posixHeredocCap) {
|
||||
ctx.console.error(
|
||||
'shell: here-document exceeds BARE_OS_SHELL_HEREDOC_MAX_BYTES cap (POSIX mode)'
|
||||
)
|
||||
ctx.exitCode = 2
|
||||
syncExit(ctx)
|
||||
return { ok: false }
|
||||
}
|
||||
}
|
||||
bodyLines.push(ln)
|
||||
}
|
||||
const vfs = ctx.vfs
|
||||
const env = vfs?.env && typeof vfs.env === 'object' ? vfs.env : {}
|
||||
let body = bodyLines.join('\n')
|
||||
if (!singleQuoted) {
|
||||
body = bodyLines.map((l) => expandWord(l, env)).join('\n')
|
||||
}
|
||||
ctx.shellHeredocOnce = body
|
||||
if (body.length > 0 && !body.endsWith('\n')) {
|
||||
ctx.shellHeredocOnce += '\n'
|
||||
}
|
||||
return { ok: true, execLine: prefix }
|
||||
}
|
||||
|
||||
@@ -65,7 +65,8 @@ import {
|
||||
tryReportMisplacedReservedStatementStart,
|
||||
execDoubleBracketLimited,
|
||||
assignShellFunctionPositionalEnv,
|
||||
casePatternList
|
||||
casePatternList,
|
||||
consumeInteractiveHeredoc
|
||||
} from './shell-stmt.js'
|
||||
|
||||
export { BARE_OS_SHELL_NOUNSET_ERROR }
|
||||
@@ -1987,65 +1988,14 @@ async function execShellLineInner(ctx, rawTrimmed) {
|
||||
}
|
||||
}
|
||||
ctx.shellSessionState.foregroundPgid = ctx.shellSessionState.sid
|
||||
const readL = ctx.readLine
|
||||
if (typeof readL === 'function') {
|
||||
const hm = execLine.match(/^(.*?)<<-?\s*(?:'([^']+)'|"([^"]+)"|(\S+))\s*$/)
|
||||
if (hm) {
|
||||
const prefix = hm[1].trimEnd()
|
||||
if (!prefix) {
|
||||
ctx.console.error(
|
||||
'shell: here-document requires a command before << on the same line'
|
||||
)
|
||||
ctx.exitCode = 2
|
||||
syncBareOsExitStatusEnv(ctx)
|
||||
return 'ok'
|
||||
}
|
||||
const delim = hm[2] ?? hm[3] ?? hm[4]
|
||||
const singleQuoted = hm[2] != null
|
||||
const posixHeredocCap = (() => {
|
||||
if (
|
||||
ctx.env?.BARE_OS_SHELL_POSIX_MODE !== '1' &&
|
||||
ctx.env?.BARE_OS_SHELL_POSIX_MODE !== 'true'
|
||||
) {
|
||||
return null
|
||||
}
|
||||
const raw = String(ctx.env.BARE_OS_SHELL_HEREDOC_MAX_BYTES || '').trim()
|
||||
const n = parseInt(raw, 10)
|
||||
if (Number.isFinite(n) && n > 0) return Math.min(2_000_000, n)
|
||||
return 262144
|
||||
})()
|
||||
/** @type {string[]} */
|
||||
const bodyLines = []
|
||||
let heredocAcc = 0
|
||||
for (;;) {
|
||||
const ln = await readL('> ')
|
||||
if (ln == null) break
|
||||
if (ln === delim) break
|
||||
if (posixHeredocCap != null) {
|
||||
heredocAcc += ln.length + 1
|
||||
if (heredocAcc > posixHeredocCap) {
|
||||
ctx.console.error(
|
||||
'shell: here-document exceeds BARE_OS_SHELL_HEREDOC_MAX_BYTES cap (POSIX mode)'
|
||||
)
|
||||
ctx.exitCode = 2
|
||||
syncBareOsExitStatusEnv(ctx)
|
||||
return 'ok'
|
||||
}
|
||||
}
|
||||
bodyLines.push(ln)
|
||||
}
|
||||
const vfs = ctx.vfs
|
||||
const env = vfs?.env && typeof vfs.env === 'object' ? vfs.env : {}
|
||||
let body = bodyLines.join('\n')
|
||||
if (!singleQuoted) {
|
||||
body = bodyLines.map((l) => expandWord(l, env)).join('\n')
|
||||
}
|
||||
ctx.shellHeredocOnce = body
|
||||
if (body.length > 0 && !body.endsWith('\n')) {
|
||||
ctx.shellHeredocOnce += '\n'
|
||||
}
|
||||
execLine = prefix
|
||||
}
|
||||
{
|
||||
const heredoc = await consumeInteractiveHeredoc(
|
||||
ctx,
|
||||
execLine,
|
||||
syncBareOsExitStatusEnv
|
||||
)
|
||||
if (!heredoc.ok) return 'ok'
|
||||
execLine = heredoc.execLine
|
||||
}
|
||||
|
||||
const tokens = tokenize(execLine)
|
||||
|
||||
@@ -0,0 +1,203 @@
|
||||
/**
|
||||
* Personal-drive physical lstat / put helpers used by createVfs.
|
||||
*/
|
||||
import { dirnameAbs, pathPrefixes } from './vfs-path.js'
|
||||
import {
|
||||
extractBareOs,
|
||||
mergeBareOsOnWrite,
|
||||
mergeEntryMetadata,
|
||||
modeAllows,
|
||||
parseUidGid,
|
||||
statFromBareOs,
|
||||
synthesizeStat,
|
||||
S_IFDIR
|
||||
} from './vfs-posix-meta.js'
|
||||
import { bareOsVfsAclDeniesDriveOp } from './bare-os-vfs-acl-enforce.js'
|
||||
|
||||
/**
|
||||
* @param {{
|
||||
* personalDrive: unknown,
|
||||
* env: Record<string, string | undefined>,
|
||||
* DIR_MARKER: string,
|
||||
* entryOn: (drive: unknown, p: string, opts?: Record<string, unknown>) => Promise<unknown>,
|
||||
* statFromEntryValue: (abs: string, r: unknown, personal: boolean, e: unknown) => unknown,
|
||||
* assertNotBootPolicyDenyVfs: (abs: string, op: string) => void,
|
||||
* assertUnionWriteNotDenied: (abs: string) => void,
|
||||
* assertGuestSensitivePersonalOp: (drive: unknown, personalPath: string, op: string, logicalAbs: string) => void
|
||||
* }} deps
|
||||
*/
|
||||
export function createVfsPersonalDriveIo(deps) {
|
||||
const {
|
||||
personalDrive,
|
||||
env,
|
||||
DIR_MARKER,
|
||||
entryOn,
|
||||
statFromEntryValue,
|
||||
assertNotBootPolicyDenyVfs,
|
||||
assertUnionWriteNotDenied,
|
||||
assertGuestSensitivePersonalOp
|
||||
} = deps
|
||||
|
||||
/**
|
||||
* `/.bare/holesail/**` on the personal drive: parent checks and traverse must walk **physical**
|
||||
* paths on that drive — logical `/.bare` may map to a different backing path under
|
||||
* account prefix, and `/` is skipped as a virtual mount point.
|
||||
*/
|
||||
async function lstatPersonalDrivePhysicalDir(physPath) {
|
||||
if (!personalDrive) return null
|
||||
const pnorm = String(physPath || '').replace(/\/+$/, '') || '/'
|
||||
if (pnorm === '/') {
|
||||
return { ...synthesizeStat('/', true, env, 'directory'), path: pnorm }
|
||||
}
|
||||
const e = await entryOn(personalDrive, pnorm, { follow: false })
|
||||
const fromVal = statFromEntryValue(
|
||||
pnorm,
|
||||
{ drive: personalDrive, path: pnorm },
|
||||
true,
|
||||
e
|
||||
)
|
||||
if (fromVal) return { ...fromVal, path: pnorm }
|
||||
const names = []
|
||||
try {
|
||||
const stream = personalDrive.readdir(pnorm)
|
||||
for await (const n of stream) names.push(n)
|
||||
} catch {
|
||||
/* missing parent */
|
||||
}
|
||||
if (names.length) {
|
||||
if (names.includes(DIR_MARKER)) {
|
||||
const mPath = `${pnorm}/${DIR_MARKER}`.replace(/\/{2,}/g, '/')
|
||||
const me = await entryOn(personalDrive, mPath, { follow: false })
|
||||
const mv = me?.value
|
||||
if (mv?.blob) {
|
||||
const bo = extractBareOs(mv)
|
||||
if (bo) {
|
||||
let perm = bo.mode & 0o777
|
||||
if (perm & 0o400) perm |= 0o100
|
||||
if (perm & 0o040) perm |= 0o010
|
||||
if (perm & 0o004) perm |= 0o001
|
||||
return statFromBareOs(
|
||||
{ ...bo, mode: S_IFDIR | perm },
|
||||
'directory',
|
||||
0,
|
||||
pnorm
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
return { ...synthesizeStat(pnorm, true, env, 'directory'), path: pnorm }
|
||||
}
|
||||
if (e) return { ...synthesizeStat(pnorm, true, env, 'directory'), path: pnorm }
|
||||
return null
|
||||
}
|
||||
|
||||
/** Directory or regular file (or symlink) on the personal drive by absolute path on that drive. */
|
||||
async function lstatPersonalDrivePhysicalAny(physPath) {
|
||||
if (!personalDrive) return null
|
||||
const pnorm = String(physPath || '').replace(/\/+$/, '') || '/'
|
||||
if (pnorm === '/') {
|
||||
return { ...synthesizeStat('/', true, env, 'directory'), path: pnorm }
|
||||
}
|
||||
const e = await entryOn(personalDrive, pnorm, { follow: false })
|
||||
const rf = statFromEntryValue(
|
||||
pnorm,
|
||||
{ drive: personalDrive, path: pnorm },
|
||||
true,
|
||||
e
|
||||
)
|
||||
if (rf) return rf
|
||||
return await lstatPersonalDrivePhysicalDir(pnorm)
|
||||
}
|
||||
|
||||
async function assertPersonalDriveAncestorWritableForCreate(physPath) {
|
||||
if (!personalDrive) {
|
||||
throw new Error('ENOENT: ' + String(physPath || ''))
|
||||
}
|
||||
const { uid: euid, gid: egid } = parseUidGid(env)
|
||||
const norm = String(physPath || '').replace(/\/+$/, '') || '/'
|
||||
let probe = dirnameAbs(norm)
|
||||
/** @type {{ pre: string, st: unknown } | null} */
|
||||
let deepest = null
|
||||
while (probe && probe !== '/') {
|
||||
const st = await lstatPersonalDrivePhysicalDir(probe)
|
||||
if (st && st.type === 'directory') {
|
||||
deepest = { pre: probe, st }
|
||||
break
|
||||
}
|
||||
probe = dirnameAbs(probe)
|
||||
}
|
||||
if (!deepest) {
|
||||
deepest = {
|
||||
pre: '/',
|
||||
st: await lstatPersonalDrivePhysicalDir('/')
|
||||
}
|
||||
}
|
||||
if (!deepest.st || !modeAllows(deepest.st, euid, egid, 'w', 'directory')) {
|
||||
throw new Error('EACCES: cannot create in ' + String(deepest?.pre ?? ''))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} logicalAbs policy / guest messaging
|
||||
* @param {string} physPath path on personalDrive
|
||||
*/
|
||||
async function putPersonalDrivePhysical(logicalAbs, physPath, buf, opts = {}) {
|
||||
assertNotBootPolicyDenyVfs(logicalAbs, 'write')
|
||||
assertUnionWriteNotDenied(logicalAbs)
|
||||
if (!personalDrive) {
|
||||
throw new Error('Read-only path (not under $HOME): ' + logicalAbs)
|
||||
}
|
||||
if (await bareOsVfsAclDeniesDriveOp(env, personalDrive, physPath, 'write')) {
|
||||
throw new Error('EACCES: ACL enforces deny write: ' + logicalAbs)
|
||||
}
|
||||
assertGuestSensitivePersonalOp(personalDrive, physPath, 'write', logicalAbs)
|
||||
const existing = await entryOn(personalDrive, physPath, { follow: false })
|
||||
const hadBlob = !!existing?.value?.blob
|
||||
if (!hadBlob) await assertPersonalDriveAncestorWritableForCreate(physPath)
|
||||
const value = existing?.value
|
||||
const prevBare = extractBareOs(value)
|
||||
const bareOs = mergeBareOsOnWrite(prevBare, env, {
|
||||
executable: opts.executable,
|
||||
bumpMtime: opts.bumpMtime !== false,
|
||||
touchCtime: opts.touchCtime === true,
|
||||
legacyExecutable: !!value?.executable,
|
||||
mtimeMs: opts.mtimeMs,
|
||||
ctimeMs: opts.ctimeMs,
|
||||
posixModeBits: opts.posixModeBits
|
||||
})
|
||||
const executable =
|
||||
opts.executable !== undefined ? !!opts.executable : !!value?.executable
|
||||
const metadata = mergeEntryMetadata(value?.metadata, bareOs)
|
||||
return personalDrive.put(physPath, buf, { executable, metadata })
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure `/.bare` and `/.bare/holesail` DIR_MARKER entries exist on the personal drive for stable
|
||||
* Holesail paths (needed when `/` is skipped as a virtual mount point in parent checks, and when
|
||||
* `/.bare` is not the same backing path as `/.bare/holesail/**` under account prefix).
|
||||
*/
|
||||
async function ensureBareHolesailStablePersonalDirTree(physPath, logicalAbs) {
|
||||
if (!personalDrive) return
|
||||
const norm = String(physPath || '').replace(/\/+$/, '') || '/'
|
||||
const d = dirnameAbs(norm)
|
||||
if (d === '/' || d === '') return
|
||||
const physPrefs = pathPrefixes(d)
|
||||
const empty = new Uint8Array(0)
|
||||
for (let i = 1; i < physPrefs.length; i++) {
|
||||
const physPre = physPrefs[i]
|
||||
const st = await lstatPersonalDrivePhysicalDir(physPre)
|
||||
if (st) continue
|
||||
const markerPath =
|
||||
physPre === '/' ? `/${DIR_MARKER}` : `${physPre}/${DIR_MARKER}`
|
||||
await putPersonalDrivePhysical(logicalAbs, markerPath, empty, {})
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
lstatPersonalDrivePhysicalDir,
|
||||
lstatPersonalDrivePhysicalAny,
|
||||
assertPersonalDriveAncestorWritableForCreate,
|
||||
putPersonalDrivePhysical,
|
||||
ensureBareHolesailStablePersonalDirTree
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
* VFS mount / mirror / tilde / system-RO alias routers used by createVfs.
|
||||
*/
|
||||
import unixPathResolve from 'unix-path-resolve'
|
||||
import { classifyBareOsVfsPseudoAbs } from './vfs-pseudo-classify.js'
|
||||
|
||||
/**
|
||||
* @param {{
|
||||
@@ -11,7 +12,18 @@ import unixPathResolve from 'unix-path-resolve'
|
||||
* systemRoAliasNorm: string,
|
||||
* getAuxiliaryMountLines?: (() => (string | null | undefined)[]) | null,
|
||||
* getAuxiliaryDrives?: (() => unknown[] | null | undefined) | null,
|
||||
* systemDrive: unknown
|
||||
* systemDrive: unknown,
|
||||
* personalDrive?: unknown,
|
||||
* env?: Record<string, string | undefined>,
|
||||
* tmpStorageRoot?: () => string,
|
||||
* varLogStorageRoot?: () => string,
|
||||
* activeHomeBasename?: () => string,
|
||||
* personalHomeStorageRoot?: () => string,
|
||||
* usePersonalAcctPrefix?: () => boolean,
|
||||
* personalLayoutRootAbs?: () => string,
|
||||
* omitHypercorePackHrpcLifecycleProc?: boolean,
|
||||
* bareOsIpc?: unknown,
|
||||
* ipcFifoLogicalToActual?: (logicalName: string) => string
|
||||
* }} deps
|
||||
*/
|
||||
export function createVfsRouteHelpers(deps) {
|
||||
@@ -22,7 +34,18 @@ export function createVfsRouteHelpers(deps) {
|
||||
systemRoAliasNorm,
|
||||
getAuxiliaryMountLines = null,
|
||||
getAuxiliaryDrives = null,
|
||||
systemDrive
|
||||
systemDrive,
|
||||
personalDrive = null,
|
||||
env = {},
|
||||
tmpStorageRoot = () => '/.bare-os/tmp',
|
||||
varLogStorageRoot = () => '/.bare-os/var/log',
|
||||
activeHomeBasename = () => '',
|
||||
personalHomeStorageRoot = () => '/',
|
||||
usePersonalAcctPrefix = () => false,
|
||||
personalLayoutRootAbs = () => '/',
|
||||
omitHypercorePackHrpcLifecycleProc = false,
|
||||
bareOsIpc = null,
|
||||
ipcFifoLogicalToActual = null
|
||||
} = deps
|
||||
|
||||
/**
|
||||
@@ -192,6 +215,160 @@ export function createVfsRouteHelpers(deps) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} absPath
|
||||
* @returns {null | Record<string, unknown>}
|
||||
*/
|
||||
function classifyPseudoAbs(absPath) {
|
||||
return classifyBareOsVfsPseudoAbs(absPath, {
|
||||
omitHypercorePackHrpcLifecycleProc,
|
||||
bareOsIpc,
|
||||
ipcFifoLogicalToActual
|
||||
})
|
||||
}
|
||||
|
||||
function route(absPath) {
|
||||
const aliasR = routeSystemRoAlias(absPath)
|
||||
if (aliasR) return aliasR
|
||||
const mirR = routeMirror(absPath)
|
||||
if (mirR) return mirR
|
||||
const mntR = routeMnt(absPath)
|
||||
if (mntR) return mntR
|
||||
|
||||
const pseudo = classifyPseudoAbs(absPath)
|
||||
if (pseudo) return pseudo
|
||||
|
||||
const tmpRoot = tmpStorageRoot()
|
||||
const tmpNorm = absPath.replace(/\/+$/, '') || '/'
|
||||
if (tmpNorm === '/tmp') {
|
||||
return { drive: personalDrive, path: tmpRoot }
|
||||
}
|
||||
if (absPath.startsWith('/tmp/')) {
|
||||
const rel = absPath.slice(5).replace(/^\/+/, '')
|
||||
const p = rel ? unixPathResolve(tmpRoot, rel) : tmpRoot
|
||||
return { drive: personalDrive, path: p }
|
||||
}
|
||||
|
||||
const varNorm = absPath.replace(/\/+$/, '') || '/'
|
||||
if (varNorm === '/var') {
|
||||
return { virtualVarRoot: true }
|
||||
}
|
||||
const varLogRoot = varLogStorageRoot()
|
||||
if (varNorm === '/var/log' || absPath === '/var/log/') {
|
||||
return { drive: personalDrive, path: varLogRoot }
|
||||
}
|
||||
if (absPath.startsWith('/var/log/')) {
|
||||
const rel = absPath.slice('/var/log/'.length).replace(/^\/+/, '')
|
||||
const p = rel ? unixPathResolve(varLogRoot, rel) : varLogRoot
|
||||
return { drive: personalDrive, path: p }
|
||||
}
|
||||
if (absPath.startsWith('/var/')) {
|
||||
return { drive: systemDrive, path: absPath }
|
||||
}
|
||||
|
||||
const h = normalizeHome()
|
||||
const activeSeg = activeHomeBasename()
|
||||
const homeRoot = personalHomeStorageRoot()
|
||||
|
||||
if (activeSeg && absPath === '/home') {
|
||||
return { virtualHomeDir: true }
|
||||
}
|
||||
|
||||
if (activeSeg && absPath.startsWith('/home/')) {
|
||||
const after = absPath.slice('/home/'.length)
|
||||
const slash = after.indexOf('/')
|
||||
const seg = slash === -1 ? after : after.slice(0, slash)
|
||||
const rest = slash === -1 ? '' : after.slice(slash + 1)
|
||||
if (seg === activeSeg) {
|
||||
if (!rest) {
|
||||
return { drive: personalDrive, path: homeRoot }
|
||||
}
|
||||
const rel = rest.replace(/^\/+/, '')
|
||||
const wwwR = routeHomeWwwAlias(rel)
|
||||
if (wwwR) return wwwR
|
||||
const p = unixPathResolve(homeRoot, rel)
|
||||
return { drive: personalDrive, path: p }
|
||||
}
|
||||
return { drive: systemDrive, path: absPath }
|
||||
}
|
||||
|
||||
if (absPath === h || absPath.startsWith(h + '/')) {
|
||||
if (absPath === h) {
|
||||
return { drive: personalDrive, path: homeRoot }
|
||||
}
|
||||
const rel = absPath.slice(h.length + 1).replace(/^\/+/, '')
|
||||
const wwwR = routeHomeWwwAlias(rel)
|
||||
if (wwwR) return wwwR
|
||||
const p = unixPathResolve(homeRoot, rel)
|
||||
return { drive: personalDrive, path: p }
|
||||
}
|
||||
|
||||
const unionNorm = absPath.replace(/\/+$/, '') || '/'
|
||||
if (
|
||||
unionNorm === '/.bare-os/union' ||
|
||||
absPath.startsWith('/.bare-os/union/')
|
||||
) {
|
||||
const p = unionNorm === '/.bare-os/union' ? '/.bare-os/union' : unionNorm
|
||||
return { drive: personalDrive, path: p }
|
||||
}
|
||||
|
||||
const snapsOn =
|
||||
env &&
|
||||
(env.BARE_OS_VFS_SNAPSHOTS === '1' || env.BARE_OS_VFS_SNAPSHOTS === 'true')
|
||||
if (
|
||||
snapsOn &&
|
||||
systemDrive &&
|
||||
typeof systemDrive.checkout === 'function'
|
||||
) {
|
||||
const snapNorm = absPath.replace(/\/+$/, '') || '/'
|
||||
if (snapNorm === '/snapshots') {
|
||||
return { virtualSnapshotRoot: true }
|
||||
}
|
||||
if (snapNorm === '/snapshots/system') {
|
||||
return { virtualSnapshotSystem: true }
|
||||
}
|
||||
const m = /^\/snapshots\/system\/([0-9]+)(\/.*)?$/.exec(absPath)
|
||||
if (m) {
|
||||
const ver = Number.parseInt(m[1], 10)
|
||||
const tail = m[2] || '/'
|
||||
const sub =
|
||||
tail === '/' ? '/' : unixPathResolve('/', tail.replace(/^\/+/, ''))
|
||||
try {
|
||||
const chk = systemDrive.checkout(ver)
|
||||
if (chk)
|
||||
return { drive: chk, path: sub, snapshotReadOnly: true }
|
||||
} catch {
|
||||
/* fall through to system path */
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const bareTop = absPath.replace(/\/+$/, '') || '/'
|
||||
if (
|
||||
personalDrive &&
|
||||
(bareTop === '/.bare' || absPath.startsWith('/.bare/'))
|
||||
) {
|
||||
const p =
|
||||
bareTop === '/.bare'
|
||||
? '/.bare'
|
||||
: absPath.replace(/\/+$/, '') || absPath
|
||||
if (usePersonalAcctPrefix()) {
|
||||
const tail = bareTop === '/.bare' ? '' : p.slice('/.bare'.length)
|
||||
// Managed Holesail persistence: keep `/.bare/holesail/**` on the personal drive root so
|
||||
// guest vs unlocked sessions share one `state.json` (not under per-session `acct/…` layout).
|
||||
if (tail === '/holesail' || tail.startsWith('/holesail/')) {
|
||||
return { drive: personalDrive, path: p }
|
||||
}
|
||||
const layoutRoot = personalLayoutRootAbs()
|
||||
const physical = layoutRoot + '/.bare' + tail
|
||||
return { drive: personalDrive, path: physical }
|
||||
}
|
||||
return { drive: personalDrive, path: p }
|
||||
}
|
||||
|
||||
return { drive: systemDrive, path: absPath }
|
||||
}
|
||||
|
||||
return {
|
||||
expandTilde,
|
||||
resolveLogical,
|
||||
@@ -200,6 +377,8 @@ export function createVfsRouteHelpers(deps) {
|
||||
routeMirror,
|
||||
routeMnt,
|
||||
routeSystemRoAlias,
|
||||
routeHomeWwwAlias
|
||||
routeHomeWwwAlias,
|
||||
classifyPseudoAbs,
|
||||
route
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,6 +33,7 @@ import {
|
||||
} from './vfs-policy.js'
|
||||
import { createVfsWarmReadCache } from './vfs-warm-cache.js'
|
||||
import { createVfsRouteHelpers } from './vfs-route.js'
|
||||
import { createVfsPersonalDriveIo } from './vfs-personal-io.js'
|
||||
import {
|
||||
BARE_OS_PROC_FILE_TO_ID_REPLICATION_OPERATOR_SURFACE,
|
||||
BARE_OS_PROC_FILE_TO_ID_PEAR_CORESTORE_HRPC,
|
||||
@@ -41,7 +42,6 @@ import {
|
||||
BARE_OS_PROC_FILE_TO_ID_PEAR_INSPECT_LOGGER_TLS,
|
||||
BARE_OS_PROC_FILE_TO_ID_HYPERCORE_PACK_HRPC_LIFECYCLE
|
||||
} from './vfs-proc-id-maps.js'
|
||||
import { classifyBareOsVfsPseudoAbs } from './vfs-pseudo-classify.js'
|
||||
import {
|
||||
isHyperdriveRootPath,
|
||||
joinLogical,
|
||||
@@ -630,10 +630,8 @@ export function createVfs(
|
||||
resolveLogical,
|
||||
getMntMap,
|
||||
pseudoMountsText,
|
||||
routeMirror,
|
||||
routeMnt,
|
||||
routeSystemRoAlias,
|
||||
routeHomeWwwAlias
|
||||
classifyPseudoAbs,
|
||||
route
|
||||
} = createVfsRouteHelpers({
|
||||
getCwd: () => cwd,
|
||||
normalizeHome,
|
||||
@@ -641,7 +639,18 @@ export function createVfs(
|
||||
systemRoAliasNorm,
|
||||
getAuxiliaryMountLines,
|
||||
getAuxiliaryDrives,
|
||||
systemDrive
|
||||
systemDrive,
|
||||
personalDrive,
|
||||
env,
|
||||
tmpStorageRoot,
|
||||
varLogStorageRoot,
|
||||
activeHomeBasename,
|
||||
personalHomeStorageRoot,
|
||||
usePersonalAcctPrefix,
|
||||
personalLayoutRootAbs,
|
||||
omitHypercorePackHrpcLifecycleProc,
|
||||
bareOsIpc,
|
||||
ipcFifoLogicalToActual
|
||||
})
|
||||
|
||||
/**
|
||||
@@ -1758,18 +1767,6 @@ export function createVfs(
|
||||
return utf8Encode('')
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} absPath
|
||||
* @returns {null | Record<string, unknown>}
|
||||
*/
|
||||
function classifyPseudoAbs(absPath) {
|
||||
return classifyBareOsVfsPseudoAbs(absPath, {
|
||||
omitHypercorePackHrpcLifecycleProc,
|
||||
bareOsIpc,
|
||||
ipcFifoLogicalToActual
|
||||
})
|
||||
}
|
||||
|
||||
function lstatVirtualPseudo(abs, r) {
|
||||
if (!r.virtualPseudo) return null
|
||||
if (r.node === 'enoent') return null
|
||||
@@ -1792,148 +1789,6 @@ export function createVfs(
|
||||
return st
|
||||
}
|
||||
|
||||
function route(absPath) {
|
||||
const aliasR = routeSystemRoAlias(absPath)
|
||||
if (aliasR) return aliasR
|
||||
const mirR = routeMirror(absPath)
|
||||
if (mirR) return mirR
|
||||
const mntR = routeMnt(absPath)
|
||||
if (mntR) return mntR
|
||||
|
||||
const pseudo = classifyPseudoAbs(absPath)
|
||||
if (pseudo) return pseudo
|
||||
|
||||
const tmpRoot = tmpStorageRoot()
|
||||
const tmpNorm = absPath.replace(/\/+$/, '') || '/'
|
||||
if (tmpNorm === '/tmp') {
|
||||
return { drive: personalDrive, path: tmpRoot }
|
||||
}
|
||||
if (absPath.startsWith('/tmp/')) {
|
||||
const rel = absPath.slice(5).replace(/^\/+/, '')
|
||||
const p = rel ? unixPathResolve(tmpRoot, rel) : tmpRoot
|
||||
return { drive: personalDrive, path: p }
|
||||
}
|
||||
|
||||
const varNorm = absPath.replace(/\/+$/, '') || '/'
|
||||
if (varNorm === '/var') {
|
||||
return { virtualVarRoot: true }
|
||||
}
|
||||
const varLogRoot = varLogStorageRoot()
|
||||
if (varNorm === '/var/log' || absPath === '/var/log/') {
|
||||
return { drive: personalDrive, path: varLogRoot }
|
||||
}
|
||||
if (absPath.startsWith('/var/log/')) {
|
||||
const rel = absPath.slice('/var/log/'.length).replace(/^\/+/, '')
|
||||
const p = rel ? unixPathResolve(varLogRoot, rel) : varLogRoot
|
||||
return { drive: personalDrive, path: p }
|
||||
}
|
||||
if (absPath.startsWith('/var/')) {
|
||||
return { drive: systemDrive, path: absPath }
|
||||
}
|
||||
|
||||
const h = normalizeHome()
|
||||
const activeSeg = activeHomeBasename()
|
||||
const homeRoot = personalHomeStorageRoot()
|
||||
|
||||
if (activeSeg && absPath === '/home') {
|
||||
return { virtualHomeDir: true }
|
||||
}
|
||||
|
||||
if (activeSeg && absPath.startsWith('/home/')) {
|
||||
const after = absPath.slice('/home/'.length)
|
||||
const slash = after.indexOf('/')
|
||||
const seg = slash === -1 ? after : after.slice(0, slash)
|
||||
const rest = slash === -1 ? '' : after.slice(slash + 1)
|
||||
if (seg === activeSeg) {
|
||||
if (!rest) {
|
||||
return { drive: personalDrive, path: homeRoot }
|
||||
}
|
||||
const rel = rest.replace(/^\/+/, '')
|
||||
const wwwR = routeHomeWwwAlias(rel)
|
||||
if (wwwR) return wwwR
|
||||
const p = unixPathResolve(homeRoot, rel)
|
||||
return { drive: personalDrive, path: p }
|
||||
}
|
||||
return { drive: systemDrive, path: absPath }
|
||||
}
|
||||
|
||||
if (absPath === h || absPath.startsWith(h + '/')) {
|
||||
if (absPath === h) {
|
||||
return { drive: personalDrive, path: homeRoot }
|
||||
}
|
||||
const rel = absPath.slice(h.length + 1).replace(/^\/+/, '')
|
||||
const wwwR = routeHomeWwwAlias(rel)
|
||||
if (wwwR) return wwwR
|
||||
const p = unixPathResolve(homeRoot, rel)
|
||||
return { drive: personalDrive, path: p }
|
||||
}
|
||||
|
||||
const unionNorm = absPath.replace(/\/+$/, '') || '/'
|
||||
if (
|
||||
unionNorm === '/.bare-os/union' ||
|
||||
absPath.startsWith('/.bare-os/union/')
|
||||
) {
|
||||
const p = unionNorm === '/.bare-os/union' ? '/.bare-os/union' : unionNorm
|
||||
return { drive: personalDrive, path: p }
|
||||
}
|
||||
|
||||
const snapsOn =
|
||||
env &&
|
||||
(env.BARE_OS_VFS_SNAPSHOTS === '1' || env.BARE_OS_VFS_SNAPSHOTS === 'true')
|
||||
if (
|
||||
snapsOn &&
|
||||
systemDrive &&
|
||||
typeof systemDrive.checkout === 'function'
|
||||
) {
|
||||
const snapNorm = absPath.replace(/\/+$/, '') || '/'
|
||||
if (snapNorm === '/snapshots') {
|
||||
return { virtualSnapshotRoot: true }
|
||||
}
|
||||
if (snapNorm === '/snapshots/system') {
|
||||
return { virtualSnapshotSystem: true }
|
||||
}
|
||||
const m = /^\/snapshots\/system\/([0-9]+)(\/.*)?$/.exec(absPath)
|
||||
if (m) {
|
||||
const ver = Number.parseInt(m[1], 10)
|
||||
const tail = m[2] || '/'
|
||||
const sub =
|
||||
tail === '/' ? '/' : unixPathResolve('/', tail.replace(/^\/+/, ''))
|
||||
try {
|
||||
const chk = systemDrive.checkout(ver)
|
||||
if (chk)
|
||||
return { drive: chk, path: sub, snapshotReadOnly: true }
|
||||
} catch {
|
||||
/* fall through to system path */
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const bareTop = absPath.replace(/\/+$/, '') || '/'
|
||||
if (
|
||||
personalDrive &&
|
||||
(bareTop === '/.bare' || absPath.startsWith('/.bare/'))
|
||||
) {
|
||||
const p =
|
||||
bareTop === '/.bare'
|
||||
? '/.bare'
|
||||
: absPath.replace(/\/+$/, '') || absPath
|
||||
if (usePersonalAcctPrefix()) {
|
||||
const tail = bareTop === '/.bare' ? '' : p.slice('/.bare'.length)
|
||||
// Managed Holesail persistence: keep `/.bare/holesail/**` on the personal drive root so
|
||||
// guest vs unlocked sessions share one `state.json` (not under per-session `acct/…` layout).
|
||||
if (tail === '/holesail' || tail.startsWith('/holesail/')) {
|
||||
return { drive: personalDrive, path: p }
|
||||
}
|
||||
const layoutRoot = personalLayoutRootAbs()
|
||||
const physical = layoutRoot + '/.bare' + tail
|
||||
return { drive: personalDrive, path: physical }
|
||||
}
|
||||
return { drive: personalDrive, path: p }
|
||||
}
|
||||
|
||||
return { drive: systemDrive, path: absPath }
|
||||
}
|
||||
|
||||
function personalVaultReaddirCacheTtlMs() {
|
||||
const raw = env && env.BARE_OS_PERSONAL_VAULT_INDEX_CACHE_MS
|
||||
if (raw == null || raw === '' || raw === '0' || raw === 'false') return 0
|
||||
@@ -2002,6 +1857,23 @@ export function createVfs(
|
||||
return null
|
||||
}
|
||||
|
||||
const {
|
||||
lstatPersonalDrivePhysicalDir,
|
||||
lstatPersonalDrivePhysicalAny,
|
||||
assertPersonalDriveAncestorWritableForCreate,
|
||||
putPersonalDrivePhysical,
|
||||
ensureBareHolesailStablePersonalDirTree
|
||||
} = createVfsPersonalDriveIo({
|
||||
personalDrive,
|
||||
env,
|
||||
DIR_MARKER,
|
||||
entryOn,
|
||||
statFromEntryValue,
|
||||
assertNotBootPolicyDenyVfs,
|
||||
assertUnionWriteNotDenied,
|
||||
assertGuestSensitivePersonalOp
|
||||
})
|
||||
|
||||
async function lstatFromAbs(abs) {
|
||||
if (abs === '/mirror' || abs === '/mirror/') {
|
||||
return { ...synthesizeStat(abs, false, env, 'directory'), path: abs }
|
||||
@@ -2330,161 +2202,6 @@ export function createVfs(
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* `/.bare/holesail/**` on the personal drive: parent checks and traverse must walk **physical**
|
||||
* paths on that drive — logical `/.bare` may map to a different backing path under
|
||||
* {@link usePersonalAcctPrefix}, and `/` is skipped as a virtual mount point.
|
||||
*/
|
||||
async function lstatPersonalDrivePhysicalDir(physPath) {
|
||||
if (!personalDrive) return null
|
||||
const pnorm = String(physPath || '').replace(/\/+$/, '') || '/'
|
||||
if (pnorm === '/') {
|
||||
return { ...synthesizeStat('/', true, env, 'directory'), path: pnorm }
|
||||
}
|
||||
const e = await entryOn(personalDrive, pnorm, { follow: false })
|
||||
const fromVal = statFromEntryValue(
|
||||
pnorm,
|
||||
{ drive: personalDrive, path: pnorm },
|
||||
true,
|
||||
e
|
||||
)
|
||||
if (fromVal) return { ...fromVal, path: pnorm }
|
||||
const names = []
|
||||
try {
|
||||
const stream = personalDrive.readdir(pnorm)
|
||||
for await (const n of stream) names.push(n)
|
||||
} catch {
|
||||
/* missing parent */
|
||||
}
|
||||
if (names.length) {
|
||||
if (names.includes(DIR_MARKER)) {
|
||||
const mPath = `${pnorm}/${DIR_MARKER}`.replace(/\/{2,}/g, '/')
|
||||
const me = await entryOn(personalDrive, mPath, { follow: false })
|
||||
const mv = me?.value
|
||||
if (mv?.blob) {
|
||||
const bo = extractBareOs(mv)
|
||||
if (bo) {
|
||||
let perm = bo.mode & 0o777
|
||||
if (perm & 0o400) perm |= 0o100
|
||||
if (perm & 0o040) perm |= 0o010
|
||||
if (perm & 0o004) perm |= 0o001
|
||||
return statFromBareOs(
|
||||
{ ...bo, mode: S_IFDIR | perm },
|
||||
'directory',
|
||||
0,
|
||||
pnorm
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
return { ...synthesizeStat(pnorm, true, env, 'directory'), path: pnorm }
|
||||
}
|
||||
if (e) return { ...synthesizeStat(pnorm, true, env, 'directory'), path: pnorm }
|
||||
return null
|
||||
}
|
||||
|
||||
/** Directory or regular file (or symlink) on the personal drive by absolute path on that drive. */
|
||||
async function lstatPersonalDrivePhysicalAny(physPath) {
|
||||
if (!personalDrive) return null
|
||||
const pnorm = String(physPath || '').replace(/\/+$/, '') || '/'
|
||||
if (pnorm === '/') {
|
||||
return { ...synthesizeStat('/', true, env, 'directory'), path: pnorm }
|
||||
}
|
||||
const e = await entryOn(personalDrive, pnorm, { follow: false })
|
||||
const rf = statFromEntryValue(
|
||||
pnorm,
|
||||
{ drive: personalDrive, path: pnorm },
|
||||
true,
|
||||
e
|
||||
)
|
||||
if (rf) return rf
|
||||
return await lstatPersonalDrivePhysicalDir(pnorm)
|
||||
}
|
||||
|
||||
async function assertPersonalDriveAncestorWritableForCreate(physPath) {
|
||||
if (!personalDrive) {
|
||||
throw new Error('ENOENT: ' + String(physPath || ''))
|
||||
}
|
||||
const { uid: euid, gid: egid } = parseUidGid(env)
|
||||
const norm = String(physPath || '').replace(/\/+$/, '') || '/'
|
||||
let probe = dirnameAbs(norm)
|
||||
/** @type {{ pre: string, st: Awaited<ReturnType<typeof lstatFromAbs>> } | null} */
|
||||
let deepest = null
|
||||
while (probe && probe !== '/') {
|
||||
const st = await lstatPersonalDrivePhysicalDir(probe)
|
||||
if (st && st.type === 'directory') {
|
||||
deepest = { pre: probe, st }
|
||||
break
|
||||
}
|
||||
probe = dirnameAbs(probe)
|
||||
}
|
||||
if (!deepest) {
|
||||
deepest = {
|
||||
pre: '/',
|
||||
st: await lstatPersonalDrivePhysicalDir('/')
|
||||
}
|
||||
}
|
||||
if (!deepest.st || !modeAllows(deepest.st, euid, egid, 'w', 'directory')) {
|
||||
throw new Error('EACCES: cannot create in ' + String(deepest?.pre ?? ''))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} logicalAbs policy / guest messaging
|
||||
* @param {string} physPath path on {@link personalDrive}
|
||||
*/
|
||||
async function putPersonalDrivePhysical(logicalAbs, physPath, buf, opts = {}) {
|
||||
assertNotBootPolicyDenyVfs(logicalAbs, 'write')
|
||||
assertUnionWriteNotDenied(logicalAbs)
|
||||
if (!personalDrive) {
|
||||
throw new Error('Read-only path (not under $HOME): ' + logicalAbs)
|
||||
}
|
||||
if (await bareOsVfsAclDeniesDriveOp(env, personalDrive, physPath, 'write')) {
|
||||
throw new Error('EACCES: ACL enforces deny write: ' + logicalAbs)
|
||||
}
|
||||
assertGuestSensitivePersonalOp(personalDrive, physPath, 'write', logicalAbs)
|
||||
const existing = await entryOn(personalDrive, physPath, { follow: false })
|
||||
const hadBlob = !!existing?.value?.blob
|
||||
if (!hadBlob) await assertPersonalDriveAncestorWritableForCreate(physPath)
|
||||
const value = existing?.value
|
||||
const prevBare = extractBareOs(value)
|
||||
const bareOs = mergeBareOsOnWrite(prevBare, env, {
|
||||
executable: opts.executable,
|
||||
bumpMtime: opts.bumpMtime !== false,
|
||||
touchCtime: opts.touchCtime === true,
|
||||
legacyExecutable: !!value?.executable,
|
||||
mtimeMs: opts.mtimeMs,
|
||||
ctimeMs: opts.ctimeMs,
|
||||
posixModeBits: opts.posixModeBits
|
||||
})
|
||||
const executable =
|
||||
opts.executable !== undefined ? !!opts.executable : !!value?.executable
|
||||
const metadata = mergeEntryMetadata(value?.metadata, bareOs)
|
||||
return personalDrive.put(physPath, buf, { executable, metadata })
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure `/.bare` and `/.bare/holesail` DIR_MARKER entries exist on the personal drive for stable
|
||||
* Holesail paths (needed when `/` is skipped as a virtual mount point in parent checks, and when
|
||||
* `/.bare` is not the same backing path as `/.bare/holesail/**` under {@link usePersonalAcctPrefix}).
|
||||
*/
|
||||
async function ensureBareHolesailStablePersonalDirTree(physPath, logicalAbs) {
|
||||
if (!personalDrive) return
|
||||
const norm = String(physPath || '').replace(/\/+$/, '') || '/'
|
||||
const d = dirnameAbs(norm)
|
||||
if (d === '/' || d === '') return
|
||||
const physPrefs = pathPrefixes(d)
|
||||
const empty = new Uint8Array(0)
|
||||
for (let i = 1; i < physPrefs.length; i++) {
|
||||
const physPre = physPrefs[i]
|
||||
const st = await lstatPersonalDrivePhysicalDir(physPre)
|
||||
if (st) continue
|
||||
const markerPath =
|
||||
physPre === '/' ? `/${DIR_MARKER}` : `${physPre}/${DIR_MARKER}`
|
||||
await putPersonalDrivePhysical(logicalAbs, markerPath, empty, {})
|
||||
}
|
||||
}
|
||||
|
||||
async function assertParentWritableForCreate(abs) {
|
||||
const parent = dirnameAbs(abs)
|
||||
if (parent === abs) return
|
||||
|
||||
@@ -82,10 +82,13 @@ import {
|
||||
tryReportMisplacedReservedStatementStart,
|
||||
casePatternList,
|
||||
execDoubleBracketLimited,
|
||||
bareOsResetShellIdentityState
|
||||
bareOsResetShellIdentityState,
|
||||
consumeInteractiveHeredoc
|
||||
} from './lib/shell-stmt.js'
|
||||
import { createVfsRouteHelpers } from './lib/vfs-route.js'
|
||||
import { createBareOsPosixFdSimMethods } from './lib/bare-os-posix-fd-sim.js'
|
||||
import { createBareOsPathconfMethods } from './lib/bare-os-pathconf.js'
|
||||
import { createBareOsLogicalFdMethods } from './lib/bare-os-logical-fd.js'
|
||||
|
||||
test('posix env flags and caps', (t) => {
|
||||
t.ok(wantPosixSocketFdBridge({ BARE_OS_POSIX_SOCKET_FD_BRIDGE: '1' }))
|
||||
@@ -553,6 +556,79 @@ test('vfs route tilde mnt and ro alias', (t) => {
|
||||
t.ok(helpers.pseudoMountsText().includes('/mnt/data'))
|
||||
})
|
||||
|
||||
test('vfs route tmp home union snapshots', (t) => {
|
||||
const personal = { id: 'p' }
|
||||
const system = { id: 'sys', checkout: (v) => ({ id: 'chk' + v }) }
|
||||
const helpers = createVfsRouteHelpers({
|
||||
getCwd: () => '/home/g',
|
||||
normalizeHome: () => '/home/g',
|
||||
systemRoAliasNorm: '',
|
||||
systemDrive: system,
|
||||
personalDrive: personal,
|
||||
env: { BARE_OS_VFS_SNAPSHOTS: '1' },
|
||||
tmpStorageRoot: () => '/.bare-os/tmp/g',
|
||||
varLogStorageRoot: () => '/.bare-os/var/log/g',
|
||||
activeHomeBasename: () => 'g',
|
||||
personalHomeStorageRoot: () => '/.bare-os/home/g',
|
||||
usePersonalAcctPrefix: () => false
|
||||
})
|
||||
t.is(helpers.route('/tmp/x').path, '/.bare-os/tmp/g/x')
|
||||
t.ok(helpers.route('/var').virtualVarRoot)
|
||||
t.is(helpers.route('/home/g/a').drive, personal)
|
||||
t.is(helpers.route('/snapshots/system/3/etc').snapshotReadOnly, true)
|
||||
t.is(helpers.route('/.bare/holesail/state.json').path, '/.bare/holesail/state.json')
|
||||
})
|
||||
|
||||
test('pathconf and getconf helpers', (t) => {
|
||||
const m = createBareOsPathconfMethods({
|
||||
env: { BARE_OS_NPROC: '8', BARE_OS_PIPELINE_MAX_STAGES: '12' },
|
||||
resolveLogical: (p) => p,
|
||||
bootHrtimeNowNs: null
|
||||
})
|
||||
t.is(m.bareOsPathconf('/etc', 'NAME_MAX'), 255)
|
||||
t.is(m.bareOsPathconf('/mirror/x', '_PC_CHOWN_RESTRICTED'), 0)
|
||||
t.is(m.bareOsPathconf('/dev/shm/a', '_PC_NAME_MAX'), 128)
|
||||
t.is(m.bareOsGetconfSysconf('_SC_NPROCESSORS_ONLN'), '8')
|
||||
t.is(m.bareOsGetconfSysconf('_SC_PAGESIZE'), '4096')
|
||||
t.is(m.bareOsGetconfSysconf('_SC_BARE_OS_PIPELINE_MAX_STAGES'), '12')
|
||||
t.is(m.bareOsGetconfSysconf('_SC_MONOTONIC_CLOCK_RES'), '0')
|
||||
})
|
||||
|
||||
test('logical fd register and sigaction', (t) => {
|
||||
const ctx = {
|
||||
bareOsLogicalFds: {},
|
||||
bareOsLogicalFdFlags: { 9: 1 },
|
||||
bareOsLogicalSigaction: {},
|
||||
bareOsSnapshotHandles: [],
|
||||
...createBareOsLogicalFdMethods()
|
||||
}
|
||||
ctx.bareOsRegisterLogicalFd(9, '/tmp/x')
|
||||
t.is(ctx.bareOsLogicalFds['9'], '/tmp/x')
|
||||
ctx.bareOsUnregisterLogicalFd(9)
|
||||
t.is(ctx.bareOsLogicalFds['9'], undefined)
|
||||
t.is(ctx.bareOsSigaction('INT', 'IGNORE').mode, 'IGNORE')
|
||||
t.is(ctx.bareOsLogicalSigaction.INT, 'IGNORE')
|
||||
ctx.bareOsRegisterSnapshotHandle({ id: 's1' })
|
||||
t.is(ctx.bareOsSnapshotHandles[0].id, 's1')
|
||||
})
|
||||
|
||||
test('consumeInteractiveHeredoc reads until delim', async (t) => {
|
||||
const lines = ['one', 'two', 'END']
|
||||
const ctx = {
|
||||
readLine: async () => lines.shift(),
|
||||
vfs: { env: {} },
|
||||
console: { error: () => {} }
|
||||
}
|
||||
const r = await consumeInteractiveHeredoc(
|
||||
ctx,
|
||||
"cat <<END",
|
||||
() => {}
|
||||
)
|
||||
t.ok(r.ok)
|
||||
t.is(r.execLine, 'cat')
|
||||
t.is(ctx.shellHeredocOnce, 'one\ntwo\n')
|
||||
})
|
||||
|
||||
test('posix fd sim pipe write read', (t) => {
|
||||
const methods = createBareOsPosixFdSimMethods({
|
||||
env: { BARE_OS_POSIX_FD_SIM: '1' },
|
||||
|
||||
Reference in New Issue
Block a user