/** * POSIX-like syscall facade used as `ctx.bareOsSyscall`. * Factory closes over executeKernel runtime objects; method body uses `this` as ctx. */ import b4a from 'b4a' import { persistBareOsMountsSnapshot } from './bare-os-mounts-persist.js' import { bindFallbackEnabled, isAddrInUse, readListeningTcpPort } from './bare-os-bind-fallback.js' import { bareOsTryAcquireCooperativeLock, bareOsRemoveFcntlWaiter, bareOsDrainFcntlLockWaiters } from './bare-os-fcntl-cooperative-lock.js' import { wantPosixSocketFdBridge, wantPosixFcntlBlockingWait, bareOsPosixDgramRecvQueueMax, bareOsPosixDgramRecvBlockMsMax, bareOsPosixAcceptQueueMax, bareOsPosixAcceptBlockMsMax, bareOsFcntlLockWaitMsMax, bareOsResolveCooperativeLockPath } from './bare-os-posix-syscall-helpers.js' import { bareOsShutdownSocketBridgeFd, prepareBridgeSendmsg } from './bare-os-socket-bridge.js' /** * @param {{ * shellEnv: Record, * bootHrtimeNowNs: (() => bigint) | null, * bareLibrary: Record, * bareOsIpc: unknown, * hdmsController: unknown, * vfs: unknown * }} deps */ export function createBareOsSyscall(deps) { const { shellEnv, bootHrtimeNowNs, bareLibrary, bareOsIpc, hdmsController, vfs } = deps /** * Minimal syscall-shaped facade for POSIX-like userland adaptation. * @param {string} op * @param {Record} [args] */ return async function bareOsSyscall(op, args = {}) { const name = String(op || '').trim() if (!name) throw new Error('bareOsSyscall: op is required') if (name === 'pathconf') { const pconfName = String(args.name || args.key || '').trim() const v = this.bareOsPathconf(String(args.path || '/'), pconfName) return { name: pconfName, value: v } } if (name === 'fsync' || name === 'fdatasync') { return { ok: true, op: name, posixAlignment: 'partial', note: 'Guest logical fsync/fdatasync is a no-op; replication and block persistence are handled by the host Hyperdrive stack.' } } if (name === 'posix_fadvise') { const p = String(args.path || '').trim() if (!p.startsWith('/')) { throw new Error( 'bareOsSyscall: posix_fadvise: absolute path required' ) } return { ok: true, op: name, path: p, offset: Number(args.offset ?? 0) || 0, len: Number(args.len ?? args.length ?? 0) || 0, advice: Number(args.advice ?? args.adv ?? 0) || 0, posixAlignment: 'simulated', note: 'posix_fadvise is a logical no-op on Hyperdrive-backed paths; use for portable hints only.' } } if (name === 'nanosleep') { const sec = Number(args.seconds ?? args.tv_sec ?? 0) const nsec = Number(args.nanoseconds ?? args.tv_nsec ?? args.ns ?? 0) let ms = (Number.isFinite(sec) ? sec : 0) * 1000 + (Number.isFinite(nsec) ? nsec : 0) / 1e6 if (!Number.isFinite(ms) || ms < 0) ms = 0 const cap = 3_600_000 if (ms > cap) ms = cap await new Promise((r) => setTimeout(r, ms)) return { ok: true, op: name, sleptMs: Math.round(ms), posixAlignment: 'partial', note: 'POSIX-like delay via host setTimeout; capped at 3600s; not clock_nanosleep(2).' } } if (name === 'clock_gettime') { const raw = String( args.clockId ?? args.clock_id ?? args.id ?? 'CLOCK_MONOTONIC' ).toUpperCase() const isReal = raw === 'REALTIME' || raw === 'CLOCK_REALTIME' || raw === '0' || raw === 'REAL_TIME' let tvSec = 0 let tvNsec = 0 if (isReal) { const ms = Date.now() tvSec = Math.floor(ms / 1000) tvNsec = (ms % 1000) * 1e6 } else if (bootHrtimeNowNs) { const ns = bootHrtimeNowNs() const bn = BigInt.asUintN(64, ns) tvSec = Number(bn / 1000000000n) tvNsec = Number(bn % 1000000000n) } else { try { const perf = globalThis.performance if (perf && typeof perf.now === 'function') { const ms = perf.now() tvSec = Math.floor(ms / 1000) tvNsec = Math.round((ms % 1000) * 1e6) } else { const ms = Date.now() tvSec = Math.floor(ms / 1000) tvNsec = (ms % 1000) * 1e6 } } catch { const ms = Date.now() tvSec = Math.floor(ms / 1000) tvNsec = (ms % 1000) * 1e6 } } return { ok: true, op: name, clockId: raw, tv_sec: tvSec, tv_nsec: tvNsec, posixAlignment: 'partial', monotonicSource: bootHrtimeNowNs ? 'bare-hrtime.bigint' : globalThis.performance?.now ? 'performance.now' : 'Date.now', note: 'Partial clock_gettime: REALTIME wall; MONOTONIC prefers bare-hrtime else performance.now sketch; not CLOCK_TAI.' } } if (name === 'mq_open') { const ipc = bareOsIpc if (!ipc || typeof ipc.mqOpen !== 'function') { return { ok: false, op: name, code: 'ENOSYS', errnoHint: 'ENOSYS', note: 'IPC message queues unavailable' } } const qname = String(args.name || args.pathname || '').trim() if (!qname) { return { ok: false, op: name, code: 'EINVAL', errnoHint: 'EINVAL', note: 'mq_open: name required' } } const r = ipc.mqOpen(qname, { maxmsg: args.maxmsg, maxBytes: args.maxBytes }) return { ok: true, op: name, ...r } } if (name === 'mq_send') { const ipc = bareOsIpc if (!ipc || typeof ipc.mqSend !== 'function') { return { ok: false, op: name, code: 'ENOSYS', errnoHint: 'ENOSYS' } } const qname = String(args.name || '').trim() if (!qname) { return { ok: false, op: name, code: 'EINVAL', errnoHint: 'EINVAL' } } if (!ipc.mqAttrs(qname)) { ipc.mqOpen(qname, { maxmsg: args.maxmsg, maxBytes: args.maxBytes }) } let body = args.data ?? args.buf ?? args.buffer if (body == null) { return { ok: false, op: name, code: 'EINVAL', errnoHint: 'EINVAL', note: 'mq_send: data required' } } if (typeof body === 'string') body = b4a.from(body, 'utf8') if (!(body instanceof Uint8Array)) { return { ok: false, op: name, code: 'EINVAL', errnoHint: 'EINVAL' } } const prio = Number(args.prio) || 0 try { ipc.mqSend(qname, prio, body) return { ok: true, op: name, bytes: body.byteLength } } catch (e) { const em = e && /** @type {Error} */ (e).message if (em && (em.includes('full') || em.includes('exceeds'))) { return { ok: false, op: name, code: 'EAGAIN', errnoHint: 'EAGAIN', posixAlignment: 'partial', note: String(em) } } throw e } } if (name === 'mq_receive') { const ipc = bareOsIpc if (!ipc || typeof ipc.mqReceive !== 'function') { return { ok: false, op: name, code: 'ENOSYS', errnoHint: 'ENOSYS' } } const qname = String(args.name || '').trim() if (!qname) { return { ok: false, op: name, code: 'EINVAL', errnoHint: 'EINVAL' } } const row = ipc.mqReceive(qname) if (!row) { return { ok: false, op: name, code: 'EAGAIN', errnoHint: 'EAGAIN', posixAlignment: 'partial', note: 'mq_receive: queue empty or unknown (mq_open first)' } } return { ok: true, op: name, prio: row.prio, data: row.data, bytesReceived: row.data.byteLength } } if (name === 'readFile') { const p = String(args.path || '').trim() if (!p.startsWith('/')) throw new Error('bareOsSyscall: absolute path required') return vfs.readFile(p) } if (name === 'writeFile') { const p = String(args.path || '').trim() if (!p.startsWith('/')) throw new Error('bareOsSyscall: absolute path required') const d = args.data const buf = typeof d === 'string' ? b4a.from(d, 'utf8') : d instanceof Uint8Array ? d : d instanceof ArrayBuffer ? new Uint8Array(d) : null if (!buf) throw new Error( 'bareOsSyscall: writeFile data must be string or bytes' ) await vfs.writeFile(p, buf) return { ok: true, path: p, bytes: buf.byteLength } } if (name === 'readdir') { const p = String(args.path || '').trim() if (!p.startsWith('/')) throw new Error('bareOsSyscall: absolute path required') return vfs.readdir(p) } if (name === 'mkdir') { const p = String(args.path || '').trim() if (!p.startsWith('/')) throw new Error('bareOsSyscall: absolute path required') const recursive = !!args.recursive const mode = args.mode == null ? undefined : Number.parseInt(String(args.mode), 10) await vfs.mkdir( p, Number.isFinite(mode) ? { recursive, mode: Math.max(0, Math.min(0o777, mode)) } : { recursive } ) return { ok: true, path: p } } if (name === 'stat') { const p = String(args.path || '').trim() if (!p.startsWith('/')) throw new Error('bareOsSyscall: absolute path required') return vfs.stat(p) } if (name === 'unlink') { const p = String(args.path || '').trim() if (!p.startsWith('/')) throw new Error('bareOsSyscall: absolute path required') await vfs.unlink(p) return { ok: true, path: p } } if (name === 'chmod') { const p = String(args.path || '').trim() if (!p.startsWith('/')) throw new Error('bareOsSyscall: absolute path required') const mode = Number.parseInt(String(args.mode || ''), 8) if (!Number.isFinite(mode)) throw new Error('bareOsSyscall: chmod mode is required') await vfs.chmod(p, mode) return { ok: true, path: p, mode: mode & 0o777 } } if (name === 'chdir') { const p = String(args.path || '').trim() if (!p.startsWith('/')) throw new Error('bareOsSyscall: absolute path required') await vfs.chdir(p) return { ok: true, cwd: vfs.getcwd() } } if (name === 'getcwd') { return { cwd: vfs.getcwd() } } if (name === 'readlink') { const p = String(args.path || '').trim() if (!p.startsWith('/')) throw new Error('bareOsSyscall: absolute path required') const out = await vfs.readlink(p) return { path: p, target: out } } if (name === 'symlink') { const linkPath = String(args.path || '').trim() const target = String(args.target || '').trim() if (!linkPath.startsWith('/') || !target) { throw new Error( 'bareOsSyscall: symlink requires absolute path and target' ) } await vfs.symlink(target, linkPath) return { ok: true, path: linkPath, target } } if (name === 'exists') { const p = String(args.path || '').trim() if (!p.startsWith('/')) throw new Error('bareOsSyscall: absolute path required') return { path: p, exists: await vfs.exists(p) } } if (name === 'lstat') { const p = String(args.path || '').trim() if (!p.startsWith('/')) throw new Error('bareOsSyscall: absolute path required') return vfs.lstat(p) } if (name === 'rmdir') { const p = String(args.path || '').trim() if (!p.startsWith('/')) throw new Error('bareOsSyscall: absolute path required') await vfs.rmdir(p) return { ok: true, path: p } } if (name === 'rename') { const from = String(args.from || args.path || '').trim() const to = String(args.to || '').trim() if (!from.startsWith('/') || !to.startsWith('/')) { throw new Error( 'bareOsSyscall: rename requires absolute from and to paths' ) } if (!(await vfs.exists(from))) { throw new Error('bareOsSyscall: rename: source not found') } const buf = await vfs.readFile(from) const u8 = buf instanceof Uint8Array ? buf : buf != null ? b4a.from(buf) : b4a.alloc(0) await vfs.writeFile(to, u8) await vfs.unlink(from) return { ok: true, from, to } } if (name === 'link') { const strict = shellEnv.BARE_OS_VFS_STRICT_HARDLINK === '1' || shellEnv.BARE_OS_VFS_STRICT_HARDLINK === 'true' if (strict) { const err = /** @type {Error & { code?: string }} */ ( new Error( 'EOPNOTSUPP: hard links are not supported on Hyperdrive (unset BARE_OS_VFS_STRICT_HARDLINK for legacy copy semantics)' ) ) err.code = 'EOPNOTSUPP' throw err } const existing = String(args.existing || args.path || '').trim() const newPath = String(args.newPath || args.to || '').trim() if (!existing.startsWith('/') || !newPath.startsWith('/')) { throw new Error('bareOsSyscall: link requires absolute paths') } if (!(await vfs.exists(existing))) { throw new Error('bareOsSyscall: link: source not found') } const buf = await vfs.readFile(existing) const u8 = buf instanceof Uint8Array ? buf : buf != null ? b4a.from(buf) : b4a.alloc(0) await vfs.writeFile(newPath, u8) return { ok: true, existing, newPath, note: 'Data copied; Hyperdrive has no shared inode hard links.' } } if (name === 'access') { const p = String(args.path || '').trim() if (!p.startsWith('/')) throw new Error('bareOsSyscall: absolute path required') const ok = await vfs.exists(p) if (!ok) throw new Error('ENOENT: access ' + p) return { ok: true, path: p } } if (name === 'utimes') { const p = String(args.path || '').trim() if (!p.startsWith('/')) throw new Error('bareOsSyscall: absolute path required') const mtimeMs = Number(args.mtimeMs ?? args.mtime) if (!Number.isFinite(mtimeMs)) { throw new Error('bareOsSyscall: utimes requires numeric mtimeMs') } let cur = await vfs.readFile(p) const u8 = cur instanceof Uint8Array ? cur : cur != null ? b4a.from(cur) : b4a.alloc(0) await vfs.writeFile(p, u8, { mtimeMs, bumpMtime: true }) return { ok: true, path: p, mtimeMs } } if (name === 'truncate' || name === 'ftruncate') { const p = String(args.path || '').trim() if (!p.startsWith('/')) throw new Error('bareOsSyscall: absolute path required') const len = Number(args.length ?? args.len ?? 0) if (!Number.isFinite(len) || len < 0) { throw new Error( 'bareOsSyscall: truncate length must be a non-negative number' ) } let cur = await vfs.readFile(p) let u8 = cur instanceof Uint8Array ? cur : cur != null ? b4a.from(cur) : b4a.alloc(0) if (u8.byteLength > len) { u8 = u8.subarray(0, len) } else if (u8.byteLength < len) { u8 = b4a.concat([u8, b4a.alloc(len - u8.byteLength)]) } await vfs.writeFile(p, u8) return { ok: true, path: p, length: len } } if (name === 'mount') { const label = String(args.label || '').trim() const source = String(args.source || '').trim() if (!/^[a-zA-Z0-9][a-zA-Z0-9._-]{0,62}$/.test(label)) { throw new Error('bareOsSyscall: mount label is invalid') } if (hdmsController.byLabel.has(label)) { throw new Error('bareOsSyscall: mount label already mounted') } if (source === 'local') { await hdmsController.create(this, label) } else if (source) { await hdmsController.addReadonly(this, label, source) } else { throw new Error('bareOsSyscall: mount source is required') } await persistBareOsMountsSnapshot(vfs, hdmsController, { console: this.console }) return { ok: true, label, source } } if (name === 'umount') { const label = String(args.label || '').trim() const force = !!args.force if (!/^[a-zA-Z0-9][a-zA-Z0-9._-]{0,62}$/.test(label)) { throw new Error('bareOsSyscall: umount label is invalid') } if (!force && typeof vfs.readdir === 'function') { try { const mntPath = `/mnt/${label}` const list = await vfs.readdir(mntPath) if ( Array.isArray(list) && list.some((n) => n && n !== '.' && n !== '..') ) { throw new Error( 'umount: target is busy (use umount -f or remove files under ' + mntPath + ')' ) } } catch (e) { const msg = e?.message || String(e) if (msg.includes('busy')) throw e /* ENOENT → not mounted at VFS layer; still try HDMS remove */ } } await hdmsController.remove(this, label) await persistBareOsMountsSnapshot(vfs, hdmsController, { console: this.console }) return { ok: true, label } } if (name === 'fsync' || name === 'fdatasync') { const p = String(args.path || '').trim() if (!p.startsWith('/')) { throw new Error('bareOsSyscall: absolute path required') } if (typeof vfs.fsync === 'function') await vfs.fsync(p) return { ok: true, path: p, op: name, note: 'Best-effort no-op unless the VFS exposes a flush primitive.' } } if (name === 'posix_fadvise') { const p = String(args.path || '').trim() if (!p.startsWith('/')) { throw new Error( 'bareOsSyscall: posix_fadvise: absolute path required' ) } return { ok: true, path: p, op: name, offset: Number(args.offset ?? 0) || 0, len: Number(args.len ?? args.length ?? 0) || 0, advice: Number(args.advice ?? args.adv ?? 0) || 0, posixAlignment: 'simulated', note: 'posix_fadvise is a logical no-op; documents Issue 7-style advice without host page-cache control.' } } if (name === 'fcntl') { const O_ACCMODE = 3 const O_APPEND = 0x400 const O_NONBLOCK = 0x800 const SETFL_MASK = O_APPEND | O_NONBLOCK const fd = Number(args.fd) if (!Number.isFinite(fd) || fd < 0 || fd > 65535) { throw new Error('bareOsSyscall: fcntl: invalid fd') } const k = String(fd >>> 0) const target = this.bareOsLogicalFds[k] const isStd = k === '0' || k === '1' || k === '2' if (!isStd && (target == null || target === '')) { const err = /** @type {Error & { code?: string }} */ ( new Error('EBADF: fcntl') ) err.code = 'EBADF' throw err } const baseAcc = () => { if (k === '0') return 0 if (k === '1' || k === '2') return 2 const s = String(target || '') if (/posix-pipe:\d+:r$/.test(s)) return 0 if (/posix-pipe:\d+:w$/.test(s)) return 1 return 2 } let cmd = args.cmd if (typeof cmd === 'string') { const u = String(cmd).toUpperCase() if (u === 'F_GETLK' || u === 'F_SETLK' || u === 'F_SETLKW') { const lockPath = bareOsResolveCooperativeLockPath(args, this) if (!lockPath) { return { ok: false, op: 'fcntl', cmd: u, code: 'EINVAL', errnoHint: 'EINVAL', cooperative: true, note: 'Cooperative advisory locks require args.path (absolute) or args.fd whose logical target is an absolute path.' } } if ( !this.bareOsCooperativeLocks || typeof this.bareOsCooperativeLocks !== 'object' ) { this.bareOsCooperativeLocks = Object.create(null) } const locks = /** @type {Record; writer: string | null }>} */ ( this.bareOsCooperativeLocks ) const owner = String( (this.env && this.env.BARE_OS_SESSION_ID) || 'session' ) const flock = args.flock && typeof args.flock === 'object' ? /** @type {Record} */ (args.flock) : {} let lType = flock.l_type ?? flock.lType ?? args.l_type ?? args.lType if (typeof lType === 'string') { const up = String(lType).toUpperCase() if (up === 'F_RDLCK' || up === 'RDLCK') lType = 0 else if (up === 'F_WRLCK' || up === 'WRLCK') lType = 1 else if (up === 'F_UNLCK' || up === 'UNLCK') lType = 2 } lType = Number(lType) if (!Number.isFinite(lType)) lType = 0 function ensureSlot(path) { let s = locks[path] if (!s) { s = { readers: new Set(), writer: null } locks[path] = s } return s } if (u === 'F_GETLK') { const slot = locks[lockPath] if (!slot || (!slot.writer && slot.readers.size === 0)) { return { ok: true, op: 'fcntl', cmd: 'F_GETLK', path: lockPath, cooperative: true, flock: { l_type: 'F_UNLCK' } } } if (slot.writer && slot.writer !== owner) { return { ok: true, op: 'fcntl', cmd: 'F_GETLK', path: lockPath, cooperative: true, flock: { l_type: 'F_WRLCK', l_pid: 1 } } } for (const sid of slot.readers) { if (sid !== owner && lType === 1) { return { ok: true, op: 'fcntl', cmd: 'F_GETLK', path: lockPath, cooperative: true, flock: { l_type: 'F_RDLCK', l_pid: 1 } } } } return { ok: true, op: 'fcntl', cmd: 'F_GETLK', path: lockPath, cooperative: true, flock: { l_type: 'F_UNLCK' } } } const slot = ensureSlot(lockPath) if (lType === 2) { if (slot.writer === owner) slot.writer = null slot.readers.delete(owner) bareOsDrainFcntlLockWaiters(this, lockPath) return { ok: true, op: 'fcntl', cmd: u, path: lockPath, cooperative: true, unlocked: true } } const wantWrite = lType === 1 if (bareOsTryAcquireCooperativeLock(slot, owner, wantWrite)) { return { ok: true, op: 'fcntl', cmd: u, path: lockPath, cooperative: true, locked: wantWrite ? 'write' : 'read' } } const blocking = u === 'F_SETLKW' && wantPosixFcntlBlockingWait(this.env) if (!blocking) { return { ok: false, op: 'fcntl', cmd: u, code: 'EAGAIN', errnoHint: 'EAGAIN', cooperative: true, note: wantWrite ? 'Write lock held by another session.' : 'Read or write lock held by another session.' } } const maxMs = bareOsFcntlLockWaitMsMax(this.env) return await new Promise((resolve) => { const entry = { resolve, timer: /** @type {ReturnType | null} */ ( null ), owner, wantWrite } entry.timer = setTimeout(() => { bareOsRemoveFcntlWaiter(this, lockPath, entry) resolve({ ok: false, op: 'fcntl', cmd: 'F_SETLKW', code: 'ETIMEDOUT', errnoHint: 'ETIMEDOUT', cooperative: true, note: 'F_SETLKW wait exceeded BARE_OS_FCNTL_LOCK_WAIT_MS_MAX (or default 30000).' }) }, maxMs) if ( !this.bareOsFcntlLockWaitQueues || typeof this.bareOsFcntlLockWaitQueues !== 'object' ) { this.bareOsFcntlLockWaitQueues = Object.create(null) } const wq = this.bareOsFcntlLockWaitQueues if (!Array.isArray(wq[lockPath])) wq[lockPath] = [] wq[lockPath].push(entry) }) } if (u === 'F_GETFL' || u === 'GETFL') cmd = 3 else if (u === 'F_SETFL' || u === 'SETFL') cmd = 4 else { const err = /** @type {Error & { code?: string }} */ ( new Error('ENOTSUP: fcntl cmd') ) err.code = 'ENOTSUP' throw err } } cmd = Number(cmd) if (cmd === 3) { const acc = baseAcc() & O_ACCMODE const extra = (this.bareOsLogicalFdFlags[k] || 0) & SETFL_MASK return { ok: true, op: 'fcntl', cmd: 'F_GETFL', fd, flags: acc | extra } } if (cmd === 4) { if ( !this.bareOsLogicalFdFlags || typeof this.bareOsLogicalFdFlags !== 'object' ) { this.bareOsLogicalFdFlags = {} } const arg = Number(args.arg ?? args.value ?? 0) >>> 0 this.bareOsLogicalFdFlags[k] = arg & SETFL_MASK const acc = baseAcc() & O_ACCMODE const extra = this.bareOsLogicalFdFlags[k] & SETFL_MASK return { ok: true, op: 'fcntl', cmd: 'F_SETFL', fd, flags: acc | extra } } const err2 = /** @type {Error & { code?: string }} */ ( new Error('ENOTSUP: fcntl cmd') ) err2.code = 'ENOTSUP' throw err2 } if (name === 'select') { const readfds = Array.isArray(args.readfds) ? args.readfds : [] const writefds = Array.isArray(args.writefds) ? args.writefds : [] /** @type {Map} */ const ev = new Map() for (const x of readfds) { const fd = Number(x) if (Number.isFinite(fd)) ev.set(fd, (ev.get(fd) || '') + 'r') } for (const x of writefds) { const fd = Number(x) if (Number.isFinite(fd)) ev.set(fd, (ev.get(fd) || '') + 'w') } const fds = [...ev.entries()].map(([fd, events]) => ({ fd, events })) const timeoutMs = Math.max( 0, Math.min(60000, Math.floor(Number(args.timeoutMs) || 0)) ) const pr = timeoutMs > 0 ? await this.bareOsPosixPoll({ fds, timeoutMs }) : this.bareOsPosixPollProbe({ fds }) const readSet = new Set( readfds.map((x) => Number(x)).filter(Number.isFinite) ) const writeSet = new Set( writefds.map((x) => Number(x)).filter(Number.isFinite) ) const readReady = [] const writeReady = [] for (const row of pr.ready || []) { const fd = row.fd const rv = String(row.revents || '') if (readSet.has(fd) && rv.includes('r')) readReady.push(fd) if (writeSet.has(fd) && rv.includes('w')) writeReady.push(fd) } return { ok: true, op: 'select', readReady, writeReady, timedOut: pr.timedOut === true, waitedMs: pr.waitedMs ?? 0, pollClock: pr.pollClock } } if (name === 'umask') { const env = this.vfs?.env if (!env || typeof env !== 'object') { throw new Error('bareOsSyscall: umask: vfs.env unavailable') } const curRaw = String(env.UMASK || '022').trim() const curMask = Number.parseInt(curRaw, 8) const validCur = Number.isFinite(curMask) && curMask >= 0 && curMask <= 0o777 if ( args.set !== undefined && args.set !== null && String(args.set).trim() !== '' ) { const next = Number.parseInt(String(args.set), 8) if (!Number.isFinite(next) || next < 0 || next > 0o777) { throw new Error('bareOsSyscall: umask: invalid mask') } env.UMASK = next.toString(8) } const nowMask = Number.parseInt(String(env.UMASK || '022'), 8) return { ok: true, op: 'umask', previous: validCur ? curMask : 0o022, current: Number.isFinite(nowMask) ? nowMask : 0o022 } } if (name === 'readv') { const fd = Number(args.fd) if (!Number.isFinite(fd)) { throw new Error('bareOsSyscall: readv: fd is required') } const iovs = Array.isArray(args.iovs) ? args.iovs : [] const maxIovs = 16 const maxPer = 65536 const maxTotal = 1024 * 1024 let totalBytes = 0 /** @type {string[]} */ const buffers = [] for (const iov of iovs.slice(0, maxIovs)) { const want = Math.min( maxPer, Math.max( 0, Math.floor( Number( iov && typeof iov === 'object' && 'length' in iov ? iov.length : iov && typeof iov === 'object' && 'maxBytes' in iov ? iov.maxBytes : 4096 ) ) ) ) if (want <= 0) { buffers.push('') continue } if (totalBytes + want > maxTotal) { throw new Error('bareOsSyscall: readv: total iov budget exceeded') } const r = this.bareOsPosixFdSimRead(fd, want) if (!r || !r.ok) { if (r && r.reason === 'EAGAIN') { const err = /** @type {Error & { code?: string }} */ ( new Error('EAGAIN: readv') ) err.code = 'EAGAIN' throw err } throw new Error( 'bareOsSyscall: readv: ' + (r && r.reason ? r.reason : 'failed') ) } const chunk = String(r.data || '') buffers.push(chunk) totalBytes += b4a.from(chunk, 'utf8').byteLength if (chunk.length < want) break } return { ok: true, op: 'readv', fd, buffers, bytesRead: totalBytes } } if (name === 'writev') { const fd = Number(args.fd) if (!Number.isFinite(fd)) { throw new Error('bareOsSyscall: writev: fd is required') } const bufs = Array.isArray(args.buffers) ? args.buffers : [] const maxChunks = 64 const maxTotal = 1024 * 1024 const pieces = [] let n = 0 for (const b of bufs.slice(0, maxChunks)) { const s = typeof b === 'string' ? b : b instanceof Uint8Array ? b4a.toString(b, 'utf8') : String(b) const add = b4a.from(s, 'utf8').byteLength n += add if (n > maxTotal) { throw new Error('bareOsSyscall: writev: total length exceeded') } pieces.push(s) } const payload = pieces.join('') const w = this.bareOsPosixFdSimWrite(fd, payload) if (!w || !w.ok) { if (w && w.reason === 'EAGAIN') { const err = /** @type {Error & { code?: string }} */ ( new Error('EAGAIN: writev') ) err.code = 'EAGAIN' throw err } throw new Error( 'bareOsSyscall: writev: ' + (w && w.reason ? w.reason : 'failed') ) } return { ok: true, op: 'writev', fd, bytesWritten: w.bytes ?? b4a.from(payload, 'utf8').byteLength } } if (name === 'getsockopt' || name === 'setsockopt') { const SOL_SOCKET = 1 const SO_KEEPALIVE = 9 const SO_SNDBUF = 7 const SO_RCVBUF = 8 const IPPROTO_TCP = 6 const TCP_NODELAY = 1 const DEF_SNDBUF = 262144 const DEF_RCVBUF = 262144 const fd = Number(args.fd) if ( wantPosixSocketFdBridge(shellEnv) && Number.isFinite(fd) && this.bareOsSocketBridgeByFd && typeof this.bareOsSocketBridgeByFd === 'object' ) { const k = String(fd >>> 0) const slot = this.bareOsSocketBridgeByFd[k] if (slot && typeof slot === 'object') { if (!slot.bridgeSockOpts) { slot.bridgeSockOpts = { soKeepalive: 1, tcpNodelay: 0, sndbuf: DEF_SNDBUF, rcvbuf: DEF_RCVBUF } } else { if (slot.bridgeSockOpts.sndbuf == null) slot.bridgeSockOpts.sndbuf = DEF_SNDBUF if (slot.bridgeSockOpts.rcvbuf == null) slot.bridgeSockOpts.rcvbuf = DEF_RCVBUF } const level = Number(args.level) >>> 0 const optname = Number(args.optname) >>> 0 if (name === 'getsockopt') { let intVal = 0 if (level === SOL_SOCKET && optname === SO_KEEPALIVE) { intVal = slot.bridgeSockOpts.soKeepalive ? 1 : 0 } else if (level === SOL_SOCKET && optname === SO_SNDBUF) { intVal = Math.max( 4096, Number(slot.bridgeSockOpts.sndbuf) || DEF_SNDBUF ) } else if (level === SOL_SOCKET && optname === SO_RCVBUF) { intVal = Math.max( 4096, Number(slot.bridgeSockOpts.rcvbuf) || DEF_RCVBUF ) } else if (level === IPPROTO_TCP && optname === TCP_NODELAY) { intVal = slot.bridgeSockOpts.tcpNodelay ? 1 : 0 } else { return { ok: false, op: name, fd, code: 'ENOPROTOOPT', errnoHint: 'ENOPROTOOPT', posixAlignment: 'partial', note: 'Bridge supports SOL_SOCKET/SO_KEEPALIVE, SO_SNDBUF, SO_RCVBUF, and IPPROTO_TCP/TCP_NODELAY.' } } return { ok: true, op: name, fd, level, optname, intValue: intVal, posixAlignment: 'partial', note: 'Logical socket-bridge option read; does not change host UDX/TCP stack.' } } const v = Number(args.value ?? args.optval ?? args.intValue ?? 0) const on = Number.isFinite(v) && v !== 0 ? 1 : 0 if (level === SOL_SOCKET && optname === SO_KEEPALIVE) { slot.bridgeSockOpts.soKeepalive = on } else if (level === IPPROTO_TCP && optname === TCP_NODELAY) { slot.bridgeSockOpts.tcpNodelay = on } else if (level === SOL_SOCKET && optname === SO_SNDBUF) { const n = Math.floor(Number(v)) if (!Number.isFinite(n) || n < 4096) { return { ok: false, op: name, fd, code: 'EINVAL', errnoHint: 'EINVAL', posixAlignment: 'partial', note: 'SO_SNDBUF must be >= 4096' } } slot.bridgeSockOpts.sndbuf = Math.min(n, 4 * 1024 * 1024) } else if (level === SOL_SOCKET && optname === SO_RCVBUF) { const n = Math.floor(Number(v)) if (!Number.isFinite(n) || n < 4096) { return { ok: false, op: name, fd, code: 'EINVAL', errnoHint: 'EINVAL', posixAlignment: 'partial', note: 'SO_RCVBUF must be >= 4096' } } slot.bridgeSockOpts.rcvbuf = Math.min(n, 4 * 1024 * 1024) } else { return { ok: false, op: name, fd, code: 'ENOPROTOOPT', errnoHint: 'ENOPROTOOPT', posixAlignment: 'partial', note: 'Bridge setsockopt: SOL_SOCKET/SO_KEEPALIVE, SO_SNDBUF, SO_RCVBUF, and IPPROTO_TCP/TCP_NODELAY.' } } return { ok: true, op: name, fd, level, optname, posixAlignment: 'partial' } } } return { ok: false, op: name, code: 'ENOSYS', errnoHint: 'ENOSYS', posixAlignment: 'ENOTSUP', note: 'getsockopt/setsockopt need BARE_OS_POSIX_SOCKET_FD_BRIDGE and a bridge socket fd; else ENOSYS.' } } if ( name === 'socket' || name === 'bind' || name === 'listen' || name === 'accept' || name === 'connect' || name === 'send' || name === 'sendmsg' || name === 'recv' || name === 'recvfrom' || name === 'recvmsg' || name === 'shutdown' ) { if (wantPosixSocketFdBridge(shellEnv)) { const st = this.bareOsPosixFdSimState const bridgeNotify = (slot, kind) => { const key = kind === 'accept' ? 'acceptWaiters' : 'recvWaiters' const waiters = slot && Array.isArray(slot[key]) ? slot[key] : null if (!waiters || waiters.length === 0) return const pending = waiters.splice(0, waiters.length) for (const resolve of pending) { try { resolve() } catch { /* ignore */ } } } const bridgeWait = (slot, timeoutMs, kind) => new Promise((resolve) => { const key = kind === 'accept' ? 'acceptWaiters' : 'recvWaiters' if (!slot[key] || !Array.isArray(slot[key])) slot[key] = [] let settled = false const done = () => { if (settled) return settled = true const waiters = slot[key] if (Array.isArray(waiters)) { const idx = waiters.indexOf(done) if (idx >= 0) waiters.splice(idx, 1) } resolve() } slot[key].push(done) const ms = Math.max(1, Math.min(100, Number(timeoutMs) || 10)) setTimeout(done, ms) }) if (name === 'socket') { const fd = st.nextFd++ const k = String(fd) if ( !this.bareOsSocketBridgeByFd || typeof this.bareOsSocketBridgeByFd !== 'object' ) { this.bareOsSocketBridgeByFd = Object.create(null) } const sockType = Number(args.type ?? args.socktype ?? 1) >>> 0 this.bareOsSocketBridgeByFd[k] = { domain: Number(args.domain ?? args.af ?? 2), type: sockType, sockType, state: 'created', recvWaiters: [], acceptWaiters: [], aliasKeys: new Set([k]) } this.bareOsRegisterLogicalFd(fd, 'bare-os-socket-bridge:idle') return { ok: true, op: name, fd, bridge: true, posixAlignment: 'partial', note: sockType === 2 ? 'Optional POSIX socket FD bridge (SOCK_DGRAM): bind(port,host) for passive UDP, or connect(port,host) for connected mode, when ctx.bare exposes bareDgram.Socket (UDX).' : 'Optional POSIX socket FD bridge (SOCK_STREAM): follow with connect(port, host) when ctx.bare exposes bareTcp.Socket.' } } if (name === 'connect') { const fd = Number(args.fd) if (!Number.isFinite(fd)) { throw new Error('bareOsSyscall: connect: fd required') } const k = String(fd >>> 0) const slot = this.bareOsSocketBridgeByFd && typeof this.bareOsSocketBridgeByFd === 'object' ? this.bareOsSocketBridgeByFd[k] : null const port = Number(args.port) const host = String(args.host || args.address || '127.0.0.1').trim() if (!Number.isFinite(port) || port <= 0 || port > 65535) { return { ok: false, op: name, code: 'EINVAL', errnoHint: 'EINVAL' } } if (!slot) { return { ok: false, op: name, code: 'EINVAL', errnoHint: 'EINVAL', note: 'connect: unknown bridge fd.' } } const isDgram = Number(slot.sockType ?? slot.type ?? 1) === 2 const dgramBound = isDgram && slot.state === 'udp_bound' && slot.sock if (!isDgram && slot.state !== 'created') { return { ok: false, op: name, code: 'ENOTSUP', errnoHint: 'ENOTSUP', posixAlignment: 'ENOTSUP', note: 'connect: requires prior bridge socket(2) fd in created state.' } } if (isDgram && slot.state !== 'created' && !dgramBound) { return { ok: false, op: name, code: 'EINVAL', errnoHint: 'EINVAL', posixAlignment: 'ENOTSUP', note: 'connect: SOCK_DGRAM bridge requires created socket or bind(2) before connect on the same fd.' } } const Tcp = bareLibrary.bareTcp && bareLibrary.bareTcp.Socket && typeof bareLibrary.bareTcp.Socket === 'function' ? bareLibrary.bareTcp.Socket : null const dg = bareLibrary.bareDgram const UdpSocket = dg && ((typeof dg.Socket === 'function' && dg.Socket) || (dg.default && typeof dg.default.Socket === 'function' && dg.default.Socket)) ? dg.Socket || dg.default.Socket : null if (isDgram) { if (!UdpSocket) { return { ok: false, op: name, code: 'ENOTSUP', errnoHint: 'ENOTSUP', posixAlignment: 'ENOTSUP', note: 'SOCK_DGRAM bridge requires bareDgram.Socket merged in ctx.bare (bare-dgram / UDX).' } } if (dgramBound) { const usock = /** @type {{ connect: Function }} */ (slot.sock) await new Promise((resolve, reject) => { try { usock.connect(port, host || '127.0.0.1', (err) => err ? reject(err) : resolve(undefined) ) } catch (e) { reject(e) } }) slot.state = 'connected' this.bareOsLogicalFds[k] = 'bare-os-socket-bridge:udp:' + (host || '127.0.0.1') + ':' + port return { ok: true, op: name, fd, bridge: true, posixAlignment: 'partial', transport: 'udp' } } const usock = new UdpSocket({}) await new Promise((resolve, reject) => { try { usock.connect(port, host || '127.0.0.1', (err) => err ? reject(err) : resolve(undefined) ) } catch (e) { reject(e) } }) slot.state = 'connected' slot.sock = usock slot.transport = 'udp' slot.dgramRecvQueue = [] slot.dgramRecvDropped = 0 const qmax = bareOsPosixDgramRecvQueueMax(shellEnv) usock.on('message', (message, rinfo) => { let u8 = message instanceof Uint8Array ? message : b4a.from(message) if (u8.byteLength > 1024 * 1024) u8 = u8.slice(0, 1024 * 1024) if (slot.dgramRecvQueue.length >= qmax) { slot.dgramRecvDropped = (slot.dgramRecvDropped || 0) + 1 return } slot.dgramRecvQueue.push({ buf: u8.slice(), rinfo: rinfo && typeof rinfo === 'object' ? { address: String(rinfo.address || ''), port: Number(rinfo.port) >>> 0 } : null }) bridgeNotify(slot, 'recv') }) this.bareOsLogicalFds[k] = 'bare-os-socket-bridge:udp:' + (host || '127.0.0.1') + ':' + port return { ok: true, op: name, fd, bridge: true, posixAlignment: 'partial', transport: 'udp' } } if (!Tcp) { return { ok: false, op: name, code: 'ENOTSUP', errnoHint: 'ENOTSUP', posixAlignment: 'ENOTSUP', note: 'connect: SOCK_STREAM bridge requires bareTcp.Socket merged in ctx.bare (bare-tcp bundle).' } } const connectHosts = host === 'localhost' ? ['127.0.0.1', '::1'] : [host || '127.0.0.1'] /** @type {unknown[]} */ const connectErrors = [] /** @type {any} */ let sock = null const connectTimeoutMs = Math.min( 300000, Math.max( 0, Number.parseInt( String( shellEnv.BARE_OS_POSIX_SOCKET_CONNECT_TIMEOUT_MS || '0' ).trim(), 10 ) || 0 ) ) try { for (const candHost of connectHosts) { const candSock = new Tcp() const connectPromise = new Promise((resolve, reject) => { try { candSock.connect( port, candHost || '127.0.0.1', {}, (err) => (err ? reject(err) : resolve(undefined)) ) } catch (e) { reject(e) } }) try { if (connectTimeoutMs > 0) { /** @type {ReturnType | null} */ let to = null const timeoutP = new Promise((_, reject) => { to = setTimeout( () => reject( Object.assign(new Error('ETIMEDOUT'), { code: 'ETIMEDOUT' }) ), connectTimeoutMs ) }) await Promise.race([connectPromise, timeoutP]).finally( () => { if (to) clearTimeout(to) } ) } else { await connectPromise } sock = candSock break } catch (e) { connectErrors.push(e) try { candSock.destroy?.() } catch { /* ignore */ } } } } catch (e) { try { sock.destroy?.() } catch { /* ignore */ } if ( e && /** @type {{ code?: string }} */ (e).code === 'ETIMEDOUT' ) { return { ok: false, op: name, code: 'ETIMEDOUT', errnoHint: 'ETIMEDOUT', posixAlignment: 'partial', note: `connect: timed out after ${connectTimeoutMs}ms (BARE_OS_POSIX_SOCKET_CONNECT_TIMEOUT_MS).` } } throw e } if (!sock) { const first = connectErrors[0] if ( first && /** @type {{ code?: string }} */ (first).code === 'ETIMEDOUT' ) { return { ok: false, op: name, code: 'ETIMEDOUT', errnoHint: 'ETIMEDOUT', posixAlignment: 'partial', note: `connect: timed out after ${connectTimeoutMs}ms (BARE_OS_POSIX_SOCKET_CONNECT_TIMEOUT_MS).` } } return { ok: false, op: name, code: 'ECONNREFUSED', errnoHint: 'ECONNREFUSED', posixAlignment: 'partial', note: `connect: all address candidates failed for host=${host || '127.0.0.1'}` } } slot.state = 'connected' slot.sock = sock slot.transport = 'tcp' slot.tcpRecvQueue = [] slot.tcpRecvDropped = 0 const tcpQmax = bareOsPosixDgramRecvQueueMax(shellEnv) sock.on('data', (message) => { let u8 = message instanceof Uint8Array ? message : b4a.from(message) if (u8.byteLength > 1024 * 1024) u8 = u8.slice(0, 1024 * 1024) if (!Array.isArray(slot.tcpRecvQueue)) slot.tcpRecvQueue = [] if (slot.tcpRecvQueue.length >= tcpQmax) { slot.tcpRecvDropped = (slot.tcpRecvDropped || 0) + 1 return } slot.tcpRecvQueue.push({ buf: u8.slice() }) bridgeNotify(slot, 'recv') }) sock.on('end', () => { slot.tcpPeerEnded = true bridgeNotify(slot, 'recv') }) sock.on('error', () => { slot.tcpSockError = true bridgeNotify(slot, 'recv') }) this.bareOsLogicalFds[k] = 'bare-os-socket-bridge:tcp:' + (host || '127.0.0.1') + ':' + port return { ok: true, op: name, fd, bridge: true, posixAlignment: 'partial', transport: 'tcp' } } if (name === 'bind') { const fd = Number(args.fd) if (!Number.isFinite(fd)) { return { ok: false, op: name, code: 'EINVAL', errnoHint: 'EINVAL', note: 'bind: fd required' } } const k = String(fd >>> 0) const slot = this.bareOsSocketBridgeByFd && typeof this.bareOsSocketBridgeByFd === 'object' ? this.bareOsSocketBridgeByFd[k] : null const isDgram = Number(slot?.sockType ?? slot?.type ?? 1) === 2 if (isDgram) { if (!slot || slot.state !== 'created') { return { ok: false, op: name, code: 'EINVAL', errnoHint: 'EINVAL', note: 'bind: requires SOCK_DGRAM bridge socket in created state.' } } const dg = bareLibrary.bareDgram const UdpSocket = dg && ((typeof dg.Socket === 'function' && dg.Socket) || (dg.default && typeof dg.default.Socket === 'function' && dg.default.Socket)) ? dg.Socket || dg.default.Socket : null if (!UdpSocket) { return { ok: false, op: name, code: 'ENOTSUP', errnoHint: 'ENOTSUP', posixAlignment: 'ENOTSUP', note: 'bind: SOCK_DGRAM bridge requires bareDgram.Socket merged in ctx.bare (bare-dgram / UDX).' } } const port = Number(args.port) const host = String(args.host || args.address || '0.0.0.0').trim() || '0.0.0.0' if (!Number.isFinite(port) || port < 0 || port > 65535) { return { ok: false, op: name, code: 'EINVAL', errnoHint: 'EINVAL', note: 'bind: port required (0–65535)' } } const bindUdpOnce = (sock, bindPort) => new Promise((resolve, reject) => { const tmo = setTimeout(() => { sock.removeAllListeners('error') reject(new Error('bind: timeout')) }, 30000) sock.once('error', (e) => { clearTimeout(tmo) sock.removeAllListeners('error') reject(e) }) try { sock.bind(bindPort, host, () => { clearTimeout(tmo) sock.removeAllListeners('error') resolve(undefined) }) } catch (e) { clearTimeout(tmo) reject(e) } }) let usock = new UdpSocket({}) try { await bindUdpOnce(usock, port) } catch (e) { try { await usock.close?.() } catch (_) {} if ( !bindFallbackEnabled(shellEnv) || !isAddrInUse(e) || port === 0 ) { return { ok: false, op: name, code: 'EADDRINUSE', errnoHint: 'EADDRINUSE', posixAlignment: 'partial', note: String( e && /** @type {Error} */ (e).message ? e.message : e ) } } usock = new UdpSocket({}) try { await bindUdpOnce(usock, 0) } catch (e2) { try { await usock.close?.() } catch (_) {} return { ok: false, op: name, code: 'EADDRINUSE', errnoHint: 'EADDRINUSE', posixAlignment: 'partial', note: String( e2 && /** @type {Error} */ (e2).message ? e2.message : e2 ) } } } let boundUdpPort = port try { const a = usock.address && typeof usock.address === 'function' ? usock.address() : null if (a && typeof a === 'object' && Number.isFinite(a.port)) boundUdpPort = Number(a.port) } catch (_) {} slot.state = 'udp_bound' slot.sock = usock slot.transport = 'udp' slot.bindPort = boundUdpPort slot.bindHost = host slot.dgramRecvQueue = [] slot.dgramRecvDropped = 0 const qmax = bareOsPosixDgramRecvQueueMax(shellEnv) usock.on('message', (message, rinfo) => { let u8 = message instanceof Uint8Array ? message : b4a.from(message) if (u8.byteLength > 1024 * 1024) u8 = u8.slice(0, 1024 * 1024) if (slot.dgramRecvQueue.length >= qmax) { slot.dgramRecvDropped = (slot.dgramRecvDropped || 0) + 1 return } slot.dgramRecvQueue.push({ buf: u8.slice(), rinfo: rinfo && typeof rinfo === 'object' ? { address: String(rinfo.address || ''), port: Number(rinfo.port) >>> 0 } : null }) bridgeNotify(slot, 'recv') }) this.bareOsLogicalFds[k] = 'bare-os-socket-bridge:udp-bound:' + host + ':' + boundUdpPort return { ok: true, op: name, fd, bridge: true, posixAlignment: 'partial', transport: 'udp', port: boundUdpPort, host } } if (!slot || slot.state !== 'created') { return { ok: false, op: name, code: 'EINVAL', errnoHint: 'EINVAL', note: 'bind: requires SOCK_STREAM bridge socket in created state.' } } const port = Number(args.port) const host = String(args.host || args.address || '0.0.0.0').trim() || '0.0.0.0' if (!Number.isFinite(port) || port < 0 || port > 65535) { return { ok: false, op: name, code: 'EINVAL', errnoHint: 'EINVAL', note: 'bind: port required (0–65535)' } } slot.bindPort = port slot.bindHost = host slot.state = 'bound' this.bareOsLogicalFds[k] = 'bare-os-socket-bridge:tcp-bound:' + host + ':' + port return { ok: true, op: name, fd, bridge: true, posixAlignment: 'partial', port, host } } if (name === 'listen') { const fd = Number(args.fd) if (!Number.isFinite(fd)) { return { ok: false, op: name, code: 'EINVAL', errnoHint: 'EINVAL', note: 'listen: fd required' } } const k = String(fd >>> 0) const slot = this.bareOsSocketBridgeByFd && typeof this.bareOsSocketBridgeByFd === 'object' ? this.bareOsSocketBridgeByFd[k] : null if (!slot || slot.state !== 'bound') { return { ok: false, op: name, code: 'EINVAL', errnoHint: 'EINVAL', note: 'listen: call bind(2) on the bridge fd first.' } } const TcpServer = bareLibrary.bareTcp && bareLibrary.bareTcp.Server && typeof bareLibrary.bareTcp.Server === 'function' ? bareLibrary.bareTcp.Server : null if (!TcpServer) { return { ok: false, op: name, code: 'ENOTSUP', errnoHint: 'ENOTSUP', posixAlignment: 'ENOTSUP', note: 'listen: bareTcp.Server not merged in ctx.bare (bare-tcp bundle).' } } const backlog = Math.min( 511, Math.max(1, Math.floor(Number(args.backlog) || 511)) ) const acceptQmax = bareOsPosixAcceptQueueMax(shellEnv) const dgramQmax = bareOsPosixDgramRecvQueueMax(shellEnv) const server = new TcpServer() slot.acceptQueue = [] server.on('connection', (sock) => { if (!Array.isArray(slot.acceptQueue)) return if (slot.acceptQueue.length >= acceptQmax) { try { sock.destroy?.() } catch (_) {} return } const wrap = { sock, tcpRecvQueue: [], tcpRecvDropped: 0 } sock.on('data', (message) => { let u8 = message instanceof Uint8Array ? message : b4a.from(message) if (u8.byteLength > 1024 * 1024) u8 = u8.slice(0, 1024 * 1024) if (wrap.tcpRecvQueue.length >= dgramQmax) { wrap.tcpRecvDropped = (wrap.tcpRecvDropped || 0) + 1 return } wrap.tcpRecvQueue.push({ buf: u8.slice() }) bridgeNotify(wrap, 'recv') }) slot.acceptQueue.push(wrap) bridgeNotify(slot, 'accept') }) try { await new Promise((resolve, reject) => { server.once('error', reject) server.once('listening', resolve) server.listen(slot.bindPort, slot.bindHost, backlog) }) } catch (e) { if ( !bindFallbackEnabled(shellEnv) || !isAddrInUse(e) || slot.bindPort === 0 ) { return { ok: false, op: name, code: 'EADDRINUSE', errnoHint: 'EADDRINUSE', posixAlignment: 'partial', note: String(e && e.message ? e.message : e) } } try { await new Promise((resolve, reject) => { server.once('error', reject) server.once('listening', resolve) server.listen(0, slot.bindHost, backlog) }) } catch (e2) { return { ok: false, op: name, code: 'EADDRINUSE', errnoHint: 'EADDRINUSE', posixAlignment: 'partial', note: String(e2 && e2.message ? e2.message : e2) } } const ephem = readListeningTcpPort(server, 0) if (Number.isFinite(ephem) && ephem > 0) slot.bindPort = ephem } slot.server = server slot.state = 'listening' this.bareOsLogicalFds[k] = 'bare-os-socket-bridge:tcp-listen:' + slot.bindHost + ':' + slot.bindPort const addr = server.address && typeof server.address === 'function' ? server.address() : null return { ok: true, op: name, fd, bridge: true, posixAlignment: 'partial', address: addr } } if (name === 'accept') { const fd = Number(args.fd) if (!Number.isFinite(fd)) { return { ok: false, op: name, code: 'EINVAL', errnoHint: 'EINVAL', note: 'accept: listening fd required' } } const k = String(fd >>> 0) const slot = this.bareOsSocketBridgeByFd && typeof this.bareOsSocketBridgeByFd === 'object' ? this.bareOsSocketBridgeByFd[k] : null if (!slot || slot.state !== 'listening') { return { ok: false, op: name, code: 'EINVAL', errnoHint: 'EINVAL', note: 'accept: fd must be a listening SOCK_STREAM bridge socket.' } } const nonblock = (this.bareOsLogicalFdFlags[k] & 0x800) === 0x800 const blockMax = bareOsPosixAcceptBlockMsMax(shellEnv) const t0 = Date.now() while (true) { const q = slot.acceptQueue if (Array.isArray(q) && q.length > 0) { const wrap = q.shift() const sock = wrap && wrap.sock if (!sock) continue const st = this.bareOsPosixFdSimState const newFd = st.nextFd++ const nk = String(newFd) this.bareOsSocketBridgeByFd[nk] = { state: 'connected', sock, transport: 'tcp', sockType: 1, type: 1, tcpRecvQueue: wrap.tcpRecvQueue, tcpRecvDropped: wrap.tcpRecvDropped || 0, recvWaiters: wrap.recvWaiters || [], aliasKeys: new Set([nk]) } this.bareOsRegisterLogicalFd( newFd, 'bare-os-socket-bridge:tcp-accepted' ) return { ok: true, op: name, fd: newFd, bridge: true, posixAlignment: 'partial', listenFd: fd } } if (nonblock) { return { ok: false, op: name, code: 'EAGAIN', errnoHint: 'EAGAIN', posixAlignment: 'EAGAIN', bridge: true, note: 'accept: no connection ready (O_NONBLOCK)' } } if (Date.now() - t0 >= blockMax) { return { ok: false, op: name, code: 'EAGAIN', errnoHint: 'EAGAIN', posixAlignment: 'partial', bridge: true, note: 'accept: timed out waiting (same budget as BARE_OS_POSIX_DGRAM_RECV_BLOCK_MS_MAX)' } } await bridgeWait(slot, blockMax - (Date.now() - t0), 'accept') } } if (name === 'shutdown') { const fd = Number(args.fd) if (!Number.isFinite(fd)) { return { ok: false, op: name, code: 'EINVAL', errnoHint: 'EINVAL', note: 'shutdown: fd required' } } return await bareOsShutdownSocketBridgeFd( /** @type {Record} */ (this), fd, args && typeof args === 'object' ? /** @type {Record} */ ( args.how ?? /** @type {Record} */ (args).shutdownHow ) : undefined ) } if ( name === 'send' || name === 'sendmsg' || name === 'recv' || name === 'recvfrom' || name === 'recvmsg' ) { const logicalOp = name const fd = Number(args.fd) if (!Number.isFinite(fd)) { return { ok: false, op: logicalOp, code: 'EINVAL', errnoHint: 'EINVAL', posixAlignment: 'ENOTSUP', note: 'send/sendmsg/recv/recvfrom/recvmsg: fd required' } } const k = String(fd >>> 0) const slot = this.bareOsSocketBridgeByFd && typeof this.bareOsSocketBridgeByFd === 'object' ? this.bareOsSocketBridgeByFd[k] : null const udpReady = slot && slot.transport === 'udp' && slot.sock && (slot.state === 'connected' || slot.state === 'udp_bound') const tcpReady = slot && slot.transport === 'tcp' && slot.sock && slot.state === 'connected' if (!udpReady && !tcpReady) { return { ok: false, op: logicalOp, code: 'ENOTSUP', errnoHint: 'ENOTSUP', posixAlignment: 'ENOTSUP', note: 'Bridge send/sendmsg/recv/recvfrom/recvmsg supported for SOCK_STREAM (connected) or SOCK_DGRAM (connected or bound) fds.' } } if (slot.transport === 'tcp') { if (logicalOp === 'send' || logicalOp === 'sendmsg') { if (slot.tcpShutWr) { return { ok: false, op: logicalOp, code: 'EPIPE', errnoHint: 'EPIPE', posixAlignment: 'partial', bridge: true, transport: 'tcp', note: 'send after SHUT_WR / half-close write side on TCP bridge (POSIX EPIPE-shaped).' } } let payload /** @type {{ from: number; to: number }[]} */ let scmRightsLocalDup = [] if (logicalOp === 'sendmsg') { const prep = prepareBridgeSendmsg( /** @type {Record} */ (this), args && typeof args === 'object' ? /** @type {Record} */ (args) : {} ) if (!prep.ok) return prep.err payload = prep.payload scmRightsLocalDup = prep.scmRightsLocalDup } else { payload = args.buf ?? args.buffer ?? args.data if (payload == null) { return { ok: false, op: logicalOp, code: 'EINVAL', errnoHint: 'EINVAL' } } if (typeof payload === 'string') payload = b4a.from(String(payload), 'utf8') if (!(payload instanceof Uint8Array)) { return { ok: false, op: logicalOp, code: 'EINVAL', errnoHint: 'EINVAL', note: 'send: buf must be Uint8Array or string' } } } await new Promise((resolve, reject) => { try { const w = slot.sock.write(payload, (err) => err ? reject(err) : resolve(undefined) ) if (w === false) { slot.sock.once('drain', () => resolve(undefined)) } } catch (e) { reject(e) } }) return { ok: true, op: logicalOp, fd, bytesSent: payload.byteLength, bridge: true, posixAlignment: 'partial', transport: 'tcp', ...(scmRightsLocalDup.length ? { scmRightsLocalDup } : {}) } } const maxBuf = Math.max( 1, Math.min( Number(args.len ?? args.length ?? args.max ?? 65536) || 65536, 1024 * 1024 ) ) const nonblock = (this.bareOsLogicalFdFlags[k] & 0x800) === 0x800 const t0 = Date.now() const blockMax = bareOsPosixAcceptBlockMsMax(shellEnv) while (true) { const q = slot.tcpRecvQueue if (Array.isArray(q) && q.length > 0) { const row = q.shift() if (row && row.buf instanceof Uint8Array) { let out = row.buf if (out.byteLength > maxBuf) { const rest = out.slice(maxBuf) out = out.slice(0, maxBuf) q.unshift({ buf: rest }) } const base = { ok: true, op: logicalOp, fd, bytesReceived: out.byteLength, buf: out, bridge: true, posixAlignment: 'partial', transport: 'tcp', tcpRecvDropped: slot.tcpRecvDropped || 0 } if (logicalOp === 'recvfrom' || logicalOp === 'recvmsg') { base.address = slot.sock.remoteAddress base.port = slot.sock.remotePort } if (logicalOp === 'recvmsg') { base.msgHdr = { schema: 1, name: slot.sock.remoteAddress != null ? { address: String(slot.sock.remoteAddress || ''), port: Number(slot.sock.remotePort) >>> 0 } : null, flags: 0, controllen: 0, note: 'TCP bridge: single flat buffer; recv ancillary controllen 0 (socketMsgSurface schema 6).' } } return base } } if (slot.tcpSockError) { return { ok: false, op: logicalOp, code: 'ECONNRESET', errnoHint: 'ECONNRESET', posixAlignment: 'partial', bridge: true, transport: 'tcp', note: 'recv: TCP socket error (bridge)', tcpRecvDropped: slot.tcpRecvDropped || 0 } } if ( slot.tcpPeerEnded && (!Array.isArray(slot.tcpRecvQueue) || slot.tcpRecvQueue.length === 0) ) { return { ok: true, op: logicalOp, fd, bytesReceived: 0, buf: new Uint8Array(0), eof: true, bridge: true, posixAlignment: 'partial', transport: 'tcp', errnoHint: 'EOF', note: 'recv: EOF after peer half-close / FIN (queue drained); aligns with poll POLLIN readiness.', tcpRecvDropped: slot.tcpRecvDropped || 0 } } if (slot.tcpShutRd && (!q || q.length === 0)) { return { ok: true, op: logicalOp, fd, bytesReceived: 0, buf: new Uint8Array(0), eof: true, bridge: true, posixAlignment: 'partial', transport: 'tcp', note: 'recv: local SHUT_RD with empty queue (EOF-shaped).', tcpRecvDropped: slot.tcpRecvDropped || 0 } } if (nonblock) { return { ok: false, op: logicalOp, code: 'EAGAIN', errnoHint: 'EAGAIN', posixAlignment: 'EAGAIN', bridge: true, note: 'recv: no data ready (O_NONBLOCK)', tcpRecvDropped: slot.tcpRecvDropped || 0 } } if (Date.now() - t0 >= blockMax) { return { ok: false, op: logicalOp, code: 'EAGAIN', errnoHint: 'EAGAIN', posixAlignment: 'partial', bridge: true, note: 'recv: timed out waiting for TCP segment (BARE_OS_POSIX_DGRAM_RECV_BLOCK_MS_MAX budget)', tcpRecvDropped: slot.tcpRecvDropped || 0 } } await bridgeWait(slot, blockMax - (Date.now() - t0), 'recv') } } if (logicalOp === 'send' || logicalOp === 'sendmsg') { let payload /** @type {{ from: number; to: number }[]} */ let scmRightsLocalDupUdp = [] if (logicalOp === 'sendmsg') { const prep = prepareBridgeSendmsg( /** @type {Record} */ (this), args && typeof args === 'object' ? /** @type {Record} */ (args) : {} ) if (!prep.ok) return prep.err payload = prep.payload scmRightsLocalDupUdp = prep.scmRightsLocalDup } else { payload = args.buf ?? args.buffer ?? args.data if (payload == null) { return { ok: false, op: logicalOp, code: 'EINVAL', errnoHint: 'EINVAL' } } if (typeof payload === 'string') payload = b4a.from(String(payload), 'utf8') if (!(payload instanceof Uint8Array)) { return { ok: false, op: logicalOp, code: 'EINVAL', errnoHint: 'EINVAL', note: 'send: buf must be Uint8Array or string' } } } if (slot.state === 'udp_bound') { const dp = Number( args.port ?? args.peerPort ?? args.sin_port ?? args.sinPort ) const dh = String( args.host || args.address || args.peerHost || args.sin_addr || args.sinAddr || '' ).trim() if (!Number.isFinite(dp) || dp <= 0 || dp > 65535 || !dh) { return { ok: false, op: logicalOp, code: 'EINVAL', errnoHint: 'EINVAL', posixAlignment: 'partial', note: 'send: bound SOCK_DGRAM bridge requires port and host (or connect(2) first for default peer).' } } await new Promise((resolve, reject) => { try { slot.sock.send(payload, dp, dh, (err) => err ? reject(err) : resolve(undefined) ) } catch (e) { reject(e) } }) } else { await new Promise((resolve, reject) => { try { slot.sock.send(payload, (err) => err ? reject(err) : resolve(undefined) ) } catch (e) { reject(e) } }) } return { ok: true, op: logicalOp, fd, bytesSent: payload.byteLength, bridge: true, posixAlignment: 'partial', ...(scmRightsLocalDupUdp.length ? { scmRightsLocalDup: scmRightsLocalDupUdp } : {}) } } const maxBuf = Math.max( 1, Math.min( Number(args.len ?? args.length ?? args.max ?? 65536) || 65536, 1024 * 1024 ) ) const nonblock = (this.bareOsLogicalFdFlags[k] & 0x800) === 0x800 const wantFrom = logicalOp === 'recvfrom' || logicalOp === 'recvmsg' const t0 = Date.now() const blockMax = bareOsPosixDgramRecvBlockMsMax(shellEnv) while (true) { const q = slot.dgramRecvQueue if (Array.isArray(q) && q.length > 0) { const row = q.shift() if (row && row.buf instanceof Uint8Array) { let out = row.buf if (out.byteLength > maxBuf) { const rest = out.slice(maxBuf) out = out.slice(0, maxBuf) q.unshift({ buf: rest, rinfo: row.rinfo }) } const base = { ok: true, op: logicalOp, fd, bytesReceived: out.byteLength, buf: out, bridge: true, posixAlignment: 'partial', dgramQueueDepth: q.length, dgramRecvDropped: slot.dgramRecvDropped || 0 } if (wantFrom && row.rinfo) { base.address = row.rinfo.address base.port = row.rinfo.port } if (logicalOp === 'recvmsg') { base.msgHdr = { schema: 1, name: row.rinfo ? { address: String(row.rinfo.address || ''), port: Number(row.rinfo.port) >>> 0 } : null, flags: 0, controllen: 0, note: 'UDP bridge: single flat buf; recv controllen 0; logical SCM_RIGHTS apply only to sendmsg result (schema 4).' } } return base } } if (nonblock) { return { ok: false, op: logicalOp, code: 'EAGAIN', errnoHint: 'EAGAIN', posixAlignment: 'EAGAIN', bridge: true, note: 'recv: no datagram ready (O_NONBLOCK)', dgramRecvDropped: slot.dgramRecvDropped || 0 } } if (Date.now() - t0 >= blockMax) { return { ok: false, op: logicalOp, code: 'EAGAIN', errnoHint: 'EAGAIN', posixAlignment: 'partial', bridge: true, note: 'recv: timed out waiting for datagram (BARE_OS_POSIX_DGRAM_RECV_BLOCK_MS_MAX)', dgramRecvDropped: slot.dgramRecvDropped || 0 } } await bridgeWait(slot, blockMax - (Date.now() - t0), 'recv') } } } return { ok: false, op: name, code: 'ENOSYS', errnoHint: 'ENOSYS', posixAlignment: 'ENOTSUP', note: 'POSIX socket-style syscalls are not kernel FDs in Bare OS; use ctx.bare TCP/UDP modules, Hyperswarm, or Protomux-framed streams. Set BARE_OS_POSIX_SOCKET_FD_BRIDGE=1 for partial socket+connect when bareTcp is merged.' } } if (name === 'posixPoll') { const timeoutMs = Math.max( 0, Math.min(60000, Math.floor(Number(args.timeoutMs) || 0)) ) if (timeoutMs > 0) { return this.bareOsPosixPoll({ fds: Array.isArray(args.fds) ? args.fds : [], timeoutMs }) } return this.bareOsPosixPollProbe({ fds: Array.isArray(args.fds) ? args.fds : [] }) } if (name === 'kill') { return this.bareOsSendSignal(args.target, String(args.signal || 'TERM')) } throw new Error(`bareOsSyscall: unsupported op "${name}"`) } }