blob: 107bb24a746f4430d74179bb6c7169534f2e9aa1 [file]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import { constants } from 'node:fs';
import { access, realpath } from 'node:fs/promises';
import { dirname, isAbsolute, resolve } from 'node:path';
import {
resolveFilesystemWorkerBundle,
type FilesystemWorkerResourceLocation,
} from './resource-resolver.js';
import {
resolveMacosExecutableDependencies,
type MacosExecutableDependencyResolution,
} from './macos-executable-dependencies.js';
import {
currentRipgrepEnvironment,
formatRipgrepEnvironmentArg,
type RipgrepEnvironment,
} from '../ripgrep-guidance.js';
import { defaultRipgrepCandidates, resolveRipgrepCandidates } from '../ripgrep-executable.js';
export interface FilesystemWorkerLaunchSpec {
program: string;
args: readonly string[];
env: Readonly<Record<string, string>>;
runtimeReadableRoots: readonly string[];
executableRoots: readonly string[];
}
export type FilesystemWorkerLaunchSpecResult =
| { ok: true; spec: FilesystemWorkerLaunchSpec }
| {
ok: false;
reason: 'worker_bundle_unavailable' | 'runtime_executable_unavailable';
message: string;
};
export type FilesystemWorkerLaunchSpecProvider = (operation: {
kind: string;
}) => Promise<FilesystemWorkerLaunchSpecResult>;
export interface CreateFilesystemWorkerLaunchSpecProviderInput {
runtime: 'node' | 'electron';
platform?: NodeJS.Platform;
executable?: string;
resourceLocation: FilesystemWorkerResourceLocation;
hostEnv?: NodeJS.ProcessEnv;
rgCandidates?: readonly string[];
tmpdir?: string;
/** @internal Test seam for deterministic Mach-O dependency inspection. */
inspectMacosExecutableDependencies?: (
executable: string,
) => Promise<MacosExecutableDependencyResolution>;
}
export function createFilesystemWorkerLaunchSpecProvider(
input: CreateFilesystemWorkerLaunchSpecProviderInput,
): FilesystemWorkerLaunchSpecProvider {
const platform = input.platform ?? process.platform;
let base: Promise<LaunchBaseResult> | undefined;
return async (operation) => {
const resolved = await (base ??= resolveLaunchBase(input, platform));
if (!resolved.ok) return resolved;
const grep =
operation.kind === 'grep'
? await resolveWorkerRipgrep(
input.rgCandidates ?? defaultRipgrepCandidates(input.hostEnv ?? process.env, platform),
platform,
input.inspectMacosExecutableDependencies ?? resolveMacosExecutableDependencies,
)
: undefined;
return { ok: true, spec: composeLaunchSpec(resolved.base, grep) };
};
}
export function buildFilesystemWorkerEnv(
runtime: 'node' | 'electron',
hostEnv: NodeJS.ProcessEnv = process.env,
controlledTmpdir = '/tmp',
platform: NodeJS.Platform = process.platform,
): Readonly<Record<string, string>> {
const env: Record<string, string> = { TMPDIR: controlledTmpdir, OPENSSL_CONF: '/dev/null' };
for (const key of ['LANG', 'LC_ALL', 'LC_CTYPE'] as const) {
const value = hostEnv[key];
if (value) env[key] = value;
}
if (platform === 'win32') {
// A Windows process launched with an explicit environment block needs
// SystemRoot for the loader, and AppContainer creation fails with
// ERROR_ENVVAR_NOT_FOUND unless LOCALAPPDATA is present — the LowBox
// infrastructure rewrites it to the container's redirected location.
for (const key of ['SystemRoot', 'SystemDrive', 'LOCALAPPDATA'] as const) {
const value = hostEnv[key];
if (value) env[key] = value;
}
}
if (runtime === 'electron') env.ELECTRON_RUN_AS_NODE = '1';
return env;
}
interface RipgrepResolution {
readonly executable: string;
readonly runtimeReadableRoots: readonly string[];
readonly executableRoots: readonly string[];
}
/** Everything in a launch that does not depend on ripgrep; resolved once. */
interface LaunchBase {
readonly runtime: 'node' | 'electron';
readonly platform: NodeJS.Platform;
readonly program: string;
readonly bundlePath: string;
readonly runtimeRoot: string;
readonly dependencyRoots: readonly string[];
readonly electronFrameworks?: string;
readonly env: Readonly<Record<string, string>>;
readonly environment?: RipgrepEnvironment;
}
type LaunchBaseResult =
| { ok: true; base: LaunchBase }
| Extract<FilesystemWorkerLaunchSpecResult, { ok: false }>;
async function resolveLaunchBase(
input: CreateFilesystemWorkerLaunchSpecProviderInput,
platform: NodeJS.Platform,
): Promise<LaunchBaseResult> {
const bundle = await resolveFilesystemWorkerBundle(input.resourceLocation);
if (!bundle.ok) {
return {
ok: false,
reason: 'worker_bundle_unavailable',
message: `Filesystem worker bundle is unavailable (${bundle.reason}).`,
};
}
const program = await resolveExecutable(input.executable ?? process.execPath);
if (!program) {
return {
ok: false,
reason: 'runtime_executable_unavailable',
message: 'Filesystem worker runtime is unavailable.',
};
}
// A packaged Windows executable lives directly inside the product-owned app
// directory. Granting its parent would widen a normal install from
// `...\Programs\Maka` to every application under `...\Programs` (or from
// `C:\Program Files\Maka` to all of `C:\Program Files`). The executable's
// own directory contains the DLL/resource substrate it needs and is the
// narrowest recursive root that works for both installed and ZIP layouts.
const runtimeRootCandidate =
platform === 'win32' ? dirname(program) : resolve(dirname(program), '..');
const runtimeRoot = await resolveReadableRoot(runtimeRootCandidate);
if (!runtimeRoot) {
return {
ok: false,
reason: 'runtime_executable_unavailable',
message: 'Filesystem worker runtime root is unavailable.',
};
}
const dependencyRoots = await resolveRuntimeDependencyRoots(program);
const electronFrameworks =
input.runtime === 'electron' && platform === 'darwin'
? await resolveReadableRoot(resolve(dirname(program), '..', 'Frameworks'))
: undefined;
if (input.runtime === 'electron' && platform === 'darwin' && !electronFrameworks) {
return {
ok: false,
reason: 'runtime_executable_unavailable',
message: 'Electron framework roots are unavailable.',
};
}
const environment = currentRipgrepEnvironment(input.hostEnv ?? process.env);
return {
ok: true,
base: {
runtime: input.runtime,
platform,
program,
bundlePath: bundle.path,
runtimeRoot,
dependencyRoots,
...(electronFrameworks ? { electronFrameworks } : {}),
env: buildFilesystemWorkerEnv(input.runtime, input.hostEnv, input.tmpdir, platform),
...(environment ? { environment } : {}),
},
};
}
function composeLaunchSpec(
base: LaunchBase,
grep: RipgrepResolution | undefined,
): FilesystemWorkerLaunchSpec {
return {
program: base.program,
// --preserve-symlinks-main skips the module loader's realpath of the
// bundle path. Inside the Windows AppContainer that realpath would
// lstat every ancestor directory (up to the volume root), which the
// sandbox grants deliberately do not allow.
//
// --no-stdio-init: Electron's run-as-node entry opens the NUL device to
// backfill missing standard handles before Node starts, and the
// AppContainer denies that device open, which aborts startup (FATAL
// node_main.cc "Unable to open nul device"). The broker always relays
// three valid standard handles into the child, so the backfill is
// unnecessary; the switch skips it and is consumed before Node's own
// option parsing.
args: [
...(base.runtime === 'electron' && base.platform === 'win32' ? ['--no-stdio-init'] : []),
...(base.platform === 'win32' ? ['--preserve-symlinks-main'] : []),
base.bundlePath,
...(base.environment
? ['--ripgrep-environment', formatRipgrepEnvironmentArg(base.environment)]
: []),
...(grep ? ['--grep-executable', grep.executable] : []),
],
env: base.env,
runtimeReadableRoots: unique([
base.bundlePath,
base.runtimeRoot,
...base.dependencyRoots,
...(grep?.runtimeReadableRoots ?? []),
]),
executableRoots: unique([
base.program,
base.runtimeRoot,
...(base.electronFrameworks ? [base.electronFrameworks] : []),
...base.dependencyRoots,
...(grep ? [grep.executable, ...grep.executableRoots] : []),
]),
};
}
async function resolveWorkerRipgrep(
candidates: readonly string[],
platform: NodeJS.Platform,
inspectMacosExecutableDependencies: (
executable: string,
) => Promise<MacosExecutableDependencyResolution>,
): Promise<RipgrepResolution | undefined> {
for await (const executable of resolveRipgrepCandidates(candidates)) {
if (platform !== 'darwin') {
return { executable, runtimeReadableRoots: [], executableRoots: [] };
}
const dependencies = await inspectMacosExecutableDependencies(executable);
if (!dependencies.ok) continue;
return {
executable,
runtimeReadableRoots: dependencies.runtimeReadableRoots,
executableRoots: dependencies.executableRoots,
};
}
return undefined;
}
async function resolveExecutable(candidate: string): Promise<string | undefined> {
if (!candidate || !isAbsolute(candidate)) return undefined;
try {
await access(candidate, constants.X_OK);
return await realpath(candidate);
} catch {
return undefined;
}
}
async function resolveReadableRoot(candidate: string): Promise<string | undefined> {
try {
return await realpath(candidate);
} catch {
return undefined;
}
}
async function resolveRuntimeDependencyRoots(program: string): Promise<readonly string[]> {
const candidates = program.startsWith('/opt/homebrew/')
? ['/opt/homebrew/opt', '/opt/homebrew/Cellar']
: program.startsWith('/usr/local/')
? ['/usr/local/opt', '/usr/local/Cellar']
: [];
const roots = await Promise.all(candidates.map(resolveReadableRoot));
return roots.filter((root): root is string => root !== undefined);
}
function unique(values: readonly string[]): readonly string[] {
return [...new Set(values)];
}