UNPKG

@struktoai/mirage-core

Version:

A Unified Virtual Filesystem For AI Agents

138 lines • 5.9 kB
// ========= Copyright 2026 @ Strukto.AI All Rights Reserved. ========= // Licensed 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. // ========= Copyright 2026 @ Strukto.AI All Rights Reserved. ========= import { specOf } from "../../spec/builtins.js"; import { FlagView } from "../../spec/flag_view.js"; import { operandStat } from "../utils/operands.js"; import { IOResult, materialize } from "../../../io/types.js"; import { DEVICE_NUMBERS_KEY, FileType, Follow, LineFacts, } from "../../../types.js"; import { detectFileType, formatFileResult } from "../file_sniff.js"; import { FILE_HEADER_BYTES, MIME_SYMLINK } from "../constants.js"; import { readPrefix } from "../../../io/cooperative.js"; import { LINK_TARGET_KEY } from "../../../types.js"; import { CycleError } from "../../../utils/path.js"; import { hasAborted } from "../../../utils/abort.js"; import { formatRecords } from "../utils/output.js"; import { linksOf, mountsOf } from "../utils/links.js"; import { vfsStatPath } from "../utils/paths.js"; const ENC = new TextEncoder(); // How `file` describes a symlink operand, or null if it is not one. // // GNU names the target verbatim as it was stored, and calls the link // broken when the target does not resolve to anything. A cycle counts // as broken: nothing is reachable through it either. async function linkDescription(path, links) { const row = links.statAt(path.virtual); if (row === null) return null; const target = typeof row.extra[LINK_TARGET_KEY] === 'string' ? row.extra[LINK_TARGET_KEY] : ''; let resolved; try { resolved = links.resolve(path.virtual); } catch (err) { if (!(err instanceof CycleError)) throw err; return `broken symbolic link to ${target}`; } if (!(await links.exists(resolved))) return `broken symbolic link to ${target}`; return `symbolic link to ${target}`; } function parseFlags(bag) { const fl = new FlagView(bag, specOf('file')); return { brief: fl.asBool('b'), mime: fl.asBool('i') }; } /** * How `file` describes a file it must not read, in libmagic's words: a * character device, or a FIFO, whose read would drain it (`fifo (named * pipe)`); null for any other file. Mirrors Python's _special_description. */ function specialDescription(s, mime) { if (s.type === FileType.FIFO) return mime ? 'inode/fifo; charset=binary' : 'fifo (named pipe)'; if (s.type !== FileType.CHAR_DEVICE) return null; if (mime) return 'inode/chardevice; charset=binary'; const raw = s.extra[DEVICE_NUMBERS_KEY]; const device = Array.isArray(raw) && raw.length === 2 ? raw : null; return device !== null ? `character special (${String(device[0])}/${String(device[1])})` : 'character special'; } /** * The first bytes of a file, the most `file` sniffs: one ranged read where the * mount has one, else the stream, closed once they are in. Mirrors Python's * _header. */ async function header(stream, readRange, p, signal) { if (readRange !== null) return materialize(readRange(p, 0, FILE_HEADER_BYTES)); return readPrefix(stream(p), FILE_HEADER_BYTES, signal); } export async function fileGeneric(paths, ctx, stat, stream, readRange = null) { if (paths.length === 0) { return [null, new IOResult({ exitCode: 1, stderr: ENC.encode('file: missing operand\n') })]; } const { brief, mime } = parseFlags(ctx.flags); // GNU always names the operand exactly as typed, never a resolved or // absolutised form, so every row is labelled with rawPath. const lines = []; // Without -L the operand arrives unfollowed, so a link is described // as a link rather than sniffed as its target. const links = linksOf(ctx) ?? null; for (const p of paths) { if (links !== null) { const described = await linkDescription(p, links); if (described !== null) { lines.push(formatFileResult(p.rawPath, mime ? MIME_SYMLINK : described, brief, false)); continue; } } const s = await operandStat(p, stat, vfsStatPath(ctx.vfs), mountsOf(ctx), linksOf(ctx)); if (s.type === FileType.DIRECTORY) { lines.push(formatFileResult(p.rawPath, FileType.DIRECTORY, brief, mime)); continue; } const special = specialDescription(s, mime); if (special !== null) { lines.push(formatFileResult(p.rawPath, special, brief, false)); continue; } let head; try { head = await header(stream, readRange, p, ctx.signal); } catch (err) { if (hasAborted(ctx.signal)) throw err; console.debug(`file: failed to read header for ${p.virtual}: ${String(err)}`); head = new Uint8Array(0); } const result = detectFileType(head, s); lines.push(formatFileResult(p.rawPath, result, brief, mime)); } const out = formatRecords(lines); return [out, new IOResult()]; } /** * A file line describes a link itself unless the last of `-L` and `-h` is * `-L` (file 5.46). Mirrors Python's `facts`. */ export function facts(fl, _operands) { return new LineFacts({ follows: fl.typedOrder('L', 'h').at(-1) === 'L' ? Follow.LAST : Follow.NONE, }); } //# sourceMappingURL=file.js.map