mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-12 20:16:15 -08:00
fix(packaging): expose bounded helper probe details
This commit is contained in:
1 parent
4302aa4058
commit
a575af78bf
10 files changed
+122
-22
No files matched your search
@@ -999,6 +999,8 @@ jobs:
|
||||
'$2 == "iptvnator:graphics-core22" && $3 == "mesa-core22:graphics-core22" { found=1 } END { exit !found }'
|
||||
snap connections iptvnator | awk \
|
||||
'$2 == "iptvnator:gnome-3-28-1804" && $3 == "gnome-3-28-1804:gnome-3-28-1804" { found=1 } END { exit !found }'
|
||||
snap connections iptvnator | awk \
|
||||
'$1 == "shared-memory" && $2 == "iptvnator:shared-memory" && $3 == ":shared-memory" { found=1 } END { exit !found }'
|
||||
xvfb-run -a env \
|
||||
LIBGL_ALWAYS_SOFTWARE=1 \
|
||||
__EGL_VENDOR_LIBRARY_FILENAMES=/tmp/hostile-egl-vendor.json \
|
||||
|
||||
@@ -278,8 +278,10 @@ Key files:
|
||||
runs the complete cached manifest/hash/helper gate before BrowserWindow
|
||||
startup and exits with one availability JSON line. A nonzero helper exit
|
||||
keeps top-level reason `helper-probe-failed`; `helperReason` is present only
|
||||
for an exact protocol-v1 line carrying a fixed allowlisted reason. Any loader
|
||||
failure remains a stable native-view fallback, never a flag-enabled success.
|
||||
for an exact protocol-v1 line carrying a fixed allowlisted reason, and its
|
||||
optional `helperDetail` must be 1–1024 printable ASCII characters. Invalid
|
||||
detail suppresses both helper fields. Any loader failure remains a stable
|
||||
native-view fallback, never a flag-enabled success.
|
||||
- In the exact packaged Flatpak `/app` context, reconstruct only Freedesktop
|
||||
Platform 24.08's immutable `__EGL_EXTERNAL_PLATFORM_CONFIG_DIRS`; its GL
|
||||
extension loader path comes from the sandbox cache. Flatpak CI must invoke
|
||||
|
||||
@@ -670,8 +670,9 @@ engine` (restart required) or
|
||||
before BrowserWindow startup and emits one availability JSON line. A nonzero
|
||||
helper exit keeps top-level reason `helper-probe-failed`; `helperReason` is
|
||||
present only for an exact protocol-v1 line carrying a fixed allowlisted
|
||||
reason. The exact packaged Flatpak `/app` context reconstructs only
|
||||
Freedesktop Platform 24.08's immutable
|
||||
reason, and its optional `helperDetail` must be 1–1024 printable ASCII
|
||||
characters. Invalid detail suppresses both helper fields. The exact packaged
|
||||
Flatpak `/app` context reconstructs only Freedesktop Platform 24.08's immutable
|
||||
`__EGL_EXTERNAL_PLATFORM_CONFIG_DIRS`; its CI smoke invokes that
|
||||
application-level probe instead of the helper directly. Bundled
|
||||
Linux packages carry hash-validated
|
||||
|
||||
+61
-5
@@ -134,6 +134,10 @@ describe('embedded-mpv frame-copy helper failures', () => {
|
||||
'shared-memory-initialize-failed',
|
||||
])('preserves the allowlisted helper reason %s', (helperReason) => {
|
||||
const fixture = createFixture(context.rootDir);
|
||||
const helperDetail =
|
||||
helperReason === 'mpv-create-failed'
|
||||
? undefined
|
||||
: 'EGL initialization failed: display unavailable';
|
||||
context.spawnRuntimeProbe.mockReturnValue({
|
||||
status: 1,
|
||||
signal: null,
|
||||
@@ -141,11 +145,7 @@ describe('embedded-mpv frame-copy helper failures', () => {
|
||||
protocol: 1,
|
||||
usable: false,
|
||||
reason: helperReason,
|
||||
...(helperReason === 'mpv-create-failed'
|
||||
? {}
|
||||
: {
|
||||
detail: 'diagnostic detail is intentionally not propagated',
|
||||
}),
|
||||
...(helperDetail ? { detail: helperDetail } : {}),
|
||||
})}\n`,
|
||||
stderr: '',
|
||||
});
|
||||
@@ -154,6 +154,32 @@ describe('embedded-mpv frame-copy helper failures', () => {
|
||||
usable: false,
|
||||
reason: 'helper-probe-failed',
|
||||
helperReason,
|
||||
...(helperDetail ? { helperDetail } : {}),
|
||||
});
|
||||
});
|
||||
|
||||
it.each([
|
||||
['one printable ASCII character', 'x'],
|
||||
['1024 printable ASCII characters', 'x'.repeat(1024)],
|
||||
])('preserves %s as helper detail', (_label, helperDetail) => {
|
||||
const fixture = createFixture(context.rootDir);
|
||||
context.spawnRuntimeProbe.mockReturnValue({
|
||||
status: 1,
|
||||
signal: null,
|
||||
stdout: `${JSON.stringify({
|
||||
protocol: 1,
|
||||
usable: false,
|
||||
reason: 'gl-context-create-failed',
|
||||
detail: helperDetail,
|
||||
})}\n`,
|
||||
stderr: '',
|
||||
});
|
||||
|
||||
expect(context.createProbe()(fixture.helperPath)).toEqual({
|
||||
usable: false,
|
||||
reason: 'helper-probe-failed',
|
||||
helperReason: 'gl-context-create-failed',
|
||||
helperDetail,
|
||||
});
|
||||
});
|
||||
|
||||
@@ -197,4 +223,34 @@ describe('embedded-mpv frame-copy helper failures', () => {
|
||||
reason: 'helper-probe-failed',
|
||||
});
|
||||
});
|
||||
|
||||
it.each([
|
||||
['empty', ''],
|
||||
['control character', 'EGL\tfailure'],
|
||||
['trailing line feed', 'EGL failure\n'],
|
||||
['DEL character', `EGL${String.fromCharCode(0x7f)}failure`],
|
||||
['non-ASCII character', 'EGL échoué'],
|
||||
['1025 characters', 'x'.repeat(1025)],
|
||||
])(
|
||||
'does not propagate any helper fields from %s detail',
|
||||
(_label, detail) => {
|
||||
const fixture = createFixture(context.rootDir);
|
||||
context.spawnRuntimeProbe.mockReturnValue({
|
||||
status: 1,
|
||||
signal: null,
|
||||
stdout: `${JSON.stringify({
|
||||
protocol: 1,
|
||||
usable: false,
|
||||
reason: 'gl-context-create-failed',
|
||||
detail,
|
||||
})}\n`,
|
||||
stderr: '',
|
||||
});
|
||||
|
||||
expect(context.createProbe()(fixture.helperPath)).toEqual({
|
||||
usable: false,
|
||||
reason: 'helper-probe-failed',
|
||||
});
|
||||
}
|
||||
);
|
||||
});
|
||||
@@ -31,9 +31,21 @@ const HELPER_RUNTIME_PROBE_FAILURE_REASON_ALLOWLIST = new Set<string>(
|
||||
Object.values(EMBEDDED_MPV_HELPER_RUNTIME_PROBE_FAILURE_REASONS)
|
||||
);
|
||||
|
||||
function parseFailedProbeReason(
|
||||
stdout: unknown
|
||||
): EmbeddedMpvHelperRuntimeProbeFailureReason | null {
|
||||
interface ParsedFailedProbe {
|
||||
helperReason: EmbeddedMpvHelperRuntimeProbeFailureReason;
|
||||
helperDetail?: string;
|
||||
}
|
||||
|
||||
function isSafeHelperDetail(value: unknown): value is string {
|
||||
return (
|
||||
typeof value === 'string' &&
|
||||
value.length >= 1 &&
|
||||
value.length <= 1024 &&
|
||||
!/[^\x20-\x7e]/.test(value)
|
||||
);
|
||||
}
|
||||
|
||||
function parseFailedProbe(stdout: unknown): ParsedFailedProbe | null {
|
||||
if (typeof stdout !== 'string' || !/^[^\r\n]+\n$/.test(stdout)) {
|
||||
return null;
|
||||
}
|
||||
@@ -58,12 +70,15 @@ function parseFailedProbeReason(
|
||||
parsed.usable !== false ||
|
||||
typeof parsed.reason !== 'string' ||
|
||||
!HELPER_RUNTIME_PROBE_FAILURE_REASON_ALLOWLIST.has(parsed.reason) ||
|
||||
(hasDetail &&
|
||||
(typeof parsed.detail !== 'string' || parsed.detail.length === 0))
|
||||
(hasDetail && !isSafeHelperDetail(parsed.detail))
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
return parsed.reason as EmbeddedMpvHelperRuntimeProbeFailureReason;
|
||||
return {
|
||||
helperReason:
|
||||
parsed.reason as EmbeddedMpvHelperRuntimeProbeFailureReason,
|
||||
...(hasDetail ? { helperDetail: parsed.detail as string } : {}),
|
||||
};
|
||||
}
|
||||
|
||||
function parseSuccessfulProbe(
|
||||
@@ -157,12 +172,12 @@ function runHelperProbe(
|
||||
return failure('helper-probe-signaled');
|
||||
}
|
||||
if (result.status !== 0) {
|
||||
const helperReason = parseFailedProbeReason(result.stdout);
|
||||
return helperReason
|
||||
const helperFailure = parseFailedProbe(result.stdout);
|
||||
return helperFailure
|
||||
? {
|
||||
usable: false,
|
||||
reason: 'helper-probe-failed',
|
||||
helperReason,
|
||||
...helperFailure,
|
||||
}
|
||||
: failure('helper-probe-failed');
|
||||
}
|
||||
|
||||
@@ -55,6 +55,7 @@ export type EmbeddedMpvFrameCopyRuntimeResult =
|
||||
usable: false;
|
||||
reason: EmbeddedMpvFrameCopyRuntimeFailureReason;
|
||||
helperReason?: EmbeddedMpvHelperRuntimeProbeFailureReason;
|
||||
helperDetail?: string;
|
||||
};
|
||||
|
||||
export interface EmbeddedMpvFrameCopyRuntimeFileSystem {
|
||||
|
||||
@@ -91,6 +91,7 @@ describe('embedded MPV runtime diagnostic', () => {
|
||||
usable: false,
|
||||
reason: 'helper-probe-failed',
|
||||
helperReason: 'gl-context-create-failed',
|
||||
helperDetail: 'EGL initialization failed: display unavailable',
|
||||
};
|
||||
const harness = createHarness(availability);
|
||||
|
||||
@@ -101,7 +102,7 @@ describe('embedded MPV runtime diagnostic', () => {
|
||||
|
||||
expect(harness.getRuntimeAvailability).toHaveBeenCalledTimes(1);
|
||||
expect(harness.writeStdout).toHaveBeenCalledWith(
|
||||
'{"usable":false,"reason":"helper-probe-failed","helperReason":"gl-context-create-failed"}\n'
|
||||
'{"usable":false,"reason":"helper-probe-failed","helperReason":"gl-context-create-failed","helperDetail":"EGL initialization failed: display unavailable"}\n'
|
||||
);
|
||||
expect(harness.writeStdout).toHaveBeenCalledTimes(1);
|
||||
expect(harness.exit).toHaveBeenCalledWith(1);
|
||||
|
||||
@@ -278,8 +278,11 @@ failures emit the stable helper reasons `shared-memory-create-failed` and
|
||||
JSON line and return zero. When the helper exits nonzero with an otherwise
|
||||
exact failure line, the application availability diagnostic keeps the
|
||||
fail-closed top-level reason `helper-probe-failed` and may add only the
|
||||
allowlisted helper reason as `helperReason`; malformed, multi-line,
|
||||
wrong-protocol, or unknown failure output never reaches that field.
|
||||
allowlisted helper reason as `helperReason`. An optional `helperDetail` is
|
||||
copied only from the same exact line when it contains 1–1024 printable ASCII
|
||||
characters; an invalid detail rejects both helper fields. Malformed,
|
||||
multi-line, wrong-protocol, or unknown failure output never reaches either
|
||||
field.
|
||||
|
||||
The startup probe and every playback helper session use the same sanitized
|
||||
loader environment selected by the validated manifest's cached `runtimeMode`.
|
||||
|
||||
@@ -234,7 +234,9 @@ missing file, hash mismatch, unusable graphics path, or shm lifecycle failure
|
||||
returns a stable reason and keeps the BrowserWindow sandbox enabled. The
|
||||
application diagnostic retains `helper-probe-failed` as the top-level reason
|
||||
for nonzero helper exits and adds `helperReason` only when the helper emitted
|
||||
one exact protocol-v1 line with a fixed allowlisted reason. The
|
||||
one exact protocol-v1 line with a fixed allowlisted reason. Its optional
|
||||
`helperDetail` is restricted to 1–1024 printable ASCII characters; invalid
|
||||
detail suppresses both helper fields. The
|
||||
installed-Snap probe therefore tests the private shared-memory confinement
|
||||
needed by playback rather than only loader and graphics startup.
|
||||
Packaging CI invokes the same gate through
|
||||
|
||||
@@ -650,6 +650,10 @@ test('Snap verifier preserves fail-closed status while exposing captured diagnos
|
||||
snapStep,
|
||||
/\$2 == "iptvnator:gnome-3-28-1804" && \$3 == "gnome-3-28-1804:gnome-3-28-1804"/
|
||||
);
|
||||
assert.match(
|
||||
snapStep,
|
||||
/\$1 == "shared-memory" && \$2 == "iptvnator:shared-memory" && \$3 == ":shared-memory"/
|
||||
);
|
||||
assert.match(
|
||||
snapStep,
|
||||
/sudo snap disconnect iptvnator:graphics-core22 mesa-core22:graphics-core22/
|
||||
@@ -688,9 +692,22 @@ test('Snap verifier preserves fail-closed status while exposing captured diagnos
|
||||
const successfulRuntimeProbe = snapStep.lastIndexOf(
|
||||
'snap run iptvnator --embedded-mpv-runtime-probe'
|
||||
);
|
||||
const graphicsConnectionAssertion = snapStep.indexOf(
|
||||
'$2 == "iptvnator:graphics-core22" && $3 == "mesa-core22:graphics-core22"'
|
||||
);
|
||||
const gnomeConnectionAssertion = snapStep.indexOf(
|
||||
'$2 == "iptvnator:gnome-3-28-1804" && $3 == "gnome-3-28-1804:gnome-3-28-1804"'
|
||||
);
|
||||
const sharedMemoryConnectionAssertion = snapStep.indexOf(
|
||||
'$1 == "shared-memory" && $2 == "iptvnator:shared-memory" && $3 == ":shared-memory"'
|
||||
);
|
||||
assert.ok(firstRuntimeProbe < secondGraphicsConnect);
|
||||
assert.ok(firstRuntimeProbe < successfulRuntimeProbe);
|
||||
assert.ok(secondGraphicsConnect < successfulRuntimeProbe);
|
||||
assert.ok(secondGraphicsConnect < graphicsConnectionAssertion);
|
||||
assert.ok(graphicsConnectionAssertion < gnomeConnectionAssertion);
|
||||
assert.ok(gnomeConnectionAssertion < sharedMemoryConnectionAssertion);
|
||||
assert.ok(sharedMemoryConnectionAssertion < successfulRuntimeProbe);
|
||||
assert.match(snapStep, /snap run iptvnator --embedded-mpv-runtime-probe/);
|
||||
for (const hostileOverride of [
|
||||
'__EGL_VENDOR_LIBRARY_FILENAMES',
|
||||
|
||||
Reference in new issue
Block a user