@struktoai/mirage-core
Version:
A Unified Virtual Filesystem For AI Agents
245 lines • 10.3 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 { readdirOp } from "../../../../commands/builtin/generic/crossmount/utils.js";
import { dispatchedReadTree } from "../../../../commands/builtin/generic_bind/files.js";
import { treeWalk, WalkBudget } from "../../../../commands/builtin/utils/tree.js";
import { specOf } from "../../../../commands/spec/builtins.js";
import { FlagView } from "../../../../commands/spec/flag_view.js";
import { parseCommand, parseToKwargs } from "../../../../commands/spec/parser.js";
import { callerSignal } from "../../../../context/session_context.js";
import { classify } from "../../../../errors/index.js";
import { encodeText } from "../../../../shell/bytes.js";
import { FileStat, FileType, PathSpec, wordText } from "../../../../types.js";
import { encodeBase64 } from "../../../../utils/base64.js";
import { toHex } from "../../../../utils/hex.js";
import { rstripSlash } from "../../../../utils/slash.js";
import { result } from "../shared.js";
import { attrError, attrOperands, attrUsageRefusal, GETFATTR_USAGE } from "./xattr.js";
const ENCODINGS = new Set(['text', 'hex', 'base64']);
const DEFAULT_MATCH = '^user\\.';
/**
* One value the way getfattr prints it after `name=`. With no `-e` the
* value is text when at most one byte in eight is unprintable (a
* trailing NUL not counted) and base64 otherwise. Text escapes NUL,
* newline and carriage return as octal and `"` and `\` with a backslash,
* and passes every other byte through raw; a trailing NUL is dropped.
* Mirrors Python's `encode_value`.
*/
export function encodeValue(value, encoding) {
const body = value.at(-1) === 0 ? value.subarray(0, -1) : value;
let chosen = encoding;
if (chosen === null) {
const unprintable = body.filter((b) => b < 0x20 || b > 0x7e).length;
chosen = body.length >= unprintable * 8 ? 'text' : 'base64';
}
if (chosen === 'hex') {
return encodeText('0x' + toHex(value));
}
if (chosen === 'base64')
return encodeText('0s' + encodeBase64(value));
const out = [0x22];
for (const b of body) {
if (b === 0 || b === 0x0a || b === 0x0d)
for (const item of encodeText('\\' + b.toString(8).padStart(3, '0')))
out.push(item);
else if (b === 0x22 || b === 0x5c)
out.push(0x5c, b);
else
out.push(b);
}
out.push(0x22);
return Uint8Array.from(out);
}
/**
* A subtree in pre-order, the way nftw hands it to getfattr -R. Every
* entry is reported, links included. A link to a directory is descended
* only when `deref` says so: for every entry under `-L`, and for an
* operand itself unless `-P`. `calls` are the walk's readdir and lstat,
* answered from a mount's `readTree` where one answers; `dispatch` follows a
* link. Mirrors Python's `_walk`.
*/
async function walk(dispatch, calls, links, path, shown, logical, deref, budget) {
const [readdir, lstat] = calls;
const entries = [[path, shown]];
let stat;
try {
stat = await lstat(path);
if (stat instanceof FileStat && stat.type === FileType.SYMLINK && deref) {
;
[stat] = await dispatch('stat', path);
}
}
catch (err) {
const code = classify(err);
if (code === 'ENOENT' || code === 'ENOTDIR')
return entries;
throw err;
}
if (!(stat instanceof FileStat) || stat.type !== FileType.DIRECTORY)
return entries;
const here = links.follow(path.virtual);
if (!budget.opens(here))
return entries;
const limit = budget.listingLimit(here);
const children = await (limit === null ? readdir(path) : readdir(path, false, limit));
for (const child of children) {
if (!budget.spend(here))
break;
const bare = rstripSlash(child);
const name = bare.slice(bare.lastIndexOf('/') + 1);
const below = `${rstripSlash(shown)}/${name}`;
entries.push(...(await walk(dispatch, calls, links, PathSpec.fromStrPath(bare), below, logical, logical, budget)));
}
return entries;
}
/**
* One path's output (its `# file:` block, or its bare values), and
* whether an attribute it was asked for is not set. An attribute that is
* not set is reported per name and skipped; any other failure (the path
* is missing) is the caller's to report. Mirrors Python's `_file_block`.
*/
async function fileBlock(dispatch, path, label, header, want, errors) {
let names;
if (want.name !== null) {
names = [want.name];
}
else {
const [listed] = (await dispatch('listxattr', path, [], { nofollow: want.nofollow }));
names = listed.filter((n) => want.matcher === null || want.matcher.test(n));
}
const out = [];
const block = [];
let missing = false;
for (const attr of names) {
if (!want.dump && !want.onlyValues) {
for (const item of encodeText(`${attr}\n`))
block.push(item);
continue;
}
let value;
try {
;
[value] = (await dispatch('getxattr', path, [], {
name: attr,
nofollow: want.nofollow,
}));
}
catch (err) {
if (classify(err) !== 'NO_XATTR')
throw err;
errors.push(`${label}: ${attr}: ${attrError(err)}\n`);
missing = true;
continue;
}
if (want.onlyValues)
for (const item of value)
out.push(item);
else
block.push(...encodeText(`${attr}=`), ...encodeValue(value, want.encoding), 0x0a);
}
if (block.length > 0)
out.push(...encodeText(`# file: ${header}\n`), ...block, 0x0a);
return [Uint8Array.from(out), missing];
}
/**
* getfattr: print the extended attributes of each path.
*
* Debian's attr 2.5.2, pinned in docker: a `# file:` block per path that
* has a matching attribute, names sorted, a blank line after each block,
* `-d`/`-n` adding `="value"`, and the default match `^user\.` (`-m -`
* matches every name). The attributes are the dispatcher's: what was set on
* the path. `-h` reads a link's own attributes. `-R` stops after
* `walkLimit` entries of a mount, as `ls -R` does, and exits 1.
* Mirrors Python's `handle_getfattr`.
*/
export async function handleGetfattr(namespace, dispatch, session, args) {
const spec = specOf('getfattr');
const parsed = parseCommand(spec, args.map((a) => wordText(a)), session.cwd, 'getfattr');
const refused = attrUsageRefusal('getfattr', parsed, GETFATTR_USAGE);
if (refused !== null)
return refused;
const fl = new FlagView(parseToKwargs(parsed), spec);
const encoding = fl.asStr('encoding') ?? null;
const targets = attrOperands(parsed);
if ((encoding !== null && !ENCODINGS.has(encoding)) || targets.length === 0) {
return result('getfattr', { exitCode: 2, stderr: GETFATTR_USAGE });
}
const pattern = fl.asStr('match') ?? DEFAULT_MATCH;
let matcher = null;
if (pattern !== '-') {
try {
matcher = new RegExp(pattern);
}
catch {
return result('getfattr', {
exitCode: 1,
stderr: `getfattr: invalid regular expression "${pattern}"\n`,
});
}
}
const name = fl.asStr('name') ?? null;
const want = {
name,
matcher,
dump: fl.asBool('dump') || name !== null,
onlyValues: fl.asBool('only_values'),
encoding,
nofollow: fl.asBool('no_dereference'),
};
const absolute = fl.asBool('absolute_names');
const out = [];
const errors = [];
let failed = false;
let warned = false;
const links = namespace.sessionLinks(session);
const mounts = links.mounts(true);
const calls = treeWalk(dispatchedReadTree(dispatch, links.mounts(false), (virtual) => links.followParent(virtual), true), readdirOp(dispatch), async (path) => (await dispatch('stat', path, [], { nofollow: true }))[0], true, callerSignal());
for (const target of targets) {
const typed = target.rawPath;
const budget = new WalkBudget(target, mounts);
const entries = fl.asBool('recursive')
? await walk(dispatch, calls, links, target, typed, fl.asBool('logical'), !fl.asBool('physical'), budget)
: [[target, typed]];
for (const [path, label] of entries) {
const header = !absolute && label.startsWith('/') ? label.replace(/^\/+/, '') || '.' : label;
let block;
let missing;
try {
;
[block, missing] = await fileBlock(dispatch, path, label, header, want, errors);
}
catch (err) {
errors.push(`getfattr: ${label}: ${attrError(err)}\n`);
failed = true;
continue;
}
if (block.length > 0 && header !== label && !want.onlyValues && !warned) {
errors.push("getfattr: Removing leading '/' from absolute path names\n");
warned = true;
}
for (const item of block)
out.push(item);
failed = failed || missing;
}
for (const line of budget.stopped)
errors.push(`getfattr: ${line}\n`);
failed = failed || budget.stopped.length > 0;
}
return result('getfattr', {
out: out.length > 0 ? Uint8Array.from(out) : null,
exitCode: failed ? 1 : 0,
stderr: errors.join(''),
});
}
//# sourceMappingURL=getfattr.js.map