Orginize
This commit is contained in:
@@ -0,0 +1,250 @@
|
||||
/**
|
||||
* Simulated POSIX pipe FDs and poll probe methods attached to the kernel `ctx`.
|
||||
*/
|
||||
import b4a from 'b4a'
|
||||
import { wantPosixSocketFdBridge } from './bare-os-posix-syscall-helpers.js'
|
||||
|
||||
/**
|
||||
* @param {{
|
||||
* env: Record<string, string | undefined>,
|
||||
* bootHrtimeNowNs: (() => bigint) | null
|
||||
* }} deps
|
||||
*/
|
||||
export function createBareOsPosixFdSimMethods(deps) {
|
||||
const { env, bootHrtimeNowNs } = deps
|
||||
|
||||
return {
|
||||
bareOsPosixFdSimCapBytes() {
|
||||
const raw = String(env.BARE_OS_POSIX_FD_SIM_MAX_BYTES || '').trim()
|
||||
const n = raw ? Number.parseInt(raw, 10) : 1048576
|
||||
return Number.isFinite(n) && n > 0
|
||||
? Math.min(n, 16 * 1024 * 1024)
|
||||
: 1048576
|
||||
},
|
||||
bareOsPosixFdSimEnabled() {
|
||||
const v = env.BARE_OS_POSIX_FD_SIM
|
||||
return v === '1' || v === 'true'
|
||||
},
|
||||
/** @returns {{ readFd: number, writeFd: number, pairId: number } | null} */
|
||||
bareOsPosixFdSimPipe() {
|
||||
if (!this.bareOsPosixFdSimEnabled()) return null
|
||||
const st = this.bareOsPosixFdSimState
|
||||
if (!st || typeof st !== 'object') return null
|
||||
const pairId = st.nextPairId++
|
||||
const readFd = st.nextFd++
|
||||
const writeFd = st.nextFd++
|
||||
if (st.nextFd > 65530) {
|
||||
st.nextFd = 10
|
||||
return null
|
||||
}
|
||||
const pk = String(pairId)
|
||||
st.queues[pk] = []
|
||||
st.byteTotals[pk] = 0
|
||||
this.bareOsRegisterLogicalFd(readFd, `posix-pipe:${pairId}:r`)
|
||||
this.bareOsRegisterLogicalFd(writeFd, `posix-pipe:${pairId}:w`)
|
||||
return { readFd, writeFd, pairId }
|
||||
},
|
||||
/** @param {number} fd */
|
||||
bareOsPosixFdSimDup(fd) {
|
||||
if (!this.bareOsPosixFdSimEnabled()) return null
|
||||
const k = String(Number(fd) >>> 0)
|
||||
const target = this.bareOsLogicalFds[k]
|
||||
if (!target || !String(target).startsWith('posix-pipe:')) return null
|
||||
const st = this.bareOsPosixFdSimState
|
||||
const newFd = st.nextFd++
|
||||
if (st.nextFd > 65530) return null
|
||||
this.bareOsRegisterLogicalFd(newFd, String(target))
|
||||
return newFd
|
||||
},
|
||||
/**
|
||||
* @param {number} writeFd
|
||||
* @param {string | Uint8Array} data
|
||||
*/
|
||||
bareOsPosixFdSimWrite(writeFd, data) {
|
||||
if (!this.bareOsPosixFdSimEnabled())
|
||||
return { ok: false, reason: 'disabled' }
|
||||
const k = String(Number(writeFd) >>> 0)
|
||||
const t = this.bareOsLogicalFds[k]
|
||||
const m = /^posix-pipe:(\d+):w$/.exec(String(t || ''))
|
||||
if (!m) return { ok: false, reason: 'not_write_end' }
|
||||
const pairId = m[1]
|
||||
const st = this.bareOsPosixFdSimState
|
||||
const q = st.queues[pairId]
|
||||
if (!q) return { ok: false, reason: 'bad_pair' }
|
||||
const buf =
|
||||
typeof data === 'string'
|
||||
? b4a.from(data, 'utf8')
|
||||
: data instanceof Uint8Array
|
||||
? data
|
||||
: b4a.from(String(data), 'utf8')
|
||||
const add = buf.byteLength
|
||||
const cap = this.bareOsPosixFdSimCapBytes()
|
||||
const prev = st.byteTotals[pairId] || 0
|
||||
if (prev + add > cap) return { ok: false, reason: 'EAGAIN' }
|
||||
st.byteTotals[pairId] = prev + add
|
||||
q.push(b4a.toString(buf, 'utf8'))
|
||||
return { ok: true, bytes: add }
|
||||
},
|
||||
/**
|
||||
* @param {number} readFd
|
||||
* @param {number} [maxBytes]
|
||||
*/
|
||||
bareOsPosixFdSimRead(readFd, maxBytes = 65536) {
|
||||
if (!this.bareOsPosixFdSimEnabled())
|
||||
return { ok: false, reason: 'disabled' }
|
||||
const cap = Math.max(1, Math.min(Number(maxBytes) || 65536, 1024 * 1024))
|
||||
const k = String(Number(readFd) >>> 0)
|
||||
const t = this.bareOsLogicalFds[k]
|
||||
const m = /^posix-pipe:(\d+):r$/.exec(String(t || ''))
|
||||
if (!m) return { ok: false, reason: 'not_read_end' }
|
||||
const pairId = m[1]
|
||||
const st = this.bareOsPosixFdSimState
|
||||
const q = st.queues[pairId]
|
||||
if (!q || q.length === 0) {
|
||||
const nonblock = (this.bareOsLogicalFdFlags[k] & 0x800) === 0x800
|
||||
if (nonblock) return { ok: false, reason: 'EAGAIN' }
|
||||
return { ok: true, data: '', eof: false }
|
||||
}
|
||||
let chunk = q.shift()
|
||||
if (!chunk) return { ok: true, data: '', eof: false }
|
||||
if (chunk.length > cap) {
|
||||
const rest = chunk.slice(cap)
|
||||
chunk = chunk.slice(0, cap)
|
||||
q.unshift(rest)
|
||||
}
|
||||
const used = b4a.from(chunk, 'utf8').byteLength
|
||||
st.byteTotals[pairId] = Math.max(0, (st.byteTotals[pairId] || 0) - used)
|
||||
return { ok: true, data: chunk, eof: false }
|
||||
},
|
||||
/**
|
||||
* Non-blocking readiness probe for logical FDs (simulated pipes + stdout/stderr write).
|
||||
* @param {{ fds?: Array<{ fd: number, events?: string }>, timeoutMs?: number }} [spec]
|
||||
* @returns {{ ok: true, ready: Array<{ fd: number, revents: string }>, waitedMs: number, timedOut?: boolean }}
|
||||
*/
|
||||
bareOsPosixPollProbe(spec = {}) {
|
||||
const fds = Array.isArray(spec.fds) ? spec.fds : []
|
||||
/** @type {Array<{ fd: number, revents: string }>} */
|
||||
const ready = []
|
||||
for (const entry of fds.slice(0, 64)) {
|
||||
const fd = Number(entry && entry.fd)
|
||||
if (!Number.isFinite(fd) || fd < 0) continue
|
||||
const ev = String(entry.events || 'rw').toLowerCase()
|
||||
const wantR = ev.includes('r')
|
||||
const wantW = ev.includes('w')
|
||||
const k = String(fd >>> 0)
|
||||
const t = this.bareOsLogicalFds[k]
|
||||
const pipeR = /^posix-pipe:(\d+):r$/.exec(String(t || ''))
|
||||
const pipeW = /^posix-pipe:(\d+):w$/.exec(String(t || ''))
|
||||
if (wantR && pipeR) {
|
||||
const st = this.bareOsPosixFdSimState
|
||||
const q = st.queues[pipeR[1]]
|
||||
if (q && q.length > 0) ready.push({ fd, revents: 'r' })
|
||||
}
|
||||
if (wantW && pipeW) {
|
||||
const st = this.bareOsPosixFdSimState
|
||||
const pairId = pipeW[1]
|
||||
const prev = st.byteTotals[pairId] || 0
|
||||
const cap = this.bareOsPosixFdSimCapBytes()
|
||||
if (prev < cap) ready.push({ fd, revents: 'w' })
|
||||
}
|
||||
if (wantW && (k === '1' || k === '2')) {
|
||||
ready.push({ fd, revents: 'w' })
|
||||
}
|
||||
if (wantR && wantPosixSocketFdBridge(this.env)) {
|
||||
const bridge =
|
||||
this.bareOsSocketBridgeByFd &&
|
||||
typeof this.bareOsSocketBridgeByFd === 'object'
|
||||
? this.bareOsSocketBridgeByFd[k]
|
||||
: null
|
||||
if (
|
||||
bridge &&
|
||||
(bridge.state === 'connected' || bridge.state === 'udp_bound') &&
|
||||
bridge.transport === 'udp' &&
|
||||
Array.isArray(bridge.dgramRecvQueue) &&
|
||||
bridge.dgramRecvQueue.length > 0
|
||||
) {
|
||||
ready.push({ fd, revents: 'r' })
|
||||
}
|
||||
if (
|
||||
bridge &&
|
||||
bridge.state === 'connected' &&
|
||||
bridge.transport === 'tcp'
|
||||
) {
|
||||
const tq = bridge.tcpRecvQueue
|
||||
const tqlen = Array.isArray(tq) ? tq.length : 0
|
||||
if (
|
||||
wantR &&
|
||||
(tqlen > 0 || bridge.tcpPeerEnded || bridge.tcpSockError)
|
||||
) {
|
||||
ready.push({ fd, revents: 'r' })
|
||||
}
|
||||
if (wantW && !bridge.tcpShutWr) {
|
||||
ready.push({ fd, revents: 'w' })
|
||||
}
|
||||
}
|
||||
if (
|
||||
bridge &&
|
||||
bridge.state === 'listening' &&
|
||||
Array.isArray(bridge.acceptQueue) &&
|
||||
bridge.acceptQueue.length > 0
|
||||
) {
|
||||
ready.push({ fd, revents: 'r' })
|
||||
}
|
||||
}
|
||||
}
|
||||
return { ok: true, ready, waitedMs: 0 }
|
||||
},
|
||||
/**
|
||||
* Poll logical FDs with bounded wait (simulated **`poll(2)`** subset for **`BARE_OS_POSIX_FD_SIM`** pipes).
|
||||
* @param {{ fds?: Array<{ fd: number, events?: string }>, timeoutMs?: number }} [spec]
|
||||
*/
|
||||
async bareOsPosixPoll(spec = {}) {
|
||||
const maxWait = Math.min(
|
||||
60000,
|
||||
Math.max(0, Math.floor(Number(spec && spec.timeoutMs) || 0))
|
||||
)
|
||||
if (maxWait <= 0) {
|
||||
const once = this.bareOsPosixPollProbe(spec)
|
||||
const tProbe = bootHrtimeNowNs ? bootHrtimeNowNs() : null
|
||||
return {
|
||||
ok: true,
|
||||
ready: once.ready,
|
||||
waitedMs: 0,
|
||||
timedOut: false,
|
||||
pollClock: tProbe != null ? 'monotonic_hrtime' : 'Date_now'
|
||||
}
|
||||
}
|
||||
const t0Ns = bootHrtimeNowNs ? bootHrtimeNowNs() : null
|
||||
const t0Wall = Date.now()
|
||||
const elapsedMs = () => {
|
||||
if (t0Ns != null && bootHrtimeNowNs) {
|
||||
const delta = Number(bootHrtimeNowNs() - t0Ns) / 1e6
|
||||
return Math.min(60000, Math.max(0, delta))
|
||||
}
|
||||
return Math.min(60000, Math.max(0, Date.now() - t0Wall))
|
||||
}
|
||||
const interval = 10
|
||||
while (elapsedMs() <= maxWait) {
|
||||
const once = this.bareOsPosixPollProbe(spec)
|
||||
if (once.ready.length) {
|
||||
return {
|
||||
ok: true,
|
||||
ready: once.ready,
|
||||
waitedMs: Math.round(elapsedMs()),
|
||||
timedOut: false,
|
||||
pollClock: t0Ns != null ? 'monotonic_hrtime' : 'Date_now'
|
||||
}
|
||||
}
|
||||
if (elapsedMs() >= maxWait) break
|
||||
await new Promise((res) => setTimeout(res, interval))
|
||||
}
|
||||
return {
|
||||
ok: true,
|
||||
ready: [],
|
||||
waitedMs: Math.round(Math.min(maxWait, elapsedMs())),
|
||||
timedOut: true,
|
||||
pollClock: t0Ns != null ? 'monotonic_hrtime' : 'Date_now'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user