@struktoai/mirage-core
Version:
A Unified Virtual Filesystem For AI Agents
119 lines • 5.24 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 "../../../../commands/spec/builtins.js";
import { FlagView } from "../../../../commands/spec/flag_view.js";
import { parseCommand, parseToKwargs } from "../../../../commands/spec/parser.js";
import { decodeText, encodeText } from "../../../../shell/bytes.js";
import { wordText } from "../../../../types.js";
import { decodeBase64 } from "../../../../utils/base64.js";
import { finish, result } from "../shared.js";
import { attrError, attrOperands, attrUsageRefusal, SETFATTR_USAGE } from "./xattr.js";
const HEX = /^[0-9a-fA-F]*$/;
const BASE64 = /^[A-Za-z0-9+/]*={0,2}$/;
/**
* A `-v` value as setfattr stores it, or null when it is malformed. `0x`
* is hex and `0s` base64, whitespace ignored. Anything else is text:
* surrounding double quotes are dropped when both ends have one, and
* `\ooo`, `\\` and `\"` are decoded while any other backslash stays as
* typed. Mirrors Python's `decode_value`.
*/
export function decodeValue(text) {
const kind = text.length > 2 && text.startsWith('0') ? (text[1] ?? '').toLowerCase() : '';
if (kind === 'x') {
const digits = text.slice(2).replace(/\s/g, '');
if (digits.length % 2 !== 0 || !HEX.test(digits))
return null;
return Uint8Array.from(digits.match(/../g) ?? [], (pair) => parseInt(pair, 16));
}
if (kind === 's') {
const digits = text.slice(2).replace(/\s/g, '');
if (digits.length % 4 !== 0 || !BASE64.test(digits))
return null;
return decodeBase64(digits);
}
let raw = encodeText(text);
if (raw.length >= 2 && raw[0] === 0x22 && raw[raw.length - 1] === 0x22) {
raw = raw.subarray(1, -1);
}
const out = [];
for (let i = 0; i < raw.length; i += 1) {
const byte = raw[i] ?? 0;
if (byte === 0x5c) {
const digits = raw.subarray(i + 1, i + 4);
if (digits.length === 3 && digits.every((b) => b >= 0x30 && b <= 0x37)) {
out.push(parseInt(decodeText(digits), 8) & 0xff);
i += 3;
continue;
}
const next = raw[i + 1];
if (next === 0x5c || next === 0x22) {
out.push(next);
i += 1;
continue;
}
}
out.push(byte);
}
return Uint8Array.from(out);
}
/**
* setfattr: set or remove one extended attribute on each path.
*
* Debian's attr 2.5.2, pinned in docker: `-n NAME [-v VALUE]` sets it (no
* `-v` is the empty value) and `-x NAME` removes it; exactly one of the
* two, with `-v` only beside `-n`, or the usage block and exit 2. A
* malformed hex or base64 value is `bad input encoding`, exit 1. A
* failure on one path is reported and the rest are still written. The
* attribute lands on the dispatcher's node table, which takes any name on
* any path (a link's own with `-h`), as macOS does; linux refuses a name
* outside its namespaces and a user attribute on a link. Mirrors
* Python's `handle_setfattr`.
*/
export async function handleSetfattr(dispatch, session, args) {
const spec = specOf('setfattr');
const parsed = parseCommand(spec, args.map((a) => wordText(a)), session.cwd, 'setfattr');
const refused = attrUsageRefusal('setfattr', parsed, SETFATTR_USAGE);
if (refused !== null)
return refused;
const fl = new FlagView(parseToKwargs(parsed), spec);
const name = fl.asStr('name') ?? null;
const remove = fl.asStr('remove') ?? null;
const typed = fl.asStr('value') ?? null;
const targets = attrOperands(parsed);
if ((name === null) === (remove === null) ||
(remove !== null && typed !== null) ||
targets.length === 0) {
return result('setfattr', { exitCode: 2, stderr: SETFATTR_USAGE });
}
const value = typed !== null ? decodeValue(typed) : new Uint8Array();
if (value === null)
return result('setfattr', { exitCode: 1, stderr: 'bad input encoding\n' });
const nofollow = fl.asBool('no_dereference');
const errors = [];
for (const target of targets) {
try {
if (name !== null) {
await dispatch('setxattr', target, [], { name, value, nofollow });
}
else {
await dispatch('removexattr', target, [], { name: remove, nofollow });
}
}
catch (err) {
errors.push(`setfattr: ${target.rawPath}: ${attrError(err)}\n`);
}
}
return finish('setfattr', errors);
}
//# sourceMappingURL=setfattr.js.map