Files
wehub-resource-sync bcbd1bdb22
Integ / changes (push) Has been skipped
Pre-commit / pre-commit (push) Failing after 1s
CLI exit codes / changes (push) Has been skipped
Test (Install) / changes (push) Has been skipped
Test (Python) / changes (push) Has been skipped
Test (TypeScript) / changes (push) Has been skipped
CLI exit codes / cli-gate (push) Has been cancelled
Test (Install) / test-install-gate (push) Has been cancelled
Integ / integ-gate (push) Has been cancelled
Test (Python) / test-python-gate (push) Has been cancelled
Test (TypeScript) / test-typescript-gate (push) Has been cancelled
Test (Install) / python-minimal (3.12) (push) Has been cancelled
Test (Install) / python-minimal (3.11) (push) Has been cancelled
Test (Install) / python-extra (agno, mirage.agents.agno) (push) Has been cancelled
Test (Install) / python-extra (chroma, mirage.resource.chroma) (push) Has been cancelled
Test (Install) / python-extra (pdf, mirage.core.filetype.pdf) (push) Has been cancelled
Integ / integ (push) Has been cancelled
Integ / integ-database (push) Has been cancelled
Integ / integ-database-ts (push) Has been cancelled
Integ / integ-data (push) Has been cancelled
Integ / integ-ssh (push) Has been cancelled
Integ / integ-ssh-ts (push) Has been cancelled
Test (Python) / audit (push) Has been cancelled
Test (TypeScript) / test (push) Has been cancelled
Test (TypeScript) / python-fs-shim (push) Has been cancelled
CLI exit codes / Python CLI (push) Has been cancelled
CLI exit codes / TypeScript CLI (push) Has been cancelled
CLI exit codes / Cross-language snapshot interop (push) Has been cancelled
Test (Python) / test (push) Has been cancelled
Test (Python) / import-isolation (deepagents, openai, mirage.agents.openai_agents) (push) Has been cancelled
Test (Python) / import-isolation (deepagents, pydantic-ai, mirage.agents.pydantic_ai) (push) Has been cancelled
Integ / integ-ts (push) Has been cancelled
Integ / integ-fuse (push) Has been cancelled
Test (Install) / python-extra (databricks, mirage.resource.databricks_volume) (push) Has been cancelled
Test (Install) / python-extra (deepagents, mirage.agents.langchain) (push) Has been cancelled
Test (Install) / python-extra (email, mirage.resource.email) (push) Has been cancelled
Test (Install) / python-extra (fuse, mirage.fuse.mount) (push) Has been cancelled
Test (Install) / python-extra (hdf5, mirage.core.filetype.hdf5) (push) Has been cancelled
Test (Install) / python-extra (hf, mirage.resource.hf_buckets) (push) Has been cancelled
Test (Install) / python-extra (lancedb, mirage.resource.lancedb) (push) Has been cancelled
Test (Install) / python-extra (langfuse, mirage.resource.langfuse) (push) Has been cancelled
Test (Install) / python-extra (mongodb, mirage.resource.mongodb) (push) Has been cancelled
Test (Install) / python-extra (nextcloud, mirage.resource.nextcloud) (push) Has been cancelled
Test (Install) / python-extra (openai, mirage.agents.openai_agents) (push) Has been cancelled
Test (Install) / python-extra (openhands, mirage.agents.openhands, 3.12) (push) Has been cancelled
Test (Install) / python-extra (parquet, mirage.core.filetype.parquet) (push) Has been cancelled
Test (Install) / python-extra (postgres, mirage.resource.postgres) (push) Has been cancelled
Test (Install) / python-extra (pydantic-ai, mirage.agents.pydantic_ai) (push) Has been cancelled
Test (Install) / python-extra (qdrant, mirage.resource.qdrant) (push) Has been cancelled
Test (Install) / python-extra (redis, mirage.resource.redis) (push) Has been cancelled
Test (Install) / python-extra (s3, mirage.resource.s3) (push) Has been cancelled
Test (Install) / python-extra (ssh, mirage.resource.ssh) (push) Has been cancelled
Test (Install) / ts-minimal (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:30:44 +08:00

760 lines
32 KiB
Python

# ========= 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 functools
from collections.abc import Callable
from typing import NamedTuple
from mirage.commands.builtin.find_parse import (FindParseError, find_expr_tail,
parse_find_expression)
from mirage.commands.builtin.generic.crossmount import (handle_cross_mount,
is_cross_mount)
from mirage.commands.builtin.utils.safeguard import maybe_with_timeout
from mirage.commands.errors import UsageError
from mirage.commands.safeguard import resolve_across_mounts, resolve_safeguard
from mirage.commands.spec import (SPECS, CommandSpec, OperandKind,
flag_kwarg_name, parse_command,
parse_to_kwargs)
from mirage.commands.spec.usage import (missing_value_error,
unknown_option_error)
from mirage.io import IOResult
from mirage.io.stream import async_chain, materialize, wrap_cachable_streams
from mirage.io.types import ByteSource
from mirage.shell.call_stack import CallStack
from mirage.shell.job_table import JobTable
from mirage.shell.types import ERREXIT_EXEMPT_TYPES
from mirage.types import PathSpec, word_text
from mirage.utils.errors import FS_ERRORS, format_fs_error
from mirage.workspace.executor.control import ReturnSignal
from mirage.workspace.executor.fanout import (_fan_out_traversal,
_should_fan_out)
from mirage.workspace.executor.find_action_dispatch import _apply_find_actions
from mirage.workspace.executor.jobs import (handle_jobs, handle_kill,
handle_ps, handle_wait)
from mirage.workspace.mount import (MountCommandUnsupported, MountEntry,
MountRegistry)
from mirage.workspace.mount.namespace import Namespace
from mirage.workspace.route import JOB_BUILTINS, Consumer, route
from mirage.workspace.session import Session, assert_mount_allowed
from mirage.workspace.types import ExecutionNode
_FIND_ACTION_FLAGS = frozenset({"delete", "print0", "ls"})
async def _exec_node(cmd_str: str, io: IOResult,
paths: list[PathSpec]) -> ExecutionNode:
"""Build the recorded execution node, materializing any streamed stderr.
Args:
cmd_str (str): Original command text for the record.
io (IOResult): Command result whose stderr/exit_code the node carries.
paths (list[PathSpec]): Classified path operands, carried so the
lazy-stream drain can respell filesystem errors as typed.
"""
# The node is a recorded artifact (compared by value, serialized via a
# sync to_dict, sometimes read twice), so the live lazy io.stderr is
# materialized to concrete bytes here. On the cross-mount path it is bytes.
return ExecutionNode(command=cmd_str,
stderr=await materialize(io.stderr),
exit_code=io.exit_code,
paths=paths)
def _check_mount_root_guard_raw(
cmd_name: str,
paths: list[PathSpec],
registry: MountRegistry,
argv: list[str],
) -> tuple[str, int] | None:
"""Refuse destructive/conflicting ops targeting a mount root.
Fires before mount resolution / cross-mount routing so a refusal
message is consistent regardless of whether the operands span mounts.
Returns (stderr_message, exit_code) when the guard fires, else None.
Args:
cmd_name (str): command name (rm/mv/mkdir/touch/ln/...).
paths (list[PathSpec]): raw positional path arguments.
registry (MountRegistry): mount registry for is_mount_root checks.
argv (list[str]): raw argv after the command name (used to spot
shorthand flags like `mkdir -p` before _parse_flags runs).
"""
if not paths:
return None
def _is_root(p: PathSpec) -> bool:
return registry.is_mount_root(p.virtual)
if cmd_name in ("rm", "rmdir"):
for p in paths:
if _is_root(p):
if cmd_name == "rmdir":
msg = (f"rmdir: failed to remove '{p.virtual}': "
f"Device or resource busy\n")
else:
msg = (f"rm: cannot remove '{p.virtual}': "
f"Device or resource busy\n")
return msg, 1
elif cmd_name == "mv":
if _is_root(paths[0]):
dst = paths[1].virtual if len(paths) > 1 else "?"
msg = (f"mv: cannot move '{paths[0].virtual}' to '{dst}': "
f"Device or resource busy\n")
return msg, 1
elif cmd_name == "mkdir":
# GNU mkdir -p makes "already exists" a no-op.
for tok in argv:
if isinstance(tok,
str) and (tok == "-p" or tok == "--parents" or
(tok.startswith("-") and "p" in tok[1:]
and not tok.startswith("--"))):
return None
for p in paths:
if _is_root(p):
msg = (f"mkdir: cannot create directory '{p.virtual}': "
f"File exists\n")
return msg, 1
elif cmd_name == "touch":
for p in paths:
if _is_root(p):
msg = (f"touch: cannot touch '{p.virtual}': "
f"Is a directory\n")
return msg, 1
elif cmd_name == "ln":
if _is_root(paths[-1]):
msg = (f"ln: failed to create link '{paths[-1].virtual}': "
f"File exists\n")
return msg, 1
return None
def _scalar_find_flags(flag_kwargs: dict) -> dict:
# `repeatable=True` on find value-flags makes parse_to_kwargs emit
# lists; bespoke backend wrappers read these as scalars. Migrated
# backends read the expression from `texts` and ignore flag_kwargs.
return {
k: (v[-1] if isinstance(v, list) and v else v)
for k, v in flag_kwargs.items()
}
async def run_on_mount(
registry: MountRegistry,
session: Session,
dispatch: Callable,
namespace: Namespace | None,
cmd_name: str,
paths: list[PathSpec],
texts: list[str],
flag_kwargs: dict,
stdin: ByteSource | None = None,
resolve_hint: PathSpec | None = None,
mount: MountEntry | None = None,
) -> tuple[ByteSource | None, IOResult]:
"""Run one already-parsed command on the mount that owns its paths.
The shared single-mount execution tail: mount resolution, grant checks,
``execute_cmd``, filesystem-error formatting, ls/find post-processing,
and read/write key prefixing. ``handle_command`` uses it for the normal
path, and passes it (bound) to the cross-mount runners so each operand
executes natively on its owning mount.
Args:
registry (MountRegistry): Mount registry.
session (Session): Session providing cwd/env/session_id.
dispatch (Callable): Workspace operation dispatcher.
namespace (Namespace | None): Addressing authority for ls symlinks.
cmd_name (str): Command name.
paths (list[PathSpec]): Positional path operands (may hold globs;
the mount wrapper expands them natively).
texts (list[str]): Positional text operands.
flag_kwargs (dict): Parsed flags forwarded to the mount command.
stdin (ByteSource | None): Standard input for the command.
resolve_hint (PathSpec | None): Mount-resolution path when ``paths``
is empty (a stream command running in stdin mode).
mount: Pre-resolved mount; skips resolution and grant checks, which
the caller already performed.
"""
if mount is None:
resolve_paths = paths or ([resolve_hint] if resolve_hint else [])
try:
mount = await registry.resolve_mount(cmd_name, resolve_paths,
session.cwd)
except MountCommandUnsupported as exc:
return None, IOResult(exit_code=1, stderr=f"{exc}\n".encode())
if mount is None:
return None, IOResult(
exit_code=127,
stderr=f"{cmd_name}: command not found".encode())
try:
assert_mount_allowed(mount.prefix)
for ps in paths:
target = registry.mount_for(ps.virtual)
assert_mount_allowed(target.prefix)
except PermissionError as exc:
return None, IOResult(exit_code=1, stderr=f"{exc}\n".encode())
if cmd_name == "find":
flag_kwargs = _scalar_find_flags(flag_kwargs)
try:
stdout, io = await mount.execute_cmd(
cmd_name,
paths,
texts,
flag_kwargs,
stdin=stdin,
cwd=session.cwd,
dispatch=dispatch,
session_id=session.session_id,
env=session.env,
exec_allowed=registry.is_exec_allowed(),
)
except UsageError as exc:
# Command-owned usage errors (extra operands, missing patterns)
# become this command's IOResult so the rest of the line keeps
# running, like a real shell (#452).
return None, IOResult(exit_code=exc.exit_code,
stderr=f"{exc}\n".encode())
except FS_ERRORS as exc:
err = format_fs_error(cmd_name, exc, paths)
return None, IOResult(exit_code=1, stderr=err)
if cmd_name == "ls" and io.exit_code == 0:
stdout = await _inject_child_mounts(stdout, registry, paths,
flag_kwargs, session.cwd)
if namespace is not None and namespace.symlinks:
stdout = await _inject_links(stdout, namespace, paths, flag_kwargs,
session.cwd)
if cmd_name == "find":
stdout, action_err = await _apply_find_actions(stdout, flag_kwargs,
registry, session.cwd)
if action_err:
existing = await materialize(io.stderr) if io.stderr else b""
io.stderr = existing + action_err
if io.exit_code == 0:
io.exit_code = 1
prefix = mount.prefix.rstrip("/")
if prefix:
io.reads = {prefix + k: v for k, v in io.reads.items()}
io.writes = {prefix + k: v for k, v in io.writes.items()}
io.cache = [prefix + p for p in io.cache]
return wrap_cachable_streams(stdout, io)
class _ParsedCommand(NamedTuple):
paths: list[PathSpec]
texts: list[str]
flag_kwargs: dict[str, object]
warnings: list[str]
invalid_options: list[str]
needs_value_options: list[str]
def _parse_flags(
parts: list[str | PathSpec],
spec: CommandSpec | None,
cmd_name: str,
cwd: str,
str_flag_paths: bool = False,
) -> _ParsedCommand:
"""Parse flags from classified parts, recovering PathSpec for PATH values.
Single-mount dispatch and cross-mount dispatch both parse through
here, so flags, texts, and parser warnings cannot drift between the
two paths (a cross-mount `grep --bogus` used to lose its warning).
Args:
parts (list[str | PathSpec]): expanded command words after the
command name; path-classified words arrive as PathSpec.
spec (CommandSpec | None): command spec, from the owning mount on
the single-mount path or the shared SPECS registry on the
cross-mount path; None falls back to type separation.
cmd_name (str): command name used in warnings.
cwd (str): current working directory for relative path resolution.
str_flag_paths (bool): keep PATH flag values as their resolved
virtual-path strings instead of PathSpec. Cross-mount
strategies read flags through FlagView, which type-checks
str, so they get the string view.
Returns:
_ParsedCommand: positional paths, positional texts, parsed flag dict
(PATH flag values recovered to PathSpec, repeatable PATH flags to
list[PathSpec]), and parser warnings (e.g. ignored unknown options).
"""
# Build string argv and PathSpec lookup
argv = [
item.virtual if isinstance(item, PathSpec) else item for item in parts
]
scope_map: dict[str, PathSpec] = {}
for item in parts:
if isinstance(item, PathSpec):
scope_map[item.virtual] = item
stripped = item.virtual.rstrip("/")
if stripped and stripped != item.virtual:
scope_map[stripped] = item
if spec is not None:
parsed = parse_command(spec, argv, cwd=cwd)
flag_kwargs = parse_to_kwargs(parsed)
# Recover PathSpec for PATH flag values; repeatable PATH flags
# arrive as a list of resolved paths and become list[PathSpec].
# A relative PATH flag value cwd-resolved by parse_command (e.g.
# csplit -f part -> /data/part) is absent from scope_map, so build a
# PathSpec for it just like positional paths do, otherwise it never
# gets the mount prefix stripped.
repeat_path_keys = {
flag_kwarg_name(name)
for opt in spec.options
if opt.value_kind == OperandKind.PATH and opt.repeatable
for name in (opt.short, opt.long) if name
}
single_path_keys = {
flag_kwarg_name(name)
for opt in spec.options
if opt.value_kind == OperandKind.PATH and not opt.repeatable
for name in (opt.short, opt.long) if name
}
if not str_flag_paths:
for key, value in flag_kwargs.items():
if key in repeat_path_keys and isinstance(value, list):
flag_kwargs[key] = [
scope_map.get(
part,
PathSpec(virtual=part,
directory=part[:part.rfind("/") + 1]
or "/",
resource_path="",
resolved=True)) for part in value
]
elif key in single_path_keys and isinstance(value, str):
flag_kwargs[key] = scope_map.get(
value,
PathSpec(virtual=value,
directory=value[:value.rfind("/") + 1] or "/",
resource_path="",
resolved=True))
elif isinstance(value, str) and value in scope_map:
flag_kwargs[key] = scope_map[value]
# Classify positional args
paths: list[PathSpec] = []
texts: list[str] = []
for value, kind in parsed.args:
if kind == OperandKind.PATH:
scope = scope_map.get(value)
if scope is None:
scope = PathSpec(
virtual=value,
directory=value[:value.rfind("/") + 1] or "/",
resource_path="",
resolved=True,
)
paths.append(scope)
else:
texts.append(value)
return _ParsedCommand(paths, texts, flag_kwargs, parsed.warnings,
parsed.invalid_options,
parsed.needs_value_options)
# No spec: separate by type
paths = [item for item in parts if isinstance(item, PathSpec)]
texts = [item for item in parts if not isinstance(item, PathSpec)]
return _ParsedCommand(paths, texts, {}, [], [], [])
def _option_error(cmd_name: str,
parsed: _ParsedCommand) -> tuple[bytes, int] | None:
"""GNU-shaped refusal for option errors the parser reported.
find is exempt: its expression tokens are validated by
parse_find_expression, which raises the GNU predicate error itself.
Args:
cmd_name (str): command name for message shape and exit code.
parsed (_ParsedCommand): parse result carrying the reports.
"""
if cmd_name == "find":
return None
if parsed.invalid_options:
return unknown_option_error(cmd_name, parsed.invalid_options[0])
if parsed.needs_value_options:
return missing_value_error(cmd_name, parsed.needs_value_options[0])
return None
async def handle_command(
execute_node: Callable,
dispatch: Callable,
registry: MountRegistry,
parts: list[str | PathSpec],
session: Session,
stdin: ByteSource | None = None,
call_stack: CallStack | None = None,
job_table: JobTable | None = None,
namespace: Namespace | None = None,
) -> tuple[ByteSource | None, IOResult, ExecutionNode]:
"""Execute a simple command.
Parts are already classified: strings for text,
PathSpec for paths. Dispatches to mount.execute_cmd.
"""
if not parts:
return None, IOResult(), ExecutionNode(command="", exit_code=0)
cmd_name = str(parts[0])
cmd_str = " ".join(p.virtual if isinstance(p, PathSpec) else p
for p in parts)
# Job builtins
if cmd_name in JOB_BUILTINS and job_table is not None:
text_parts = [
p.virtual if isinstance(p, PathSpec) else p for p in parts
]
if cmd_name in ("wait", "fg"):
return await handle_wait(job_table, text_parts)
if cmd_name == "kill":
return await handle_kill(job_table, text_parts)
if cmd_name == "jobs":
return await handle_jobs(job_table, text_parts)
if cmd_name == "ps":
return await handle_ps(job_table, text_parts)
# Shell functions
if cmd_name in session.functions:
func_body = session.functions[cmd_name]
cs = call_stack or CallStack()
# Positional args carry the word as typed ($1 stays sub/a.txt).
text_args = [word_text(p) for p in parts[1:]]
cs.push(text_args, function_name=cmd_name)
saved_locals: dict[str, str | None] = {}
session._local_vars = saved_locals
try:
all_stdout: list = []
merged_io = IOResult()
last_exec = ExecutionNode(command=cmd_name, exit_code=0)
for cmd in func_body:
try:
stdout, io, last_exec = await execute_node(
cmd, session, stdin, cs)
except ReturnSignal as sig:
if sig.stderr:
merged_io = await merged_io.merge(
IOResult(stderr=sig.stderr))
merged_io.exit_code = sig.exit_code
break
if stdout is not None:
all_stdout.append(stdout)
merged_io = await merged_io.merge(io)
if (io.exit_code != 0 and session.shell_options.get("errexit")
and cmd.type not in ERREXIT_EXEMPT_TYPES):
merged_io.exit_code = io.exit_code
break
combined = async_chain(*all_stdout) if all_stdout else None
last_exec.exit_code = merged_io.exit_code
return combined, merged_io, last_exec
finally:
cs.pop()
for key, old_val in saved_locals.items():
if old_val is None:
session.env.pop(key, None)
else:
session.env[key] = old_val
session._local_vars = None
# Cross-mount: paths span different mounts (e.g. cp /ram/a /disk/b).
# Use dispatch to read/write across mounts directly.
path_scopes = [p for p in parts[1:] if isinstance(p, PathSpec)]
raw_argv = [p.virtual if isinstance(p, PathSpec) else p for p in parts[1:]]
early_guard = _check_mount_root_guard_raw(cmd_name, path_scopes, registry,
raw_argv)
if early_guard is not None:
msg, code = early_guard
return None, IOResult(exit_code=code,
stderr=msg.encode()), ExecutionNode(
command=cmd_str,
exit_code=code,
stderr=msg.encode())
# Unknown name: nobody registers it; fail like bash before any
# backend work. The mount-root guard stays ahead of this so
# protective refusals keep their specific messages.
if route(cmd_name, session, registry) is Consumer.UNKNOWN:
err = f"{cmd_name}: command not found\n".encode()
return None, IOResult(exit_code=127,
stderr=err), ExecutionNode(command=cmd_str,
exit_code=127,
stderr=err)
find_expr_tokens: list[str] | None = None
if cmd_name == "find":
find_expr_tokens = find_expr_tail(raw_argv)
try:
parse_find_expression(find_expr_tokens)
except FindParseError as exc:
msg = f"{exc}\n"
return None, IOResult(exit_code=1,
stderr=msg.encode()), ExecutionNode(
command=cmd_str,
exit_code=1,
stderr=msg.encode())
if is_cross_mount(cmd_name, path_scopes, registry):
# Cross-mount execution bypasses a resource command handler. Parse
# against the shared spec so flags and text operands do not depend on
# the source mount. The bound single-mount runner lets the strategy
# runners execute each operand natively on its owning mount.
cross_parsed = _parse_flags(parts[1:],
SPECS.get(cmd_name),
cmd_name,
session.cwd,
str_flag_paths=True)
cross_texts = (find_expr_tokens
if find_expr_tokens is not None else cross_parsed.texts)
cross_refusal = _option_error(cmd_name, cross_parsed)
if cross_refusal is not None:
msg, code = cross_refusal
return None, IOResult(exit_code=code,
stderr=msg), ExecutionNode(command=cmd_str,
exit_code=code,
stderr=msg)
run_single = functools.partial(run_on_mount, registry, session,
dispatch, namespace)
stdout, io = await handle_cross_mount(cmd_name,
path_scopes,
cross_texts,
cross_parsed.flag_kwargs,
dispatch,
run_single,
stdin=stdin)
if cross_parsed.warnings:
warn = "".join(f"{cmd_name}: {w}\n"
for w in cross_parsed.warnings).encode()
existing = await materialize(io.stderr) if io.stderr else b""
io.stderr = warn + existing
# The native sub-runs carry their own mount's safeguard; the
# cross-mount command as a whole uses the strictest one across the
# operand mounts, regardless of which sub-run merged last.
mounts = []
for s in path_scopes:
try:
mounts.append(registry.mount_for(s.virtual))
except ValueError:
pass
io.safeguard = (resolve_across_mounts(cmd_name, mounts)
if mounts else resolve_safeguard(cmd_name))
stdout = maybe_with_timeout(stdout, io.safeguard, cmd_name)
return stdout, io, await _exec_node(cmd_str, io, path_scopes)
# Reject unsupported cross-mount commands
if len(path_scopes) >= 2:
mount_prefixes = set()
for s in path_scopes:
try:
mount_prefixes.add(registry.mount_for(s.virtual).prefix)
except ValueError:
pass
if len(mount_prefixes) > 1:
prefixes_str = ", ".join(sorted(mount_prefixes))
err = (f"{cmd_name}: paths span multiple mounts "
f"({prefixes_str}), cross-mount not supported\n")
return None, IOResult(
exit_code=1,
stderr=err.encode(),
), ExecutionNode(command=cmd_str, exit_code=1)
try:
mount = await registry.resolve_mount(cmd_name, path_scopes,
session.cwd)
except MountCommandUnsupported as exc:
err = f"{exc}\n".encode()
return None, IOResult(exit_code=1,
stderr=err), ExecutionNode(command=cmd_str,
exit_code=1,
stderr=err)
if mount is None:
return None, IOResult(
exit_code=127,
stderr=f"{cmd_name}: command not found".encode(),
), ExecutionNode(command=cmd_str, exit_code=127)
try:
assert_mount_allowed(mount.prefix)
for ps in path_scopes:
target = registry.mount_for(ps.virtual)
assert_mount_allowed(target.prefix)
except PermissionError as exc:
err = f"{exc}\n".encode()
return None, IOResult(exit_code=1,
stderr=err), ExecutionNode(command=cmd_str,
exit_code=1,
stderr=err)
# Parse flags upstream — mount receives clean args
single_parsed = _parse_flags(parts[1:], mount.spec_for(cmd_name), cmd_name,
session.cwd)
paths, texts, flag_kwargs, parse_warnings = (single_parsed.paths,
single_parsed.texts,
single_parsed.flag_kwargs,
single_parsed.warnings)
refusal = _option_error(cmd_name, single_parsed)
if refusal is not None:
msg, code = refusal
return None, IOResult(exit_code=code,
stderr=msg), ExecutionNode(command=cmd_str,
exit_code=code,
stderr=msg)
if find_expr_tokens is not None:
texts = find_expr_tokens
flag_kwargs = _scalar_find_flags(flag_kwargs)
warn_bytes = ("".join(
f"{cmd_name}: {w}\n"
for w in parse_warnings).encode() if parse_warnings else b"")
if _should_fan_out(cmd_name, paths, flag_kwargs, registry):
stdout, io, node = await _fan_out_traversal(cmd_name, paths, texts,
flag_kwargs, registry,
mount, session.cwd,
cmd_str, stdin)
if warn_bytes:
existing = await materialize(io.stderr) if io.stderr else b""
io.stderr = warn_bytes + existing
node.stderr = warn_bytes + (node.stderr or b"")
return stdout, io, node
stdout, io = await run_on_mount(registry,
session,
dispatch,
namespace,
cmd_name,
paths,
texts,
flag_kwargs,
stdin=stdin,
mount=mount)
if warn_bytes:
existing = await materialize(io.stderr) if io.stderr else b""
io.stderr = warn_bytes + existing
stdout = maybe_with_timeout(stdout, io.safeguard, cmd_name)
io.stderr = maybe_with_timeout(io.stderr, io.safeguard, cmd_name)
return stdout, io, await _exec_node(cmd_str, io, paths)
async def _inject_links(
stdout: ByteSource | None,
namespace: Namespace,
paths: list[PathSpec],
flag_kwargs: dict,
cwd: str,
) -> ByteSource | None:
"""Append symlink entries living under the listed directory.
Links are namespace state, invisible to backend readdir, so ``ls``
surfaces them the same way child mounts are surfaced. Long form
renders GNU-style ``name -> target``.
Args:
stdout (ByteSource | None): backend ls output.
namespace (Namespace): addressing authority holding the link table.
paths (list[PathSpec]): positional ls operands.
flag_kwargs (dict): parsed ls flags.
cwd (str): current working directory fallback operand.
"""
if flag_kwargs.get("d") is True or flag_kwargs.get("R") is True:
return stdout
if len(paths) > 1:
return stdout
listed = paths[0].virtual if paths else cwd
links = namespace.links_under(listed)
if not links:
return stdout
existing_bytes = await materialize(stdout) if stdout is not None else b""
existing = existing_bytes.decode("utf-8")
long_form = flag_kwargs.get("args_l") is True
classify = flag_kwargs.get("F") is True
present: set[str] = set()
for line in existing.split("\n"):
if line == "":
continue
name = line.split("\t")[-1] if long_form else line.rstrip("/*@|=")
if name:
present.add(name)
extras: list[str] = []
for name in sorted(links):
if name in present:
continue
if long_form:
extras.append(f"l\t-\t-\t{name} -> {links[name]}")
else:
extras.append(f"{name}@" if classify else name)
if not extras:
return stdout
sep = "" if existing == "" or existing.endswith("\n") else "\n"
return (existing + sep + "\n".join(extras) + "\n").encode("utf-8")
async def _inject_child_mounts(
stdout: ByteSource | None,
registry: MountRegistry,
paths: list[PathSpec],
flag_kwargs: dict,
cwd: str,
) -> ByteSource | None:
if flag_kwargs.get("d") is True or flag_kwargs.get("R") is True:
return stdout
if len(paths) > 1:
return stdout
listed = paths[0].virtual if paths else cwd
include_hidden = (flag_kwargs.get("a") is True
or flag_kwargs.get("A") is True)
child_names = registry.child_mount_names(listed, include_hidden)
if not child_names:
return stdout
existing_bytes = await materialize(stdout) if stdout is not None else b""
existing = existing_bytes.decode("utf-8")
long_form = flag_kwargs.get("args_l") is True
classify = flag_kwargs.get("F") is True
present: set[str] = set()
for line in existing.split("\n"):
if line == "":
continue
if long_form:
name = line.split("\t")[-1]
else:
name = line.rstrip("/*@|=")
if name:
present.add(name)
extras: list[str] = []
for name in child_names:
if name in present:
continue
if long_form:
extras.append(f"d\t-\t-\t{name}")
else:
extras.append(f"{name}/" if classify else name)
if not extras:
return stdout
sep = "" if existing == "" or existing.endswith("\n") else "\n"
return (existing + sep + "\n".join(extras)).encode("utf-8")