257 lines
6.6 KiB
JavaScript
257 lines
6.6 KiB
JavaScript
/**
|
|
* POSIX socket-FD bridge helpers (logical dup, shutdown, sendmsg prep).
|
|
* Takes the guest ctx; no executeKernel closure.
|
|
*/
|
|
import {
|
|
bareOsEnsureSocketSlotAliasKeys,
|
|
bareOsParseSendmsgScmRights,
|
|
bareOsScmRightsMaxFds,
|
|
wantPosixSocketScmRights
|
|
} from './bare-os-socket-scm-rights.js'
|
|
import {
|
|
bareOsFlattenSendmsgIovs,
|
|
bareOsSendmsgAncillaryRejectReason
|
|
} from './bare-os-posix-syscall-helpers.js'
|
|
|
|
/**
|
|
* @param {Record<string, unknown>} c ctx
|
|
* @param {number} srcFd
|
|
* @returns {number | null}
|
|
*/
|
|
export function bareOsDupLogicalFdForScm(c, srcFd) {
|
|
const k = String(Number(srcFd) >>> 0)
|
|
const bridge = c.bareOsSocketBridgeByFd
|
|
const slot = bridge && typeof bridge === 'object' ? bridge[k] : null
|
|
if (slot && typeof slot === 'object') {
|
|
if (
|
|
slot.state === 'listening' ||
|
|
slot.state === 'created' ||
|
|
slot.state === 'bound'
|
|
) {
|
|
return null
|
|
}
|
|
bareOsEnsureSocketSlotAliasKeys(slot, k)
|
|
const st = c.bareOsPosixFdSimState
|
|
const newFd = st.nextFd++
|
|
if (st.nextFd > 65530) return null
|
|
const nk = String(newFd)
|
|
if (
|
|
!c.bareOsSocketBridgeByFd ||
|
|
typeof c.bareOsSocketBridgeByFd !== 'object'
|
|
)
|
|
return null
|
|
c.bareOsSocketBridgeByFd[nk] = slot
|
|
slot.aliasKeys.add(nk)
|
|
const targ = c.bareOsLogicalFds[k] || 'bare-os-socket-bridge:dup'
|
|
c.bareOsRegisterLogicalFd(newFd, String(targ))
|
|
const fl = c.bareOsLogicalFdFlags[k]
|
|
if (typeof fl === 'number') c.bareOsLogicalFdFlags[nk] = fl
|
|
return newFd
|
|
}
|
|
const target = c.bareOsLogicalFds[k]
|
|
if (!target) return null
|
|
const st = c.bareOsPosixFdSimState
|
|
const newFd = st.nextFd++
|
|
if (st.nextFd > 65530) return null
|
|
const nk = String(newFd)
|
|
c.bareOsRegisterLogicalFd(newFd, String(target))
|
|
const fl = c.bareOsLogicalFdFlags[k]
|
|
if (typeof fl === 'number') c.bareOsLogicalFdFlags[nk] = fl
|
|
return newFd
|
|
}
|
|
|
|
/**
|
|
* @param {Record<string, unknown>} c ctx
|
|
* @param {number} fd
|
|
*/
|
|
export async function bareOsShutdownSocketBridgeFd(c, fd, howMaybe) {
|
|
const k = String(Number(fd) >>> 0)
|
|
const bridge = c.bareOsSocketBridgeByFd
|
|
if (!bridge || typeof bridge !== 'object') {
|
|
return {
|
|
ok: false,
|
|
op: 'shutdown',
|
|
code: 'EINVAL',
|
|
errnoHint: 'EINVAL',
|
|
note: 'shutdown: not a bridge fd'
|
|
}
|
|
}
|
|
const slot = bridge[k]
|
|
if (!slot || typeof slot !== 'object') {
|
|
return {
|
|
ok: false,
|
|
op: 'shutdown',
|
|
code: 'EINVAL',
|
|
errnoHint: 'EINVAL',
|
|
note: 'shutdown: not a bridge fd'
|
|
}
|
|
}
|
|
bareOsEnsureSocketSlotAliasKeys(slot, k)
|
|
const howN = Number(howMaybe)
|
|
const how = Number.isFinite(howN)
|
|
? Math.min(2, Math.max(0, Math.floor(howN)))
|
|
: 2
|
|
const tcpHalf =
|
|
slot.transport === 'tcp' &&
|
|
slot.state === 'connected' &&
|
|
slot.sock &&
|
|
(how === 0 || how === 1)
|
|
if (tcpHalf) {
|
|
if (how === 0) slot.tcpShutRd = true
|
|
if (how === 1) {
|
|
slot.tcpShutWr = true
|
|
try {
|
|
slot.sock.end?.()
|
|
} catch (_) {}
|
|
}
|
|
return {
|
|
ok: true,
|
|
op: 'shutdown',
|
|
bridge: true,
|
|
posixAlignment: 'partial',
|
|
how,
|
|
note:
|
|
how === 0
|
|
? 'TCP bridge: SHUT_RD — recv drains then EOF; poll marks readable when queue drained after peer FIN.'
|
|
: 'TCP bridge: SHUT_WR — further send returns EPIPE-shaped failure.'
|
|
}
|
|
}
|
|
delete bridge[k]
|
|
c.bareOsUnregisterLogicalFd(fd)
|
|
slot.aliasKeys.delete(k)
|
|
if (slot.aliasKeys.size > 0) {
|
|
return {
|
|
ok: true,
|
|
op: 'shutdown',
|
|
bridge: true,
|
|
posixAlignment: 'partial',
|
|
scmRightsRefDup: true
|
|
}
|
|
}
|
|
if (slot.state === 'listening' && slot.server) {
|
|
try {
|
|
slot.server.close?.()
|
|
} catch (_) {}
|
|
slot.server = null
|
|
if (Array.isArray(slot.acceptQueue)) {
|
|
for (const w of slot.acceptQueue) {
|
|
try {
|
|
w.sock?.destroy?.()
|
|
} catch (_) {}
|
|
}
|
|
slot.acceptQueue = []
|
|
}
|
|
slot.state = 'closed'
|
|
return {
|
|
ok: true,
|
|
op: 'shutdown',
|
|
bridge: true,
|
|
posixAlignment: 'partial'
|
|
}
|
|
}
|
|
if (
|
|
(slot.state === 'connected' || slot.state === 'udp_bound') &&
|
|
slot.sock
|
|
) {
|
|
try {
|
|
slot.sock.destroy?.()
|
|
} catch (_) {}
|
|
try {
|
|
await slot.sock.close?.()
|
|
} catch (_) {}
|
|
slot.state = 'closed'
|
|
return {
|
|
ok: true,
|
|
op: 'shutdown',
|
|
bridge: true,
|
|
posixAlignment: 'partial'
|
|
}
|
|
}
|
|
return {
|
|
ok: false,
|
|
op: 'shutdown',
|
|
code: 'ENOTSUP',
|
|
errnoHint: 'ENOTSUP',
|
|
posixAlignment: 'ENOTSUP',
|
|
note: 'shutdown: bridge fd not connected, bound, or listening.'
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param {Record<string, unknown>} c ctx (`c.env` is the guest shell env)
|
|
* @param {Record<string, unknown>} args
|
|
*/
|
|
export function prepareBridgeSendmsg(c, args) {
|
|
const a =
|
|
args && typeof args === 'object'
|
|
? /** @type {Record<string, unknown>} */ (args)
|
|
: {}
|
|
const env =
|
|
c && c.env && typeof c.env === 'object'
|
|
? /** @type {Record<string, string | undefined>} */ (c.env)
|
|
: {}
|
|
const scmOn = wantPosixSocketScmRights(env)
|
|
const parsed = bareOsParseSendmsgScmRights(
|
|
a,
|
|
scmOn,
|
|
bareOsScmRightsMaxFds(env)
|
|
)
|
|
if (!parsed.ok) {
|
|
return {
|
|
ok: false,
|
|
err: {
|
|
ok: false,
|
|
op: 'sendmsg',
|
|
code: 'ENOTSUP',
|
|
errnoHint: 'ENOTSUP',
|
|
posixAlignment: 'ENOTSUP',
|
|
ancillaryReject: parsed.reject,
|
|
note: 'sendmsg: ancillary control rejected (see ancillaryReject).'
|
|
}
|
|
}
|
|
}
|
|
if (!scmOn) {
|
|
const leg = bareOsSendmsgAncillaryRejectReason(a)
|
|
if (leg) {
|
|
return {
|
|
ok: false,
|
|
err: {
|
|
ok: false,
|
|
op: 'sendmsg',
|
|
code: 'ENOTSUP',
|
|
errnoHint: 'ENOTSUP',
|
|
posixAlignment: 'ENOTSUP',
|
|
ancillaryReject: leg,
|
|
note: 'sendmsg: ancillary control messages (cmsgs/control/controllen/msgHdr.*) are unsupported on the socket FD bridge without BARE_OS_POSIX_SOCKET_SCM_RIGHTS; use payload-only send or bare IPC.'
|
|
}
|
|
}
|
|
}
|
|
}
|
|
/** @type {{ from: number; to: number }[]} */
|
|
const scmRightsLocalDup = []
|
|
for (const src of parsed.fds) {
|
|
const to = bareOsDupLogicalFdForScm(c, src)
|
|
if (to == null) {
|
|
return {
|
|
ok: false,
|
|
err: {
|
|
ok: false,
|
|
op: 'sendmsg',
|
|
code: 'EBADF',
|
|
errnoHint: 'EBADF',
|
|
posixAlignment: 'ENOTSUP',
|
|
note: 'sendmsg: SCM_RIGHTS logical dup failed (unknown fd, idle/listen/bound socket, or FD budget).'
|
|
}
|
|
}
|
|
}
|
|
scmRightsLocalDup.push({ from: Number(src) >>> 0, to })
|
|
}
|
|
const f = bareOsFlattenSendmsgIovs(a)
|
|
if (!f.ok) return { ok: false, err: f.err }
|
|
return {
|
|
ok: true,
|
|
payload: f.buf,
|
|
scmRightsLocalDup
|
|
}
|
|
}
|