@struktoai/mirage-core
Version:
A Unified Virtual Filesystem For AI Agents
138 lines • 5.9 kB
JavaScript
// ========= 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