mirror of
https://github.com/stablyai/orca.git
synced 2026-10-31 09:07:40 +00:00
* [comment audit] Align legacy failure-read counts with cached display * [comment audit] Condense shared provider lifecycle and browser recovery comments [skip ci] * [comment audit] Condense shared generation, path and preview comments [skip ci] * [comment audit] Condense host, file and Git contract rationale [skip ci] * [comment audit] Tighten Git lifecycle and provider contract comments [skip ci] * [comment audit] Clarify shared host authority and launch isolation [skip ci] * [comment audit] simplify local access mobile and native chat contracts [skip ci] * [comment audit] simplify native chat presentation and state contracts [skip ci] * [comment audit] simplify native runtime and provider contracts [skip ci] * [comment audit] simplify migration and orchestration contracts [skip ci] * [comment audit] simplify plugin trust and process capture rationale [skip ci] * [comment audit] simplify terminal delivery and remote compatibility rationale [skip ci] * [comment audit] clarify remote recovery and launch wire rationale [skip ci] * [comment audit] shorten shared RPC compatibility and runtime evidence comments [skip ci] * [comment audit] simplify secure storage shell startup and sidebar rationale [skip ci] * [comment audit] clarify sidebar, skill and SSH invariants [skip ci] * [comment audit] condense structured chat delivery and timing rationale [skip ci] * [comment audit] condense terminal replay and telemetry invariants [skip ci] * [comment audit] clarify launch permission and terminal delivery rationale [skip ci] * [comment audit] clarify workspace ownership and layout contracts [skip ci] * [comment audit] wait for fitted and painted SSH terminal links * [comment audit] clarify layout persistence and session ownership [skip ci] * [comment audit] clarify host routing and workspace lifecycle contracts [skip ci] * [comment audit] clarify startup persistence and app lifetime comments [skip ci] * [comment audit] clarify recovery and activity boundaries [skip ci] * [comment audit] condense activity and artifact invariants [skip ci] * [comment audit] condense automation authority contracts [skip ci] * [comment audit] simplify automation scheduling and history contracts [skip ci] * [comment audit] clarify browser chrome and annotation lifecycles [skip ci] * [comment audit] clarify browser routing and retained guest ownership [skip ci] * [comment audit] clarify browser recovery and document ownership comments [skip ci] * [comment audit] restore hosted runners required by native IME fixtures * [comment audit] shorten preview, palette and dashboard invariants [skip ci] * [comment audit] clarify terminal preview, dashboard and retention comments [skip ci] * [comment audit] clarify diff-note recovery and editor lifecycle comments [skip ci] * [comment audit] shorten rich-text and combined-diff ownership comments [skip ci] * [comment audit] shorten autosave, draft recovery and model ownership comments [skip ci] * [comment audit] shorten save-queue, notebook and Markdown constraints [skip ci] * [comment audit] shorten Monaco, PDF and rich Markdown safeguards [skip ci] * [comment audit] clarify rich Markdown source and selection safeguards [skip ci] * [comment audit] clarify editor loading and emulator stream lifetimes [skip ci] * [comment audit] trim emulator and feature tour narration [skip ci] * [comment audit] clarify floating workspace and GitHub cache ownership [skip ci] * [comment audit] trim provider controls and Linear filter explanations [skip ci] * [comment audit] clarify pairing and chat resume lifetimes [skip ci] * [comment audit] clarify chat delivery and transcript lifetimes [skip ci] * [comment audit] clarify session options and host-owned status [skip ci] * [comment audit] clarify draft durability and live-status precedence [skip ci] * [comment audit] Trim chat send and model ownership explanations [skip ci] * [comment audit] Shorten transcript ownership and send recovery comments [skip ci] * [comment audit] Simplify native chat composer and transcript hook comments [skip ci] * [comment audit] Shorten native chat scroll and structured session explanations [skip ci] * [comment audit] Trim workspace picker and onboarding commentary [skip ci] * [comment audit] Simplify pet rendering, review cache and quick open comments [skip ci] * [comment audit] Trim sidebar and session history explanations [skip ci] * [comment audit] Condense session resume and review checks comments [skip ci] * [comment audit] Shorten checks state and file explorer refresh comments [skip ci] * [comment audit] Trim plugin bridge and source-control action rationale [skip ci] * [comment audit] Trim source-control listing and review rationale [skip ci] * [comment audit] Shorten sidebar lifecycle and explorer safeguards [skip ci] * [comment audit] Condense Settings account and appearance rationale [skip ci] * [comment audit] Trim pairing and repository Settings explanations [skip ci] * [comment audit] Condense Settings search SSH and terminal safeguards [skip ci] * [comment audit] Shorten Settings voice and provider ownership comments [skip ci] * [comment audit] Condense Settings draft persistence and host-state rationale [skip ci] * [comment audit] Trim Settings hooks and sidebar setup rationale [skip ci] * [comment audit] Condense sidebar deletion and board-filter safeguards [skip ci] * [comment audit] Condense worktree cards, host ownership and Markdown contracts [skip ci] * [comment audit] Condense sidebar drag, folder and filtering contracts [skip ci] * [comment audit] Condense sidebar parity, sleep and host ownership contracts [skip ci] * [comment audit] Prune focus-read baseline after comment cleanup * [comment audit] Clarify sidebar visibility, board drops and agent evidence [skip ci] * [comment audit] Tighten sidebar listing, drag and reveal contracts [skip ci] * [comment audit] Clarify viewport geometry and skill update safeguards [skip ci] * [comment audit] Tighten skill inventory, usage and host status comments [skip ci] * [comment audit] Clarify usage ownership and resource kill safeguards [skip ci] * [comment audit] Tighten tab selection, rename and search contracts [skip ci] * [comment audit] Tighten tab-strip geometry and drag ownership comments [skip ci] * [comment audit] Tighten Tasks recovery, pagination and mutation comments [skip ci] * [comment audit] Tighten provider loading and terminal recovery comments [skip ci] * [comment audit] Tighten completion, cadence and PTY ownership comments [skip ci] * [comment audit] Clarify terminal restore and parked-pane boundaries [skip ci] * [comment audit] Tighten PTY startup and replay explanations [skip ci] * [comment audit] Clarify PTY identity and delivery ownership [skip ci] * [comment audit] Shorten remote reconnect and input replay rationale [skip ci] * [comment audit] Condense terminal parking clipboard and file link comments [skip ci] * [comment audit] Clarify IME ownership and trim trace history [skip ci] * [comment audit] Shorten pane recovery and parked watcher explanations [skip ci] * [comment audit] Condense snapshot replay and visibility recovery rationale [skip ci] * [comment audit] Shorten terminal parking and keyboard ownership rationale [skip ci] * [comment audit] Trim renderer lifecycle and menu rationale [skip ci] * [comment audit] Condense workspace cleanup ownership and settlement notes [skip ci] * [comment audit] Clarify renderer hook evidence and source ownership [skip ci] * [comment audit] Tighten IPC ownership and reconnect explanations [skip ci] * [comment audit] Trim renderer focus, paste and attention comments [skip ci] * [comment audit] Shorten scroll, locale and agent lifecycle comments [skip ci] * [comment audit] Trim agent launch, resume and ownership commentary [skip ci] * [comment audit] Shorten account, browser and editor comments [skip ci] * [comment audit] Clarify host-mirror waits, previews and input ownership [skip ci] * [comment audit] Condense launch recovery and editor tokenization comments [skip ci] * [comment audit] Condense workspace selection and terminal pane rationale [skip ci] * [comment audit] Condense terminal fitting and rendering recovery comments [skip ci] * [comment audit] Condense terminal input and host routing explanations [skip ci] * [comment audit] Condense ownership and sleeping-session recovery rationale [skip ci] * [comment audit] Condense structured launch and terminal link explanations [skip ci] * [comment audit] Condense terminal ownership and workspace file routing rationale [skip ci] * [comment audit] Condense session persistence and workspace activation rationale [skip ci] * [comment audit] Condense workspace routing and browser ownership rationale [skip ci] * [comment audit] Condense mirror recovery and layout authority rationale [skip ci] * [comment audit] Condense runtime transport and file safety rationale [skip ci] * [comment audit] Condense runtime graph and structured session rationale [skip ci] * [comment audit] Condense browser creation and remote tab recovery rationale [skip ci] * [comment audit] Condense snapshot authority and publisher recovery rationale [skip ci] * [comment audit] Condense frame, startup and provider cache rationale [skip ci] * [comment audit] Condense host twin and agent lifecycle rationale [skip ci] * [comment audit] Condense browser authority and editor cleanup rationale [skip ci] * [comment audit] Condense editor buffer identity and review cache rationale [skip ci] * [comment audit] Condense provider lifetime and repository ownership rationale [skip ci] * [comment audit] Condense reconnect, hydration and terminal retirement rationale [skip ci] * [comment audit] Condense recovery, reconciliation and persistence rationale [skip ci] * [comment audit] Condense cleanup ownership and listing admission rationale [skip ci] * [comment audit] Condense teardown ordering and terminal ownership rationale [skip ci] * [comment audit] Condense recovery ledger and web host-boundary rationale [skip ci] * [comment audit] Condense web reconnect and credential lifecycle rationale [skip ci] * [comment audit] Condense cloud rollout and assignment safety rationale [skip ci] * [comment audit] Condense relay session and database recovery rationale [skip ci] * [comment audit] Condense relay placement and protocol compatibility rationale [skip ci] * [comment audit] Condense build and packaging constraints [skip ci] * [comment audit] Condense CI gates and validation isolation rationale [skip ci] * [comment audit] Condense catalog identity and browser validation rationale [skip ci] * [comment audit] Condense mobile browser and frame-budget rationale [skip ci] * [comment audit] Shorten mobile route and browser test explanations [skip ci] * [comment audit] Condense editor and session seam test rationale [skip ci] * [comment audit] Trim session and terminal closure history [skip ci] * [comment audit] Shorten terminal browser and census explanations [skip ci] * [comment audit] Preserve root-only E2E diagnostic baseline * [comment audit] Condense mobile seam and HTML preview test rationale [skip ci] * [comment audit] Condense preview instrumentation and render harness rationale [skip ci] * [comment audit] Condense bundle contracts and native-build constraints [skip ci] * [comment audit] Shorten native packaging and CI routing rationale [skip ci] * [comment audit] Condense release tooling and benchmark constraints [skip ci] * [comment audit] Condense packaging and platform gate rationale [skip ci] * [comment audit] Shorten mobile route and history rationale [skip ci] * [comment audit] Condense mobile browser and component rationale [skip ci] * [comment audit] Shorten drawer and workspace-form rationale [skip ci] * [comment audit] Trim mobile review and editor-document explanations [skip ci] * [comment audit] Condense editor lifecycle and Markdown explanations [skip ci] * [comment audit] Trim mobile file, diagnostics and dictation commentary [skip ci] * [comment audit] Simplify mobile host-list and workspace-view explanations [skip ci] * [comment audit] Clarify mobile shell lifetime and bridge delivery constraints [skip ci] * [comment audit] Condense bridge stream, native request and audio rationale [skip ci] * [comment audit] Reduce bridge schema and page-client commentary [skip ci] * [comment audit] Tighten mobile page bridge contracts [skip ci] * [comment audit] Clarify mobile cache and session constraints [skip ci] * [comment audit] Condense mobile route and lifecycle rationale [skip ci] * [comment audit] Tighten mobile shell hook timing rationale [skip ci] * [comment audit] Clarify route handoff and notification compatibility [skip ci] * [comment audit] Clarify push registration and dictation cleanup constraints [skip ci] * [comment audit] Condense keyboard geometry and native media ownership rationale [skip ci] * [comment audit] Tighten mobile chat scroll and composer concurrency rationale [skip ci] * [comment audit] Condense mobile reply compatibility and upload ownership rationale [skip ci] * [comment audit] Condense optimistic echo and scroll ownership rationale [skip ci] * [comment audit] Tighten stream replay and queued session ownership rationale [skip ci] * [comment audit] Condense mobile composer and reply-schema constraints [skip ci] * [comment audit] Clarify mobile transcript recovery and session ownership [skip ci] * [comment audit] Clarify mobile queue and source-control compatibility [skip ci] * [comment audit] Condense storage and task launch constraints [skip ci] * [comment audit] Clarify task schemas and terminal document ownership [skip ci] * [comment audit] Tighten terminal input and viewport rationale [skip ci] * [comment audit] Shorten terminal and RPC recorder contracts [skip ci] * [comment audit] Condense recorder engine and connection contracts [skip ci] * [comment audit] Shorten host credential and relay recovery comments [skip ci] * [comment audit] Tighten pairing recovery and bundle transport rationale [skip ci] * [comment audit] Clarify RPC delivery and workspace catalog boundaries [skip ci] * [comment audit] Condense catalog and relay entry contracts [skip ci] * [comment audit] Shorten shutdown and SSH relay delivery contracts [skip ci] * [comment audit] Condense WSL relay and E2E regression rationale [skip ci] * [comment audit] Shorten cross-version harness and renderer fixture explanations [skip ci] * [comment audit] Condense paired-host and E2E lifecycle rationale [skip ci] * [comment audit] Shorten layout oracle and regression explanations [skip ci] * [comment audit] Condense paired recovery and ownership regression contracts [skip ci] * [comment audit] Shorten restart, sidebar and SSH lifecycle contracts [skip ci] * [comment audit] Condense terminal rendering and IME regression rationale [skip ci] * [comment audit] Shorten terminal recovery and benchmark contracts [skip ci] * [comment audit] Condense tooling and Windows lifecycle proof rationale [skip ci] * [comment audit] Condense workflow routing and release rationale [skip ci] * [comment audit] Condense mobile security and installer contracts [skip ci] * [comment audit] Shorten native rationale and stylesheet workarounds [skip ci] * [comment audit] Shorten Windows visibility probe rationale [skip ci] * [comment audit] Condense embedded release and signing rationale [skip ci] * [comment audit] Retain current main test behavior in final reconciliation [skip ci] * [comment audit] Complete final comment review across current source inventory * [comment audit] Handle consolidated audit reports before final CI * [comment audit] Preserve credential storage census detail [skip ci] * [comment audit] Scale broad changed E2E selections with verified timing shards * [comment audit] Align E2E assertions with browser close and settled session metadata * [comment audit] Match existing E2Es to current resource and windowing contracts * [comment audit] Observe the shared server outage notice in reconnect coverage * [comment audit] Make existing renderer probes measure settled tab and PTY work * [comment audit] Align palette E2E selector with task search * [comment audit] Refresh remaining palette search selectors * [comment audit] Keep selector correction within selected audit specs * [comment audit] Await emoji fixture settings inside renderer evaluation * [comment audit] Cancel fixture fit retries between terminal tests --------- Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
476 lines
17 KiB
JavaScript
476 lines
17 KiB
JavaScript
const { readdirSync, openSync, readSync, closeSync } = require('node:fs')
|
||
const { spawnSync } = require('node:child_process')
|
||
const { join, relative } = require('node:path')
|
||
|
||
// Gate bundled Linux binaries against Ubuntu 20.04 symbol floors to prevent native load crashes (#9902); see docs/reference/linux-glibc-compatibility.md.
|
||
const MIN_GLIBC = Object.freeze([2, 31])
|
||
|
||
// Gate glibc plus system libstdc++ ABI families; Orca bundles no C++ runtime for desktop.
|
||
const VERSION_FLOORS = Object.freeze([
|
||
Object.freeze({ prefix: 'GLIBC_', floor: MIN_GLIBC }),
|
||
Object.freeze({ prefix: 'GLIBCXX_', floor: Object.freeze([3, 4, 28]) }),
|
||
Object.freeze({ prefix: 'CXXABI_', floor: Object.freeze([1, 3, 12]) })
|
||
])
|
||
const FLOOR_LABEL = 'Ubuntu 20.04 (glibc 2.31 / libstdc++ GLIBCXX_3.4.28)'
|
||
|
||
function glibcFloorProfile(label, glibc, glibcxx, cxxabi) {
|
||
return Object.freeze({
|
||
label,
|
||
families: Object.freeze([
|
||
Object.freeze({ prefix: 'GLIBC_', floor: Object.freeze(glibc) }),
|
||
Object.freeze({ prefix: 'GLIBCXX_', floor: Object.freeze(glibcxx) }),
|
||
Object.freeze({ prefix: 'CXXABI_', floor: Object.freeze(cxxabi) })
|
||
])
|
||
})
|
||
}
|
||
|
||
/** The packaged desktop app's floor; the default for every check here. */
|
||
const DESKTOP_GLIBC_FLOOR = Object.freeze({ label: FLOOR_LABEL, families: VERSION_FLOORS })
|
||
|
||
// Server slots target pinned Node’s glibc 2.28 hosts, including RHEL8/Debian10 GCC8; desktop’s 2.31 floor would leave older hosts without terminals (D6).
|
||
const SERVER_SLOT_GLIBC_FLOOR = glibcFloorProfile(
|
||
'glibc 2.28 (RHEL 8 / Debian 10, libstdc++ GLIBCXX_3.4.25)',
|
||
[2, 28],
|
||
[3, 4, 25],
|
||
[1, 3, 11]
|
||
)
|
||
|
||
// The linux-x64-glibc217 compat slot beside the unofficial glibc-217 Node (design D6 rung B).
|
||
const COMPAT_SLOT_GLIBC_FLOOR = glibcFloorProfile(
|
||
'glibc 2.17 (CentOS 7, libstdc++ GLIBCXX_3.4.19)',
|
||
[2, 17],
|
||
[3, 4, 19],
|
||
[1, 3, 7]
|
||
)
|
||
|
||
// Lazy speech-worker sherpa prebuilt requires GCC11 GLIBCXX_3.4.29; exempt C++ floor only, retain glibc gate because it is not launch-critical.
|
||
const LIBSTDCXX_FLOOR_EXEMPT = /(?:^|[/\\])sherpa-onnx/
|
||
|
||
// VER_FLG_WEAK needs may resolve null and use fallback; they must not raise required floor.
|
||
const VER_FLG_WEAK = 0x2
|
||
|
||
/** Parse a "2.34" / "3.4.28" version string into a numeric tuple. */
|
||
function parseGlibcVersion(versionStr) {
|
||
return versionStr.split('.').map((part) => Number.parseInt(part, 10))
|
||
}
|
||
|
||
/** Compare two numeric version tuples; missing trailing parts are 0. */
|
||
function compareGlibcVersions(a, b) {
|
||
const length = Math.max(a.length, b.length)
|
||
for (let i = 0; i < length; i += 1) {
|
||
const diff = (a[i] ?? 0) - (b[i] ?? 0)
|
||
if (diff !== 0) {
|
||
return diff < 0 ? -1 : 1
|
||
}
|
||
}
|
||
return 0
|
||
}
|
||
|
||
// Read objdump -p Version References, not just symbols: symbol-less ABI markers (DT_RELR/glibc2.36) still block loader. Rows are 0xHASH 0xFLAGS <n> <NAME>.
|
||
function parseVersionNeeds(objdumpOutput) {
|
||
const needs = []
|
||
let library = null
|
||
let inSection = false
|
||
for (const line of objdumpOutput.split('\n')) {
|
||
if (line.startsWith('Version References:')) {
|
||
inSection = true
|
||
continue
|
||
}
|
||
if (!inSection) {
|
||
continue
|
||
}
|
||
// Any new non-indented line ends the Version References block.
|
||
if (!/^\s/.test(line)) {
|
||
inSection = false
|
||
continue
|
||
}
|
||
const libraryMatch = line.match(/^\s+required from (\S+):/)
|
||
if (libraryMatch) {
|
||
library = libraryMatch[1]
|
||
continue
|
||
}
|
||
const entryMatch = line.match(/^\s+0x[0-9a-fA-F]+\s+0x([0-9a-fA-F]+)\s+\d+\s+(\S+)/)
|
||
if (entryMatch) {
|
||
const flags = Number.parseInt(entryMatch[1], 16)
|
||
needs.push({ library, name: entryMatch[2], weak: (flags & VER_FLG_WEAK) !== 0 })
|
||
}
|
||
}
|
||
return needs
|
||
}
|
||
|
||
// Compare numeric GLIBC/GLIBCXX/CXXABI versions; reject all nonnumeric GLIBC markers/private ABI. Named C++ ABI nodes ship on floor; unrelated families remain ungated.
|
||
function isVersionNodeAboveFloor(name, glibcFloor = DESKTOP_GLIBC_FLOOR) {
|
||
for (const { prefix, floor } of glibcFloor.families) {
|
||
if (!name.startsWith(prefix)) {
|
||
continue
|
||
}
|
||
const rest = name.slice(prefix.length)
|
||
if (/^[0-9]+(?:\.[0-9]+)*$/.test(rest)) {
|
||
return compareGlibcVersions(parseGlibcVersion(rest), floor) > 0
|
||
}
|
||
// Non-numeric suffix: reject every glibc node (ABI markers and PRIVATE).
|
||
return prefix === 'GLIBC_'
|
||
}
|
||
return false
|
||
}
|
||
|
||
function isLibstdcxxNode(name) {
|
||
return name.startsWith('GLIBCXX_') || name.startsWith('CXXABI_')
|
||
}
|
||
|
||
// Gate load-time requirements; exempt sherpa C++ needs only, never its glibc needs.
|
||
function findFloorViolations(needs, filePath = '', glibcFloor = DESKTOP_GLIBC_FLOOR) {
|
||
const exemptLibstdcxx = LIBSTDCXX_FLOOR_EXEMPT.test(filePath)
|
||
return needs.filter(
|
||
(need) =>
|
||
!need.weak &&
|
||
isVersionNodeAboveFloor(need.name, glibcFloor) &&
|
||
!(exemptLibstdcxx && isLibstdcxxNode(need.name))
|
||
)
|
||
}
|
||
|
||
// Ubuntu20.04 keeps openpty/forkpty in libutil until glibc2.34 merge: require DT_NEEDED even when newer libc compat aliases satisfy link-time floor.
|
||
// Pins node-pty’s -l:libutil.so.1; omit pthread_sigmask because Node/Electron already loads libpthread.
|
||
const RELOCATED_SYMBOL_PROVIDERS = Object.freeze({
|
||
openpty: 'libutil.so.1',
|
||
forkpty: 'libutil.so.1'
|
||
})
|
||
|
||
// Require providing DSO in DT_NEEDED for relocated imports that newer build host could resolve from libc alias.
|
||
function findMissingProviderDeps(importedSymbols, neededLibraries) {
|
||
const missing = []
|
||
for (const [symbol, library] of Object.entries(RELOCATED_SYMBOL_PROVIDERS)) {
|
||
if (importedSymbols.has(symbol) && !neededLibraries.has(library)) {
|
||
missing.push({ symbol, library })
|
||
}
|
||
}
|
||
return missing
|
||
}
|
||
|
||
// ELF e_machine values for the Linux slices we package. Names match electron-builder's Arch enum.
|
||
const ELF_MACHINE_BY_ARCH = Object.freeze({ x64: 0x3e, arm64: 0xb7 })
|
||
const ARCH_BY_ELF_MACHINE = Object.freeze({ 0x3e: 'x64', 0xb7: 'arm64' })
|
||
|
||
// Read little-endian ELF machine separately: cross-build can ship host-arch addon whose symbol floors pass but target cannot load.
|
||
function readElfMachine(filePath) {
|
||
let fd
|
||
try {
|
||
fd = openSync(filePath, 'r')
|
||
const header = Buffer.alloc(20)
|
||
if (readSync(fd, header, 0, 20, 0) !== 20) {
|
||
return null
|
||
}
|
||
// EI_DATA (offset 5) must be ELFDATA2LSB for a little-endian e_machine read.
|
||
if (header[5] !== 1) {
|
||
return null
|
||
}
|
||
return header.readUInt16LE(18)
|
||
} catch {
|
||
return null
|
||
} finally {
|
||
if (fd !== undefined) {
|
||
closeSync(fd)
|
||
}
|
||
}
|
||
}
|
||
|
||
// Arch tokens that appear in vendored per-architecture package/directory names.
|
||
const ARCH_TOKEN_PATTERN = /(?:^|[^a-z0-9])(arm64|aarch64|x64|x86_64)(?:[^a-z0-9]|$)/i
|
||
const ARCH_BY_TOKEN = Object.freeze({ arm64: 'arm64', aarch64: 'arm64', x64: 'x64', x86_64: 'x64' })
|
||
|
||
// Judge multi-arch vendored binaries by declared path architecture; valid arm64 watcher may intentionally ship inside x64 slice.
|
||
function declaredArchFromPath(filePath) {
|
||
const match = ARCH_TOKEN_PATTERN.exec(filePath)
|
||
return match ? ARCH_BY_TOKEN[match[1].toLowerCase()] : null
|
||
}
|
||
|
||
function findArchViolation(filePath, targetArch, rootDir) {
|
||
// Judge declared payload arch, not slice; use relative paths so outer linux-arm64-unpacked directory cannot label every file arm64.
|
||
const declared = declaredArchFromPath(rootDir ? relative(rootDir, filePath) : filePath)
|
||
const expectedArch = declared ?? targetArch
|
||
const expected = ELF_MACHINE_BY_ARCH[expectedArch]
|
||
if (expected === undefined) {
|
||
return null
|
||
}
|
||
const machine = readElfMachine(filePath)
|
||
if (machine === null || machine === expected) {
|
||
return null
|
||
}
|
||
return {
|
||
machine,
|
||
actual: ARCH_BY_ELF_MACHINE[machine] ?? `0x${machine.toString(16)}`,
|
||
expectedArch,
|
||
declared: declared !== null
|
||
}
|
||
}
|
||
|
||
function isElfFile(filePath) {
|
||
let fd
|
||
try {
|
||
fd = openSync(filePath, 'r')
|
||
const header = Buffer.alloc(4)
|
||
const bytesRead = readSync(fd, header, 0, 4, 0)
|
||
return bytesRead === 4 && header[0] === 0x7f && header.toString('latin1', 1, 4) === 'ELF'
|
||
} catch {
|
||
return false
|
||
} finally {
|
||
if (fd !== undefined) {
|
||
closeSync(fd)
|
||
}
|
||
}
|
||
}
|
||
|
||
/** Recursively collect ELF native binaries (`.node`, `.so[.N]`, executables). */
|
||
function collectNativeBinaries(rootDir) {
|
||
const binaries = []
|
||
const walk = (dir) => {
|
||
let entries
|
||
try {
|
||
entries = readdirSync(dir, { withFileTypes: true })
|
||
} catch {
|
||
return
|
||
}
|
||
for (const entry of entries) {
|
||
const fullPath = join(dir, entry.name)
|
||
if (entry.isSymbolicLink()) {
|
||
continue
|
||
}
|
||
if (entry.isDirectory()) {
|
||
walk(fullPath)
|
||
continue
|
||
}
|
||
if (!entry.isFile()) {
|
||
continue
|
||
}
|
||
// Inspect .node/.so and ELF-magic executables; skip asar/assets without invoking objdump.
|
||
const looksNative = entry.name.endsWith('.node') || /\.so(\.\d+)*$/.test(entry.name)
|
||
if (looksNative || !entry.name.includes('.')) {
|
||
if (isElfFile(fullPath)) {
|
||
binaries.push(fullPath)
|
||
}
|
||
}
|
||
}
|
||
}
|
||
walk(rootDir)
|
||
return binaries.sort()
|
||
}
|
||
|
||
function resolveObjdump(explicitPath) {
|
||
const candidates = [explicitPath, 'objdump', 'llvm-objdump'].filter(Boolean)
|
||
for (const candidate of candidates) {
|
||
const probe = spawnSync(candidate, ['--version'], { encoding: 'utf8', env: cLocaleEnv() })
|
||
if (!probe.error && probe.status === 0) {
|
||
return candidate
|
||
}
|
||
}
|
||
return null
|
||
}
|
||
|
||
// Force C locale/LC_ALL to keep objdump’s gettext-translated section headers parseable, including hosts with LANGUAGE set.
|
||
function cLocaleEnv() {
|
||
return { ...process.env, LC_ALL: 'C', LANG: 'C' }
|
||
}
|
||
|
||
// Fail closed on objdump spawn/exit/signal errors: unreadable/truncated/unsupported binary must not pass floor check.
|
||
function runObjdump(objdumpPath, flag, filePath) {
|
||
const result = spawnSync(objdumpPath, [flag, filePath], {
|
||
encoding: 'utf8',
|
||
maxBuffer: 64 * 1024 * 1024,
|
||
env: cLocaleEnv()
|
||
})
|
||
if (result.error) {
|
||
throw new Error(
|
||
`[verify-linux-glibc-floor] could not run objdump on ${filePath}: ${result.error.message}`
|
||
)
|
||
}
|
||
if (result.signal || result.status !== 0) {
|
||
throw new Error(
|
||
`[verify-linux-glibc-floor] objdump ${flag} failed for ${filePath} ` +
|
||
`(status ${result.status}, signal ${result.signal ?? 'none'}): ${(result.stderr || '').trim()}`
|
||
)
|
||
}
|
||
return result.stdout || ''
|
||
}
|
||
|
||
/** DT_NEEDED shared-library names from `objdump -p` (` NEEDED <lib>`). */
|
||
function parseNeededLibraries(objdumpOutput) {
|
||
const needed = new Set()
|
||
for (const line of objdumpOutput.split('\n')) {
|
||
const match = line.match(/^\s+NEEDED\s+(\S+)/)
|
||
if (match) {
|
||
needed.add(match[1])
|
||
}
|
||
}
|
||
return needed
|
||
}
|
||
|
||
/** Undefined (imported) dynamic symbol base names from `objdump -T` (`*UND*`). */
|
||
function parseImportedSymbols(objdumpOutput) {
|
||
const imported = new Set()
|
||
for (const line of objdumpOutput.split('\n')) {
|
||
if (!line.includes('*UND*')) {
|
||
continue
|
||
}
|
||
// The symbol name is the final token; strip any @VERSION suffix.
|
||
const token = line.trim().split(/\s+/).pop()
|
||
if (token) {
|
||
imported.add(token.split('@')[0])
|
||
}
|
||
}
|
||
return imported
|
||
}
|
||
|
||
/** Version needs + DT_NEEDED from a single `objdump -p` (fail-closed). */
|
||
function readDynamicInfo(filePath, objdumpPath) {
|
||
const output = runObjdump(objdumpPath, '-p', filePath)
|
||
const versionNeeds = parseVersionNeeds(output)
|
||
const neededLibraries = parseNeededLibraries(output)
|
||
return {
|
||
versionNeeds,
|
||
neededLibraries,
|
||
// LLVM prints an empty Dynamic Section even for static executables.
|
||
isStatic:
|
||
/^Program Header:/m.test(output) &&
|
||
/^\s+LOAD\s+off\s+0x[0-9a-f]+/m.test(output) &&
|
||
!/^\s+(?:DYNAMIC|INTERP)\s+off\s+/m.test(output) &&
|
||
versionNeeds.length === 0 &&
|
||
neededLibraries.size === 0
|
||
}
|
||
}
|
||
|
||
function isMuslTemplatePayload(filePath, neededLibraries, versionNeeds) {
|
||
return (
|
||
/(?:^|[/\\])orcad-template[/\\]targets[/\\]linux-(?:x64|arm64)-musl[/\\]/.test(filePath) &&
|
||
[...neededLibraries].some(
|
||
(name) => name === 'libc.so' || /^libc\.musl-[\w-]+\.so\.1$/.test(name)
|
||
) &&
|
||
![...neededLibraries, ...versionNeeds.map((need) => need.library)].some((name) =>
|
||
/^(?:libc\.so\.6|libm\.so\.6|libpthread\.so\.0|libdl\.so\.2|librt\.so\.1|ld-linux.*)$/.test(
|
||
name
|
||
)
|
||
)
|
||
)
|
||
}
|
||
|
||
/** Imported (undefined) dynamic symbols from `objdump -T` (fail-closed). */
|
||
function readImportedSymbols(filePath, objdumpPath) {
|
||
return parseImportedSymbols(runObjdump(objdumpPath, '-T', filePath))
|
||
}
|
||
|
||
// Gate all native binaries under root with selected/default desktop floor; missing objdump on Linux must throw rather than silently skip.
|
||
function verifyLinuxGlibcFloor(rootDir, options = {}) {
|
||
const binaries = collectNativeBinaries(rootDir)
|
||
const targetArch = options.targetArch
|
||
const glibcFloor = options.glibcFloor ?? DESKTOP_GLIBC_FLOOR
|
||
if (binaries.length === 0) {
|
||
console.log(`[verify-linux-glibc-floor] OK — no bundled native binaries under ${rootDir}`)
|
||
return
|
||
}
|
||
|
||
// Resolve objdump only when ELF files exist so empty fixtures work without binutils.
|
||
const objdumpPath = resolveObjdump(options.objdumpPath)
|
||
if (!objdumpPath) {
|
||
throw new Error(
|
||
'[verify-linux-glibc-floor] objdump not found. Install binutils on the Linux ' +
|
||
'packaging host so the glibc-floor gate can inspect bundled native binaries.'
|
||
)
|
||
}
|
||
|
||
// Reject wrong architecture before version inspection; its valid symbol versions cannot establish target compatibility.
|
||
const archOffenders = binaries
|
||
.map((filePath) => ({ filePath, violation: findArchViolation(filePath, targetArch, rootDir) }))
|
||
.filter(({ violation }) => violation !== null)
|
||
if (archOffenders.length > 0) {
|
||
const detail = archOffenders
|
||
.map(
|
||
({ filePath, violation }) =>
|
||
` ${relative(rootDir, filePath) || filePath} is ${violation.actual}, expected ` +
|
||
`${violation.expectedArch}${violation.declared ? ' (from its own path)' : ''}`
|
||
)
|
||
.join('\n')
|
||
throw new Error(
|
||
`[verify-linux-glibc-floor] ${archOffenders.length} bundled native binar` +
|
||
`${archOffenders.length === 1 ? 'y is' : 'ies are'} built for the wrong architecture ` +
|
||
`(target ${targetArch}), so the app will fail to load them at runtime:\n${detail}\n` +
|
||
'Cross-building a Linux slice can pack the host architecture despite a forced rebuild; ' +
|
||
'build this slice on a native runner.'
|
||
)
|
||
}
|
||
|
||
const offenders = []
|
||
for (const filePath of binaries) {
|
||
const { versionNeeds, neededLibraries, isStatic } = readDynamicInfo(filePath, objdumpPath)
|
||
const isMuslTarget = isMuslTemplatePayload(filePath, neededLibraries, versionNeeds)
|
||
// Remote musl payloads use their host's C++ runtime, not Ubuntu's libstdc++ or libutil.
|
||
const floorViolations = findFloorViolations(versionNeeds, filePath, glibcFloor).filter(
|
||
(need) => !isMuslTarget || !isLibstdcxxNode(need.name)
|
||
)
|
||
// Inspect dynamic symbols only if relocated provider is absent from DT_NEEDED; healthy case avoids extra objdump.
|
||
const providerViolations =
|
||
!isStatic &&
|
||
!isMuslTarget &&
|
||
Object.values(RELOCATED_SYMBOL_PROVIDERS).some((library) => !neededLibraries.has(library))
|
||
? findMissingProviderDeps(readImportedSymbols(filePath, objdumpPath), neededLibraries)
|
||
: []
|
||
if (floorViolations.length > 0 || providerViolations.length > 0) {
|
||
offenders.push({ filePath, floorViolations, providerViolations })
|
||
}
|
||
}
|
||
|
||
if (offenders.length > 0) {
|
||
const detail = offenders
|
||
.map(({ filePath, floorViolations, providerViolations }) => {
|
||
const reasons = []
|
||
if (floorViolations.length > 0) {
|
||
const nodes = [...new Set(floorViolations.map((v) => v.name))].sort()
|
||
const libraries = [...new Set(floorViolations.map((v) => v.library).filter(Boolean))]
|
||
reasons.push(
|
||
`needs ${nodes.join(', ')}${libraries.length > 0 ? ` (from ${libraries.join(', ')})` : ''}`
|
||
)
|
||
}
|
||
for (const { symbol, library } of providerViolations) {
|
||
reasons.push(`imports ${symbol} but ${library} is not in DT_NEEDED`)
|
||
}
|
||
return ` ${relative(rootDir, filePath) || filePath} ${reasons.join('; ')}`
|
||
})
|
||
.join('\n')
|
||
throw new Error(
|
||
`[verify-linux-glibc-floor] ${offenders.length} bundled native binar${offenders.length === 1 ? 'y' : 'ies'} ` +
|
||
`will not load on ${glibcFloor.label}, so the app will crash on startup there:\n${detail}\n` +
|
||
'See docs/reference/linux-glibc-compatibility.md — rebuild the offending module against an older ' +
|
||
'toolchain or pin the relocated symbols (as config/patches/node-pty@1.1.0.patch does).'
|
||
)
|
||
}
|
||
|
||
console.log(
|
||
`[verify-linux-glibc-floor] OK — ${binaries.length} bundled native binaries meet applicable ${glibcFloor.label} requirements`
|
||
)
|
||
}
|
||
|
||
module.exports = {
|
||
MIN_GLIBC,
|
||
ELF_MACHINE_BY_ARCH,
|
||
readElfMachine,
|
||
declaredArchFromPath,
|
||
findArchViolation,
|
||
VERSION_FLOORS,
|
||
FLOOR_LABEL,
|
||
DESKTOP_GLIBC_FLOOR,
|
||
SERVER_SLOT_GLIBC_FLOOR,
|
||
COMPAT_SLOT_GLIBC_FLOOR,
|
||
RELOCATED_SYMBOL_PROVIDERS,
|
||
parseGlibcVersion,
|
||
compareGlibcVersions,
|
||
parseVersionNeeds,
|
||
parseNeededLibraries,
|
||
parseImportedSymbols,
|
||
isVersionNodeAboveFloor,
|
||
isLibstdcxxNode,
|
||
findFloorViolations,
|
||
findMissingProviderDeps,
|
||
collectNativeBinaries,
|
||
readDynamicInfo,
|
||
readImportedSymbols,
|
||
verifyLinuxGlibcFloor
|
||
}
|