Skip to main content

agentos_kernel/
kernel.rs

1use crate::bridge::LifecycleState;
2use crate::command_registry::{CommandDriver, CommandRegistry};
3use crate::device_layer::{create_device_layer, DeviceLayer};
4use crate::dns::{
5    format_dns_resource, resolve_dns, resolve_dns_records, DnsConfig, DnsLookupPolicy,
6    DnsRecordResolution, DnsResolution, DnsResolverErrorKind, HickoryDnsResolver,
7    SharedDnsResolver,
8};
9use crate::fd_table::{
10    AnonymousFile, AnonymousFileUsage, FdEntry, FdStat, FdTableError, FdTableManager,
11    FileDescription, FileLockManager, FileLockTarget, FlockOperation, ProcessFdTable, RecordLock,
12    RecordLockType, SharedAnonymousFile, TransferredFd, FD_CLOEXEC, FILETYPE_CHARACTER_DEVICE,
13    FILETYPE_DIRECTORY, FILETYPE_PIPE, FILETYPE_REGULAR_FILE, FILETYPE_SOCKET_DGRAM,
14    FILETYPE_SOCKET_STREAM, FILETYPE_SYMBOLIC_LINK, F_DUPFD, O_APPEND, O_CREAT, O_DIRECT,
15    O_DIRECTORY, O_EXCL, O_NOFOLLOW, O_NONBLOCK, O_RDONLY, O_RDWR, O_TRUNC, O_WRONLY,
16};
17use crate::mount_table::{MountEntry, MountOptions, MountTable, MountedFileSystem};
18use crate::network_policy::format_tcp_resource;
19use crate::permissions::{
20    check_command_execution, check_network_access, FsOperation, NetworkOperation, PermissionError,
21    PermissionedFileSystem, Permissions,
22};
23use crate::pipe_manager::{PipeError, PipeManager};
24use crate::poll::{
25    PollEvents, PollFd, PollNotifier, PollResult, PollTarget, PollTargetEntry, PollTargetResult,
26    POLLERR, POLLHUP, POLLIN, POLLNVAL, POLLOUT,
27};
28use crate::process_table::{
29    DriverProcess, ProcessContext, ProcessExitCallback, ProcessInfo, ProcessStatus, ProcessTable,
30    ProcessTableError, ProcessWaitResult, SigmaskHow, SignalSet, DEFAULT_PROCESS_UMASK, SIGCONT,
31    SIGPIPE, SIGSTOP, SIGTSTP, SIGWINCH,
32};
33use crate::pty::{
34    LineDisciplineConfig, PartialTermios, PtyError, PtyManager, PtyWindowSize, Termios,
35};
36use crate::resource_accounting::{
37    measure_filesystem_usage, FileSystemStats, FileSystemUsage, ResourceAccountant, ResourceError,
38    ResourceLimits, ResourceSnapshot, DEFAULT_MAX_OPEN_FDS,
39};
40use crate::root_fs::{
41    encode_snapshot, RootFileSystem, RootFilesystemError, RootFilesystemSnapshot,
42};
43use crate::socket_table::{
44    DatagramSocketOption, InetSocketAddress, OpaqueTransferredRight, ReceivedDatagram, SocketId,
45    SocketMulticastMembership, SocketReadiness, SocketRecord, SocketShutdown, SocketSpec,
46    SocketState, SocketTable, SocketTableError, SocketType, TransferredSocketRight,
47};
48use crate::user::{ProcessIdentity, UserConfig, UserManager};
49use crate::vfs::{
50    normalize_path, VfsError, VfsResult, VirtualDirEntry, VirtualFileSystem, VirtualStat,
51    VirtualTimeSpec, VirtualUtimeSpec, MAX_PATH_LENGTH, RENAME_EXCHANGE, S_IFDIR, S_IFLNK, S_IFREG,
52};
53use hickory_proto::rr::RecordType;
54use std::any::Any;
55use std::collections::{BTreeMap, BTreeSet, VecDeque};
56use std::error::Error;
57use std::fmt;
58#[cfg(test)]
59use std::sync::OnceLock;
60use std::sync::{Arc, Condvar, Mutex, MutexGuard, WaitTimeoutResult};
61use std::time::Duration;
62use web_time::{Instant, SystemTime, UNIX_EPOCH};
63
64pub type KernelResult<T> = Result<T, KernelError>;
65pub use crate::process_table::{ProcessWaitEvent as WaitPidEvent, WaitPidFlags};
66
67pub const SEEK_SET: u8 = 0;
68pub const SEEK_CUR: u8 = 1;
69pub const SEEK_END: u8 = 2;
70const EXECUTABLE_PERMISSION_BITS: u32 = 0o111;
71const SHEBANG_LINE_MAX_BYTES: usize = 256;
72const MAX_EXEC_INTERPRETER_DEPTH: usize = 4;
73const MAX_UNIX_SOCKET_SYMLINKS: usize = 40;
74const UNIX_SOCKET_FILE_TYPE: u32 = 0o140000;
75const UNIX_DAC_WRITE: u32 = 0o2;
76const UNIX_DAC_SEARCH: u32 = 0o1;
77
78#[derive(Debug, Clone, PartialEq, Eq)]
79pub struct KernelError {
80    code: &'static str,
81    message: String,
82}
83
84impl KernelError {
85    pub fn code(&self) -> &'static str {
86        self.code
87    }
88
89    fn new(code: &'static str, message: impl Into<String>) -> Self {
90        Self {
91            code,
92            message: message.into(),
93        }
94    }
95
96    fn disposed() -> Self {
97        Self::new("EINVAL", "kernel VM is disposed")
98    }
99
100    fn no_such_process(pid: u32) -> Self {
101        Self::new("ESRCH", format!("no such process {pid}"))
102    }
103
104    fn bad_file_descriptor(fd: u32) -> Self {
105        Self::new("EBADF", format!("bad file descriptor {fd}"))
106    }
107
108    fn permission_denied(message: impl Into<String>) -> Self {
109        Self::new("EPERM", message)
110    }
111
112    fn command_not_found(command: &str) -> Self {
113        Self::new("ENOENT", format!("command not found: {command}"))
114    }
115}
116
117impl fmt::Display for KernelError {
118    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
119        write!(f, "{}: {}", self.code, self.message)
120    }
121}
122
123impl Error for KernelError {}
124
125fn linux_shebang_interpreter(header: &[u8], path: &str) -> KernelResult<Option<String>> {
126    if !header.starts_with(b"#!") {
127        return Ok(None);
128    }
129
130    let payload = &header[2..];
131    let newline = payload.iter().position(|byte| *byte == b'\n');
132    let line = newline.map_or(payload, |index| &payload[..index]);
133    let line_end = line
134        .iter()
135        .rposition(|byte| !matches!(*byte, b' ' | b'\t'))
136        .map(|index| index + 1)
137        .ok_or_else(|| KernelError::new("ENOEXEC", format!("invalid shebang line: {path}")))?;
138    let line = &line[..line_end];
139    let interpreter_start = line
140        .iter()
141        .position(|byte| !matches!(*byte, b' ' | b'\t'))
142        .ok_or_else(|| KernelError::new("ENOEXEC", format!("invalid shebang line: {path}")))?;
143    let interpreter_tail = &line[interpreter_start..];
144    let separator = interpreter_tail
145        .iter()
146        .position(|byte| matches!(*byte, b' ' | b'\t'));
147
148    // Linux accepts a truncated optional argument, but it rejects a shebang
149    // whose interpreter pathname itself reaches the end of BINPRM_BUF_SIZE.
150    if newline.is_none() && header.len() >= SHEBANG_LINE_MAX_BYTES && separator.is_none() {
151        return Err(KernelError::new(
152            "ENOEXEC",
153            format!("shebang interpreter path exceeds the Linux header limit: {path}"),
154        ));
155    }
156
157    let interpreter_end = separator.unwrap_or(interpreter_tail.len());
158    let interpreter = std::str::from_utf8(&interpreter_tail[..interpreter_end])
159        .map_err(|_| KernelError::new("ENOEXEC", format!("invalid shebang line: {path}")))?;
160    if interpreter.is_empty() {
161        return Err(KernelError::new(
162            "ENOEXEC",
163            format!("invalid shebang line: {path}"),
164        ));
165    }
166    Ok(Some(interpreter.to_owned()))
167}
168
169#[derive(Clone)]
170pub struct KernelVmConfig {
171    pub vm_id: String,
172    pub env: BTreeMap<String, String>,
173    pub cwd: String,
174    pub user: UserConfig,
175    pub permissions: Permissions,
176    pub loopback_exempt_ports: BTreeSet<u16>,
177    pub dns: DnsConfig,
178    pub dns_resolver: SharedDnsResolver,
179    pub resources: ResourceLimits,
180    pub zombie_ttl: Duration,
181}
182
183impl KernelVmConfig {
184    pub fn new(vm_id: impl Into<String>) -> Self {
185        Self {
186            vm_id: vm_id.into(),
187            env: BTreeMap::new(),
188            cwd: String::from("/workspace"),
189            user: UserConfig::default(),
190            permissions: Permissions::default(),
191            loopback_exempt_ports: BTreeSet::new(),
192            dns: DnsConfig::default(),
193            dns_resolver: Arc::new(HickoryDnsResolver::default()),
194            resources: ResourceLimits::default(),
195            zombie_ttl: Duration::from_secs(60),
196        }
197    }
198}
199
200#[derive(Debug, Clone, Default)]
201pub struct SpawnOptions {
202    pub requester_driver: Option<String>,
203    pub parent_pid: Option<u32>,
204    pub env: BTreeMap<String, String>,
205    pub cwd: Option<String>,
206}
207
208#[derive(Debug, Clone, Default, PartialEq, Eq)]
209pub struct VirtualProcessOptions {
210    pub parent_pid: Option<u32>,
211    pub env: BTreeMap<String, String>,
212    pub cwd: Option<String>,
213}
214
215#[derive(Debug, Clone, Default, PartialEq, Eq)]
216pub struct ExecOptions {
217    pub requester_driver: Option<String>,
218    pub parent_pid: Option<u32>,
219    pub env: BTreeMap<String, String>,
220    pub cwd: Option<String>,
221}
222
223#[derive(Debug, Clone, PartialEq, Eq)]
224pub struct RecursiveDirEntry {
225    pub path: String,
226    pub is_directory: bool,
227    pub is_symbolic_link: bool,
228    pub size: u64,
229}
230
231#[derive(Debug, Clone, Default, PartialEq, Eq)]
232pub struct OpenShellOptions {
233    pub requester_driver: Option<String>,
234    pub command: Option<String>,
235    pub args: Vec<String>,
236    pub env: BTreeMap<String, String>,
237    pub cwd: Option<String>,
238}
239
240#[derive(Debug, Clone, PartialEq, Eq)]
241pub struct WaitPidResult {
242    pub pid: u32,
243    pub status: i32,
244}
245
246#[derive(Debug, Clone, PartialEq, Eq)]
247pub struct WaitPidEventResult {
248    pub pid: u32,
249    pub status: i32,
250    pub event: WaitPidEvent,
251}
252
253#[derive(Debug)]
254pub struct ReceivedFdMessage {
255    pub payload: Vec<u8>,
256    pub rights: Vec<ReceivedFdRight>,
257    pub payload_truncated: bool,
258    pub control_truncated: bool,
259    pub full_length: usize,
260}
261
262#[derive(Clone)]
263pub enum FdTransferRequest {
264    Fd(u32),
265    Opaque(OpaqueTransferredRight),
266}
267
268impl fmt::Debug for FdTransferRequest {
269    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
270        match self {
271            Self::Fd(fd) => f.debug_tuple("Fd").field(fd).finish(),
272            Self::Opaque(resource) => f
273                .debug_tuple("Opaque")
274                .field(&(Arc::as_ptr(resource) as *const ()))
275                .finish(),
276        }
277    }
278}
279
280pub enum ReceivedFdRight {
281    Fd(u32),
282    Opaque(OpaqueTransferredRight),
283}
284
285impl fmt::Debug for ReceivedFdRight {
286    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
287        match self {
288            Self::Fd(fd) => f.debug_tuple("Fd").field(fd).finish(),
289            Self::Opaque(resource) => f
290                .debug_tuple("Opaque")
291                .field(&(Arc::as_ptr(resource) as *const ()))
292                .finish(),
293        }
294    }
295}
296
297#[derive(Debug, Clone, Copy, PartialEq, Eq)]
298pub struct ProcessFdSnapshotEntry {
299    pub fd: u32,
300    pub fd_flags: u32,
301    pub status_flags: u32,
302    pub filetype: u8,
303    pub is_socket: bool,
304    pub is_pipe: bool,
305    pub is_pty: bool,
306}
307
308#[derive(Debug, Clone, PartialEq, Eq)]
309pub struct ProcessFdDirEntry {
310    pub name: String,
311    pub ino: u64,
312    pub is_directory: bool,
313    pub is_symbolic_link: bool,
314}
315
316/// The canonical VFS object selected by Linux-style AF_UNIX pathname lookup.
317///
318/// `canonical_path` preserves the actual directory reached through symlinks
319/// while `stat` carries the `(dev, ino)` identity the sidecar must use to
320/// distinguish socket nodes across mounts.
321#[derive(Debug, Clone, PartialEq, Eq)]
322pub struct UnixSocketPathNode {
323    pub canonical_path: String,
324    pub stat: VirtualStat,
325}
326
327struct UnixSocketBindTarget {
328    canonical_path: String,
329    parent: UnixSocketPathNode,
330    identity: ProcessIdentity,
331}
332
333#[derive(Debug, Clone)]
334struct FdSocketEntry {
335    description: Arc<FileDescription>,
336    socket_id: SocketId,
337    mode: u32,
338    uid: u32,
339    gid: u32,
340}
341
342type FdSocketRegistry = Arc<Mutex<BTreeMap<u64, FdSocketEntry>>>;
343
344enum OpenFileRemovalBacking {
345    Anonymous {
346        descriptions: Vec<Arc<FileDescription>>,
347        backing: SharedAnonymousFile,
348    },
349    LinkedAlias {
350        descriptions: Vec<Arc<FileDescription>>,
351        live_path: String,
352    },
353}
354
355#[derive(Debug, Clone)]
356struct ResolvedSpawnCommand {
357    command: String,
358    args: Vec<String>,
359    driver: CommandDriver,
360}
361
362#[derive(Debug, Clone)]
363struct ShebangCommand {
364    interpreter: String,
365    args: Vec<String>,
366}
367
368#[derive(Clone)]
369pub struct KernelProcessHandle {
370    pid: u32,
371    driver: String,
372    process: Arc<StubDriverProcess>,
373}
374
375impl fmt::Debug for KernelProcessHandle {
376    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
377        f.debug_struct("KernelProcessHandle")
378            .field("pid", &self.pid)
379            .field("driver", &self.driver)
380            .finish_non_exhaustive()
381    }
382}
383
384impl KernelProcessHandle {
385    pub fn pid(&self) -> u32 {
386        self.pid
387    }
388
389    pub fn driver(&self) -> &str {
390        &self.driver
391    }
392
393    pub fn finish(&self, exit_code: i32) {
394        self.process.finish(exit_code);
395    }
396
397    pub fn kill(&self, signal: i32) {
398        self.process.kill(signal);
399    }
400
401    pub fn wait(&self, timeout: Duration) -> Option<i32> {
402        self.process.wait(timeout)
403    }
404
405    pub fn kill_signals(&self) -> Vec<i32> {
406        self.process.kill_signals()
407    }
408}
409
410#[derive(Debug, Clone)]
411pub struct OpenShellHandle {
412    process: KernelProcessHandle,
413    master_fd: u32,
414    slave_fd: u32,
415    pty_path: String,
416}
417
418impl OpenShellHandle {
419    pub fn process(&self) -> &KernelProcessHandle {
420        &self.process
421    }
422
423    pub fn pid(&self) -> u32 {
424        self.process.pid()
425    }
426
427    pub fn master_fd(&self) -> u32 {
428        self.master_fd
429    }
430
431    pub fn slave_fd(&self) -> u32 {
432        self.slave_fd
433    }
434
435    pub fn pty_path(&self) -> &str {
436        &self.pty_path
437    }
438}
439
440pub struct KernelVm<F> {
441    vm_id: String,
442    boot_time_ms: u64,
443    boot_instant: Instant,
444    filesystem: PermissionedFileSystem<DeviceLayer<F>>,
445    permissions: Permissions,
446    loopback_exempt_ports: BTreeSet<u16>,
447    dns: DnsConfig,
448    dns_resolver: SharedDnsResolver,
449    env: BTreeMap<String, String>,
450    cwd: String,
451    commands: CommandRegistry,
452    fd_tables: Arc<Mutex<FdTableManager>>,
453    processes: ProcessTable,
454    pipes: PipeManager,
455    ptys: PtyManager,
456    sockets: SocketTable,
457    fd_sockets: FdSocketRegistry,
458    poll_notifier: PollNotifier,
459    users: UserManager,
460    resources: ResourceAccountant,
461    filesystem_usage_cache: Option<FileSystemUsage>,
462    no_posix_acl_cache: BTreeSet<(u64, u64, u64, u32, u32, u32, u32)>,
463    anonymous_file_usage: Arc<AnonymousFileUsage>,
464    file_locks: FileLockManager,
465    unnamed_files: BTreeMap<u64, UnnamedFile>,
466    next_unnamed_file_id: u64,
467    driver_pids: Arc<Mutex<BTreeMap<String, BTreeSet<u32>>>>,
468    terminated: bool,
469}
470
471const UNNAMED_FILE_PREFIX: &str = ".agentos-tmpfile-";
472
473#[derive(Debug, Clone)]
474struct UnnamedFile {
475    path: String,
476    linkable: bool,
477}
478
479pub fn is_internal_unnamed_file_name(name: &str) -> bool {
480    name.starts_with(UNNAMED_FILE_PREFIX)
481}
482
483// Cleanup spans every independently owned kernel resource table. Keeping the
484// tables explicit makes teardown ordering visible at the call sites.
485#[allow(clippy::too_many_arguments)]
486fn cleanup_process_resources(
487    fd_tables: &Mutex<FdTableManager>,
488    file_locks: &FileLockManager,
489    pipes: &PipeManager,
490    ptys: &PtyManager,
491    sockets: &SocketTable,
492    fd_sockets: &FdSocketRegistry,
493    driver_pids: &Mutex<BTreeMap<String, BTreeSet<u32>>>,
494    pid: u32,
495) {
496    let mut cleanup = Vec::new();
497    {
498        let mut tables = lock_or_recover(fd_tables);
499        let descriptors = tables
500            .get(pid)
501            .map(|table| {
502                table
503                    .iter()
504                    .map(|entry| (entry.fd, Arc::clone(&entry.description), entry.filetype))
505                    .collect::<Vec<_>>()
506            })
507            .unwrap_or_default();
508
509        cleanup_process_resources_test_hook();
510
511        if let Some(table) = tables.get_mut(pid) {
512            for (fd, description, filetype) in &descriptors {
513                table.close(*fd);
514                cleanup.push((Arc::clone(description), *filetype));
515            }
516        }
517        tables.remove(pid);
518    }
519
520    for (description, filetype) in cleanup {
521        close_special_resource_if_needed(
522            file_locks,
523            pipes,
524            ptys,
525            sockets,
526            fd_sockets,
527            &description,
528            filetype,
529        );
530    }
531    file_locks.release_process(pid);
532
533    sockets.remove_all_for_pid(pid);
534
535    let mut owners = lock_or_recover(driver_pids);
536    for pids in owners.values_mut() {
537        pids.remove(&pid);
538    }
539}
540
541fn dispose_kernel_vm_resources<F>(kernel: &mut KernelVm<F>) {
542    kernel.processes.terminate_all();
543    let pids = lock_or_recover(&kernel.fd_tables).pids();
544    for pid in pids {
545        cleanup_process_resources(
546            kernel.fd_tables.as_ref(),
547            &kernel.file_locks,
548            &kernel.pipes,
549            &kernel.ptys,
550            &kernel.sockets,
551            &kernel.fd_sockets,
552            kernel.driver_pids.as_ref(),
553            pid,
554        );
555    }
556    lock_or_recover(&kernel.driver_pids).clear();
557    kernel.terminated = true;
558}
559
560#[cfg(test)]
561type CleanupProcessResourcesHook = Arc<dyn Fn() + Send + Sync + 'static>;
562
563#[cfg(test)]
564fn cleanup_process_resources_test_hook() {
565    let hook = lock_or_recover(cleanup_process_resources_test_hook_slot()).clone();
566    if let Some(hook) = hook {
567        hook();
568    }
569}
570
571#[cfg(not(test))]
572fn cleanup_process_resources_test_hook() {}
573
574#[cfg(test)]
575fn cleanup_process_resources_test_hook_slot() -> &'static Mutex<Option<CleanupProcessResourcesHook>>
576{
577    static HOOK: OnceLock<Mutex<Option<CleanupProcessResourcesHook>>> = OnceLock::new();
578    HOOK.get_or_init(|| Mutex::new(None))
579}
580
581#[cfg(test)]
582fn set_cleanup_process_resources_test_hook(hook: Option<CleanupProcessResourcesHook>) {
583    *lock_or_recover(cleanup_process_resources_test_hook_slot()) = hook;
584}
585
586fn close_special_resource_if_needed(
587    file_locks: &FileLockManager,
588    pipes: &PipeManager,
589    ptys: &PtyManager,
590    sockets: &SocketTable,
591    fd_sockets: &FdSocketRegistry,
592    description: &Arc<FileDescription>,
593    filetype: u8,
594) {
595    if description.ref_count() != 0 {
596        return;
597    }
598
599    file_locks.release_owner(description.id());
600
601    if filetype == FILETYPE_PIPE && pipes.is_pipe(description.id()) {
602        pipes.close(description.id());
603    }
604
605    if ptys.is_pty(description.id()) {
606        ptys.close(description.id());
607    }
608
609    prune_fd_sockets(sockets, fd_sockets);
610}
611
612fn prune_fd_sockets(sockets: &SocketTable, fd_sockets: &FdSocketRegistry) {
613    loop {
614        let socket_ids = {
615            let mut registry = lock_or_recover(fd_sockets);
616            let closed = registry
617                .iter()
618                .filter_map(|(description_id, entry)| {
619                    (entry.description.ref_count() == 0)
620                        .then_some((*description_id, entry.socket_id))
621                })
622                .collect::<Vec<_>>();
623            for (description_id, _) in &closed {
624                registry.remove(description_id);
625            }
626            closed
627                .into_iter()
628                .map(|(_, socket_id)| socket_id)
629                .collect::<Vec<_>>()
630        };
631        if socket_ids.is_empty() {
632            return;
633        }
634        for socket_id in socket_ids {
635            if let Err(error) = sockets.remove(socket_id) {
636                eprintln!(
637                    "[agentos] failed to remove closed descriptor-owned socket {socket_id}: {error}"
638                );
639            }
640        }
641    }
642}
643
644#[derive(Debug, Clone, PartialEq, Eq)]
645enum ProcNode {
646    RootDir,
647    MountsFile,
648    CpuInfoFile,
649    MemInfoFile,
650    LoadAvgFile,
651    UptimeFile,
652    VersionFile,
653    SelfLink { pid: u32 },
654    PidDir { pid: u32 },
655    PidFdDir { pid: u32 },
656    PidCmdline { pid: u32 },
657    PidEnviron { pid: u32 },
658    PidCwdLink { pid: u32 },
659    PidStatFile { pid: u32 },
660    PidStatusFile { pid: u32 },
661    PidFdLink { pid: u32, fd: u32 },
662}
663
664impl<F: VirtualFileSystem + 'static> KernelVm<F> {
665    pub fn new(filesystem: F, config: KernelVmConfig) -> Self {
666        let vm_id = config.vm_id;
667        let boot_time_ms = now_ms();
668        let boot_instant = Instant::now();
669        let permissions = config.permissions.clone();
670        let users = UserManager::from_config(config.user);
671        let process_table = ProcessTable::with_zombie_ttl(config.zombie_ttl);
672        let process_table_for_pty = process_table.clone();
673        let max_open_fds = config
674            .resources
675            .max_open_fds
676            .unwrap_or(DEFAULT_MAX_OPEN_FDS);
677        let fd_tables = Arc::new(Mutex::new(FdTableManager::with_max_fds(max_open_fds)));
678        // A descriptor may own several disjoint byte ranges. Keep the
679        // VM-wide table bounded and use the existing open-fd resource knob as
680        // the explicit way to raise that derived limit.
681        let file_locks =
682            FileLockManager::with_record_lock_limit(max_open_fds.saturating_mul(16).max(16));
683        let driver_pids = Arc::new(Mutex::new(BTreeMap::new()));
684        let poll_notifier = PollNotifier::default();
685        let pipes = PipeManager::with_notifier(poll_notifier.clone());
686        let ptys = PtyManager::with_signal_handler_and_notifier(
687            Arc::new(move |pgid, signal| {
688                let _ = process_table_for_pty.kill(-(pgid as i32), signal);
689            }),
690            poll_notifier.clone(),
691        );
692        let sockets = SocketTable::new();
693        let fd_sockets = Arc::new(Mutex::new(BTreeMap::new()));
694
695        let fd_tables_for_exit = Arc::clone(&fd_tables);
696        let file_locks_for_exit = file_locks.clone();
697        let driver_pids_for_exit = Arc::clone(&driver_pids);
698        let pipes_for_exit = pipes.clone();
699        let ptys_for_exit = ptys.clone();
700        let sockets_for_exit = sockets.clone();
701        let fd_sockets_for_exit = Arc::clone(&fd_sockets);
702        process_table.set_on_process_exit(Some(Arc::new(move |pid| {
703            cleanup_process_resources(
704                fd_tables_for_exit.as_ref(),
705                &file_locks_for_exit,
706                &pipes_for_exit,
707                &ptys_for_exit,
708                &sockets_for_exit,
709                &fd_sockets_for_exit,
710                driver_pids_for_exit.as_ref(),
711                pid,
712            );
713        })));
714
715        let filesystem = PermissionedFileSystem::new(
716            create_device_layer(filesystem),
717            vm_id.clone(),
718            permissions.clone(),
719        );
720        // Usage accounting is kernel-internal: the cache is populated lazily by
721        // `filesystem_usage()` through the RAW filesystem so no guest-attributable
722        // permission check fires at construction (or ever) for quota bookkeeping.
723        let filesystem_usage_cache = None;
724        let anonymous_file_usage = Arc::new(AnonymousFileUsage::default());
725
726        Self {
727            vm_id: vm_id.clone(),
728            boot_time_ms,
729            boot_instant,
730            filesystem,
731            permissions,
732            loopback_exempt_ports: config.loopback_exempt_ports,
733            dns: config.dns,
734            dns_resolver: config.dns_resolver,
735            env: config.env,
736            cwd: config.cwd,
737            commands: CommandRegistry::new(),
738            fd_tables,
739            processes: process_table,
740            pipes,
741            ptys,
742            sockets,
743            fd_sockets,
744            poll_notifier,
745            users,
746            resources: ResourceAccountant::new(config.resources),
747            filesystem_usage_cache,
748            no_posix_acl_cache: BTreeSet::new(),
749            anonymous_file_usage,
750            file_locks,
751            unnamed_files: BTreeMap::new(),
752            next_unnamed_file_id: 0,
753            driver_pids,
754            terminated: false,
755        }
756    }
757
758    pub fn vm_id(&self) -> &str {
759        &self.vm_id
760    }
761
762    pub fn state(&self) -> LifecycleState {
763        if self.terminated {
764            LifecycleState::Terminated
765        } else if self.processes.running_count() > 0 {
766            LifecycleState::Busy
767        } else {
768            LifecycleState::Ready
769        }
770    }
771
772    pub fn commands(&self) -> BTreeMap<String, String> {
773        self.commands.list()
774    }
775
776    pub fn filesystem(&self) -> &PermissionedFileSystem<DeviceLayer<F>> {
777        &self.filesystem
778    }
779
780    pub fn filesystem_mut(&mut self) -> &mut PermissionedFileSystem<DeviceLayer<F>> {
781        &mut self.filesystem
782    }
783
784    pub fn user_manager(&self) -> &UserManager {
785        &self.users
786    }
787
788    pub fn environment(&self) -> &BTreeMap<String, String> {
789        &self.env
790    }
791
792    pub fn process_identity(
793        &self,
794        requester_driver: &str,
795        pid: u32,
796    ) -> KernelResult<ProcessIdentity> {
797        self.assert_driver_owns(requester_driver, pid)?;
798        Ok(self
799            .processes
800            .get(pid)
801            .ok_or_else(|| KernelError::no_such_process(pid))?
802            .identity)
803    }
804
805    pub fn user_profile(&self) -> UserManager {
806        self.users.clone()
807    }
808
809    pub fn getuid(&self, requester_driver: &str, pid: u32) -> KernelResult<u32> {
810        Ok(self.process_identity(requester_driver, pid)?.uid)
811    }
812
813    pub fn getgid(&self, requester_driver: &str, pid: u32) -> KernelResult<u32> {
814        Ok(self.process_identity(requester_driver, pid)?.gid)
815    }
816
817    pub fn geteuid(&self, requester_driver: &str, pid: u32) -> KernelResult<u32> {
818        Ok(self.process_identity(requester_driver, pid)?.euid)
819    }
820
821    pub fn getegid(&self, requester_driver: &str, pid: u32) -> KernelResult<u32> {
822        Ok(self.process_identity(requester_driver, pid)?.egid)
823    }
824
825    pub fn getgroups(&self, requester_driver: &str, pid: u32) -> KernelResult<Vec<u32>> {
826        Ok(self
827            .process_identity(requester_driver, pid)?
828            .supplementary_gids)
829    }
830
831    pub fn getresuid(&self, requester_driver: &str, pid: u32) -> KernelResult<(u32, u32, u32)> {
832        let identity = self.process_identity(requester_driver, pid)?;
833        Ok((identity.uid, identity.euid, identity.suid))
834    }
835
836    pub fn getresgid(&self, requester_driver: &str, pid: u32) -> KernelResult<(u32, u32, u32)> {
837        let identity = self.process_identity(requester_driver, pid)?;
838        Ok((identity.gid, identity.egid, identity.sgid))
839    }
840
841    pub fn setuid(&self, requester_driver: &str, pid: u32, uid: u32) -> KernelResult<()> {
842        let current = self.process_identity(requester_driver, pid)?;
843        if current.euid == 0 {
844            self.setresuid(requester_driver, pid, Some(uid), Some(uid), Some(uid))
845        } else if uid == current.uid || uid == current.suid {
846            self.setresuid(requester_driver, pid, None, Some(uid), None)
847        } else {
848            Err(credential_transition_denied("setuid", uid))
849        }
850    }
851
852    pub fn seteuid(&self, requester_driver: &str, pid: u32, euid: u32) -> KernelResult<()> {
853        self.setresuid(requester_driver, pid, None, Some(euid), None)
854    }
855
856    pub fn setreuid(
857        &self,
858        requester_driver: &str,
859        pid: u32,
860        uid: Option<u32>,
861        euid: Option<u32>,
862    ) -> KernelResult<()> {
863        let current = self.process_identity(requester_driver, pid)?;
864        let next_uid = uid.unwrap_or(current.uid);
865        let next_euid = euid.unwrap_or(current.euid);
866        let update_saved = uid.is_some() || (euid.is_some() && next_euid != current.uid);
867        self.setresuid(
868            requester_driver,
869            pid,
870            uid,
871            euid,
872            update_saved.then_some(next_euid),
873        )?;
874        debug_assert_eq!(self.process_identity(requester_driver, pid)?.uid, next_uid);
875        Ok(())
876    }
877
878    pub fn setresuid(
879        &self,
880        requester_driver: &str,
881        pid: u32,
882        uid: Option<u32>,
883        euid: Option<u32>,
884        suid: Option<u32>,
885    ) -> KernelResult<()> {
886        let mut identity = self.process_identity(requester_driver, pid)?;
887        if identity.euid != 0 {
888            let allowed = [identity.uid, identity.euid, identity.suid];
889            for requested in [uid, euid, suid].into_iter().flatten() {
890                if !allowed.contains(&requested) {
891                    return Err(credential_transition_denied("setresuid", requested));
892                }
893            }
894        }
895        if let Some(uid) = uid {
896            identity.uid = uid;
897        }
898        if let Some(euid) = euid {
899            identity.euid = euid;
900        }
901        if let Some(suid) = suid {
902            identity.suid = suid;
903        }
904        self.processes.set_identity(pid, identity)?;
905        Ok(())
906    }
907
908    pub fn setgid(&self, requester_driver: &str, pid: u32, gid: u32) -> KernelResult<()> {
909        let current = self.process_identity(requester_driver, pid)?;
910        if current.euid == 0 {
911            self.setresgid(requester_driver, pid, Some(gid), Some(gid), Some(gid))
912        } else if gid == current.gid || gid == current.sgid {
913            self.setresgid(requester_driver, pid, None, Some(gid), None)
914        } else {
915            Err(credential_transition_denied("setgid", gid))
916        }
917    }
918
919    pub fn setegid(&self, requester_driver: &str, pid: u32, egid: u32) -> KernelResult<()> {
920        self.setresgid(requester_driver, pid, None, Some(egid), None)
921    }
922
923    pub fn setregid(
924        &self,
925        requester_driver: &str,
926        pid: u32,
927        gid: Option<u32>,
928        egid: Option<u32>,
929    ) -> KernelResult<()> {
930        let current = self.process_identity(requester_driver, pid)?;
931        let next_egid = egid.unwrap_or(current.egid);
932        let update_saved = gid.is_some() || (egid.is_some() && next_egid != current.gid);
933        self.setresgid(
934            requester_driver,
935            pid,
936            gid,
937            egid,
938            update_saved.then_some(next_egid),
939        )
940    }
941
942    pub fn setresgid(
943        &self,
944        requester_driver: &str,
945        pid: u32,
946        gid: Option<u32>,
947        egid: Option<u32>,
948        sgid: Option<u32>,
949    ) -> KernelResult<()> {
950        let mut identity = self.process_identity(requester_driver, pid)?;
951        if identity.euid != 0 {
952            let allowed = [identity.gid, identity.egid, identity.sgid];
953            for requested in [gid, egid, sgid].into_iter().flatten() {
954                if !allowed.contains(&requested) {
955                    return Err(credential_transition_denied("setresgid", requested));
956                }
957            }
958        }
959        if let Some(gid) = gid {
960            identity.gid = gid;
961        }
962        if let Some(egid) = egid {
963            identity.egid = egid;
964        }
965        if let Some(sgid) = sgid {
966            identity.sgid = sgid;
967        }
968        self.processes.set_identity(pid, identity)?;
969        Ok(())
970    }
971
972    pub fn setgroups(
973        &self,
974        requester_driver: &str,
975        pid: u32,
976        groups: Vec<u32>,
977    ) -> KernelResult<()> {
978        const MAX_SUPPLEMENTARY_GROUPS: usize = 64;
979        let mut identity = self.process_identity(requester_driver, pid)?;
980        if identity.euid != 0 {
981            return Err(KernelError::new(
982                "EPERM",
983                "setgroups requires effective uid 0",
984            ));
985        }
986        if groups.len() > MAX_SUPPLEMENTARY_GROUPS {
987            return Err(KernelError::new(
988                "EINVAL",
989                format!(
990                    "setgroups count {} exceeds limit {MAX_SUPPLEMENTARY_GROUPS}",
991                    groups.len()
992                ),
993            ));
994        }
995        let mut normalized = Vec::with_capacity(groups.len());
996        for gid in groups {
997            if !normalized.contains(&gid) {
998                normalized.push(gid);
999            }
1000        }
1001        identity.supplementary_gids = normalized;
1002        self.processes.set_identity(pid, identity)?;
1003        Ok(())
1004    }
1005
1006    pub fn switch_user(&self, requester_driver: &str, pid: u32, uid: u32) -> KernelResult<()> {
1007        let current = self.process_identity(requester_driver, pid)?;
1008        if current.euid != 0 {
1009            return Err(KernelError::new(
1010                "EPERM",
1011                "switch_user requires effective uid 0",
1012            ));
1013        }
1014        let account = self
1015            .users
1016            .account(uid)
1017            .cloned()
1018            .ok_or_else(|| KernelError::new("ENOENT", format!("unknown uid {uid}")))?;
1019        let identity = ProcessIdentity {
1020            uid: account.uid,
1021            gid: account.gid,
1022            euid: account.uid,
1023            egid: account.gid,
1024            suid: account.uid,
1025            sgid: account.gid,
1026            supplementary_gids: account.supplementary_gids,
1027        };
1028        self.processes.set_identity(pid, identity)?;
1029        Ok(())
1030    }
1031
1032    pub fn getpwuid(&self, uid: u32) -> KernelResult<String> {
1033        self.users
1034            .getpwuid(uid)
1035            .ok_or_else(|| KernelError::new("ENOENT", format!("unknown uid {uid}")))
1036    }
1037
1038    pub fn getpwnam(&self, username: &str) -> KernelResult<String> {
1039        self.users
1040            .getpwnam(username)
1041            .ok_or_else(|| KernelError::new("ENOENT", format!("unknown user {username}")))
1042    }
1043
1044    pub fn getpwent(&self, index: usize) -> KernelResult<String> {
1045        self.users
1046            .passwd_entries()
1047            .get(index)
1048            .cloned()
1049            .ok_or_else(|| KernelError::new("ENOENT", "end of passwd database"))
1050    }
1051
1052    pub fn getgrgid(&self, gid: u32) -> KernelResult<String> {
1053        self.users
1054            .getgrgid(gid)
1055            .ok_or_else(|| KernelError::new("ENOENT", format!("unknown gid {gid}")))
1056    }
1057
1058    pub fn getgrnam(&self, name: &str) -> KernelResult<String> {
1059        self.users
1060            .getgrnam(name)
1061            .ok_or_else(|| KernelError::new("ENOENT", format!("unknown group {name}")))
1062    }
1063
1064    pub fn getgrent(&self, index: usize) -> KernelResult<String> {
1065        self.users
1066            .group_entries()
1067            .get(index)
1068            .cloned()
1069            .ok_or_else(|| KernelError::new("ENOENT", "end of group database"))
1070    }
1071
1072    pub fn resource_snapshot(&self) -> ResourceSnapshot {
1073        let fd_tables = lock_or_recover(&self.fd_tables);
1074        self.resources.snapshot(
1075            &self.processes,
1076            &fd_tables,
1077            &self.pipes,
1078            &self.ptys,
1079            &self.sockets,
1080        )
1081    }
1082
1083    pub fn resource_limits(&self) -> &ResourceLimits {
1084        self.resources.limits()
1085    }
1086
1087    pub fn set_permissions(&mut self, permissions: Permissions) {
1088        self.filesystem.set_permissions(permissions.clone());
1089        self.permissions = permissions;
1090    }
1091
1092    pub fn set_loopback_exempt_ports(&mut self, ports: BTreeSet<u16>) {
1093        self.loopback_exempt_ports = ports;
1094    }
1095
1096    pub fn extend_loopback_exempt_ports(&mut self, ports: impl IntoIterator<Item = u16>) {
1097        self.loopback_exempt_ports.extend(ports);
1098    }
1099
1100    pub fn resolve_dns(
1101        &self,
1102        hostname: &str,
1103        policy: DnsLookupPolicy,
1104    ) -> KernelResult<DnsResolution> {
1105        self.assert_not_terminated()?;
1106        if matches!(policy, DnsLookupPolicy::CheckPermissions) {
1107            let resource = format_dns_resource(hostname).map_err(map_dns_resolver_error)?;
1108            check_network_access(
1109                &self.vm_id,
1110                &self.permissions,
1111                NetworkOperation::Dns,
1112                &resource,
1113            )?;
1114        }
1115
1116        resolve_dns(&self.dns, self.dns_resolver.as_ref(), hostname).map_err(map_dns_resolver_error)
1117    }
1118
1119    pub fn resolve_dns_records(
1120        &self,
1121        hostname: &str,
1122        record_type: RecordType,
1123        policy: DnsLookupPolicy,
1124    ) -> KernelResult<DnsRecordResolution> {
1125        self.assert_not_terminated()?;
1126        if matches!(policy, DnsLookupPolicy::CheckPermissions) {
1127            let resource = format_dns_resource(hostname).map_err(map_dns_resolver_error)?;
1128            check_network_access(
1129                &self.vm_id,
1130                &self.permissions,
1131                NetworkOperation::Dns,
1132                &resource,
1133            )?;
1134        }
1135
1136        resolve_dns_records(&self.dns, self.dns_resolver.as_ref(), hostname, record_type)
1137            .map_err(map_dns_resolver_error)
1138    }
1139
1140    pub fn register_driver(&mut self, driver: CommandDriver) -> KernelResult<()> {
1141        self.assert_not_terminated()?;
1142        let driver_name = driver.name().to_owned();
1143        let populate_driver = driver.clone();
1144        self.commands.register(driver)?;
1145        lock_or_recover(&self.driver_pids)
1146            .entry(driver_name)
1147            .or_default();
1148        self.commands
1149            .populate_driver_bin(&mut self.filesystem, &populate_driver)?;
1150        Ok(())
1151    }
1152
1153    pub fn exec(
1154        &mut self,
1155        command: &str,
1156        options: ExecOptions,
1157    ) -> KernelResult<KernelProcessHandle> {
1158        self.spawn_process(
1159            "sh",
1160            vec![String::from("-c"), String::from(command)],
1161            SpawnOptions {
1162                requester_driver: options.requester_driver,
1163                parent_pid: options.parent_pid,
1164                env: options.env,
1165                cwd: options.cwd,
1166            },
1167        )
1168    }
1169
1170    pub fn open_shell(&mut self, options: OpenShellOptions) -> KernelResult<OpenShellHandle> {
1171        let command = options.command.unwrap_or_else(|| String::from("sh"));
1172        let requester_driver = options.requester_driver.clone();
1173        let process = self.spawn_process(
1174            &command,
1175            options.args,
1176            SpawnOptions {
1177                requester_driver: requester_driver.clone(),
1178                parent_pid: None,
1179                env: options.env,
1180                cwd: options.cwd,
1181            },
1182        )?;
1183        let owner = requester_driver.as_deref().unwrap_or(process.driver());
1184        let (master_fd, slave_fd, pty_path) = self.open_pty(owner, process.pid())?;
1185        self.setpgid(owner, process.pid(), process.pid())?;
1186        self.pty_set_foreground_pgid(owner, process.pid(), master_fd, process.pid())?;
1187        Ok(OpenShellHandle {
1188            process,
1189            master_fd,
1190            slave_fd,
1191            pty_path,
1192        })
1193    }
1194
1195    pub fn read_file(&mut self, path: &str) -> KernelResult<Vec<u8>> {
1196        self.assert_not_terminated()?;
1197        self.read_file_internal(None, path)
1198    }
1199
1200    pub fn pread_file(&mut self, path: &str, offset: u64, length: usize) -> KernelResult<Vec<u8>> {
1201        self.assert_not_terminated()?;
1202        self.reject_unix_socket_data_path(path, "ENXIO")?;
1203        self.resources.check_pread_length(length)?;
1204        Ok(VirtualFileSystem::pread(
1205            &mut self.filesystem,
1206            path,
1207            offset,
1208            length,
1209        )?)
1210    }
1211
1212    pub fn pread_file_for_process(
1213        &mut self,
1214        requester_driver: &str,
1215        pid: u32,
1216        path: &str,
1217        offset: u64,
1218        length: usize,
1219    ) -> KernelResult<Vec<u8>> {
1220        self.assert_not_terminated()?;
1221        self.assert_driver_owns(requester_driver, pid)?;
1222        self.check_dac_access(pid, path, DAC_READ)?;
1223        self.reject_unix_socket_data_path(path, "ENXIO")?;
1224        self.resources.check_pread_length(length)?;
1225        Ok(VirtualFileSystem::pread(
1226            &mut self.filesystem,
1227            path,
1228            offset,
1229            length,
1230        )?)
1231    }
1232
1233    pub fn read_file_for_process(
1234        &mut self,
1235        requester_driver: &str,
1236        pid: u32,
1237        path: &str,
1238    ) -> KernelResult<Vec<u8>> {
1239        self.assert_not_terminated()?;
1240        self.assert_driver_owns(requester_driver, pid)?;
1241        self.check_dac_access(pid, path, DAC_READ)?;
1242        self.read_file_internal(Some(pid), path)
1243    }
1244
1245    pub fn write_file(&mut self, path: &str, content: impl Into<Vec<u8>>) -> KernelResult<()> {
1246        self.assert_not_terminated()?;
1247        self.reject_read_only_resolved_write_path(path)?;
1248        self.reject_unix_socket_data_path(path, "ENXIO")?;
1249        let content = content.into();
1250        let new_size = content.len() as u64;
1251        let existing = self.storage_stat(path)?;
1252        self.check_write_file_limits_with_existing(path, existing.as_ref(), new_size)?;
1253        self.filesystem.write_file(path, content)?;
1254        self.update_filesystem_usage_cache_for_write(path, existing.as_ref(), new_size);
1255        Ok(())
1256    }
1257
1258    /// Resolve the canonical candidate for an AF_UNIX pathname bind without
1259    /// mutating the filesystem.
1260    ///
1261    /// This is the preflight used by sidecars to enforce host-mount policy on
1262    /// the actual symlink-resolved destination before the socket inode is
1263    /// created. The subsequent bind call repeats every lookup and DAC check.
1264    pub fn resolve_unix_socket_bind_target_for_process(
1265        &mut self,
1266        requester_driver: &str,
1267        pid: u32,
1268        cwd: &str,
1269        path: &str,
1270    ) -> KernelResult<String> {
1271        Ok(self
1272            .resolve_unix_socket_bind_target(requester_driver, pid, cwd, path)?
1273            .canonical_path)
1274    }
1275
1276    fn resolve_unix_socket_bind_target(
1277        &mut self,
1278        requester_driver: &str,
1279        pid: u32,
1280        cwd: &str,
1281        path: &str,
1282    ) -> KernelResult<UnixSocketBindTarget> {
1283        self.assert_not_terminated()?;
1284        let identity = self.process_identity(requester_driver, pid)?;
1285        let (absolute_path, mut components, trailing_slash) =
1286            unix_socket_absolute_components(cwd, path)?;
1287
1288        let Some(basename) = components.pop_back() else {
1289            return Err(unix_socket_address_in_use(&absolute_path));
1290        };
1291
1292        // A trailing slash or a final dot component asks pathname lookup for
1293        // an existing directory. bind(2) cannot replace that entry: Linux
1294        // reports EADDRINUSE when it resolves and the lookup error otherwise.
1295        if trailing_slash || matches!(basename.as_str(), "." | "..") {
1296            let (_, full_components, _) = unix_socket_absolute_components(cwd, path)?;
1297            resolve_unix_socket_components(
1298                self.raw_filesystem_mut(),
1299                &identity,
1300                full_components,
1301                false,
1302                false,
1303            )?;
1304            return Err(unix_socket_address_in_use(&absolute_path));
1305        }
1306
1307        let parent = resolve_unix_socket_components(
1308            self.raw_filesystem_mut(),
1309            &identity,
1310            components,
1311            true,
1312            true,
1313        )?;
1314        check_unix_dac(
1315            &identity,
1316            &parent.stat,
1317            UNIX_DAC_WRITE | UNIX_DAC_SEARCH,
1318            "bind",
1319            &parent.canonical_path,
1320        )?;
1321
1322        let canonical_path = join_absolute_path(&parent.canonical_path, &basename);
1323        self.reject_read_only_entry_write_path(&canonical_path)?;
1324        self.filesystem
1325            .check_virtual_path(FsOperation::Write, &canonical_path)
1326            .map_err(KernelError::from)?;
1327
1328        match self.raw_filesystem_mut().lstat(&canonical_path) {
1329            Ok(_) => return Err(unix_socket_address_in_use(&canonical_path)),
1330            Err(error) if error.code() == "ENOENT" => {}
1331            Err(error) => return Err(error.into()),
1332        }
1333
1334        Ok(UnixSocketBindTarget {
1335            canonical_path,
1336            parent,
1337            identity,
1338        })
1339    }
1340
1341    /// Perform Linux pathname lookup and materialize the persistent inode for
1342    /// an AF_UNIX bind.
1343    ///
1344    /// Unlike the generic file helpers, this preserves raw `.`/`..`
1345    /// traversal, follows symlinks only in the dirname, checks POSIX DAC with
1346    /// the process's effective credentials, and never creates missing parent
1347    /// directories. The returned canonical path and `(dev, ino)` identify the
1348    /// exact dentry the sidecar must register.
1349    pub fn bind_unix_socket_path_for_process(
1350        &mut self,
1351        requester_driver: &str,
1352        pid: u32,
1353        cwd: &str,
1354        path: &str,
1355    ) -> KernelResult<UnixSocketPathNode> {
1356        let target = self.resolve_unix_socket_bind_target(requester_driver, pid, cwd, path)?;
1357        let UnixSocketBindTarget {
1358            canonical_path,
1359            parent,
1360            identity,
1361        } = target;
1362        let umask = self.processes.get_umask(pid)?;
1363
1364        self.check_write_file_limits(&canonical_path, 0)?;
1365        if let Err(error) = VirtualFileSystem::create_file_exclusive(
1366            &mut self.filesystem,
1367            &canonical_path,
1368            Vec::new(),
1369        ) {
1370            return if error.code() == "EEXIST" {
1371                Err(unix_socket_address_in_use(&canonical_path))
1372            } else {
1373                Err(error.into())
1374            };
1375        }
1376
1377        let mode = UNIX_SOCKET_FILE_TYPE | (0o777 & !(umask & 0o777));
1378        let gid = if parent.stat.mode & 0o2000 != 0 {
1379            parent.stat.gid
1380        } else {
1381            identity.egid
1382        };
1383        let metadata_result = (|| -> VfsResult<VirtualStat> {
1384            let filesystem = self.raw_filesystem_mut();
1385            filesystem.chown(&canonical_path, identity.euid, gid)?;
1386            filesystem.chmod(&canonical_path, mode)?;
1387            filesystem.lstat(&canonical_path)
1388        })();
1389        let stat = match metadata_result {
1390            Ok(stat) => stat,
1391            Err(error) => {
1392                let cleanup = self.raw_filesystem_mut().remove_file(&canonical_path);
1393                return match cleanup {
1394                    Ok(()) => Err(error.into()),
1395                    Err(cleanup_error) => Err(KernelError::new(
1396                        error.code(),
1397                        format!(
1398                            "failed to initialize Unix socket inode metadata: {error}; \
1399                             rollback also failed: {cleanup_error}"
1400                        ),
1401                    )),
1402                };
1403            }
1404        };
1405
1406        self.update_filesystem_usage_cache_for_inode_create(&canonical_path, 0);
1407        Ok(UnixSocketPathNode {
1408            canonical_path,
1409            stat,
1410        })
1411    }
1412
1413    /// Resolve an AF_UNIX connect target with Linux pathname and DAC rules.
1414    /// The final symlink is followed, search permission is required on every
1415    /// traversed directory, and the selected socket inode must be writable by
1416    /// the process's effective credentials.
1417    pub fn resolve_unix_socket_connect_target_for_process(
1418        &mut self,
1419        requester_driver: &str,
1420        pid: u32,
1421        cwd: &str,
1422        path: &str,
1423    ) -> KernelResult<UnixSocketPathNode> {
1424        self.assert_not_terminated()?;
1425        let identity = self.process_identity(requester_driver, pid)?;
1426        let (_, components, trailing_slash) = unix_socket_absolute_components(cwd, path)?;
1427        let target = resolve_unix_socket_components(
1428            self.raw_filesystem_mut(),
1429            &identity,
1430            components,
1431            true,
1432            trailing_slash,
1433        )?;
1434        self.filesystem
1435            .check_virtual_path(FsOperation::Write, &target.canonical_path)
1436            .map_err(KernelError::from)?;
1437        check_unix_dac(
1438            &identity,
1439            &target.stat,
1440            UNIX_DAC_WRITE,
1441            "connect",
1442            &target.canonical_path,
1443        )?;
1444        if target.stat.mode & 0o170000 != UNIX_SOCKET_FILE_TYPE {
1445            return Err(KernelError::new(
1446                "ECONNREFUSED",
1447                format!(
1448                    "Unix socket connect target is not a socket: {}",
1449                    target.canonical_path
1450                ),
1451            ));
1452        }
1453        Ok(target)
1454    }
1455
1456    /// Writes `content` at `offset` within an existing file, growing (and
1457    /// zero-filling) it as needed. This is the positional counterpart to
1458    /// [`Self::pread_file`]: it lets a descriptor-based caller (the shared WASI
1459    /// runner over the browser wire, which has no kernel fd offsets) write a
1460    /// region without the lossy, non-atomic read-modify-write it would
1461    /// otherwise have to do client-side. Enforcement matches `write_file`:
1462    /// read-only paths are rejected and the resulting file size is charged
1463    /// against the resource limits before the write.
1464    pub fn pwrite_file(
1465        &mut self,
1466        path: &str,
1467        offset: u64,
1468        content: impl Into<Vec<u8>>,
1469    ) -> KernelResult<()> {
1470        self.assert_not_terminated()?;
1471        self.reject_read_only_resolved_write_path(path)?;
1472        self.reject_unix_socket_data_path(path, "ENXIO")?;
1473        let content = content.into();
1474        let existing = self.storage_stat(path)?;
1475        let existing_size = existing.as_ref().map(|stat| stat.size).unwrap_or(0);
1476        let end = offset.saturating_add(content.len() as u64);
1477        self.check_write_file_limits_with_existing(
1478            path,
1479            existing.as_ref(),
1480            existing_size.max(end),
1481        )?;
1482        self.filesystem.pwrite(path, content, offset)?;
1483        self.update_filesystem_usage_cache_for_write(
1484            path,
1485            existing.as_ref(),
1486            existing_size.max(end),
1487        );
1488        Ok(())
1489    }
1490
1491    pub fn write_file_for_process(
1492        &mut self,
1493        requester_driver: &str,
1494        pid: u32,
1495        path: &str,
1496        content: impl Into<Vec<u8>>,
1497        mode: Option<u32>,
1498    ) -> KernelResult<()> {
1499        self.assert_not_terminated()?;
1500        self.assert_driver_owns(requester_driver, pid)?;
1501        let existed = self.exists_internal(Some(pid), path)?;
1502        if existed {
1503            self.check_dac_access(pid, path, DAC_WRITE)?;
1504        } else {
1505            self.check_dac_parent_access(pid, path, DAC_WRITE | DAC_EXECUTE)?;
1506        }
1507        let content = content.into();
1508        let new_size = content.len() as u64;
1509        self.reject_read_only_resolved_write_path(path)?;
1510        self.reject_unix_socket_data_path(path, "ENXIO")?;
1511        let existing = self.storage_stat(path)?;
1512        self.check_write_file_limits_with_existing(path, existing.as_ref(), new_size)?;
1513        VirtualFileSystem::write_file_with_mode(&mut self.filesystem, path, content, mode)
1514            .map_err(|error| {
1515                KernelError::new(
1516                    error.code(),
1517                    format!("create storage write for '{path}' failed: {error}"),
1518                )
1519            })?;
1520        self.update_filesystem_usage_cache_for_write(path, existing.as_ref(), new_size);
1521        if !existed {
1522            let umask = self.processes.get_umask(pid)?;
1523            self.apply_process_creation_metadata(pid, path, mode.unwrap_or(0o666), umask, false)
1524                .map_err(|error| {
1525                    KernelError::new(
1526                        error.code(),
1527                        format!("create metadata for '{path}' failed: {error}"),
1528                    )
1529                })?;
1530        } else {
1531            self.clear_setid_after_write(pid, path)?;
1532        }
1533        Ok(())
1534    }
1535
1536    pub fn create_dir(&mut self, path: &str) -> KernelResult<()> {
1537        self.assert_not_terminated()?;
1538        self.reject_read_only_entry_write_path(path)?;
1539        self.check_create_dir_limits(path)?;
1540        self.filesystem.create_dir(path)?;
1541        self.update_filesystem_usage_cache_for_inode_create(path, 0);
1542        Ok(())
1543    }
1544
1545    pub fn create_dir_for_process(
1546        &mut self,
1547        requester_driver: &str,
1548        pid: u32,
1549        path: &str,
1550        mode: Option<u32>,
1551    ) -> KernelResult<()> {
1552        self.assert_not_terminated()?;
1553        self.assert_driver_owns(requester_driver, pid)?;
1554        let existed = self.exists_internal(Some(pid), path)?;
1555        if !existed {
1556            self.check_dac_parent_access(pid, path, DAC_WRITE | DAC_EXECUTE)?;
1557        }
1558        self.reject_read_only_entry_write_path(path)?;
1559        self.check_create_dir_limits(path)?;
1560        VirtualFileSystem::create_dir_with_mode(&mut self.filesystem, path, mode)?;
1561        self.update_filesystem_usage_cache_for_inode_create(path, 0);
1562        if !existed {
1563            let umask = self.processes.get_umask(pid)?;
1564            self.apply_process_creation_metadata(pid, path, mode.unwrap_or(0o777), umask, true)?;
1565        }
1566        Ok(())
1567    }
1568
1569    pub fn mkdir(&mut self, path: &str, recursive: bool) -> KernelResult<()> {
1570        self.assert_not_terminated()?;
1571        self.reject_read_only_entry_write_path(path)?;
1572        let created_paths = self.missing_directory_paths(path, recursive)?;
1573        self.check_mkdir_limits(path, recursive)?;
1574        self.filesystem.mkdir(path, recursive)?;
1575        self.update_filesystem_usage_cache_for_inode_creates(path, created_paths.len());
1576        Ok(())
1577    }
1578
1579    pub fn mkdir_for_process(
1580        &mut self,
1581        requester_driver: &str,
1582        pid: u32,
1583        path: &str,
1584        recursive: bool,
1585        mode: Option<u32>,
1586    ) -> KernelResult<()> {
1587        self.assert_not_terminated()?;
1588        self.assert_driver_owns(requester_driver, pid)?;
1589        let created_paths = self.missing_directory_paths(path, recursive)?;
1590        if let Some(first_created) = created_paths.first() {
1591            self.check_dac_parent_access(pid, first_created, DAC_WRITE | DAC_EXECUTE)?;
1592        } else {
1593            self.check_dac_access(pid, path, DAC_EXECUTE)?;
1594        }
1595        self.reject_read_only_entry_write_path(path)?;
1596        self.check_mkdir_limits(path, recursive)?;
1597        VirtualFileSystem::mkdir_with_mode(&mut self.filesystem, path, recursive, mode)?;
1598        if !created_paths.is_empty() {
1599            let umask = self.processes.get_umask(pid)?;
1600            let mode = mode.unwrap_or(0o777);
1601            for created_path in &created_paths {
1602                self.apply_process_creation_metadata(pid, created_path, mode, umask, true)?;
1603            }
1604        }
1605        self.update_filesystem_usage_cache_for_inode_creates(path, created_paths.len());
1606        Ok(())
1607    }
1608
1609    pub fn mknod_for_process(
1610        &mut self,
1611        requester_driver: &str,
1612        pid: u32,
1613        path: &str,
1614        mode: u32,
1615        rdev: u64,
1616    ) -> KernelResult<()> {
1617        self.assert_not_terminated()?;
1618        self.assert_driver_owns(requester_driver, pid)?;
1619        if !matches!(mode & 0o170000, 0o010000 | 0o020000 | 0o060000) {
1620            return Err(KernelError::new(
1621                "EOPNOTSUPP",
1622                format!("unsupported special inode type for {path}"),
1623            ));
1624        }
1625        self.check_dac_parent_access(pid, path, DAC_WRITE | DAC_EXECUTE)?;
1626        self.reject_read_only_entry_write_path(path)?;
1627        self.check_create_dir_limits(path)?;
1628        self.filesystem.mknod(path, mode, rdev)?;
1629        self.update_filesystem_usage_cache_for_inode_create(path, 0);
1630        let umask = self.processes.get_umask(pid)?;
1631        self.apply_process_creation_metadata(pid, path, mode & 0o7777, umask, false)?;
1632        Ok(())
1633    }
1634
1635    pub fn umask(
1636        &self,
1637        requester_driver: &str,
1638        pid: u32,
1639        new_mask: Option<u32>,
1640    ) -> KernelResult<u32> {
1641        self.assert_driver_owns(requester_driver, pid)?;
1642        match new_mask {
1643            Some(mask) => Ok(self.processes.set_umask(pid, mask)?),
1644            None => Ok(self.processes.get_umask(pid)?),
1645        }
1646    }
1647
1648    pub fn exists(&self, path: &str) -> KernelResult<bool> {
1649        self.assert_not_terminated()?;
1650        self.exists_internal(None, path)
1651    }
1652
1653    pub fn exists_for_process(
1654        &mut self,
1655        requester_driver: &str,
1656        pid: u32,
1657        path: &str,
1658    ) -> KernelResult<bool> {
1659        self.assert_not_terminated()?;
1660        self.assert_driver_owns(requester_driver, pid)?;
1661        if let Err(error) = self.check_dac_traversal(pid, path) {
1662            if matches!(error.code(), "EACCES" | "ENOENT" | "ENOTDIR" | "ELOOP") {
1663                return Ok(false);
1664            }
1665            return Err(error);
1666        }
1667        self.exists_internal(Some(pid), path)
1668    }
1669
1670    pub fn stat(&mut self, path: &str) -> KernelResult<VirtualStat> {
1671        self.assert_not_terminated()?;
1672        self.stat_internal(None, path)
1673    }
1674
1675    pub fn stat_for_process(
1676        &mut self,
1677        requester_driver: &str,
1678        pid: u32,
1679        path: &str,
1680    ) -> KernelResult<VirtualStat> {
1681        self.assert_not_terminated()?;
1682        self.assert_driver_owns(requester_driver, pid)?;
1683        self.check_dac_traversal(pid, path)?;
1684        self.stat_internal(Some(pid), path)
1685    }
1686
1687    pub fn filesystem_stats_for_process(
1688        &mut self,
1689        requester_driver: &str,
1690        pid: u32,
1691        path: &str,
1692    ) -> KernelResult<FileSystemStats> {
1693        self.assert_not_terminated()?;
1694        self.assert_driver_owns(requester_driver, pid)?;
1695        self.check_dac_traversal(pid, path)?;
1696        self.stat_internal(Some(pid), path)?;
1697
1698        let max_bytes = self.resource_limits().max_filesystem_bytes;
1699        let max_inodes = self.resource_limits().max_inode_count;
1700        let filesystem = self.raw_filesystem_mut();
1701        let filesystem_any = filesystem as &mut dyn Any;
1702        if let Some(mount_table) = filesystem_any.downcast_mut::<MountTable>() {
1703            return mount_table
1704                .path_stats(path, max_bytes, max_inodes)
1705                .map_err(KernelError::from);
1706        }
1707
1708        let usage = measure_filesystem_usage(filesystem)?;
1709        let total_bytes = max_bytes
1710            .unwrap_or(usage.total_bytes)
1711            .max(usage.total_bytes);
1712        let total_inodes = max_inodes
1713            .map(|value| value as u64)
1714            .unwrap_or(usage.inode_count as u64)
1715            .max(usage.inode_count as u64);
1716        Ok(FileSystemStats {
1717            total_bytes,
1718            used_bytes: usage.total_bytes,
1719            available_bytes: total_bytes.saturating_sub(usage.total_bytes),
1720            total_inodes,
1721            free_inodes: total_inodes.saturating_sub(usage.inode_count as u64),
1722        })
1723    }
1724
1725    pub fn access_for_process(
1726        &mut self,
1727        requester_driver: &str,
1728        pid: u32,
1729        path: &str,
1730        access: u32,
1731        effective_ids: bool,
1732    ) -> KernelResult<()> {
1733        self.assert_driver_owns(requester_driver, pid)?;
1734        self.check_dac_traversal(pid, path)?;
1735        let mut identity = self.process_identity(requester_driver, pid)?;
1736        if !effective_ids {
1737            identity.euid = identity.uid;
1738            identity.egid = identity.gid;
1739        }
1740        let stat = self.filesystem.stat(path)?;
1741        self.check_dac_mode_with_acl(&identity, &stat, access & 0o7, path)
1742    }
1743
1744    pub fn lstat(&self, path: &str) -> KernelResult<VirtualStat> {
1745        self.assert_not_terminated()?;
1746        self.lstat_internal(None, path)
1747    }
1748
1749    pub fn lstat_for_process(
1750        &mut self,
1751        requester_driver: &str,
1752        pid: u32,
1753        path: &str,
1754    ) -> KernelResult<VirtualStat> {
1755        self.assert_not_terminated()?;
1756        self.assert_driver_owns(requester_driver, pid)?;
1757        self.check_dac_traversal(pid, path)?;
1758        self.lstat_internal(Some(pid), path)
1759    }
1760
1761    pub fn read_link(&self, path: &str) -> KernelResult<String> {
1762        self.assert_not_terminated()?;
1763        self.read_link_internal(None, path)
1764    }
1765
1766    pub fn read_link_for_process(
1767        &mut self,
1768        requester_driver: &str,
1769        pid: u32,
1770        path: &str,
1771    ) -> KernelResult<String> {
1772        self.assert_not_terminated()?;
1773        self.assert_driver_owns(requester_driver, pid)?;
1774        self.check_dac_traversal(pid, path)?;
1775        self.read_link_internal(Some(pid), path)
1776    }
1777
1778    pub fn read_dir(&mut self, path: &str) -> KernelResult<Vec<String>> {
1779        self.assert_not_terminated()?;
1780        let entries = self.read_dir_internal(None, path)?;
1781        self.resources.check_readdir_entries(entries.len())?;
1782        Ok(entries)
1783    }
1784
1785    pub fn read_dir_for_process(
1786        &mut self,
1787        requester_driver: &str,
1788        pid: u32,
1789        path: &str,
1790    ) -> KernelResult<Vec<String>> {
1791        self.assert_not_terminated()?;
1792        self.assert_driver_owns(requester_driver, pid)?;
1793        self.check_dac_access(pid, path, DAC_READ | DAC_EXECUTE)?;
1794        let mut entries = self.read_dir_internal(Some(pid), path)?;
1795        entries.retain(|entry| !is_internal_unnamed_file_name(entry));
1796        self.resources.check_readdir_entries(entries.len())?;
1797        Ok(entries)
1798    }
1799
1800    pub fn read_dir_with_types_for_process(
1801        &mut self,
1802        requester_driver: &str,
1803        pid: u32,
1804        path: &str,
1805    ) -> KernelResult<Vec<VirtualDirEntry>> {
1806        self.assert_not_terminated()?;
1807        self.assert_driver_owns(requester_driver, pid)?;
1808        self.check_dac_access(pid, path, DAC_READ | DAC_EXECUTE)?;
1809        let mut entries = self.read_dir_with_types_internal(Some(pid), path)?;
1810        entries.retain(|entry| !is_internal_unnamed_file_name(&entry.name));
1811        self.resources.check_readdir_entries(entries.len())?;
1812        Ok(entries)
1813    }
1814
1815    /// Lists a directory with each child's file type in one call. This is the
1816    /// typed counterpart to [`Self::read_dir`]: it lets a descriptor-based caller
1817    /// recover Dirent kinds (`readdir({ withFileTypes })`) without an extra
1818    /// `lstat` round-trip per entry. Reuses `read_dir_internal` so proc, the
1819    /// readdir-entry limit, and read-permission checks behave identically; the
1820    /// per-entry `lstat` is in-process (no wire hops).
1821    pub fn read_dir_with_types(&mut self, path: &str) -> KernelResult<Vec<VirtualDirEntry>> {
1822        self.assert_not_terminated()?;
1823        let names = self.read_dir_internal(None, path)?;
1824        self.resources.check_readdir_entries(names.len())?;
1825        let mut entries = Vec::with_capacity(names.len());
1826        for name in names {
1827            let child = normalize_path(&format!("{path}/{name}"));
1828            let stat = self.lstat_internal(None, &child)?;
1829            entries.push(VirtualDirEntry {
1830                name,
1831                is_directory: stat.is_directory,
1832                is_symbolic_link: stat.is_symbolic_link,
1833            });
1834        }
1835        Ok(entries)
1836    }
1837
1838    pub fn read_dir_recursive(
1839        &mut self,
1840        path: &str,
1841        max_depth: Option<usize>,
1842    ) -> KernelResult<Vec<RecursiveDirEntry>> {
1843        self.assert_not_terminated()?;
1844        let depth_limit = self.effective_recursive_fs_depth(max_depth)?;
1845        let caller_limited = max_depth.is_some();
1846        let mut entries = Vec::new();
1847        let mut queue = VecDeque::from([(normalize_path(path), 0usize)]);
1848
1849        while let Some((dir_path, depth)) = queue.pop_front() {
1850            self.resources.check_recursive_fs_depth(depth)?;
1851            let names = self.read_dir_internal(None, &dir_path)?;
1852            self.resources.check_readdir_entries(names.len())?;
1853
1854            for name in names {
1855                if matches!(name.as_str(), "." | "..") {
1856                    continue;
1857                }
1858                let child = join_child_path(&dir_path, &name);
1859                let stat = self.lstat_internal(None, &child)?;
1860                let entry = RecursiveDirEntry {
1861                    path: child.clone(),
1862                    is_directory: stat.is_directory,
1863                    is_symbolic_link: stat.is_symbolic_link,
1864                    size: stat.size,
1865                };
1866                entries.push(entry);
1867                self.resources.check_recursive_fs_entries(entries.len())?;
1868
1869                if stat.is_directory && !stat.is_symbolic_link {
1870                    let child_depth = depth.saturating_add(1);
1871                    if child_depth <= depth_limit {
1872                        queue.push_back((child, child_depth));
1873                    } else if !caller_limited {
1874                        self.resources.check_recursive_fs_depth(child_depth)?;
1875                    }
1876                }
1877            }
1878        }
1879
1880        Ok(entries)
1881    }
1882
1883    pub fn copy_path(&mut self, from: &str, to: &str, recursive: bool) -> KernelResult<()> {
1884        self.assert_not_terminated()?;
1885        let mut entries = 0usize;
1886        self.copy_path_inner(from, to, recursive, 0, &mut entries)?;
1887        Ok(())
1888    }
1889
1890    pub fn remove_path(&mut self, path: &str, recursive: bool) -> KernelResult<()> {
1891        self.assert_not_terminated()?;
1892        let mut entries = 0usize;
1893        self.remove_path_inner(path, recursive, 0, &mut entries)
1894    }
1895
1896    pub fn move_path(&mut self, from: &str, to: &str) -> KernelResult<()> {
1897        self.assert_not_terminated()?;
1898        match self.rename(from, to) {
1899            Ok(()) => Ok(()),
1900            Err(error) if error.code() == "EXDEV" => {
1901                self.copy_path(from, to, true)?;
1902                self.remove_path(from, true)
1903            }
1904            Err(error) => Err(error),
1905        }
1906    }
1907
1908    pub fn remove_file(&mut self, path: &str) -> KernelResult<()> {
1909        self.assert_not_terminated()?;
1910        self.reject_read_only_entry_write_path(path)?;
1911        let removed = self.storage_lstat(path)?;
1912        let detached = self.prepare_anonymous_file_backing(path, removed.as_ref())?;
1913        self.filesystem.remove_file(path)?;
1914        match detached {
1915            Some(OpenFileRemovalBacking::Anonymous {
1916                descriptions,
1917                backing,
1918            }) => {
1919                for description in descriptions {
1920                    description.detach_path(path, Arc::clone(&backing));
1921                }
1922            }
1923            Some(OpenFileRemovalBacking::LinkedAlias {
1924                descriptions,
1925                live_path,
1926            }) => {
1927                for description in descriptions {
1928                    description.rebind_deleted_path(path, &live_path);
1929                }
1930            }
1931            None => {}
1932        }
1933        self.update_filesystem_usage_cache_for_remove(path, removed.as_ref());
1934        Ok(())
1935    }
1936
1937    pub fn remove_file_for_process(
1938        &mut self,
1939        requester_driver: &str,
1940        pid: u32,
1941        path: &str,
1942    ) -> KernelResult<()> {
1943        self.assert_driver_owns(requester_driver, pid)?;
1944        self.check_dac_parent_access(pid, path, DAC_WRITE | DAC_EXECUTE)?;
1945        self.check_sticky_directory_removal(pid, path)?;
1946        self.remove_file(path)
1947    }
1948
1949    pub fn remove_dir(&mut self, path: &str) -> KernelResult<()> {
1950        self.assert_not_terminated()?;
1951        self.reject_read_only_entry_write_path(path)?;
1952        let removed = self.storage_lstat(path)?;
1953        let detached = self.prepare_detached_directory_backing(path, removed.as_ref());
1954        self.filesystem.remove_dir(path)?;
1955        if let Some((descriptions, stat)) = detached {
1956            for description in descriptions {
1957                description.detach_directory(path, stat.clone());
1958            }
1959        }
1960        if removed.as_ref().is_some_and(|stat| stat.is_directory) {
1961            self.update_filesystem_usage_cache_for_inode_delete(path, 0);
1962        }
1963        Ok(())
1964    }
1965
1966    pub fn remove_dir_for_process(
1967        &mut self,
1968        requester_driver: &str,
1969        pid: u32,
1970        path: &str,
1971    ) -> KernelResult<()> {
1972        self.assert_driver_owns(requester_driver, pid)?;
1973        self.check_dac_parent_access(pid, path, DAC_WRITE | DAC_EXECUTE)?;
1974        self.check_sticky_directory_removal(pid, path)?;
1975        self.remove_dir(path)
1976    }
1977
1978    pub fn rename(&mut self, old_path: &str, new_path: &str) -> KernelResult<()> {
1979        self.rename_at2(old_path, new_path, 0)
1980    }
1981
1982    pub fn rename_at2(&mut self, old_path: &str, new_path: &str, flags: u32) -> KernelResult<()> {
1983        self.assert_not_terminated()?;
1984        self.reject_read_only_entry_write_path(old_path)?;
1985        self.reject_read_only_entry_write_path(new_path)?;
1986        self.check_rename_copy_up_limits(old_path, new_path)?;
1987        let replaced = self.storage_lstat(new_path)?;
1988        let detached_destination =
1989            self.prepare_anonymous_file_backing(new_path, replaced.as_ref())?;
1990        let detached_directory_destination =
1991            self.prepare_detached_directory_backing(new_path, replaced.as_ref());
1992        self.filesystem.rename_at2(old_path, new_path, flags)?;
1993        if flags == RENAME_EXCHANGE {
1994            let temporary = format!(
1995                "/.agentos-open-rename-exchange-{}",
1996                self.next_unnamed_file_id
1997            );
1998            self.next_unnamed_file_id = self.next_unnamed_file_id.saturating_add(1);
1999            self.rename_open_file_descriptions(old_path, &temporary);
2000            self.rename_open_file_descriptions(new_path, old_path);
2001            self.rename_open_file_descriptions(&temporary, new_path);
2002            self.invalidate_filesystem_usage_cache();
2003            return Ok(());
2004        }
2005        match detached_destination {
2006            Some(OpenFileRemovalBacking::Anonymous {
2007                descriptions,
2008                backing,
2009            }) => {
2010                for description in descriptions {
2011                    description.detach_path(new_path, Arc::clone(&backing));
2012                }
2013            }
2014            Some(OpenFileRemovalBacking::LinkedAlias {
2015                descriptions,
2016                live_path,
2017            }) => {
2018                for description in descriptions {
2019                    description.rebind_deleted_path(new_path, &live_path);
2020                }
2021            }
2022            None => {}
2023        }
2024        if let Some((descriptions, stat)) = detached_directory_destination {
2025            for description in descriptions {
2026                description.detach_directory(new_path, stat.clone());
2027            }
2028        }
2029        self.rename_open_file_descriptions(old_path, new_path);
2030        // Rename can be a pure metadata move, a destination replacement, or an
2031        // overlay copy-up/removal with hard-link aliasing. Drop the cached root
2032        // usage because the local byte/inode delta is not knowable here.
2033        self.invalidate_filesystem_usage_cache();
2034        Ok(())
2035    }
2036
2037    pub fn rename_for_process(
2038        &mut self,
2039        requester_driver: &str,
2040        pid: u32,
2041        old_path: &str,
2042        new_path: &str,
2043    ) -> KernelResult<()> {
2044        self.rename_at2_for_process(requester_driver, pid, old_path, new_path, 0)
2045    }
2046
2047    pub fn rename_at2_for_process(
2048        &mut self,
2049        requester_driver: &str,
2050        pid: u32,
2051        old_path: &str,
2052        new_path: &str,
2053        flags: u32,
2054    ) -> KernelResult<()> {
2055        self.assert_driver_owns(requester_driver, pid)?;
2056        self.check_dac_parent_access(pid, old_path, DAC_WRITE | DAC_EXECUTE)?;
2057        self.check_dac_parent_access(pid, new_path, DAC_WRITE | DAC_EXECUTE)?;
2058        self.check_sticky_directory_removal(pid, old_path)?;
2059        if self.exists_internal(Some(pid), new_path)? {
2060            self.check_sticky_directory_removal(pid, new_path)?;
2061        }
2062        self.rename_at2(old_path, new_path, flags)
2063    }
2064
2065    pub fn realpath(&self, path: &str) -> KernelResult<String> {
2066        self.assert_not_terminated()?;
2067        self.realpath_internal(None, path)
2068    }
2069
2070    pub fn realpath_for_process(
2071        &mut self,
2072        requester_driver: &str,
2073        pid: u32,
2074        path: &str,
2075    ) -> KernelResult<String> {
2076        self.assert_not_terminated()?;
2077        self.assert_driver_owns(requester_driver, pid)?;
2078        self.check_dac_traversal(pid, path)?;
2079        self.realpath_internal(Some(pid), path)
2080    }
2081
2082    pub fn symlink(&mut self, target: &str, link_path: &str) -> KernelResult<()> {
2083        self.assert_not_terminated()?;
2084        if is_proc_path(target) {
2085            self.filesystem
2086                .check_virtual_path(FsOperation::Write, link_path)
2087                .map_err(KernelError::from)?;
2088            return Err(read_only_filesystem_error(link_path));
2089        }
2090        self.reject_read_only_entry_write_path(link_path)?;
2091        self.check_symlink_limits(target, link_path)?;
2092        self.filesystem.symlink(target, link_path)?;
2093        self.update_filesystem_usage_cache_for_inode_create(link_path, target.len() as u64);
2094        Ok(())
2095    }
2096
2097    pub fn symlink_for_process(
2098        &mut self,
2099        requester_driver: &str,
2100        pid: u32,
2101        target: &str,
2102        link_path: &str,
2103    ) -> KernelResult<()> {
2104        self.assert_driver_owns(requester_driver, pid)?;
2105        self.check_dac_parent_access(pid, link_path, DAC_WRITE | DAC_EXECUTE)?;
2106        self.symlink(target, link_path)
2107    }
2108
2109    pub fn chmod(&mut self, path: &str, mode: u32) -> KernelResult<()> {
2110        self.assert_not_terminated()?;
2111        self.reject_read_only_resolved_write_path(path)?;
2112        self.filesystem.chmod(path, mode)?;
2113        self.sync_access_acl_mode(path, mode)?;
2114        self.no_posix_acl_cache.clear();
2115        Ok(())
2116    }
2117
2118    pub fn chmod_for_process(
2119        &mut self,
2120        requester_driver: &str,
2121        pid: u32,
2122        path: &str,
2123        mut mode: u32,
2124    ) -> KernelResult<()> {
2125        let identity = self.process_identity(requester_driver, pid)?;
2126        self.check_dac_traversal(pid, path)?;
2127        let stat = self.filesystem.stat(path)?;
2128        if identity.euid != 0 && identity.euid != stat.uid {
2129            return Err(KernelError::new(
2130                "EPERM",
2131                format!("chmod requires ownership of {path}"),
2132            ));
2133        }
2134        if identity.euid != 0
2135            && identity.egid != stat.gid
2136            && !identity.supplementary_gids.contains(&stat.gid)
2137        {
2138            mode &= !0o2000;
2139        }
2140        self.chmod(path, mode)
2141    }
2142
2143    pub fn link(&mut self, old_path: &str, new_path: &str) -> KernelResult<()> {
2144        self.assert_not_terminated()?;
2145        if is_proc_path(old_path) {
2146            self.filesystem
2147                .check_virtual_path(FsOperation::Write, new_path)
2148                .map_err(KernelError::from)?;
2149            return Err(read_only_filesystem_error(new_path));
2150        }
2151        self.reject_read_only_resolved_write_path(old_path)?;
2152        self.reject_read_only_entry_write_path(new_path)?;
2153        self.filesystem.link(old_path, new_path)?;
2154        // Hard-link creation makes another directory entry for an already
2155        // reachable inode, so measured root usage is unchanged.
2156        Ok(())
2157    }
2158
2159    pub fn link_for_process(
2160        &mut self,
2161        requester_driver: &str,
2162        pid: u32,
2163        old_path: &str,
2164        new_path: &str,
2165    ) -> KernelResult<()> {
2166        self.assert_driver_owns(requester_driver, pid)?;
2167        self.check_dac_traversal(pid, old_path)?;
2168        self.check_dac_parent_access(pid, new_path, DAC_WRITE | DAC_EXECUTE)?;
2169        self.link(old_path, new_path)
2170    }
2171
2172    pub fn chown(&mut self, path: &str, uid: u32, gid: u32) -> KernelResult<()> {
2173        self.assert_not_terminated()?;
2174        self.reject_read_only_resolved_write_path(path)?;
2175        self.filesystem.chown(path, uid, gid)?;
2176        self.no_posix_acl_cache.clear();
2177        Ok(())
2178    }
2179
2180    pub fn chown_for_process(
2181        &mut self,
2182        requester_driver: &str,
2183        pid: u32,
2184        path: &str,
2185        uid: u32,
2186        gid: u32,
2187        follow_symlinks: bool,
2188    ) -> KernelResult<()> {
2189        self.assert_not_terminated()?;
2190        let identity = self.process_identity(requester_driver, pid)?;
2191        let stat = if follow_symlinks {
2192            self.stat_for_process(requester_driver, pid, path)?
2193        } else {
2194            self.lstat_for_process(requester_driver, pid, path)?
2195        };
2196        let (next_uid, next_gid) = validate_chown_request(&identity, &stat, uid, gid, path)?;
2197
2198        if follow_symlinks {
2199            self.reject_read_only_resolved_write_path(path)?;
2200        } else {
2201            self.reject_read_only_entry_write_path(path)?;
2202        }
2203        self.filesystem
2204            .chown_spec(path, next_uid, next_gid, follow_symlinks)?;
2205        if let Some(mode) = linux_chown_cleared_mode(&stat) {
2206            self.filesystem.chmod(path, mode)?;
2207        }
2208        Ok(())
2209    }
2210
2211    pub fn lchown_for_process(
2212        &mut self,
2213        requester_driver: &str,
2214        pid: u32,
2215        path: &str,
2216        uid: u32,
2217        gid: u32,
2218    ) -> KernelResult<()> {
2219        let identity = self.process_identity(requester_driver, pid)?;
2220        self.check_dac_traversal(pid, path)?;
2221        let stat = self.filesystem.lstat(path)?;
2222        if identity.euid != 0 {
2223            let owns_file = identity.euid == stat.uid;
2224            let keeps_owner = uid == stat.uid;
2225            let allowed_group = gid == identity.egid || identity.supplementary_gids.contains(&gid);
2226            if !owns_file || !keeps_owner || !allowed_group {
2227                return Err(KernelError::new(
2228                    "EPERM",
2229                    format!("lchown is not permitted for {path}"),
2230                ));
2231            }
2232        }
2233        self.reject_read_only_entry_write_path(path)?;
2234        self.filesystem.lchown(path, uid, gid)?;
2235        Ok(())
2236    }
2237
2238    pub fn get_xattr(
2239        &mut self,
2240        path: &str,
2241        name: &str,
2242        follow_symlinks: bool,
2243    ) -> KernelResult<Vec<u8>> {
2244        self.assert_not_terminated()?;
2245        Ok(self.filesystem.get_xattr(path, name, follow_symlinks)?)
2246    }
2247
2248    pub fn get_xattr_for_process(
2249        &mut self,
2250        requester_driver: &str,
2251        pid: u32,
2252        path: &str,
2253        name: &str,
2254        follow_symlinks: bool,
2255    ) -> KernelResult<Vec<u8>> {
2256        let identity = self.process_identity(requester_driver, pid)?;
2257        self.check_dac_traversal(pid, path)?;
2258        check_xattr_namespace(&identity, name, false, path)?;
2259        let stat = if follow_symlinks {
2260            self.filesystem.stat(path)?
2261        } else {
2262            self.filesystem.lstat(path)?
2263        };
2264        self.check_dac_mode_with_acl(&identity, &stat, DAC_READ, path)?;
2265        self.get_xattr(path, name, follow_symlinks)
2266    }
2267
2268    pub fn list_xattrs(&mut self, path: &str, follow_symlinks: bool) -> KernelResult<Vec<String>> {
2269        self.assert_not_terminated()?;
2270        Ok(self.filesystem.list_xattrs(path, follow_symlinks)?)
2271    }
2272
2273    pub fn list_xattrs_for_process(
2274        &mut self,
2275        requester_driver: &str,
2276        pid: u32,
2277        path: &str,
2278        follow_symlinks: bool,
2279    ) -> KernelResult<Vec<String>> {
2280        let identity = self.process_identity(requester_driver, pid)?;
2281        self.check_dac_traversal(pid, path)?;
2282        let stat = if follow_symlinks {
2283            self.filesystem.stat(path)?
2284        } else {
2285            self.filesystem.lstat(path)?
2286        };
2287        self.check_dac_mode_with_acl(&identity, &stat, DAC_READ, path)?;
2288        let mut names = self.list_xattrs(path, follow_symlinks)?;
2289        if identity.euid != 0 {
2290            names.retain(|name| !name.starts_with("trusted.") && !name.starts_with("security."));
2291        }
2292        Ok(names)
2293    }
2294
2295    pub fn set_xattr(
2296        &mut self,
2297        path: &str,
2298        name: &str,
2299        value: Vec<u8>,
2300        flags: u32,
2301        follow_symlinks: bool,
2302    ) -> KernelResult<()> {
2303        self.assert_not_terminated()?;
2304        if follow_symlinks {
2305            self.reject_read_only_resolved_write_path(path)?;
2306        } else {
2307            self.reject_read_only_entry_write_path(path)?;
2308        }
2309        self.filesystem
2310            .set_xattr(path, name, value, flags, follow_symlinks)?;
2311        self.no_posix_acl_cache.clear();
2312        Ok(())
2313    }
2314
2315    #[allow(clippy::too_many_arguments)]
2316    pub fn set_xattr_for_process(
2317        &mut self,
2318        requester_driver: &str,
2319        pid: u32,
2320        path: &str,
2321        name: &str,
2322        value: Vec<u8>,
2323        flags: u32,
2324        follow_symlinks: bool,
2325    ) -> KernelResult<()> {
2326        let identity = self.process_identity(requester_driver, pid)?;
2327        self.check_dac_traversal(pid, path)?;
2328        check_xattr_namespace(&identity, name, true, path)?;
2329        let stat = if follow_symlinks {
2330            self.filesystem.stat(path)?
2331        } else {
2332            self.filesystem.lstat(path)?
2333        };
2334        check_xattr_inode_write_policy(&stat, name, path)?;
2335        if name.starts_with("system.posix_acl_") {
2336            if identity.euid != 0 && identity.euid != stat.uid {
2337                return Err(KernelError::new(
2338                    "EPERM",
2339                    format!("setting {name} requires ownership of {path}"),
2340                ));
2341            }
2342        } else {
2343            self.check_dac_mode_with_acl(&identity, &stat, DAC_WRITE, path)?;
2344        }
2345        let acl = if name == POSIX_ACL_ACCESS || name == POSIX_ACL_DEFAULT {
2346            let acl = PosixAcl::parse(&value, path)?;
2347            if name == POSIX_ACL_DEFAULT && !stat.is_directory {
2348                return Err(KernelError::new(
2349                    "EACCES",
2350                    format!("default ACL requires a directory: {path}"),
2351                ));
2352            }
2353            Some(acl)
2354        } else {
2355            None
2356        };
2357        self.set_xattr(path, name, value, flags, follow_symlinks)?;
2358        if name == POSIX_ACL_ACCESS {
2359            let mode = acl.expect("access ACL was parsed").mode(stat.mode);
2360            self.filesystem.chmod(path, mode)?;
2361        }
2362        Ok(())
2363    }
2364
2365    pub fn remove_xattr(
2366        &mut self,
2367        path: &str,
2368        name: &str,
2369        follow_symlinks: bool,
2370    ) -> KernelResult<()> {
2371        self.assert_not_terminated()?;
2372        if follow_symlinks {
2373            self.reject_read_only_resolved_write_path(path)?;
2374        } else {
2375            self.reject_read_only_entry_write_path(path)?;
2376        }
2377        self.filesystem.remove_xattr(path, name, follow_symlinks)?;
2378        self.no_posix_acl_cache.clear();
2379        Ok(())
2380    }
2381
2382    pub fn remove_xattr_for_process(
2383        &mut self,
2384        requester_driver: &str,
2385        pid: u32,
2386        path: &str,
2387        name: &str,
2388        follow_symlinks: bool,
2389    ) -> KernelResult<()> {
2390        let identity = self.process_identity(requester_driver, pid)?;
2391        self.check_dac_traversal(pid, path)?;
2392        check_xattr_namespace(&identity, name, true, path)?;
2393        let stat = if follow_symlinks {
2394            self.filesystem.stat(path)?
2395        } else {
2396            self.filesystem.lstat(path)?
2397        };
2398        check_xattr_inode_write_policy(&stat, name, path)?;
2399        if name.starts_with("system.posix_acl_") {
2400            if identity.euid != 0 && identity.euid != stat.uid {
2401                return Err(KernelError::new(
2402                    "EPERM",
2403                    format!("removing {name} requires ownership of {path}"),
2404                ));
2405            }
2406        } else {
2407            self.check_dac_mode_with_acl(&identity, &stat, DAC_WRITE, path)?;
2408        }
2409        self.remove_xattr(path, name, follow_symlinks)
2410    }
2411
2412    pub fn utimes(&mut self, path: &str, atime_ms: u64, mtime_ms: u64) -> KernelResult<()> {
2413        self.utimes_spec(
2414            path,
2415            VirtualUtimeSpec::Set(VirtualTimeSpec::from_millis(atime_ms)),
2416            VirtualUtimeSpec::Set(VirtualTimeSpec::from_millis(mtime_ms)),
2417        )
2418    }
2419
2420    pub fn utimes_spec(
2421        &mut self,
2422        path: &str,
2423        atime: VirtualUtimeSpec,
2424        mtime: VirtualUtimeSpec,
2425    ) -> KernelResult<()> {
2426        self.assert_not_terminated()?;
2427        self.reject_read_only_resolved_write_path(path)?;
2428        Ok(self.filesystem.utimes_spec(path, atime, mtime, true)?)
2429    }
2430
2431    pub fn utimes_spec_for_process(
2432        &mut self,
2433        requester_driver: &str,
2434        pid: u32,
2435        path: &str,
2436        atime: VirtualUtimeSpec,
2437        mtime: VirtualUtimeSpec,
2438        follow_symlinks: bool,
2439    ) -> KernelResult<()> {
2440        let identity = self.process_identity(requester_driver, pid)?;
2441        self.check_dac_traversal(pid, path)?;
2442        let stat = if follow_symlinks {
2443            self.filesystem.stat(path)?
2444        } else {
2445            self.filesystem.lstat(path)?
2446        };
2447        let owns_file = identity.euid == 0 || identity.euid == stat.uid;
2448        let sets_both_to_now =
2449            matches!(atime, VirtualUtimeSpec::Now) && matches!(mtime, VirtualUtimeSpec::Now);
2450        let omits_both =
2451            matches!(atime, VirtualUtimeSpec::Omit) && matches!(mtime, VirtualUtimeSpec::Omit);
2452        if !owns_file && sets_both_to_now {
2453            self.check_dac_mode_with_acl(&identity, &stat, DAC_WRITE, path)?;
2454        } else if !owns_file && !omits_both {
2455            return Err(KernelError::new(
2456                "EPERM",
2457                format!("changing timestamps requires ownership of {path}"),
2458            ));
2459        }
2460        if follow_symlinks {
2461            self.utimes_spec(path, atime, mtime)
2462        } else {
2463            self.lutimes(path, atime, mtime)
2464        }
2465    }
2466
2467    pub fn lutimes(
2468        &mut self,
2469        path: &str,
2470        atime: VirtualUtimeSpec,
2471        mtime: VirtualUtimeSpec,
2472    ) -> KernelResult<()> {
2473        self.assert_not_terminated()?;
2474        self.reject_read_only_entry_write_path(path)?;
2475        Ok(self.filesystem.utimes_spec(path, atime, mtime, false)?)
2476    }
2477
2478    pub fn futimes(
2479        &mut self,
2480        requester_driver: &str,
2481        pid: u32,
2482        fd: u32,
2483        atime: VirtualUtimeSpec,
2484        mtime: VirtualUtimeSpec,
2485    ) -> KernelResult<()> {
2486        self.assert_not_terminated()?;
2487        let path = self
2488            .description_for_fd(requester_driver, pid, fd)?
2489            .path()
2490            .to_owned();
2491        self.reject_read_only_resolved_write_path(&path)?;
2492        Ok(self.filesystem.utimes_spec(&path, atime, mtime, true)?)
2493    }
2494
2495    pub fn truncate(&mut self, path: &str, length: u64) -> KernelResult<()> {
2496        self.assert_not_terminated()?;
2497        self.reject_read_only_resolved_write_path(path)?;
2498        self.reject_unix_socket_data_path(path, "EINVAL")?;
2499        let existing = self.storage_stat(path)?;
2500        self.check_truncate_limits_with_existing(path, existing.as_ref(), length)?;
2501        self.filesystem.truncate(path, length)?;
2502        self.update_filesystem_usage_cache_for_write(path, existing.as_ref(), length);
2503        Ok(())
2504    }
2505
2506    pub fn truncate_for_process(
2507        &mut self,
2508        requester_driver: &str,
2509        pid: u32,
2510        path: &str,
2511        length: u64,
2512    ) -> KernelResult<()> {
2513        self.assert_driver_owns(requester_driver, pid)?;
2514        self.check_dac_access(pid, path, DAC_WRITE)?;
2515        self.truncate(path, length)?;
2516        self.clear_setid_after_write(pid, path)
2517    }
2518
2519    pub fn fd_truncate(
2520        &mut self,
2521        requester_driver: &str,
2522        pid: u32,
2523        fd: u32,
2524        length: u64,
2525    ) -> KernelResult<()> {
2526        self.assert_driver_owns(requester_driver, pid)?;
2527        let entry = {
2528            let tables = lock_or_recover(&self.fd_tables);
2529            tables
2530                .get(pid)
2531                .and_then(|table| table.get(fd))
2532                .cloned()
2533                .ok_or_else(|| KernelError::bad_file_descriptor(fd))?
2534        };
2535        if let Some(stat) = entry.description.anonymous_stat() {
2536            self.check_path_resize_limits_with_existing(stat.size, length)?;
2537            entry
2538                .description
2539                .anonymous_truncate(length)
2540                .expect("anonymous stat and backing must agree")?;
2541            return Ok(());
2542        }
2543        if entry.description.flags() & 0b11 == crate::fd_table::O_RDONLY {
2544            return Err(KernelError::bad_file_descriptor(fd));
2545        }
2546        let path = entry.description.path().to_owned();
2547        self.truncate(&path, length)?;
2548        self.clear_setid_after_write(pid, &path)
2549    }
2550
2551    pub fn fd_allocate(
2552        &mut self,
2553        requester_driver: &str,
2554        pid: u32,
2555        fd: u32,
2556        offset: u64,
2557        length: u64,
2558    ) -> KernelResult<()> {
2559        self.assert_driver_owns(requester_driver, pid)?;
2560        let entry = {
2561            let tables = lock_or_recover(&self.fd_tables);
2562            tables
2563                .get(pid)
2564                .and_then(|table| table.get(fd))
2565                .cloned()
2566                .ok_or_else(|| KernelError::bad_file_descriptor(fd))?
2567        };
2568        if entry.description.flags() & 0b11 == crate::fd_table::O_RDONLY {
2569            return Err(KernelError::bad_file_descriptor(fd));
2570        }
2571        let end = offset
2572            .checked_add(length)
2573            .ok_or_else(|| KernelError::new("EINVAL", "allocation range overflows"))?;
2574        let path = entry.description.path().to_owned();
2575        self.reject_read_only_resolved_write_path(&path)?;
2576        if length == 0 {
2577            return Ok(());
2578        }
2579        let existing = self.storage_stat(&path)?;
2580        let new_size = existing.as_ref().map_or(end, |stat| stat.size.max(end));
2581        self.check_truncate_limits_with_existing(&path, existing.as_ref(), new_size)?;
2582        self.filesystem.allocate(&path, offset, length)?;
2583        self.update_filesystem_usage_cache_for_write(&path, existing.as_ref(), new_size);
2584        self.clear_setid_after_write(pid, &path)
2585    }
2586
2587    pub fn fd_punch_hole(
2588        &mut self,
2589        requester_driver: &str,
2590        pid: u32,
2591        fd: u32,
2592        offset: u64,
2593        length: u64,
2594    ) -> KernelResult<()> {
2595        self.assert_driver_owns(requester_driver, pid)?;
2596        let entry = {
2597            let tables = lock_or_recover(&self.fd_tables);
2598            tables
2599                .get(pid)
2600                .and_then(|table| table.get(fd))
2601                .cloned()
2602                .ok_or_else(|| KernelError::bad_file_descriptor(fd))?
2603        };
2604        if entry.description.flags() & 0b11 == crate::fd_table::O_RDONLY {
2605            return Err(KernelError::bad_file_descriptor(fd));
2606        }
2607        offset
2608            .checked_add(length)
2609            .ok_or_else(|| KernelError::new("EINVAL", "hole-punch range overflows"))?;
2610        let path = entry.description.path().to_owned();
2611        self.reject_read_only_resolved_write_path(&path)?;
2612        self.filesystem.punch_hole(&path, offset, length)?;
2613        self.clear_setid_after_write(pid, &path)
2614    }
2615
2616    pub fn fd_zero_range(
2617        &mut self,
2618        requester_driver: &str,
2619        pid: u32,
2620        fd: u32,
2621        offset: u64,
2622        length: u64,
2623        keep_size: bool,
2624    ) -> KernelResult<()> {
2625        self.assert_driver_owns(requester_driver, pid)?;
2626        let entry = {
2627            let tables = lock_or_recover(&self.fd_tables);
2628            tables
2629                .get(pid)
2630                .and_then(|table| table.get(fd))
2631                .cloned()
2632                .ok_or_else(|| KernelError::bad_file_descriptor(fd))?
2633        };
2634        if entry.description.flags() & 0b11 == crate::fd_table::O_RDONLY {
2635            return Err(KernelError::bad_file_descriptor(fd));
2636        }
2637        let end = offset
2638            .checked_add(length)
2639            .ok_or_else(|| KernelError::new("EINVAL", "zero range overflows"))?;
2640        let path = entry.description.path().to_owned();
2641        self.reject_read_only_resolved_write_path(&path)?;
2642        let existing = self.storage_stat(&path)?;
2643        let old_size = existing
2644            .as_ref()
2645            .ok_or_else(|| KernelError::new("ENOENT", format!("no such file: {path}")))?
2646            .size;
2647        let new_size = if keep_size {
2648            old_size
2649        } else {
2650            old_size.max(end)
2651        };
2652        self.check_truncate_limits_with_existing(&path, existing.as_ref(), new_size)?;
2653        self.filesystem
2654            .zero_range(&path, offset, length, keep_size)?;
2655        self.update_filesystem_usage_cache_for_write(&path, existing.as_ref(), new_size);
2656        self.clear_setid_after_write(pid, &path)
2657    }
2658
2659    pub fn fd_insert_range(
2660        &mut self,
2661        requester_driver: &str,
2662        pid: u32,
2663        fd: u32,
2664        offset: u64,
2665        length: u64,
2666    ) -> KernelResult<()> {
2667        self.assert_driver_owns(requester_driver, pid)?;
2668        let entry = {
2669            let tables = lock_or_recover(&self.fd_tables);
2670            tables
2671                .get(pid)
2672                .and_then(|table| table.get(fd))
2673                .cloned()
2674                .ok_or_else(|| KernelError::bad_file_descriptor(fd))?
2675        };
2676        if entry.description.flags() & 0b11 == crate::fd_table::O_RDONLY {
2677            return Err(KernelError::bad_file_descriptor(fd));
2678        }
2679        let path = entry.description.path().to_owned();
2680        self.reject_read_only_resolved_write_path(&path)?;
2681        let existing = self.storage_stat(&path)?;
2682        let old_size = existing
2683            .as_ref()
2684            .ok_or_else(|| KernelError::new("ENOENT", format!("no such file: {path}")))?
2685            .size;
2686        let new_size = old_size
2687            .checked_add(length)
2688            .ok_or_else(|| KernelError::new("EINVAL", "insert range size overflows"))?;
2689        self.check_truncate_limits_with_existing(&path, existing.as_ref(), new_size)?;
2690        self.filesystem.insert_range(&path, offset, length)?;
2691        self.update_filesystem_usage_cache_for_write(&path, existing.as_ref(), new_size);
2692        self.clear_setid_after_write(pid, &path)
2693    }
2694
2695    pub fn fd_collapse_range(
2696        &mut self,
2697        requester_driver: &str,
2698        pid: u32,
2699        fd: u32,
2700        offset: u64,
2701        length: u64,
2702    ) -> KernelResult<()> {
2703        self.assert_driver_owns(requester_driver, pid)?;
2704        let entry = {
2705            let tables = lock_or_recover(&self.fd_tables);
2706            tables
2707                .get(pid)
2708                .and_then(|table| table.get(fd))
2709                .cloned()
2710                .ok_or_else(|| KernelError::bad_file_descriptor(fd))?
2711        };
2712        if entry.description.flags() & 0b11 == crate::fd_table::O_RDONLY {
2713            return Err(KernelError::bad_file_descriptor(fd));
2714        }
2715        let path = entry.description.path().to_owned();
2716        self.reject_read_only_resolved_write_path(&path)?;
2717        let existing = self.storage_stat(&path)?;
2718        let old_size = existing
2719            .as_ref()
2720            .ok_or_else(|| KernelError::new("ENOENT", format!("no such file: {path}")))?
2721            .size;
2722        self.filesystem.collapse_range(&path, offset, length)?;
2723        let new_size = old_size.saturating_sub(length);
2724        self.update_filesystem_usage_cache_for_write(&path, existing.as_ref(), new_size);
2725        self.clear_setid_after_write(pid, &path)
2726    }
2727
2728    pub fn fd_allocated_ranges(
2729        &mut self,
2730        requester_driver: &str,
2731        pid: u32,
2732        fd: u32,
2733    ) -> KernelResult<Vec<(u64, u64)>> {
2734        self.assert_driver_owns(requester_driver, pid)?;
2735        let path = {
2736            let tables = lock_or_recover(&self.fd_tables);
2737            tables
2738                .get(pid)
2739                .and_then(|table| table.get(fd))
2740                .map(|entry| entry.description.path().to_owned())
2741                .ok_or_else(|| KernelError::bad_file_descriptor(fd))?
2742        };
2743        Ok(self.filesystem.allocated_ranges(&path)?)
2744    }
2745
2746    pub fn fd_unwritten_ranges(
2747        &mut self,
2748        requester_driver: &str,
2749        pid: u32,
2750        fd: u32,
2751    ) -> KernelResult<Vec<(u64, u64)>> {
2752        self.assert_driver_owns(requester_driver, pid)?;
2753        let path = {
2754            let tables = lock_or_recover(&self.fd_tables);
2755            tables
2756                .get(pid)
2757                .and_then(|table| table.get(fd))
2758                .map(|entry| entry.description.path().to_owned())
2759                .ok_or_else(|| KernelError::bad_file_descriptor(fd))?
2760        };
2761        Ok(self.filesystem.unwritten_ranges(&path)?)
2762    }
2763
2764    pub fn check_execute_for_process(
2765        &mut self,
2766        requester_driver: &str,
2767        pid: u32,
2768        path: &str,
2769    ) -> KernelResult<()> {
2770        self.assert_driver_owns(requester_driver, pid)?;
2771        let stat = self.filesystem.stat(path)?;
2772        if stat.is_directory {
2773            return Err(KernelError::new(
2774                "EACCES",
2775                format!("permission denied, execute '{path}'"),
2776            ));
2777        }
2778        self.check_dac_access(pid, path, DAC_EXECUTE)
2779    }
2780
2781    pub fn list_processes(&self) -> BTreeMap<u32, ProcessInfo> {
2782        self.processes.list_processes()
2783    }
2784
2785    pub fn zombie_timer_count(&self) -> usize {
2786        self.processes.zombie_timer_count()
2787    }
2788
2789    pub fn reap_due_zombies(&self) {
2790        self.processes.reap_due_zombies();
2791    }
2792
2793    pub fn next_zombie_reap_deadline(&self) -> Option<std::time::Instant> {
2794        self.processes.next_zombie_reap_deadline()
2795    }
2796
2797    pub fn spawn_process(
2798        &mut self,
2799        command: &str,
2800        args: Vec<String>,
2801        options: SpawnOptions,
2802    ) -> KernelResult<KernelProcessHandle> {
2803        self.spawn_process_with_process_group(command, args, options, None)
2804    }
2805
2806    pub fn spawn_process_with_process_group(
2807        &mut self,
2808        command: &str,
2809        args: Vec<String>,
2810        options: SpawnOptions,
2811        requested_pgid: Option<u32>,
2812    ) -> KernelResult<KernelProcessHandle> {
2813        self.spawn_process_with_process_group_and_cloexec(
2814            command,
2815            args,
2816            options,
2817            requested_pgid,
2818            false,
2819        )
2820    }
2821
2822    /// Create the fork half of a process whose exec is deferred until the
2823    /// caller has applied POSIX spawn file actions.
2824    ///
2825    /// Unlike ordinary combined spawn, this preserves `FD_CLOEXEC` sources.
2826    /// The caller must invoke [`Self::close_process_cloexec_fds`] after all
2827    /// file actions succeed and before exposing the new process image.
2828    pub fn spawn_process_with_process_group_preserving_cloexec(
2829        &mut self,
2830        command: &str,
2831        args: Vec<String>,
2832        options: SpawnOptions,
2833        requested_pgid: Option<u32>,
2834    ) -> KernelResult<KernelProcessHandle> {
2835        self.spawn_process_with_process_group_and_cloexec(
2836            command,
2837            args,
2838            options,
2839            requested_pgid,
2840            true,
2841        )
2842    }
2843
2844    fn spawn_process_with_process_group_and_cloexec(
2845        &mut self,
2846        command: &str,
2847        args: Vec<String>,
2848        options: SpawnOptions,
2849        requested_pgid: Option<u32>,
2850        preserve_cloexec: bool,
2851    ) -> KernelResult<KernelProcessHandle> {
2852        self.assert_not_terminated()?;
2853        if let (Some(requester), Some(parent_pid)) =
2854            (options.requester_driver.as_deref(), options.parent_pid)
2855        {
2856            self.assert_driver_owns(requester, parent_pid)?;
2857        }
2858
2859        let parent_context = options
2860            .parent_pid
2861            .map(|pid| self.processes.inherited_context(pid))
2862            .transpose()?;
2863        let cwd = options.cwd.clone().unwrap_or_else(|| {
2864            parent_context
2865                .as_ref()
2866                .map(|context| context.cwd.clone())
2867                .unwrap_or_else(|| self.cwd.clone())
2868        });
2869        let resolved = self.resolve_spawn_command(command, &args, &cwd, options.parent_pid)?;
2870
2871        self.resources
2872            .check_process_argv_bytes(&resolved.command, &resolved.args)?;
2873        self.resources
2874            .check_process_env_bytes(&self.env, &options.env)?;
2875
2876        let mut env = parent_context
2877            .as_ref()
2878            .map(|context| context.env.clone())
2879            .unwrap_or_else(|| self.env.clone());
2880        env.extend(options.env.clone());
2881        check_command_execution(
2882            &self.vm_id,
2883            &self.permissions,
2884            &resolved.command,
2885            &resolved.args,
2886            Some(&cwd),
2887            &env,
2888        )?;
2889
2890        let inherited_fds = {
2891            let tables = lock_or_recover(&self.fd_tables);
2892            options
2893                .parent_pid
2894                .and_then(|pid| tables.get(pid).map(ProcessFdTable::len_for_exec))
2895                .unwrap_or(3)
2896        };
2897        self.resources
2898            .check_process_spawn(&self.resource_snapshot(), inherited_fds)?;
2899        let process_umask = match options.parent_pid {
2900            Some(parent_pid) => self.processes.get_umask(parent_pid)?,
2901            None => DEFAULT_PROCESS_UMASK,
2902        };
2903
2904        let mut context = parent_context.unwrap_or_else(|| ProcessContext {
2905            identity: self.users.identity(),
2906            ..ProcessContext::default()
2907        });
2908        context.ppid = options.parent_pid.unwrap_or(0);
2909        context.env = env;
2910        context.cwd = cwd;
2911        context.umask = process_umask;
2912
2913        self.register_process(
2914            resolved.driver.name().to_owned(),
2915            resolved.command,
2916            resolved.args,
2917            context,
2918            options.requester_driver.as_deref(),
2919            requested_pgid,
2920            preserve_cloexec,
2921        )
2922    }
2923
2924    /// Replace a running process image without allocating a new PID or FD
2925    /// table. This is the kernel half of execve(2): supplied argv/env replace
2926    /// the old image, cwd and process relationships remain attached to the
2927    /// same process, and only FD_CLOEXEC descriptors are closed.
2928    pub fn exec_process(
2929        &mut self,
2930        requester_driver: &str,
2931        pid: u32,
2932        command: &str,
2933        args: Vec<String>,
2934        env: BTreeMap<String, String>,
2935        cwd: String,
2936    ) -> KernelResult<()> {
2937        self.exec_process_retaining_internal_fds(
2938            requester_driver,
2939            pid,
2940            command,
2941            args,
2942            env,
2943            cwd,
2944            &[],
2945            &[],
2946            None,
2947        )
2948    }
2949
2950    /// Validate the literal pathname supplied to execve(2) without committing
2951    /// a process-image replacement. This preserves Linux pathname/type/mode
2952    /// errno behavior for sidecars that launch the file through an internal
2953    /// language-runtime driver.
2954    pub fn validate_executable_path(&mut self, path: &str, cwd: &str) -> KernelResult<String> {
2955        self.assert_not_terminated()?;
2956        self.resolve_executable_path(path, cwd, None)?
2957            .ok_or_else(|| KernelError::command_not_found(path))
2958    }
2959
2960    /// Validate the image chain for an in-place WASM exec replacement. Linux
2961    /// applies the same pathname/type/mode checks to each `#!` interpreter as
2962    /// it does to the originally requested script. The runner compiles the
2963    /// resulting WASM image before asking the sidecar to commit, but the
2964    /// trusted kernel remains responsible for enforcing those guest-visible
2965    /// checks and errno values.
2966    pub fn validate_wasm_exec_image(&mut self, path: &str, cwd: &str) -> KernelResult<()> {
2967        self.assert_not_terminated()?;
2968        self.validate_wasm_exec_image_inner(path, cwd, 0)
2969    }
2970
2971    /// Sidecar variant of [`Self::exec_process`] which keeps host-only
2972    /// plumbing descriptors that are stored in the process FD table as an
2973    /// implementation detail. Those descriptors are never part of the guest's
2974    /// Linux-visible FD set; all guest descriptors still obey FD_CLOEXEC.
2975    #[allow(clippy::too_many_arguments)]
2976    pub fn exec_process_retaining_internal_fds(
2977        &mut self,
2978        requester_driver: &str,
2979        pid: u32,
2980        command: &str,
2981        args: Vec<String>,
2982        env: BTreeMap<String, String>,
2983        _cwd: String,
2984        retained_internal_fds: &[u32],
2985        additional_cloexec_fds: &[u32],
2986        image_command: Option<&str>,
2987    ) -> KernelResult<()> {
2988        self.assert_not_terminated()?;
2989        self.assert_driver_owns(requester_driver, pid)?;
2990        // execve has no cwd argument. Resolve the new image from the process's
2991        // existing working directory and retain that directory across the
2992        // image replacement regardless of what an internal caller supplied.
2993        let cwd = self
2994            .processes
2995            .get(pid)
2996            .ok_or_else(|| KernelError::no_such_process(pid))?
2997            .cwd;
2998        if let Some(image_command) = image_command {
2999            // The sidecar launches the image through its runtime driver, but
3000            // Linux still applies execve pathname checks to the guest file.
3001            // Validate the literal path (including directory and execute-bit
3002            // checks) before the commit point.
3003            self.validate_executable_path(image_command, &cwd)?;
3004        }
3005        let resolved = self.resolve_spawn_command(command, &args, &cwd, Some(pid))?;
3006        let image_command = image_command.unwrap_or(&resolved.command);
3007        let (committed_argv0, committed_args) = resolved
3008            .args
3009            .split_first()
3010            .map(|(argv0, args)| (argv0.clone(), args.to_vec()))
3011            .unwrap_or_else(|| (String::new(), Vec::new()));
3012        self.resources
3013            .check_process_argv_bytes(&committed_argv0, &committed_args)?;
3014        self.resources
3015            .check_process_env_bytes(&BTreeMap::new(), &env)?;
3016        check_command_execution(
3017            &self.vm_id,
3018            &self.permissions,
3019            image_command,
3020            &committed_args,
3021            Some(&cwd),
3022            &env,
3023        )?;
3024
3025        // Keep the FD table locked across the only fallible commit operation.
3026        // If ProcessTable::exec rejects the replacement, no descriptor has
3027        // changed. Once it succeeds, removing entries already present in this
3028        // table is infallible, so callers can safely treat Ok as the execve
3029        // point of no return.
3030        let closed_entries = {
3031            let mut tables = lock_or_recover(&self.fd_tables);
3032            let table = tables
3033                .get_mut(pid)
3034                .ok_or_else(|| KernelError::no_such_process(pid))?;
3035            let mut fds = table
3036                .close_on_exec_fds()
3037                .into_iter()
3038                .collect::<BTreeSet<_>>();
3039            // libc also tracks CLOEXEC for runner-local descriptors. Close any
3040            // forwarded descriptor that belongs to the kernel table and leave
3041            // runner-local handles for the in-place image swap to close.
3042            fds.extend(
3043                additional_cloexec_fds
3044                    .iter()
3045                    .copied()
3046                    .filter(|fd| table.get(*fd).is_some()),
3047            );
3048
3049            self.processes.exec(
3050                pid,
3051                resolved.driver.name().to_owned(),
3052                committed_argv0,
3053                committed_args,
3054                env,
3055                cwd,
3056            )?;
3057
3058            let mut closed_entries = Vec::with_capacity(fds.len());
3059            for fd in fds {
3060                if retained_internal_fds.contains(&fd) {
3061                    continue;
3062                }
3063                if let Some(entry) = table.get(fd).cloned() {
3064                    // Presence was checked while holding the table lock. This
3065                    // cannot fail and therefore cannot leave exec half-committed.
3066                    let closed = table.close(fd);
3067                    debug_assert!(closed);
3068                    closed_entries.push((entry.description, entry.filetype));
3069                }
3070            }
3071            closed_entries
3072        };
3073        for (description, filetype) in closed_entries {
3074            if let Some(target) = description.lock_target() {
3075                self.file_locks.release_process_target(pid, target);
3076            }
3077            self.close_special_resource_if_needed(&description, filetype);
3078        }
3079        Ok(())
3080    }
3081
3082    pub fn create_virtual_process(
3083        &mut self,
3084        requester_driver: &str,
3085        driver: &str,
3086        command: &str,
3087        args: Vec<String>,
3088        options: VirtualProcessOptions,
3089    ) -> KernelResult<KernelProcessHandle> {
3090        self.create_virtual_process_with_process_group(
3091            requester_driver,
3092            driver,
3093            command,
3094            args,
3095            options,
3096            None,
3097        )
3098    }
3099
3100    pub fn create_virtual_process_with_process_group(
3101        &mut self,
3102        requester_driver: &str,
3103        driver: &str,
3104        command: &str,
3105        args: Vec<String>,
3106        options: VirtualProcessOptions,
3107        requested_pgid: Option<u32>,
3108    ) -> KernelResult<KernelProcessHandle> {
3109        self.assert_not_terminated()?;
3110        if let Some(parent_pid) = options.parent_pid {
3111            self.assert_driver_owns(requester_driver, parent_pid)?;
3112        }
3113
3114        let parent_context = options
3115            .parent_pid
3116            .map(|pid| self.processes.inherited_context(pid))
3117            .transpose()?;
3118        let cwd = options.cwd.clone().unwrap_or_else(|| {
3119            parent_context
3120                .as_ref()
3121                .map(|context| context.cwd.clone())
3122                .unwrap_or_else(|| self.cwd.clone())
3123        });
3124        self.resources.check_process_argv_bytes(command, &args)?;
3125        self.resources
3126            .check_process_env_bytes(&self.env, &options.env)?;
3127
3128        let mut env = parent_context
3129            .as_ref()
3130            .map(|context| context.env.clone())
3131            .unwrap_or_else(|| self.env.clone());
3132        env.extend(options.env.clone());
3133        check_command_execution(
3134            &self.vm_id,
3135            &self.permissions,
3136            command,
3137            &args,
3138            Some(&cwd),
3139            &env,
3140        )?;
3141
3142        let inherited_fds = {
3143            let tables = lock_or_recover(&self.fd_tables);
3144            options
3145                .parent_pid
3146                .and_then(|pid| tables.get(pid).map(ProcessFdTable::len))
3147                .unwrap_or(3)
3148        };
3149        self.resources
3150            .check_process_spawn(&self.resource_snapshot(), inherited_fds)?;
3151        let process_umask = match options.parent_pid {
3152            Some(parent_pid) => self.processes.get_umask(parent_pid)?,
3153            None => DEFAULT_PROCESS_UMASK,
3154        };
3155
3156        let mut context = parent_context.unwrap_or_else(|| ProcessContext {
3157            identity: self.users.identity(),
3158            ..ProcessContext::default()
3159        });
3160        context.ppid = options.parent_pid.unwrap_or(0);
3161        context.env = env;
3162        context.cwd = cwd;
3163        context.umask = process_umask;
3164
3165        self.register_process(
3166            String::from(driver),
3167            String::from(command),
3168            args,
3169            context,
3170            Some(requester_driver),
3171            requested_pgid,
3172            false,
3173        )
3174    }
3175
3176    pub fn read_process_stdin(
3177        &mut self,
3178        requester_driver: &str,
3179        pid: u32,
3180        length: usize,
3181        timeout: Option<Duration>,
3182    ) -> KernelResult<Option<Vec<u8>>> {
3183        self.fd_read_with_timeout_result(requester_driver, pid, 0, length, timeout)
3184    }
3185
3186    pub fn write_process_stdout(
3187        &mut self,
3188        requester_driver: &str,
3189        pid: u32,
3190        data: &[u8],
3191    ) -> KernelResult<usize> {
3192        self.fd_write(requester_driver, pid, 1, data)
3193    }
3194
3195    pub fn write_process_stderr(
3196        &mut self,
3197        requester_driver: &str,
3198        pid: u32,
3199        data: &[u8],
3200    ) -> KernelResult<usize> {
3201        self.fd_write(requester_driver, pid, 2, data)
3202    }
3203
3204    pub fn exit_process(
3205        &mut self,
3206        requester_driver: &str,
3207        pid: u32,
3208        exit_code: i32,
3209    ) -> KernelResult<()> {
3210        self.assert_driver_owns(requester_driver, pid)?;
3211        self.processes.mark_exited(pid, exit_code);
3212        Ok(())
3213    }
3214
3215    #[allow(clippy::too_many_arguments)]
3216    fn register_process(
3217        &mut self,
3218        driver_name: String,
3219        command: String,
3220        args: Vec<String>,
3221        mut ctx: ProcessContext,
3222        requester_driver: Option<&str>,
3223        requested_pgid: Option<u32>,
3224        preserve_cloexec: bool,
3225    ) -> KernelResult<KernelProcessHandle> {
3226        let pid = self.processes.allocate_pid()?;
3227        ctx.pid = pid;
3228
3229        if let (Some(requester), Some(target_pgid)) = (requester_driver, requested_pgid) {
3230            if target_pgid != 0 && target_pgid != pid {
3231                if let Some(group_owner) =
3232                    self.processes
3233                        .list_processes()
3234                        .into_values()
3235                        .find(|process| {
3236                            process.pgid == target_pgid && process.status == ProcessStatus::Running
3237                        })
3238                {
3239                    if group_owner.driver != requester {
3240                        return Err(KernelError::permission_denied(format!(
3241                            "driver \"{requester}\" cannot join process group {target_pgid} owned by \"{}\"",
3242                            group_owner.driver
3243                        )));
3244                    }
3245                }
3246            }
3247        }
3248
3249        let process = Arc::new(StubDriverProcess::default());
3250        self.processes.register_with_process_group(
3251            pid,
3252            driver_name.clone(),
3253            command,
3254            args,
3255            ctx.clone(),
3256            process.clone(),
3257            requested_pgid,
3258        )?;
3259
3260        {
3261            let mut tables = lock_or_recover(&self.fd_tables);
3262            if ctx.ppid != 0 {
3263                let parent_pid = ctx.ppid;
3264                if preserve_cloexec {
3265                    tables.fork_preserving_cloexec(parent_pid, pid);
3266                } else {
3267                    tables.fork(parent_pid, pid);
3268                }
3269            } else {
3270                tables.create(pid);
3271            }
3272        }
3273
3274        let mut owners = lock_or_recover(&self.driver_pids);
3275        owners.entry(driver_name.clone()).or_default().insert(pid);
3276        if let Some(requester) = requester_driver {
3277            owners
3278                .entry(String::from(requester))
3279                .or_default()
3280                .insert(pid);
3281        }
3282
3283        Ok(KernelProcessHandle {
3284            pid,
3285            driver: driver_name,
3286            process,
3287        })
3288    }
3289
3290    pub fn waitpid(&mut self, pid: u32) -> KernelResult<WaitPidResult> {
3291        let (pid, status) = self.processes.waitpid(pid)?;
3292        self.cleanup_process_resources(pid);
3293        Ok(WaitPidResult { pid, status })
3294    }
3295
3296    pub fn waitpid_with_options(
3297        &mut self,
3298        requester_driver: &str,
3299        waiter_pid: u32,
3300        pid: i32,
3301        flags: WaitPidFlags,
3302    ) -> KernelResult<Option<WaitPidEventResult>> {
3303        self.assert_driver_owns(requester_driver, waiter_pid)?;
3304        let result = self.processes.waitpid_for(waiter_pid, pid, flags)?;
3305        Ok(result.map(|result| self.finish_waitpid_event(result)))
3306    }
3307
3308    pub fn take_nonterminal_wait_event(
3309        &self,
3310        requester_driver: &str,
3311        waiter_pid: u32,
3312        pid: i32,
3313        flags: WaitPidFlags,
3314    ) -> KernelResult<Option<WaitPidEventResult>> {
3315        self.assert_driver_owns(requester_driver, waiter_pid)?;
3316        let result = self
3317            .processes
3318            .take_nonterminal_wait_event_for(waiter_pid, pid, flags)?;
3319        Ok(result.map(|result| WaitPidEventResult {
3320            pid: result.pid,
3321            status: result.status,
3322            event: result.event,
3323        }))
3324    }
3325
3326    pub fn wait_and_reap(&mut self, pid: u32) -> KernelResult<(u32, i32)> {
3327        let result = self.waitpid(pid)?;
3328        Ok((result.pid, result.status))
3329    }
3330
3331    pub fn open_pipe(&mut self, requester_driver: &str, pid: u32) -> KernelResult<(u32, u32)> {
3332        self.assert_not_terminated()?;
3333        self.assert_driver_owns(requester_driver, pid)?;
3334        let identity = self.process_identity(requester_driver, pid)?;
3335        self.resources
3336            .check_pipe_allocation(&self.resource_snapshot())?;
3337        let (read_fd, write_fd, read_description_id) = {
3338            let mut tables = lock_or_recover(&self.fd_tables);
3339            let table = tables
3340                .get_mut(pid)
3341                .ok_or_else(|| KernelError::no_such_process(pid))?;
3342            let (read_fd, write_fd) = self.pipes.create_pipe_fds(table)?;
3343            let read_description_id = table
3344                .get(read_fd)
3345                .expect("new pipe read descriptor must exist")
3346                .description
3347                .id();
3348            (read_fd, write_fd, read_description_id)
3349        };
3350        self.pipes
3351            .set_owner(read_description_id, identity.euid, identity.egid)?;
3352        Ok((read_fd, write_fd))
3353    }
3354
3355    pub fn fd_pipe_has_reader_in_other_process(
3356        &self,
3357        requester_driver: &str,
3358        pid: u32,
3359        fd: u32,
3360    ) -> KernelResult<bool> {
3361        self.assert_driver_owns(requester_driver, pid)?;
3362        let tables = lock_or_recover(&self.fd_tables);
3363        let write_description_id = tables
3364            .get(pid)
3365            .and_then(|table| table.get(fd))
3366            .ok_or_else(|| KernelError::bad_file_descriptor(fd))?
3367            .description
3368            .id();
3369        Ok(tables.pids().into_iter().any(|other_pid| {
3370            other_pid != pid
3371                && tables.get(other_pid).is_some_and(|table| {
3372                    table.values().any(|entry| {
3373                        self.pipes
3374                            .is_write_to_read_pair(write_description_id, entry.description.id())
3375                    })
3376                })
3377        }))
3378    }
3379
3380    pub fn fd_snapshot(
3381        &self,
3382        requester_driver: &str,
3383        pid: u32,
3384    ) -> KernelResult<Vec<ProcessFdSnapshotEntry>> {
3385        self.assert_driver_owns(requester_driver, pid)?;
3386        let tables = lock_or_recover(&self.fd_tables);
3387        let table = tables
3388            .get(pid)
3389            .ok_or_else(|| KernelError::no_such_process(pid))?;
3390        Ok(table
3391            .values()
3392            .map(|entry| ProcessFdSnapshotEntry {
3393                fd: entry.fd,
3394                fd_flags: entry.fd_flags,
3395                status_flags: entry.status_flags | entry.description.flags(),
3396                filetype: entry.filetype,
3397                is_socket: self.fd_socket_id(&entry.description).is_some(),
3398                is_pipe: self.pipes.is_pipe(entry.description.id()),
3399                is_pty: self.ptys.is_pty(entry.description.id()),
3400            })
3401            .collect())
3402    }
3403
3404    /// Create a connected AF_UNIX socket pair whose endpoints live in the
3405    /// process descriptor table. The socket records are owned by their open
3406    /// file descriptions rather than by a PID so SCM_RIGHTS and spawn
3407    /// inheritance preserve them after the creating process exits.
3408    pub fn fd_socketpair(
3409        &mut self,
3410        requester_driver: &str,
3411        pid: u32,
3412        socket_type: SocketType,
3413        nonblocking: bool,
3414        close_on_exec: bool,
3415    ) -> KernelResult<(u32, u32)> {
3416        self.assert_not_terminated()?;
3417        self.assert_driver_owns(requester_driver, pid)?;
3418        let identity = self.process_identity(requester_driver, pid)?;
3419        let spec = match socket_type {
3420            SocketType::Stream => SocketSpec::unix_stream(),
3421            SocketType::Datagram => SocketSpec::unix_datagram(),
3422            SocketType::SeqPacket => SocketSpec::unix_seqpacket(),
3423        };
3424
3425        let mut snapshot = self.resource_snapshot();
3426        self.resources.check_fd_allocation(&snapshot, 2)?;
3427        for _ in 0..2 {
3428            self.resources.check_socket_allocation(&snapshot)?;
3429            snapshot.sockets = snapshot.sockets.saturating_add(1);
3430            self.resources.check_socket_state_transition(
3431                &snapshot,
3432                SocketState::Created,
3433                SocketState::Connected,
3434            )?;
3435            snapshot.socket_connections = snapshot.socket_connections.saturating_add(1);
3436        }
3437
3438        let status_flags = if nonblocking { O_NONBLOCK } else { 0 };
3439        let fd_flags = if close_on_exec { FD_CLOEXEC } else { 0 };
3440        let filetype = if socket_type == SocketType::Datagram {
3441            FILETYPE_SOCKET_DGRAM
3442        } else {
3443            FILETYPE_SOCKET_STREAM
3444        };
3445        let (first_fd, second_fd, first_description, second_description) = {
3446            let mut tables = lock_or_recover(&self.fd_tables);
3447            let table = tables
3448                .get_mut(pid)
3449                .ok_or_else(|| KernelError::no_such_process(pid))?;
3450            table.open_pair_with_details(
3451                "socketpair:first",
3452                "socketpair:second",
3453                status_flags,
3454                fd_flags,
3455                filetype,
3456            )?
3457        };
3458
3459        // PID 0 is reserved for description-owned sockets. Process cleanup
3460        // must not tear them down while another process or ancillary message
3461        // still retains the open file description.
3462        let first_socket = match self.sockets.allocate(0, spec) {
3463            Ok(socket) => socket.id(),
3464            Err(error) => {
3465                let mut tables = lock_or_recover(&self.fd_tables);
3466                if let Some(table) = tables.get_mut(pid) {
3467                    table.close(first_fd);
3468                    table.close(second_fd);
3469                }
3470                return Err(error.into());
3471            }
3472        };
3473        let second_socket = match self.sockets.allocate(0, spec) {
3474            Ok(socket) => socket.id(),
3475            Err(error) => {
3476                if let Err(cleanup_error) = self.sockets.remove(first_socket) {
3477                    eprintln!(
3478                        "[agentos] failed to roll back first socketpair socket {first_socket}: {cleanup_error}"
3479                    );
3480                }
3481                let mut tables = lock_or_recover(&self.fd_tables);
3482                if let Some(table) = tables.get_mut(pid) {
3483                    table.close(first_fd);
3484                    table.close(second_fd);
3485                }
3486                return Err(error.into());
3487            }
3488        };
3489        if let Err(error) = self.sockets.connect_pair(first_socket, second_socket) {
3490            for socket_id in [first_socket, second_socket] {
3491                if let Err(cleanup_error) = self.sockets.remove(socket_id) {
3492                    eprintln!(
3493                        "[agentos] failed to roll back socketpair socket {socket_id}: {cleanup_error}"
3494                    );
3495                }
3496            }
3497            let mut tables = lock_or_recover(&self.fd_tables);
3498            if let Some(table) = tables.get_mut(pid) {
3499                table.close(first_fd);
3500                table.close(second_fd);
3501            }
3502            return Err(error.into());
3503        }
3504
3505        {
3506            let mut registry = lock_or_recover(&self.fd_sockets);
3507            registry.insert(
3508                first_description.id(),
3509                FdSocketEntry {
3510                    description: first_description,
3511                    socket_id: first_socket,
3512                    mode: 0o777,
3513                    uid: identity.euid,
3514                    gid: identity.egid,
3515                },
3516            );
3517            registry.insert(
3518                second_description.id(),
3519                FdSocketEntry {
3520                    description: second_description,
3521                    socket_id: second_socket,
3522                    mode: 0o777,
3523                    uid: identity.euid,
3524                    gid: identity.egid,
3525                },
3526            );
3527        }
3528        self.poll_notifier.notify();
3529        Ok((first_fd, second_fd))
3530    }
3531
3532    /// Attach an existing kernel socket to a description-owned fd. Sidecar
3533    /// transports use this when a raw socket becomes transferable through
3534    /// SCM_RIGHTS; owner 0 keeps process teardown from destroying the socket
3535    /// while the open description is queued in another process.
3536    pub fn fd_adopt_socket(
3537        &mut self,
3538        requester_driver: &str,
3539        pid: u32,
3540        socket_id: SocketId,
3541        status_flags: u32,
3542    ) -> KernelResult<u32> {
3543        self.assert_not_terminated()?;
3544        self.assert_driver_owns(requester_driver, pid)?;
3545        let identity = self.process_identity(requester_driver, pid)?;
3546        let socket = self
3547            .sockets
3548            .get(socket_id)
3549            .ok_or_else(|| KernelError::new("ENOENT", format!("no such socket {socket_id}")))?;
3550        if socket.owner_pid() != pid && socket.owner_pid() != 0 {
3551            return Err(KernelError::permission_denied(format!(
3552                "process {pid} does not own socket {socket_id}"
3553            )));
3554        }
3555        self.resources
3556            .check_fd_allocation(&self.resource_snapshot(), 1)?;
3557        let filetype = if socket.spec().socket_type == SocketType::Datagram {
3558            FILETYPE_SOCKET_DGRAM
3559        } else {
3560            FILETYPE_SOCKET_STREAM
3561        };
3562        let (fd, description) = {
3563            let mut tables = lock_or_recover(&self.fd_tables);
3564            let table = tables
3565                .get_mut(pid)
3566                .ok_or_else(|| KernelError::no_such_process(pid))?;
3567            let fd = table.open_with_details(
3568                &format!("socket:{socket_id}"),
3569                status_flags,
3570                filetype,
3571                None,
3572            )?;
3573            let description = Arc::clone(
3574                &table
3575                    .get(fd)
3576                    .expect("newly adopted socket fd must exist")
3577                    .description,
3578            );
3579            (fd, description)
3580        };
3581        self.sockets.reassign_owner(socket_id, 0)?;
3582        lock_or_recover(&self.fd_sockets).insert(
3583            description.id(),
3584            FdSocketEntry {
3585                description,
3586                socket_id,
3587                mode: 0o777,
3588                uid: identity.euid,
3589                gid: identity.egid,
3590            },
3591        );
3592        Ok(fd)
3593    }
3594
3595    /// Attach an existing kernel socket directly to a transferable open file
3596    /// description. Unlike `fd_adopt_socket`, this does not allocate a
3597    /// temporary descriptor in the sender, matching SCM_RIGHTS behavior when
3598    /// the sender is already at its per-process fd limit.
3599    pub fn fd_adopt_socket_transfer(
3600        &mut self,
3601        requester_driver: &str,
3602        pid: u32,
3603        socket_id: SocketId,
3604        status_flags: u32,
3605    ) -> KernelResult<TransferredFd> {
3606        self.assert_not_terminated()?;
3607        self.assert_driver_owns(requester_driver, pid)?;
3608        let identity = self.process_identity(requester_driver, pid)?;
3609        let socket = self
3610            .sockets
3611            .get(socket_id)
3612            .ok_or_else(|| KernelError::new("ENOENT", format!("no such socket {socket_id}")))?;
3613        if socket.owner_pid() != pid && socket.owner_pid() != 0 {
3614            return Err(KernelError::permission_denied(format!(
3615                "process {pid} does not own socket {socket_id}"
3616            )));
3617        }
3618        let filetype = if socket.spec().socket_type == SocketType::Datagram {
3619            FILETYPE_SOCKET_DGRAM
3620        } else {
3621            FILETYPE_SOCKET_STREAM
3622        };
3623        let transfer = {
3624            let tables = lock_or_recover(&self.fd_tables);
3625            let table = tables
3626                .get(pid)
3627                .ok_or_else(|| KernelError::no_such_process(pid))?;
3628            table.create_transfer(&format!("socket:{socket_id}"), status_flags, filetype)
3629        };
3630        self.sockets.reassign_owner(socket_id, 0)?;
3631        lock_or_recover(&self.fd_sockets).insert(
3632            transfer.description_id(),
3633            FdSocketEntry {
3634                description: transfer.description(),
3635                socket_id,
3636                mode: 0o777,
3637                uid: identity.euid,
3638                gid: identity.egid,
3639            },
3640        );
3641        Ok(transfer)
3642    }
3643
3644    pub fn fd_transfer(
3645        &self,
3646        requester_driver: &str,
3647        pid: u32,
3648        fd: u32,
3649    ) -> KernelResult<TransferredFd> {
3650        self.assert_driver_owns(requester_driver, pid)?;
3651        let tables = lock_or_recover(&self.fd_tables);
3652        let table = tables
3653            .get(pid)
3654            .ok_or_else(|| KernelError::no_such_process(pid))?;
3655        Ok(table.transfer(fd)?)
3656    }
3657
3658    /// Install a transferred open file description at an exact descriptor in
3659    /// another process. Unlike reopening `TransferredFd`'s path, this preserves
3660    /// the same description identity, offset, status flags, and special-resource
3661    /// ownership that existed when the transfer was captured.
3662    pub fn fd_install_transfer_at(
3663        &mut self,
3664        requester_driver: &str,
3665        pid: u32,
3666        fd: u32,
3667        fd_flags: u32,
3668        transfer: &TransferredFd,
3669    ) -> KernelResult<()> {
3670        self.assert_not_terminated()?;
3671        self.assert_driver_owns(requester_driver, pid)?;
3672        let replaced = {
3673            let mut tables = lock_or_recover(&self.fd_tables);
3674            let table = tables
3675                .get_mut(pid)
3676                .ok_or_else(|| KernelError::no_such_process(pid))?;
3677            let replaced = table
3678                .get(fd)
3679                .map(|entry| (Arc::clone(&entry.description), entry.filetype));
3680            table.install_transferred_at(transfer, fd, fd_flags)?;
3681            replaced
3682        };
3683        if let Some((description, filetype)) = replaced {
3684            self.close_special_resource_if_needed(&description, filetype);
3685        }
3686        Ok(())
3687    }
3688
3689    pub fn fd_socket_sendmsg(
3690        &mut self,
3691        requester_driver: &str,
3692        pid: u32,
3693        socket_fd: u32,
3694        data: &[u8],
3695        rights_fds: &[u32],
3696    ) -> KernelResult<usize> {
3697        let rights = rights_fds
3698            .iter()
3699            .copied()
3700            .map(FdTransferRequest::Fd)
3701            .collect::<Vec<_>>();
3702        self.fd_socket_sendmsg_transfers(requester_driver, pid, socket_fd, data, &rights)
3703    }
3704
3705    pub fn fd_socket_sendmsg_transfers(
3706        &mut self,
3707        requester_driver: &str,
3708        pid: u32,
3709        socket_fd: u32,
3710        data: &[u8],
3711        transfer_requests: &[FdTransferRequest],
3712    ) -> KernelResult<usize> {
3713        self.assert_not_terminated()?;
3714        self.assert_driver_owns(requester_driver, pid)?;
3715        let (socket_id, rights) = {
3716            let tables = lock_or_recover(&self.fd_tables);
3717            let table = tables
3718                .get(pid)
3719                .ok_or_else(|| KernelError::no_such_process(pid))?;
3720            let socket_entry = table
3721                .get(socket_fd)
3722                .ok_or_else(|| KernelError::bad_file_descriptor(socket_fd))?;
3723            let socket_id = self
3724                .fd_socket_id(&socket_entry.description)
3725                .ok_or_else(|| KernelError::new("ENOTSOCK", "descriptor is not a socket"))?;
3726            let rights = transfer_requests
3727                .iter()
3728                .map(|request| match request {
3729                    FdTransferRequest::Fd(fd) => table
3730                        .transfer(*fd)
3731                        .map(TransferredSocketRight::Fd)
3732                        .map_err(KernelError::from),
3733                    FdTransferRequest::Opaque(resource) => {
3734                        Ok(TransferredSocketRight::Opaque(Arc::clone(resource)))
3735                    }
3736                })
3737                .collect::<KernelResult<Vec<_>>>()?;
3738            (socket_id, rights)
3739        };
3740
3741        let socket = self
3742            .sockets
3743            .get(socket_id)
3744            .ok_or_else(|| KernelError::bad_file_descriptor(socket_fd))?;
3745        self.sockets.check_write(socket_id)?;
3746        let snapshot = self.resource_snapshot();
3747        if socket.spec().socket_type == SocketType::Stream {
3748            self.resources
3749                .check_socket_buffer_growth(&snapshot, data.len())?;
3750        } else {
3751            self.resources
3752                .check_socket_datagram_enqueue(&snapshot, data.len())?;
3753        }
3754        let written = self.sockets.send_message(socket_id, data, rights)?;
3755        if written > 0 || socket.spec().socket_type != SocketType::Stream {
3756            self.poll_notifier.notify();
3757        }
3758        Ok(written)
3759    }
3760
3761    // recvmsg(2) exposes independent buffer, rights, and flag controls; keep
3762    // that syscall-shaped surface explicit for parity with the guest ABI.
3763    #[allow(clippy::too_many_arguments)]
3764    pub fn fd_socket_recvmsg(
3765        &mut self,
3766        requester_driver: &str,
3767        pid: u32,
3768        socket_fd: u32,
3769        max_bytes: usize,
3770        max_rights: usize,
3771        close_on_exec: bool,
3772        peek: bool,
3773        dontwait: bool,
3774        waitall: bool,
3775    ) -> KernelResult<Option<ReceivedFdMessage>> {
3776        self.assert_not_terminated()?;
3777        self.assert_driver_owns(requester_driver, pid)?;
3778        let (socket_id, available_fds, nonblocking, socket_type) = {
3779            let tables = lock_or_recover(&self.fd_tables);
3780            let table = tables
3781                .get(pid)
3782                .ok_or_else(|| KernelError::no_such_process(pid))?;
3783            let socket_entry = table
3784                .get(socket_fd)
3785                .ok_or_else(|| KernelError::bad_file_descriptor(socket_fd))?;
3786            let socket_id = self
3787                .fd_socket_id(&socket_entry.description)
3788                .ok_or_else(|| KernelError::new("ENOTSOCK", "descriptor is not a socket"))?;
3789            (
3790                socket_id,
3791                table.available_fd_capacity(),
3792                (socket_entry.description.flags() | socket_entry.status_flags) & O_NONBLOCK != 0,
3793                self.sockets
3794                    .get(socket_id)
3795                    .ok_or_else(|| KernelError::bad_file_descriptor(socket_fd))?
3796                    .spec()
3797                    .socket_type,
3798            )
3799        };
3800
3801        let deadline = (!nonblocking && !dontwait)
3802            .then(|| {
3803                self.blocking_read_timeout()
3804                    .map(|wait| Instant::now() + wait)
3805            })
3806            .flatten();
3807        let mut message = loop {
3808            let generation = self.poll_notifier.snapshot();
3809            let wait_for_full = waitall
3810                && socket_type == SocketType::Stream
3811                && !nonblocking
3812                && !dontwait
3813                && max_bytes > 0;
3814            match self
3815                .sockets
3816                .recv_message(socket_id, max_bytes, peek || wait_for_full)
3817            {
3818                Ok(Some(message)) => {
3819                    let peer_closed = self
3820                        .sockets
3821                        .poll(socket_id, POLLHUP)
3822                        .map(|events| events.intersects(POLLHUP))
3823                        .unwrap_or(false);
3824                    if wait_for_full && message.full_length < max_bytes && !peer_closed {
3825                        drop(message);
3826                    } else if wait_for_full && !peek {
3827                        break self.sockets.recv_message(socket_id, max_bytes, false)?;
3828                    } else {
3829                        break Some(message);
3830                    }
3831                }
3832                Ok(None) => break None,
3833                Err(error) if error.code() == "EAGAIN" && !nonblocking && !dontwait => {
3834                    let remaining =
3835                        deadline.map(|deadline| deadline.saturating_duration_since(Instant::now()));
3836                    if matches!(remaining, Some(duration) if duration.is_zero())
3837                        || !self.poll_notifier.wait_for_change(generation, remaining)
3838                    {
3839                        return Err(KernelError::new(
3840                            "EAGAIN",
3841                            "blocking socket receive timed out; raise limits.resources.maxBlockingReadMs",
3842                        ));
3843                    }
3844                }
3845                Err(error) => return Err(error.into()),
3846            }
3847        };
3848        let Some(mut message) = message.take() else {
3849            return Ok(None);
3850        };
3851        let mut kernel_fd_count = 0usize;
3852        let mut install_count = 0usize;
3853        for right in message.rights.iter().take(max_rights) {
3854            if matches!(right, TransferredSocketRight::Fd(_)) {
3855                if kernel_fd_count >= available_fds {
3856                    break;
3857                }
3858                kernel_fd_count += 1;
3859            }
3860            install_count += 1;
3861        }
3862        let discarded = message.rights.split_off(install_count);
3863        let control_truncated = !discarded.is_empty();
3864        let mut fd_transfers = Vec::with_capacity(kernel_fd_count);
3865        for right in &message.rights {
3866            if let TransferredSocketRight::Fd(fd) = right {
3867                fd_transfers.push(fd.clone());
3868            }
3869        }
3870        let installed_fds = if fd_transfers.is_empty() {
3871            Vec::new()
3872        } else {
3873            let mut tables = lock_or_recover(&self.fd_tables);
3874            let table = tables
3875                .get_mut(pid)
3876                .ok_or_else(|| KernelError::no_such_process(pid))?;
3877            table.install_transferred(&fd_transfers, close_on_exec)?
3878        };
3879        let mut installed_fds = installed_fds.into_iter();
3880        let rights = message
3881            .rights
3882            .drain(..)
3883            .map(|right| match right {
3884                TransferredSocketRight::Fd(_) => ReceivedFdRight::Fd(
3885                    installed_fds
3886                        .next()
3887                        .expect("every retained fd transfer must be installed"),
3888                ),
3889                TransferredSocketRight::Opaque(resource) => ReceivedFdRight::Opaque(resource),
3890            })
3891            .collect();
3892        debug_assert!(installed_fds.next().is_none());
3893        drop(discarded);
3894        drop(fd_transfers);
3895        prune_fd_sockets(&self.sockets, &self.fd_sockets);
3896        self.poll_notifier.notify();
3897        Ok(Some(ReceivedFdMessage {
3898            payload: message.payload,
3899            rights,
3900            payload_truncated: message.truncated,
3901            control_truncated,
3902            full_length: message.full_length,
3903        }))
3904    }
3905
3906    pub fn fd_socket_shutdown(
3907        &mut self,
3908        requester_driver: &str,
3909        pid: u32,
3910        socket_fd: u32,
3911        how: SocketShutdown,
3912    ) -> KernelResult<()> {
3913        self.assert_not_terminated()?;
3914        self.assert_driver_owns(requester_driver, pid)?;
3915        let socket_id = self.fd_socket_id_for_fd(pid, socket_fd)?;
3916        self.sockets.shutdown(socket_id, how)?;
3917        prune_fd_sockets(&self.sockets, &self.fd_sockets);
3918        self.poll_notifier.notify();
3919        Ok(())
3920    }
3921
3922    pub fn open_pty(
3923        &mut self,
3924        requester_driver: &str,
3925        pid: u32,
3926    ) -> KernelResult<(u32, u32, String)> {
3927        self.assert_not_terminated()?;
3928        self.assert_driver_owns(requester_driver, pid)?;
3929        self.resources
3930            .check_pty_allocation(&self.resource_snapshot())?;
3931        let mut tables = lock_or_recover(&self.fd_tables);
3932        let table = tables
3933            .get_mut(pid)
3934            .ok_or_else(|| KernelError::no_such_process(pid))?;
3935        Ok(self.ptys.create_pty_fds(table)?)
3936    }
3937
3938    pub fn socket_create(
3939        &mut self,
3940        requester_driver: &str,
3941        pid: u32,
3942        spec: SocketSpec,
3943    ) -> KernelResult<SocketId> {
3944        self.assert_not_terminated()?;
3945        self.assert_driver_owns(requester_driver, pid)?;
3946        // Native sidecars admit socket/connection counts through the shared
3947        // capability registry. Retain the legacy snapshot admission only for
3948        // kernel consumers that have not injected that ledger (including the
3949        // browser build).
3950        if !self.sockets.has_resource_ledger() {
3951            self.resources
3952                .check_socket_allocation(&self.resource_snapshot())?;
3953        }
3954        Ok(self.sockets.allocate(pid, spec)?.id())
3955    }
3956
3957    #[cfg(not(target_arch = "wasm32"))]
3958    pub fn set_socket_resource_ledger(
3959        &mut self,
3960        resources: Arc<agentos_runtime::accounting::ResourceLedger>,
3961    ) -> KernelResult<()> {
3962        self.sockets.set_resource_ledger(resources)?;
3963        Ok(())
3964    }
3965
3966    pub fn set_socket_readiness_sink<S>(&mut self, sink: Option<S>)
3967    where
3968        S: Fn(SocketReadiness) + Send + Sync + 'static,
3969    {
3970        self.sockets.set_readiness_sink(sink);
3971    }
3972
3973    pub fn socket_get(&self, socket_id: SocketId) -> Option<SocketRecord> {
3974        self.sockets.get(socket_id)
3975    }
3976
3977    pub fn socket_records_for_pid(&self, pid: u32) -> Vec<SocketRecord> {
3978        self.sockets.records_for_owner(pid)
3979    }
3980
3981    pub fn socket_bind_inet(
3982        &mut self,
3983        requester_driver: &str,
3984        pid: u32,
3985        socket_id: SocketId,
3986        address: InetSocketAddress,
3987    ) -> KernelResult<()> {
3988        self.assert_not_terminated()?;
3989        self.assert_driver_owns(requester_driver, pid)?;
3990        let existing = self
3991            .sockets
3992            .get(socket_id)
3993            .ok_or_else(|| KernelError::new("ENOENT", format!("no such socket {socket_id}")))?;
3994        if existing.owner_pid() != pid && existing.owner_pid() != 0 {
3995            return Err(KernelError::permission_denied(format!(
3996                "process {pid} does not own socket {socket_id}"
3997            )));
3998        }
3999        check_network_access(
4000            &self.vm_id,
4001            &self.permissions,
4002            NetworkOperation::Listen,
4003            &format_tcp_resource(address.host(), address.port()),
4004        )?;
4005
4006        self.sockets.bind_inet(socket_id, address)?;
4007        self.poll_notifier.notify();
4008        Ok(())
4009    }
4010
4011    pub fn socket_bind_unix(
4012        &mut self,
4013        requester_driver: &str,
4014        pid: u32,
4015        socket_id: SocketId,
4016        path: impl Into<String>,
4017    ) -> KernelResult<()> {
4018        self.assert_not_terminated()?;
4019        self.assert_driver_owns(requester_driver, pid)?;
4020        let existing = self
4021            .sockets
4022            .get(socket_id)
4023            .ok_or_else(|| KernelError::new("ENOENT", format!("no such socket {socket_id}")))?;
4024        if existing.owner_pid() != pid && existing.owner_pid() != 0 {
4025            return Err(KernelError::permission_denied(format!(
4026                "process {pid} does not own socket {socket_id}"
4027            )));
4028        }
4029
4030        self.sockets
4031            .bind_unix(socket_id, normalize_path(&path.into()))?;
4032        self.poll_notifier.notify();
4033        Ok(())
4034    }
4035
4036    pub fn socket_listen(
4037        &mut self,
4038        requester_driver: &str,
4039        pid: u32,
4040        socket_id: SocketId,
4041        backlog: usize,
4042    ) -> KernelResult<()> {
4043        self.assert_not_terminated()?;
4044        self.assert_driver_owns(requester_driver, pid)?;
4045        let existing = self
4046            .sockets
4047            .get(socket_id)
4048            .ok_or_else(|| KernelError::new("ENOENT", format!("no such socket {socket_id}")))?;
4049        if existing.owner_pid() != pid && existing.owner_pid() != 0 {
4050            return Err(KernelError::permission_denied(format!(
4051                "process {pid} does not own socket {socket_id}"
4052            )));
4053        }
4054        if let Some(address) = existing.local_address() {
4055            check_network_access(
4056                &self.vm_id,
4057                &self.permissions,
4058                NetworkOperation::Listen,
4059                &format_tcp_resource(address.host(), address.port()),
4060            )?;
4061        }
4062
4063        self.sockets.listen(socket_id, backlog)?;
4064        self.poll_notifier.notify();
4065        Ok(())
4066    }
4067
4068    pub fn socket_queue_incoming_tcp_connection(
4069        &mut self,
4070        requester_driver: &str,
4071        pid: u32,
4072        listener_socket_id: SocketId,
4073        peer_address: InetSocketAddress,
4074    ) -> KernelResult<()> {
4075        self.assert_not_terminated()?;
4076        self.assert_driver_owns(requester_driver, pid)?;
4077        let existing = self.sockets.get(listener_socket_id).ok_or_else(|| {
4078            KernelError::new("ENOENT", format!("no such socket {listener_socket_id}"))
4079        })?;
4080        if existing.owner_pid() != pid && existing.owner_pid() != 0 {
4081            return Err(KernelError::permission_denied(format!(
4082                "process {pid} does not own socket {listener_socket_id}"
4083            )));
4084        }
4085
4086        self.sockets
4087            .enqueue_incoming_tcp_connection(listener_socket_id, peer_address)?;
4088        self.poll_notifier.notify();
4089        Ok(())
4090    }
4091
4092    pub fn socket_accept(
4093        &mut self,
4094        requester_driver: &str,
4095        pid: u32,
4096        listener_socket_id: SocketId,
4097    ) -> KernelResult<SocketId> {
4098        self.assert_not_terminated()?;
4099        self.assert_driver_owns(requester_driver, pid)?;
4100        let existing = self.sockets.get(listener_socket_id).ok_or_else(|| {
4101            KernelError::new("ENOENT", format!("no such socket {listener_socket_id}"))
4102        })?;
4103        if existing.owner_pid() != pid && existing.owner_pid() != 0 {
4104            return Err(KernelError::permission_denied(format!(
4105                "process {pid} does not own socket {listener_socket_id}"
4106            )));
4107        }
4108
4109        if !self.sockets.has_resource_ledger() {
4110            let snapshot = self.resource_snapshot();
4111            self.resources.check_socket_allocation(&snapshot)?;
4112            self.resources.check_socket_state_transition(
4113                &snapshot,
4114                SocketState::Created,
4115                SocketState::Connected,
4116            )?;
4117        }
4118
4119        let socket_id = self.sockets.accept(listener_socket_id)?.id();
4120        self.poll_notifier.notify();
4121        Ok(socket_id)
4122    }
4123
4124    pub fn socket_connect_pair(
4125        &mut self,
4126        requester_driver: &str,
4127        pid: u32,
4128        socket_id: SocketId,
4129        peer_socket_id: SocketId,
4130    ) -> KernelResult<()> {
4131        self.assert_not_terminated()?;
4132        self.assert_driver_owns(requester_driver, pid)?;
4133        let existing = self
4134            .sockets
4135            .get(socket_id)
4136            .ok_or_else(|| KernelError::new("ENOENT", format!("no such socket {socket_id}")))?;
4137        if existing.owner_pid() != pid && existing.owner_pid() != 0 {
4138            return Err(KernelError::permission_denied(format!(
4139                "process {pid} does not own socket {socket_id}"
4140            )));
4141        }
4142
4143        let peer = self.sockets.get(peer_socket_id).ok_or_else(|| {
4144            KernelError::new("ENOENT", format!("no such socket {peer_socket_id}"))
4145        })?;
4146        self.assert_driver_owns(requester_driver, peer.owner_pid())?;
4147
4148        if !self.sockets.has_resource_ledger() {
4149            let mut snapshot = self.resource_snapshot();
4150            for current_state in [existing.state(), peer.state()] {
4151                self.resources.check_socket_state_transition(
4152                    &snapshot,
4153                    current_state,
4154                    SocketState::Connected,
4155                )?;
4156                if !current_state.counts_as_connection() {
4157                    snapshot.socket_connections = snapshot.socket_connections.saturating_add(1);
4158                }
4159            }
4160        }
4161
4162        self.sockets.connect_pair(socket_id, peer_socket_id)?;
4163        self.poll_notifier.notify();
4164        Ok(())
4165    }
4166
4167    pub fn socket_connect_unix(
4168        &mut self,
4169        requester_driver: &str,
4170        pid: u32,
4171        socket_id: SocketId,
4172        target_path: impl Into<String>,
4173    ) -> KernelResult<()> {
4174        self.assert_not_terminated()?;
4175        self.assert_driver_owns(requester_driver, pid)?;
4176        let existing = self
4177            .sockets
4178            .get(socket_id)
4179            .ok_or_else(|| KernelError::new("ENOENT", format!("no such socket {socket_id}")))?;
4180        if existing.owner_pid() != pid && existing.owner_pid() != 0 {
4181            return Err(KernelError::permission_denied(format!(
4182                "process {pid} does not own socket {socket_id}"
4183            )));
4184        }
4185
4186        let target_path = normalize_path(&target_path.into());
4187        self.sockets
4188            .find_bound_unix_socket(&target_path)
4189            .ok_or_else(|| {
4190                KernelError::new(
4191                    "ECONNREFUSED",
4192                    format!("no listening socket bound at path {target_path}"),
4193                )
4194            })?;
4195
4196        if !self.sockets.has_resource_ledger() {
4197            let mut snapshot = self.resource_snapshot();
4198            self.resources.check_socket_allocation(&snapshot)?;
4199            for current_state in [existing.state(), SocketState::Created] {
4200                self.resources.check_socket_state_transition(
4201                    &snapshot,
4202                    current_state,
4203                    SocketState::Connected,
4204                )?;
4205                if !current_state.counts_as_connection() {
4206                    snapshot.socket_connections = snapshot.socket_connections.saturating_add(1);
4207                }
4208            }
4209        }
4210
4211        self.sockets
4212            .connect_to_bound_unix_stream(socket_id, target_path)?;
4213        self.poll_notifier.notify();
4214        Ok(())
4215    }
4216
4217    pub fn socket_connect_inet_loopback(
4218        &mut self,
4219        requester_driver: &str,
4220        pid: u32,
4221        socket_id: SocketId,
4222        target_address: InetSocketAddress,
4223    ) -> KernelResult<()> {
4224        self.assert_not_terminated()?;
4225        self.assert_driver_owns(requester_driver, pid)?;
4226        let existing = self
4227            .sockets
4228            .get(socket_id)
4229            .ok_or_else(|| KernelError::new("ENOENT", format!("no such socket {socket_id}")))?;
4230        if existing.owner_pid() != pid && existing.owner_pid() != 0 {
4231            return Err(KernelError::permission_denied(format!(
4232                "process {pid} does not own socket {socket_id}"
4233            )));
4234        }
4235        check_network_access(
4236            &self.vm_id,
4237            &self.permissions,
4238            NetworkOperation::Http,
4239            &format_tcp_resource(target_address.host(), target_address.port()),
4240        )?;
4241        self.check_loopback_port_allowed(
4242            SocketSpec::tcp(),
4243            &target_address,
4244            "TCP loopback connect",
4245        )?;
4246
4247        self.sockets
4248            .find_bound_inet_socket(SocketSpec::tcp(), &target_address)
4249            .ok_or_else(|| {
4250                KernelError::new(
4251                    "ECONNREFUSED",
4252                    format!(
4253                        "no listening socket bound at {}:{}",
4254                        target_address.host(),
4255                        target_address.port()
4256                    ),
4257                )
4258            })?;
4259
4260        if !self.sockets.has_resource_ledger() {
4261            let mut snapshot = self.resource_snapshot();
4262            self.resources.check_socket_allocation(&snapshot)?;
4263            for current_state in [existing.state(), SocketState::Created] {
4264                self.resources.check_socket_state_transition(
4265                    &snapshot,
4266                    current_state,
4267                    SocketState::Connected,
4268                )?;
4269                if !current_state.counts_as_connection() {
4270                    snapshot.socket_connections = snapshot.socket_connections.saturating_add(1);
4271                }
4272            }
4273        }
4274
4275        self.sockets
4276            .connect_to_bound_inet_stream(socket_id, target_address)?;
4277        self.poll_notifier.notify();
4278        Ok(())
4279    }
4280
4281    pub fn socket_send_to_inet_loopback(
4282        &mut self,
4283        requester_driver: &str,
4284        pid: u32,
4285        socket_id: SocketId,
4286        target_address: InetSocketAddress,
4287        data: &[u8],
4288    ) -> KernelResult<usize> {
4289        self.assert_not_terminated()?;
4290        self.assert_driver_owns(requester_driver, pid)?;
4291        let existing = self
4292            .sockets
4293            .get(socket_id)
4294            .ok_or_else(|| KernelError::new("ENOENT", format!("no such socket {socket_id}")))?;
4295        if existing.owner_pid() != pid && existing.owner_pid() != 0 {
4296            return Err(KernelError::permission_denied(format!(
4297                "process {pid} does not own socket {socket_id}"
4298            )));
4299        }
4300        if existing.spec() != SocketSpec::udp()
4301            || existing.state() != SocketState::Bound
4302            || existing.local_address().is_none()
4303        {
4304            self.sockets
4305                .check_send_to_bound_udp_socket(socket_id, target_address.clone())?;
4306        }
4307        check_network_access(
4308            &self.vm_id,
4309            &self.permissions,
4310            NetworkOperation::Http,
4311            &format_tcp_resource(target_address.host(), target_address.port()),
4312        )?;
4313        self.check_loopback_port_allowed(SocketSpec::udp(), &target_address, "UDP loopback send")?;
4314
4315        self.sockets
4316            .check_send_to_bound_udp_socket(socket_id, target_address.clone())?;
4317        if !self.sockets.has_resource_ledger() {
4318            self.resources
4319                .check_socket_datagram_enqueue(&self.resource_snapshot(), data.len())?;
4320        }
4321        let written = self
4322            .sockets
4323            .send_to_bound_udp_socket(socket_id, target_address, data)?;
4324        if written > 0 {
4325            self.poll_notifier.notify();
4326        }
4327        Ok(written)
4328    }
4329
4330    pub fn socket_connect_udp_loopback(
4331        &mut self,
4332        requester_driver: &str,
4333        pid: u32,
4334        socket_id: SocketId,
4335        target_address: InetSocketAddress,
4336    ) -> KernelResult<()> {
4337        self.assert_not_terminated()?;
4338        self.assert_driver_owns(requester_driver, pid)?;
4339        let existing = self
4340            .sockets
4341            .get(socket_id)
4342            .ok_or_else(|| KernelError::new("ENOENT", format!("no such socket {socket_id}")))?;
4343        if existing.owner_pid() != pid {
4344            return Err(KernelError::permission_denied(format!(
4345                "process {pid} does not own socket {socket_id}"
4346            )));
4347        }
4348        if existing.spec().socket_type != SocketType::Datagram
4349            || existing.state() != SocketState::Bound
4350            || existing.local_address().is_none()
4351        {
4352            return Err(KernelError::new(
4353                "EINVAL",
4354                format!("UDP socket {socket_id} must be bound before connect"),
4355            ));
4356        }
4357        check_network_access(
4358            &self.vm_id,
4359            &self.permissions,
4360            NetworkOperation::Http,
4361            &format_tcp_resource(target_address.host(), target_address.port()),
4362        )?;
4363        self.check_loopback_port_allowed(existing.spec(), &target_address, "UDP loopback connect")?;
4364        self.sockets
4365            .connect_bound_udp_socket(socket_id, target_address)?;
4366        Ok(())
4367    }
4368
4369    pub fn socket_disconnect_udp(
4370        &mut self,
4371        requester_driver: &str,
4372        pid: u32,
4373        socket_id: SocketId,
4374    ) -> KernelResult<()> {
4375        self.assert_not_terminated()?;
4376        self.assert_driver_owns(requester_driver, pid)?;
4377        let existing = self
4378            .sockets
4379            .get(socket_id)
4380            .ok_or_else(|| KernelError::new("ENOENT", format!("no such socket {socket_id}")))?;
4381        if existing.owner_pid() != pid {
4382            return Err(KernelError::permission_denied(format!(
4383                "process {pid} does not own socket {socket_id}"
4384            )));
4385        }
4386        self.sockets.disconnect_bound_udp_socket(socket_id)?;
4387        Ok(())
4388    }
4389
4390    fn check_loopback_port_allowed(
4391        &self,
4392        spec: SocketSpec,
4393        target_address: &InetSocketAddress,
4394        operation: &str,
4395    ) -> KernelResult<()> {
4396        if self
4397            .sockets
4398            .find_bound_inet_socket(spec, target_address)
4399            .is_some()
4400            || self.loopback_exempt_ports.contains(&target_address.port())
4401        {
4402            return Ok(());
4403        }
4404
4405        Err(KernelError::permission_denied(format!(
4406            "{operation} to {}:{} is not owned by this VM and is not loopback-exempt",
4407            target_address.host(),
4408            target_address.port()
4409        )))
4410    }
4411
4412    pub fn socket_recv_datagram(
4413        &mut self,
4414        requester_driver: &str,
4415        pid: u32,
4416        socket_id: SocketId,
4417        max_bytes: usize,
4418    ) -> KernelResult<Option<ReceivedDatagram>> {
4419        self.assert_not_terminated()?;
4420        self.assert_driver_owns(requester_driver, pid)?;
4421        let existing = self
4422            .sockets
4423            .get(socket_id)
4424            .ok_or_else(|| KernelError::new("ENOENT", format!("no such socket {socket_id}")))?;
4425        if existing.owner_pid() != pid && existing.owner_pid() != 0 {
4426            return Err(KernelError::permission_denied(format!(
4427                "process {pid} does not own socket {socket_id}"
4428            )));
4429        }
4430
4431        let result = self.sockets.recv_datagram(socket_id, max_bytes)?;
4432        if result.is_some() {
4433            self.poll_notifier.notify();
4434        }
4435        Ok(result)
4436    }
4437
4438    #[cfg(not(target_arch = "wasm32"))]
4439    pub fn socket_recv_datagram_charged(
4440        &mut self,
4441        requester_driver: &str,
4442        pid: u32,
4443        socket_id: SocketId,
4444        max_bytes: usize,
4445    ) -> KernelResult<Option<crate::socket_table::ChargedReceivedDatagram>> {
4446        self.assert_not_terminated()?;
4447        self.assert_driver_owns(requester_driver, pid)?;
4448        let existing = self
4449            .sockets
4450            .get(socket_id)
4451            .ok_or_else(|| KernelError::new("ENOENT", format!("no such socket {socket_id}")))?;
4452        if existing.owner_pid() != pid {
4453            return Err(KernelError::permission_denied(format!(
4454                "process {pid} does not own socket {socket_id}"
4455            )));
4456        }
4457        let result = self.sockets.recv_datagram_charged(socket_id, max_bytes)?;
4458        if result.is_some() {
4459            self.poll_notifier.notify();
4460        }
4461        Ok(result)
4462    }
4463
4464    pub fn socket_set_datagram_option(
4465        &mut self,
4466        requester_driver: &str,
4467        pid: u32,
4468        socket_id: SocketId,
4469        option: DatagramSocketOption,
4470        enabled: bool,
4471    ) -> KernelResult<()> {
4472        self.assert_not_terminated()?;
4473        self.assert_driver_owns(requester_driver, pid)?;
4474        let existing = self
4475            .sockets
4476            .get(socket_id)
4477            .ok_or_else(|| KernelError::new("ENOENT", format!("no such socket {socket_id}")))?;
4478        if existing.owner_pid() != pid && existing.owner_pid() != 0 {
4479            return Err(KernelError::permission_denied(format!(
4480                "process {pid} does not own socket {socket_id}"
4481            )));
4482        }
4483
4484        self.sockets
4485            .set_datagram_socket_option(socket_id, option, enabled)?;
4486        self.poll_notifier.notify();
4487        Ok(())
4488    }
4489
4490    pub fn socket_add_membership(
4491        &mut self,
4492        requester_driver: &str,
4493        pid: u32,
4494        socket_id: SocketId,
4495        membership: SocketMulticastMembership,
4496    ) -> KernelResult<()> {
4497        self.assert_not_terminated()?;
4498        self.assert_driver_owns(requester_driver, pid)?;
4499        let existing = self
4500            .sockets
4501            .get(socket_id)
4502            .ok_or_else(|| KernelError::new("ENOENT", format!("no such socket {socket_id}")))?;
4503        if existing.owner_pid() != pid && existing.owner_pid() != 0 {
4504            return Err(KernelError::permission_denied(format!(
4505                "process {pid} does not own socket {socket_id}"
4506            )));
4507        }
4508
4509        self.sockets
4510            .add_multicast_membership(socket_id, membership)?;
4511        self.poll_notifier.notify();
4512        Ok(())
4513    }
4514
4515    pub fn socket_drop_membership(
4516        &mut self,
4517        requester_driver: &str,
4518        pid: u32,
4519        socket_id: SocketId,
4520        membership: SocketMulticastMembership,
4521    ) -> KernelResult<()> {
4522        self.assert_not_terminated()?;
4523        self.assert_driver_owns(requester_driver, pid)?;
4524        let existing = self
4525            .sockets
4526            .get(socket_id)
4527            .ok_or_else(|| KernelError::new("ENOENT", format!("no such socket {socket_id}")))?;
4528        if existing.owner_pid() != pid && existing.owner_pid() != 0 {
4529            return Err(KernelError::permission_denied(format!(
4530                "process {pid} does not own socket {socket_id}"
4531            )));
4532        }
4533
4534        self.sockets
4535            .drop_multicast_membership(socket_id, membership)?;
4536        self.poll_notifier.notify();
4537        Ok(())
4538    }
4539
4540    pub fn socket_set_state(
4541        &mut self,
4542        requester_driver: &str,
4543        pid: u32,
4544        socket_id: SocketId,
4545        state: SocketState,
4546    ) -> KernelResult<()> {
4547        self.assert_not_terminated()?;
4548        self.assert_driver_owns(requester_driver, pid)?;
4549        let existing = self
4550            .sockets
4551            .get(socket_id)
4552            .ok_or_else(|| KernelError::new("ENOENT", format!("no such socket {socket_id}")))?;
4553        if existing.owner_pid() != pid && existing.owner_pid() != 0 {
4554            return Err(KernelError::permission_denied(format!(
4555                "process {pid} does not own socket {socket_id}"
4556            )));
4557        }
4558
4559        if !self.sockets.has_resource_ledger() {
4560            self.resources.check_socket_state_transition(
4561                &self.resource_snapshot(),
4562                existing.state(),
4563                state,
4564            )?;
4565        }
4566        self.sockets.update_state(socket_id, state)?;
4567        self.poll_notifier.notify();
4568        Ok(())
4569    }
4570
4571    pub fn socket_write(
4572        &mut self,
4573        requester_driver: &str,
4574        pid: u32,
4575        socket_id: SocketId,
4576        data: &[u8],
4577    ) -> KernelResult<usize> {
4578        self.assert_not_terminated()?;
4579        self.assert_driver_owns(requester_driver, pid)?;
4580        let existing = self
4581            .sockets
4582            .get(socket_id)
4583            .ok_or_else(|| KernelError::new("ENOENT", format!("no such socket {socket_id}")))?;
4584        if existing.owner_pid() != pid && existing.owner_pid() != 0 {
4585            return Err(KernelError::permission_denied(format!(
4586                "process {pid} does not own socket {socket_id}"
4587            )));
4588        }
4589
4590        self.sockets.check_write(socket_id)?;
4591        if !self.sockets.has_resource_ledger() {
4592            self.resources
4593                .check_socket_buffer_growth(&self.resource_snapshot(), data.len())?;
4594        }
4595        let written = self.sockets.write(socket_id, data)?;
4596        if written > 0 {
4597            self.poll_notifier.notify();
4598        }
4599        Ok(written)
4600    }
4601
4602    pub fn socket_read(
4603        &mut self,
4604        requester_driver: &str,
4605        pid: u32,
4606        socket_id: SocketId,
4607        max_bytes: usize,
4608    ) -> KernelResult<Option<Vec<u8>>> {
4609        self.assert_not_terminated()?;
4610        self.assert_driver_owns(requester_driver, pid)?;
4611        let existing = self
4612            .sockets
4613            .get(socket_id)
4614            .ok_or_else(|| KernelError::new("ENOENT", format!("no such socket {socket_id}")))?;
4615        if existing.owner_pid() != pid && existing.owner_pid() != 0 {
4616            return Err(KernelError::permission_denied(format!(
4617                "process {pid} does not own socket {socket_id}"
4618            )));
4619        }
4620
4621        let result = self.sockets.read(socket_id, max_bytes)?;
4622        if result.is_some() {
4623            self.poll_notifier.notify();
4624        }
4625        Ok(result)
4626    }
4627
4628    pub fn socket_shutdown(
4629        &mut self,
4630        requester_driver: &str,
4631        pid: u32,
4632        socket_id: SocketId,
4633        how: SocketShutdown,
4634    ) -> KernelResult<()> {
4635        self.assert_not_terminated()?;
4636        self.assert_driver_owns(requester_driver, pid)?;
4637        let existing = self
4638            .sockets
4639            .get(socket_id)
4640            .ok_or_else(|| KernelError::new("ENOENT", format!("no such socket {socket_id}")))?;
4641        if existing.owner_pid() != pid && existing.owner_pid() != 0 {
4642            return Err(KernelError::permission_denied(format!(
4643                "process {pid} does not own socket {socket_id}"
4644            )));
4645        }
4646
4647        self.sockets.shutdown(socket_id, how)?;
4648        self.poll_notifier.notify();
4649        Ok(())
4650    }
4651
4652    pub fn socket_close(
4653        &mut self,
4654        requester_driver: &str,
4655        pid: u32,
4656        socket_id: SocketId,
4657    ) -> KernelResult<()> {
4658        self.assert_not_terminated()?;
4659        self.assert_driver_owns(requester_driver, pid)?;
4660        let existing = self
4661            .sockets
4662            .get(socket_id)
4663            .ok_or_else(|| KernelError::new("ENOENT", format!("no such socket {socket_id}")))?;
4664        if existing.owner_pid() != pid && existing.owner_pid() != 0 {
4665            return Err(KernelError::permission_denied(format!(
4666                "process {pid} does not own socket {socket_id}"
4667            )));
4668        }
4669
4670        self.sockets.remove(socket_id)?;
4671        self.poll_notifier.notify();
4672        Ok(())
4673    }
4674
4675    pub fn fd_open(
4676        &mut self,
4677        requester_driver: &str,
4678        pid: u32,
4679        path: &str,
4680        flags: u32,
4681        mode: Option<u32>,
4682    ) -> KernelResult<u32> {
4683        self.assert_not_terminated()?;
4684        self.assert_driver_owns(requester_driver, pid)?;
4685        self.validate_fd_open_flags(pid, path, flags)?;
4686        if let Some(existing_fd) = parse_dev_fd_path(path)? {
4687            {
4688                let tables = lock_or_recover(&self.fd_tables);
4689                let table = tables
4690                    .get(pid)
4691                    .ok_or_else(|| KernelError::no_such_process(pid))?;
4692                table
4693                    .get(existing_fd)
4694                    .ok_or_else(|| KernelError::bad_file_descriptor(existing_fd))?;
4695            }
4696            self.resources
4697                .check_fd_allocation(&self.resource_snapshot(), 1)?;
4698            let mut tables = lock_or_recover(&self.fd_tables);
4699            let table = tables
4700                .get_mut(pid)
4701                .ok_or_else(|| KernelError::no_such_process(pid))?;
4702            let entry = table
4703                .get(existing_fd)
4704                .cloned()
4705                .ok_or_else(|| KernelError::bad_file_descriptor(existing_fd))?;
4706            return Ok(table.dup_with_status_flags(
4707                existing_fd,
4708                Some(entry.status_flags | (flags & O_NONBLOCK)),
4709            )?);
4710        }
4711
4712        if let Some(proc_node) = self.resolve_proc_node(path, Some(pid))? {
4713            if open_requires_write_access(flags) {
4714                self.filesystem
4715                    .check_virtual_path(FsOperation::Write, path)
4716                    .map_err(KernelError::from)?;
4717                return Err(read_only_filesystem_error(path));
4718            }
4719
4720            if matches!(
4721                proc_node,
4722                ProcNode::SelfLink { .. }
4723                    | ProcNode::PidCwdLink { .. }
4724                    | ProcNode::PidFdLink { .. }
4725            ) {
4726                let target = self.proc_symlink_target(&proc_node)?;
4727                return self.fd_open(requester_driver, pid, &target, flags, mode);
4728            }
4729
4730            self.filesystem
4731                .check_virtual_path(FsOperation::Read, path)
4732                .map_err(KernelError::from)?;
4733            self.resources
4734                .check_fd_allocation(&self.resource_snapshot(), 1)?;
4735            let mut tables = lock_or_recover(&self.fd_tables);
4736            let table = tables
4737                .get_mut(pid)
4738                .ok_or_else(|| KernelError::no_such_process(pid))?;
4739            return Ok(table.open_with_details(
4740                &self.proc_canonical_path(&proc_node),
4741                flags,
4742                proc_filetype(&proc_node),
4743                None,
4744            )?);
4745        }
4746
4747        if open_requires_write_access(flags) {
4748            self.reject_read_only_resolved_write_path(path)?;
4749        }
4750        let existed = self.exists_internal(Some(pid), path)?;
4751        if existed {
4752            let mut access = 0;
4753            if flags & (O_WRONLY | O_RDWR) != O_WRONLY {
4754                access |= DAC_READ;
4755            }
4756            if flags & (O_WRONLY | O_RDWR) != 0 || flags & O_TRUNC != 0 {
4757                access |= DAC_WRITE;
4758            }
4759            self.check_dac_access(pid, path, access)?;
4760        } else if flags & O_CREAT != 0 {
4761            self.check_dac_parent_access(pid, path, DAC_WRITE | DAC_EXECUTE)?;
4762        }
4763        if existed {
4764            let stat = VirtualFileSystem::stat(&mut self.filesystem, path)?;
4765            if stat.mode & 0o170000 == 0o010000 {
4766                if flags & O_CREAT != 0 && flags & O_EXCL != 0 {
4767                    return Err(KernelError::new(
4768                        "EEXIST",
4769                        format!("file already exists, open '{path}'"),
4770                    ));
4771                }
4772                let key = (stat.dev, stat.ino);
4773                if !self.pipes.has_named_pipe(key) {
4774                    self.resources
4775                        .check_pipe_allocation(&self.resource_snapshot())?;
4776                }
4777                self.resources
4778                    .check_fd_allocation(&self.resource_snapshot(), 1)?;
4779                let timeout = self.blocking_read_timeout();
4780                let pipe = self.pipes.open_named_pipe(key, path, flags, timeout)?;
4781                let mut tables = lock_or_recover(&self.fd_tables);
4782                let table = tables
4783                    .get_mut(pid)
4784                    .ok_or_else(|| KernelError::no_such_process(pid))?;
4785                return match table.open_with(Arc::clone(&pipe.description), FILETYPE_PIPE, None) {
4786                    Ok(fd) => Ok(fd),
4787                    Err(error) => {
4788                        self.pipes.close(pipe.description.id());
4789                        Err(error.into())
4790                    }
4791                };
4792            }
4793        }
4794        let (filetype, lock_target) = self.prepare_fd_open(path, flags, mode)?;
4795        let description_path = if filetype == FILETYPE_DIRECTORY {
4796            self.realpath_internal(Some(pid), path)?
4797        } else {
4798            path.to_owned()
4799        };
4800        if flags & O_CREAT != 0 && !existed {
4801            let umask = self.processes.get_umask(pid)?;
4802            self.apply_process_creation_metadata(pid, path, mode.unwrap_or(0o666), umask, false)?;
4803        } else if flags & O_TRUNC != 0 {
4804            self.clear_setid_after_write(pid, path)?;
4805        }
4806        self.resources
4807            .check_fd_allocation(&self.resource_snapshot(), 1)?;
4808        let mut tables = lock_or_recover(&self.fd_tables);
4809        let table = tables
4810            .get_mut(pid)
4811            .ok_or_else(|| KernelError::no_such_process(pid))?;
4812        Ok(table.open_with_details(&description_path, flags, filetype, lock_target)?)
4813    }
4814
4815    pub fn fd_open_tmpfile(
4816        &mut self,
4817        requester_driver: &str,
4818        pid: u32,
4819        directory: &str,
4820        flags: u32,
4821        mode: u32,
4822        linkable: bool,
4823    ) -> KernelResult<u32> {
4824        self.assert_not_terminated()?;
4825        self.assert_driver_owns(requester_driver, pid)?;
4826        if flags & 0b11 == crate::fd_table::O_RDONLY {
4827            return Err(KernelError::new(
4828                "EINVAL",
4829                "O_TMPFILE requires write access",
4830            ));
4831        }
4832        let directory_stat = self.stat_for_process(requester_driver, pid, directory)?;
4833        if !directory_stat.is_directory {
4834            return Err(KernelError::new(
4835                "ENOTDIR",
4836                format!("O_TMPFILE target is not a directory: {directory}"),
4837            ));
4838        }
4839        self.check_dac_access(pid, directory, DAC_WRITE | DAC_EXECUTE)?;
4840        self.reject_read_only_resolved_write_path(directory)?;
4841
4842        let hidden_path = loop {
4843            let id = self.next_unnamed_file_id;
4844            self.next_unnamed_file_id =
4845                self.next_unnamed_file_id.checked_add(1).ok_or_else(|| {
4846                    KernelError::new("EMFILE", "unnamed-file identifier space exhausted")
4847                })?;
4848            let candidate = normalize_path(&format!("{directory}/{UNNAMED_FILE_PREFIX}{pid}-{id}"));
4849            if !self.exists_internal(Some(pid), &candidate)? {
4850                break candidate;
4851            }
4852        };
4853
4854        let fd = self.fd_open(
4855            requester_driver,
4856            pid,
4857            &hidden_path,
4858            (flags & !O_TRUNC) | O_CREAT | O_EXCL,
4859            Some(mode),
4860        )?;
4861        let description_id = self.description_for_fd(requester_driver, pid, fd)?.id();
4862        self.unnamed_files.insert(
4863            description_id,
4864            UnnamedFile {
4865                path: hidden_path,
4866                linkable,
4867            },
4868        );
4869        Ok(fd)
4870    }
4871
4872    pub fn fd_link_tmpfile_for_process(
4873        &mut self,
4874        requester_driver: &str,
4875        pid: u32,
4876        fd: u32,
4877        destination: &str,
4878    ) -> KernelResult<()> {
4879        self.assert_not_terminated()?;
4880        self.assert_driver_owns(requester_driver, pid)?;
4881        let description = self.description_for_fd(requester_driver, pid, fd)?;
4882        let source = if let Some(unnamed) = self.unnamed_files.get(&description.id()) {
4883            if !unnamed.linkable {
4884                return Err(KernelError::new(
4885                    "ENOENT",
4886                    "O_EXCL unnamed files cannot be linked",
4887                ));
4888            }
4889            unnamed.path.clone()
4890        } else {
4891            description.path().to_string()
4892        };
4893        self.check_dac_parent_access(pid, destination, DAC_WRITE | DAC_EXECUTE)?;
4894        self.link(&source, destination)
4895    }
4896
4897    pub fn fd_read(
4898        &mut self,
4899        requester_driver: &str,
4900        pid: u32,
4901        fd: u32,
4902        length: usize,
4903    ) -> KernelResult<Vec<u8>> {
4904        Ok(self
4905            .fd_read_with_timeout_result(requester_driver, pid, fd, length, None)?
4906            .unwrap_or_default())
4907    }
4908
4909    pub fn fd_read_with_timeout_result(
4910        &mut self,
4911        requester_driver: &str,
4912        pid: u32,
4913        fd: u32,
4914        length: usize,
4915        timeout: Option<Duration>,
4916    ) -> KernelResult<Option<Vec<u8>>> {
4917        self.assert_driver_owns(requester_driver, pid)?;
4918        let entry = {
4919            let tables = lock_or_recover(&self.fd_tables);
4920            tables
4921                .get(pid)
4922                .and_then(|table| table.get(fd))
4923                .cloned()
4924                .ok_or_else(|| KernelError::bad_file_descriptor(fd))?
4925        };
4926        if entry.description.flags() & 0b11 == O_WRONLY {
4927            return Err(KernelError::bad_file_descriptor(fd));
4928        }
4929
4930        if let Some(socket_id) = self.fd_socket_id(&entry.description) {
4931            self.resources.check_pread_length(length)?;
4932            let nonblocking = (entry.description.flags() | entry.status_flags) & O_NONBLOCK != 0;
4933            let wait = if nonblocking {
4934                Some(Duration::ZERO)
4935            } else {
4936                timeout.or_else(|| self.blocking_read_timeout())
4937            };
4938            let deadline = wait.map(|wait| Instant::now() + wait);
4939            let result = loop {
4940                let generation = self.poll_notifier.snapshot();
4941                match self.sockets.read(socket_id, length) {
4942                    Ok(result) => break result,
4943                    Err(error) if error.code() == "EAGAIN" && !nonblocking => {
4944                        let remaining = deadline
4945                            .map(|deadline| deadline.saturating_duration_since(Instant::now()));
4946                        if matches!(remaining, Some(duration) if duration.is_zero())
4947                            || !self.poll_notifier.wait_for_change(generation, remaining)
4948                        {
4949                            return Err(KernelError::new(
4950                                "EAGAIN",
4951                                "blocking socket read timed out; raise limits.resources.maxBlockingReadMs",
4952                            ));
4953                        }
4954                    }
4955                    Err(error) => return Err(error.into()),
4956                }
4957            };
4958            prune_fd_sockets(&self.sockets, &self.fd_sockets);
4959            if result.is_some() {
4960                self.poll_notifier.notify();
4961            }
4962            return Ok(result);
4963        }
4964
4965        if self.pipes.is_pipe(entry.description.id()) {
4966            let result = self.pipes.read_with_timeout(
4967                entry.description.id(),
4968                length,
4969                if (entry.description.flags() | entry.status_flags) & O_NONBLOCK != 0 {
4970                    Some(Duration::ZERO)
4971                } else {
4972                    timeout.or_else(|| self.blocking_read_timeout())
4973                },
4974            )?;
4975            return Ok(result);
4976        }
4977
4978        if self.ptys.is_pty(entry.description.id()) {
4979            return Ok(self.ptys.read_with_timeout(
4980                entry.description.id(),
4981                length,
4982                if (entry.description.flags() | entry.status_flags) & O_NONBLOCK != 0 {
4983                    Some(Duration::ZERO)
4984                } else {
4985                    timeout.or_else(|| self.blocking_read_timeout())
4986                },
4987            )?);
4988        }
4989
4990        self.resources.check_pread_length(length)?;
4991
4992        let path = entry.description.path();
4993        if is_proc_path(&path) {
4994            let bytes = self.proc_read_file_from_open_path(Some(pid), &path)?;
4995            let start = entry.description.cursor() as usize;
4996            let end = start.saturating_add(length).min(bytes.len());
4997            let chunk = if start >= bytes.len() {
4998                Vec::new()
4999            } else {
5000                bytes[start..end].to_vec()
5001            };
5002            entry.description.set_cursor(
5003                entry
5004                    .description
5005                    .cursor()
5006                    .saturating_add(chunk.len() as u64),
5007            );
5008            return Ok(Some(chunk));
5009        }
5010
5011        let cursor = entry.description.cursor();
5012        let bytes = if let Some(bytes) = entry.description.anonymous_pread(cursor, length) {
5013            bytes
5014        } else {
5015            VirtualFileSystem::pread(&mut self.filesystem, &path, cursor, length)?
5016        };
5017        entry
5018            .description
5019            .set_cursor(cursor.saturating_add(bytes.len() as u64));
5020        Ok(Some(bytes))
5021    }
5022
5023    pub fn fd_write(
5024        &mut self,
5025        requester_driver: &str,
5026        pid: u32,
5027        fd: u32,
5028        data: &[u8],
5029    ) -> KernelResult<usize> {
5030        self.fd_write_with_mode(requester_driver, pid, fd, data, false)
5031    }
5032
5033    /// Attempt one write without blocking on bounded kernel transport state.
5034    ///
5035    /// This preserves the descriptor's guest-visible status flags. Trusted
5036    /// sidecar actors use it to park a synchronous guest write and retry after
5037    /// readiness changes instead of blocking the process-wide reactor.
5038    pub fn fd_write_nonblocking(
5039        &mut self,
5040        requester_driver: &str,
5041        pid: u32,
5042        fd: u32,
5043        data: &[u8],
5044    ) -> KernelResult<usize> {
5045        self.fd_write_with_mode(requester_driver, pid, fd, data, true)
5046    }
5047
5048    fn fd_write_with_mode(
5049        &mut self,
5050        requester_driver: &str,
5051        pid: u32,
5052        fd: u32,
5053        data: &[u8],
5054        force_nonblocking: bool,
5055    ) -> KernelResult<usize> {
5056        self.assert_driver_owns(requester_driver, pid)?;
5057        self.resources.check_fd_write_size(data.len())?;
5058        let entry = {
5059            let tables = lock_or_recover(&self.fd_tables);
5060            tables
5061                .get(pid)
5062                .and_then(|table| table.get(fd))
5063                .cloned()
5064                .ok_or_else(|| KernelError::bad_file_descriptor(fd))?
5065        };
5066        if let Some(socket_id) = self.fd_socket_id(&entry.description) {
5067            let socket = self
5068                .sockets
5069                .get(socket_id)
5070                .ok_or_else(|| KernelError::bad_file_descriptor(fd))?;
5071            self.sockets.check_write(socket_id)?;
5072            let snapshot = self.resource_snapshot();
5073            if socket.spec().socket_type == SocketType::Stream {
5074                self.resources
5075                    .check_socket_buffer_growth(&snapshot, data.len())?;
5076            } else {
5077                self.resources
5078                    .check_socket_datagram_enqueue(&snapshot, data.len())?;
5079            }
5080            let written = self.sockets.write(socket_id, data)?;
5081            if written > 0 || socket.spec().socket_type != SocketType::Stream {
5082                self.poll_notifier.notify();
5083            }
5084            return Ok(written);
5085        }
5086
5087        if self.pipes.is_pipe(entry.description.id()) {
5088            return match self.pipes.write_with_mode(
5089                entry.description.id(),
5090                data,
5091                force_nonblocking
5092                    || (entry.description.flags() | entry.status_flags) & O_NONBLOCK != 0,
5093            ) {
5094                Ok(bytes) => Ok(bytes),
5095                Err(error) => {
5096                    if error.code() == "EPIPE" {
5097                        self.processes.kill(pid as i32, SIGPIPE)?;
5098                    }
5099                    Err(error.into())
5100                }
5101            };
5102        }
5103
5104        if self.ptys.is_pty(entry.description.id()) {
5105            return Ok(self.ptys.write(entry.description.id(), data)?);
5106        }
5107
5108        let path = entry.description.path();
5109        if let Some(stat) = entry.description.anonymous_stat() {
5110            if entry.description.flags() & 0b11 == O_RDONLY {
5111                return Err(KernelError::bad_file_descriptor(fd));
5112            }
5113            let cursor = if entry.description.flags() & O_APPEND != 0 {
5114                stat.size
5115            } else {
5116                entry.description.cursor()
5117            };
5118            let required_size = stat.size.max(checked_write_end(cursor, data.len())?);
5119            self.check_path_resize_limits_with_existing(stat.size, required_size)?;
5120            let new_size = entry
5121                .description
5122                .anonymous_pwrite(cursor, data)
5123                .expect("anonymous stat and backing must agree")?;
5124            debug_assert_eq!(new_size, required_size);
5125            entry
5126                .description
5127                .set_cursor(cursor.saturating_add(data.len() as u64));
5128            return Ok(data.len());
5129        }
5130        self.reject_read_only_resolved_write_path(&path)?;
5131        if entry.description.flags() & 0b11 == O_RDONLY {
5132            return Err(KernelError::bad_file_descriptor(fd));
5133        }
5134        if is_virtual_device_storage_path(&path) {
5135            VirtualFileSystem::write_file(&mut self.filesystem, &path, data.to_vec())?;
5136            let cursor = entry.description.cursor();
5137            entry
5138                .description
5139                .set_cursor(cursor.saturating_add(data.len() as u64));
5140            return Ok(data.len());
5141        }
5142        let current_size = self.current_storage_file_size(&path)?;
5143        let cursor = entry.description.cursor();
5144        if entry.description.flags() & O_APPEND != 0 {
5145            check_direct_io_alignment(entry.description.flags(), current_size, data.len())?;
5146            let required_size = current_size.max(checked_write_end(current_size, data.len())?);
5147            self.check_path_resize_limits_with_existing(current_size, required_size)?;
5148            let new_len = VirtualFileSystem::append_file(&mut self.filesystem, &path, data)?;
5149            self.update_filesystem_usage_cache_for_resize(&path, current_size, new_len);
5150            self.clear_setid_after_write(pid, &path)?;
5151            entry.description.set_cursor(new_len);
5152            return Ok(data.len());
5153        }
5154
5155        check_direct_io_alignment(entry.description.flags(), cursor, data.len())?;
5156        let required_size = current_size.max(checked_write_end(cursor, data.len())?);
5157        self.check_path_resize_limits_with_existing(current_size, required_size)?;
5158        VirtualFileSystem::pwrite(&mut self.filesystem, &path, data, cursor)?;
5159        self.update_filesystem_usage_cache_for_resize(&path, current_size, required_size);
5160        self.clear_setid_after_write(pid, &path)?;
5161        entry
5162            .description
5163            .set_cursor(cursor.saturating_add(data.len() as u64));
5164        Ok(data.len())
5165    }
5166
5167    /// Probe a pipe write without parking the caller when the pipe is full.
5168    /// Other descriptor kinds retain the regular `fd_write` behavior.
5169    pub fn fd_write_nonblocking_pipe(
5170        &mut self,
5171        requester_driver: &str,
5172        pid: u32,
5173        fd: u32,
5174        data: &[u8],
5175    ) -> KernelResult<usize> {
5176        self.assert_driver_owns(requester_driver, pid)?;
5177        self.resources.check_fd_write_size(data.len())?;
5178        let entry = {
5179            let tables = lock_or_recover(&self.fd_tables);
5180            tables
5181                .get(pid)
5182                .and_then(|table| table.get(fd))
5183                .cloned()
5184                .ok_or_else(|| KernelError::bad_file_descriptor(fd))?
5185        };
5186        if !self.pipes.is_pipe(entry.description.id()) {
5187            return self.fd_write(requester_driver, pid, fd, data);
5188        }
5189        match self
5190            .pipes
5191            .write_with_mode(entry.description.id(), data, true)
5192        {
5193            Ok(bytes) => Ok(bytes),
5194            Err(error) => {
5195                if error.code() == "EPIPE" {
5196                    self.processes.kill(pid as i32, SIGPIPE)?;
5197                }
5198                Err(error.into())
5199            }
5200        }
5201    }
5202
5203    pub fn poll_fds(
5204        &self,
5205        requester_driver: &str,
5206        pid: u32,
5207        fds: Vec<PollFd>,
5208        timeout_ms: i32,
5209    ) -> KernelResult<PollResult> {
5210        let targets = fds
5211            .into_iter()
5212            .map(|poll_fd| PollTargetEntry::fd(poll_fd.fd, poll_fd.events))
5213            .collect::<Vec<_>>();
5214        let result = self.poll_targets(requester_driver, pid, targets, timeout_ms)?;
5215        Ok(PollResult {
5216            ready_count: result.ready_count,
5217            fds: result
5218                .targets
5219                .into_iter()
5220                .map(|target| match target.target {
5221                    PollTarget::Fd(fd) => PollFd {
5222                        fd,
5223                        events: target.events,
5224                        revents: target.revents,
5225                    },
5226                    PollTarget::Socket(_) => unreachable!("fd poll should only include fd targets"),
5227                })
5228                .collect(),
5229        })
5230    }
5231
5232    /// A cloneable, Send handle for waiting on kernel poll-state changes off
5233    /// the kernel owner's thread. Pair with a zero-timeout `poll_fds` /
5234    /// `fd_read_with_timeout_result` re-check on the owning thread.
5235    pub fn poll_wait_handle(&self) -> crate::poll::PollWaitHandle {
5236        crate::poll::PollWaitHandle::new(self.poll_notifier.clone())
5237    }
5238
5239    pub fn poll_targets(
5240        &self,
5241        requester_driver: &str,
5242        pid: u32,
5243        mut targets: Vec<PollTargetEntry>,
5244        timeout_ms: i32,
5245    ) -> KernelResult<PollTargetResult> {
5246        self.assert_driver_owns(requester_driver, pid)?;
5247        if timeout_ms < -1 {
5248            return Err(KernelError::new(
5249                "EINVAL",
5250                format!("invalid poll timeout {timeout_ms}"),
5251            ));
5252        }
5253
5254        let timeout = if timeout_ms < 0 {
5255            None
5256        } else {
5257            Some(Duration::from_millis(timeout_ms as u64))
5258        };
5259        let deadline = timeout.map(|duration| Instant::now() + duration);
5260
5261        loop {
5262            let observed_generation = self.poll_notifier.snapshot();
5263            let ready_count = self.populate_poll_target_revents(pid, &mut targets)?;
5264            if ready_count > 0 || matches!(timeout, Some(duration) if duration.is_zero()) {
5265                return Ok(PollTargetResult {
5266                    ready_count,
5267                    targets,
5268                });
5269            }
5270
5271            let remaining = deadline.map(|target| target.saturating_duration_since(Instant::now()));
5272            if matches!(remaining, Some(duration) if duration.is_zero()) {
5273                return Ok(PollTargetResult {
5274                    ready_count,
5275                    targets,
5276                });
5277            }
5278
5279            if !self
5280                .poll_notifier
5281                .wait_for_change(observed_generation, remaining)
5282            {
5283                return Ok(PollTargetResult {
5284                    ready_count,
5285                    targets,
5286                });
5287            }
5288        }
5289    }
5290
5291    pub fn fd_seek(
5292        &mut self,
5293        requester_driver: &str,
5294        pid: u32,
5295        fd: u32,
5296        offset: i64,
5297        whence: u8,
5298    ) -> KernelResult<u64> {
5299        self.assert_driver_owns(requester_driver, pid)?;
5300        let entry = {
5301            let tables = lock_or_recover(&self.fd_tables);
5302            tables
5303                .get(pid)
5304                .and_then(|table| table.get(fd))
5305                .cloned()
5306                .ok_or_else(|| KernelError::bad_file_descriptor(fd))?
5307        };
5308
5309        if self.pipes.is_pipe(entry.description.id())
5310            || self.ptys.is_pty(entry.description.id())
5311            || self.fd_socket_id(&entry.description).is_some()
5312        {
5313            return Err(KernelError::new("ESPIPE", "illegal seek"));
5314        }
5315
5316        let base = match whence {
5317            SEEK_SET => 0_i128,
5318            SEEK_CUR => i128::from(entry.description.cursor()),
5319            SEEK_END => {
5320                let path = entry.description.path();
5321                let size = if let Some(stat) = entry.description.anonymous_stat() {
5322                    stat.size
5323                } else if is_proc_path(&path) {
5324                    self.proc_stat_from_open_path(Some(pid), &path)?.size
5325                } else {
5326                    self.filesystem.stat(&path)?.size
5327                };
5328                i128::from(size)
5329            }
5330            _ => {
5331                return Err(KernelError::new(
5332                    "EINVAL",
5333                    format!("invalid whence {whence}"),
5334                ));
5335            }
5336        };
5337        let next = base + i128::from(offset);
5338        if next < 0 {
5339            return Err(KernelError::new("EINVAL", "negative seek position"));
5340        }
5341        let next = u64::try_from(next)
5342            .map_err(|_| KernelError::new("EINVAL", "seek position out of range"))?;
5343        entry.description.set_cursor(next);
5344        Ok(next)
5345    }
5346
5347    pub fn fd_pread(
5348        &mut self,
5349        requester_driver: &str,
5350        pid: u32,
5351        fd: u32,
5352        length: usize,
5353        offset: u64,
5354    ) -> KernelResult<Vec<u8>> {
5355        self.assert_driver_owns(requester_driver, pid)?;
5356        self.resources.check_pread_length(length)?;
5357        let entry = {
5358            let tables = lock_or_recover(&self.fd_tables);
5359            tables
5360                .get(pid)
5361                .and_then(|table| table.get(fd))
5362                .cloned()
5363                .ok_or_else(|| KernelError::bad_file_descriptor(fd))?
5364        };
5365        if entry.description.flags() & 0b11 == O_WRONLY {
5366            return Err(KernelError::bad_file_descriptor(fd));
5367        }
5368
5369        if self.pipes.is_pipe(entry.description.id())
5370            || self.ptys.is_pty(entry.description.id())
5371            || self.fd_socket_id(&entry.description).is_some()
5372        {
5373            return Err(KernelError::new("ESPIPE", "illegal seek"));
5374        }
5375
5376        let path = entry.description.path();
5377        if is_proc_path(&path) {
5378            let bytes = self.proc_read_file_from_open_path(Some(pid), &path)?;
5379            let start = usize::try_from(offset)
5380                .map_err(|_| KernelError::new("EINVAL", "pread offset out of range"))?;
5381            let end = start.saturating_add(length).min(bytes.len());
5382            return Ok(if start >= bytes.len() {
5383                Vec::new()
5384            } else {
5385                bytes[start..end].to_vec()
5386            });
5387        }
5388
5389        if let Some(bytes) = entry.description.anonymous_pread(offset, length) {
5390            return Ok(bytes);
5391        }
5392        Ok(VirtualFileSystem::pread(
5393            &mut self.filesystem,
5394            &path,
5395            offset,
5396            length,
5397        )?)
5398    }
5399
5400    pub fn fd_pwrite(
5401        &mut self,
5402        requester_driver: &str,
5403        pid: u32,
5404        fd: u32,
5405        data: &[u8],
5406        offset: u64,
5407    ) -> KernelResult<usize> {
5408        self.assert_driver_owns(requester_driver, pid)?;
5409        self.resources.check_fd_write_size(data.len())?;
5410        let entry = {
5411            let tables = lock_or_recover(&self.fd_tables);
5412            tables
5413                .get(pid)
5414                .and_then(|table| table.get(fd))
5415                .cloned()
5416                .ok_or_else(|| KernelError::bad_file_descriptor(fd))?
5417        };
5418        if self.pipes.is_pipe(entry.description.id())
5419            || self.ptys.is_pty(entry.description.id())
5420            || self.fd_socket_id(&entry.description).is_some()
5421        {
5422            return Err(KernelError::new("ESPIPE", "illegal seek"));
5423        }
5424
5425        let path = entry.description.path();
5426        if let Some(stat) = entry.description.anonymous_stat() {
5427            let required_size = stat.size.max(checked_write_end(offset, data.len())?);
5428            self.check_path_resize_limits_with_existing(stat.size, required_size)?;
5429            if entry.description.flags() & 0b11 == O_RDONLY {
5430                return Err(KernelError::bad_file_descriptor(fd));
5431            }
5432            let new_size = entry
5433                .description
5434                .anonymous_pwrite(offset, data)
5435                .expect("anonymous stat and backing must agree")?;
5436            debug_assert_eq!(new_size, required_size);
5437            return Ok(data.len());
5438        }
5439        self.reject_read_only_resolved_write_path(&path)?;
5440
5441        let current_size = self.current_storage_file_size(&path)?;
5442        let required_size = current_size.max(checked_write_end(offset, data.len())?);
5443        self.check_path_resize_limits_with_existing(current_size, required_size)?;
5444        if entry.description.flags() & 0b11 == O_RDONLY {
5445            return Err(KernelError::bad_file_descriptor(fd));
5446        }
5447        VirtualFileSystem::pwrite(&mut self.filesystem, &path, data.to_vec(), offset)?;
5448        self.update_filesystem_usage_cache_for_resize(&path, current_size, required_size);
5449        Ok(data.len())
5450    }
5451
5452    pub fn fd_chmod(
5453        &mut self,
5454        requester_driver: &str,
5455        pid: u32,
5456        fd: u32,
5457        mode: u32,
5458    ) -> KernelResult<()> {
5459        let description = self.description_for_fd(requester_driver, pid, fd)?;
5460        if description.detached_chmod(mode) {
5461            return Ok(());
5462        }
5463        if self.pipes.is_pipe(description.id()) {
5464            self.pipes.chmod(description.id(), mode)?;
5465            return Ok(());
5466        }
5467        if let Some(socket) = lock_or_recover(&self.fd_sockets).get_mut(&description.id()) {
5468            socket.mode = mode & 0o7777;
5469            return Ok(());
5470        }
5471        let path = description.path();
5472        self.chmod(&path, mode)
5473    }
5474
5475    pub fn fd_chmod_for_process(
5476        &mut self,
5477        requester_driver: &str,
5478        pid: u32,
5479        fd: u32,
5480        mode: u32,
5481    ) -> KernelResult<()> {
5482        let identity = self.process_identity(requester_driver, pid)?;
5483        let stat = self.dev_fd_stat(requester_driver, pid, fd)?;
5484        if identity.euid != 0 && identity.euid != stat.uid {
5485            return Err(KernelError::new(
5486                "EPERM",
5487                format!("operation not permitted, process does not own fd {fd}"),
5488            ));
5489        }
5490        self.fd_chmod(requester_driver, pid, fd, mode)
5491    }
5492
5493    pub fn fd_chown_for_process(
5494        &mut self,
5495        requester_driver: &str,
5496        pid: u32,
5497        fd: u32,
5498        uid: u32,
5499        gid: u32,
5500    ) -> KernelResult<()> {
5501        let description = self.description_for_fd(requester_driver, pid, fd)?;
5502        let identity = self.process_identity(requester_driver, pid)?;
5503        let stat = self.dev_fd_stat(requester_driver, pid, fd)?;
5504        let (next_uid, next_gid) =
5505            validate_chown_request(&identity, &stat, uid, gid, &format!("fd {fd}"))?;
5506        let changed_mode = linux_chown_cleared_mode(&stat);
5507        if description.detached_chown(next_uid, next_gid, changed_mode) {
5508            return Ok(());
5509        }
5510        if self.pipes.is_pipe(description.id()) {
5511            self.pipes.set_owner(description.id(), next_uid, next_gid)?;
5512            return Ok(());
5513        }
5514        if let Some(socket) = lock_or_recover(&self.fd_sockets).get_mut(&description.id()) {
5515            socket.uid = next_uid;
5516            socket.gid = next_gid;
5517            return Ok(());
5518        }
5519
5520        let fd_type = self.fd_stat(requester_driver, pid, fd)?.filetype;
5521        if !matches!(fd_type, FILETYPE_REGULAR_FILE | FILETYPE_DIRECTORY) {
5522            // Character devices have no mutable descriptor-owned inode in
5523            // AgentOS. The fixed unprivileged guest can only reach this branch
5524            // for the Linux no-op (-1, -1) request.
5525            return Ok(());
5526        }
5527        let path = description.path();
5528        self.reject_read_only_resolved_write_path(&path)?;
5529        self.filesystem.chown(&path, next_uid, next_gid)?;
5530        if let Some(mode) = changed_mode {
5531            self.filesystem.chmod(&path, mode)?;
5532        }
5533        Ok(())
5534    }
5535
5536    pub fn fd_dup(&mut self, requester_driver: &str, pid: u32, fd: u32) -> KernelResult<u32> {
5537        self.assert_driver_owns(requester_driver, pid)?;
5538        {
5539            let tables = lock_or_recover(&self.fd_tables);
5540            let table = tables
5541                .get(pid)
5542                .ok_or_else(|| KernelError::no_such_process(pid))?;
5543            table
5544                .get(fd)
5545                .ok_or_else(|| KernelError::bad_file_descriptor(fd))?;
5546        }
5547        self.resources
5548            .check_fd_allocation(&self.resource_snapshot(), 1)?;
5549        let mut tables = lock_or_recover(&self.fd_tables);
5550        let table = tables
5551            .get_mut(pid)
5552            .ok_or_else(|| KernelError::no_such_process(pid))?;
5553        Ok(table.dup(fd)?)
5554    }
5555
5556    pub fn fd_dup2(
5557        &mut self,
5558        requester_driver: &str,
5559        pid: u32,
5560        old_fd: u32,
5561        new_fd: u32,
5562    ) -> KernelResult<()> {
5563        self.assert_driver_owns(requester_driver, pid)?;
5564        let (replaced, needs_fd_growth) = {
5565            let tables = lock_or_recover(&self.fd_tables);
5566            let table = tables
5567                .get(pid)
5568                .ok_or_else(|| KernelError::no_such_process(pid))?;
5569            table
5570                .get(old_fd)
5571                .ok_or_else(|| KernelError::bad_file_descriptor(old_fd))?;
5572            let replaced = if old_fd == new_fd {
5573                None
5574            } else {
5575                table.get(new_fd).cloned()
5576            };
5577            if new_fd as usize >= table.max_fds() {
5578                return Err(KernelError::bad_file_descriptor(new_fd));
5579            }
5580            let needs_fd_growth = old_fd != new_fd && replaced.is_none();
5581            (replaced, needs_fd_growth)
5582        };
5583        if needs_fd_growth {
5584            self.resources
5585                .check_fd_allocation(&self.resource_snapshot(), 1)?;
5586        }
5587        {
5588            let mut tables = lock_or_recover(&self.fd_tables);
5589            let table = tables
5590                .get_mut(pid)
5591                .ok_or_else(|| KernelError::no_such_process(pid))?;
5592            table.dup2(old_fd, new_fd)?;
5593        }
5594
5595        if let Some(entry) = replaced {
5596            if let Some(target) = entry.description.lock_target() {
5597                self.file_locks.release_process_target(pid, target);
5598            }
5599            self.close_special_resource_if_needed(&entry.description, entry.filetype);
5600        }
5601        Ok(())
5602    }
5603
5604    pub fn fd_close(&mut self, requester_driver: &str, pid: u32, fd: u32) -> KernelResult<()> {
5605        self.assert_driver_owns(requester_driver, pid)?;
5606        let (description, filetype, lock_target) = {
5607            let mut tables = lock_or_recover(&self.fd_tables);
5608            let table = tables
5609                .get_mut(pid)
5610                .ok_or_else(|| KernelError::no_such_process(pid))?;
5611            let entry = table
5612                .get(fd)
5613                .cloned()
5614                .ok_or_else(|| KernelError::bad_file_descriptor(fd))?;
5615            table.close(fd);
5616            let lock_target = entry.description.lock_target();
5617            (entry.description, entry.filetype, lock_target)
5618        };
5619        if let Some(target) = lock_target {
5620            self.file_locks.release_process_target(pid, target);
5621        }
5622        self.close_special_resource_if_needed(&description, filetype);
5623        self.cleanup_unnamed_file_if_closed(&description)?;
5624        Ok(())
5625    }
5626
5627    /// Commit the descriptor half of exec by closing every descriptor still
5628    /// marked `FD_CLOEXEC` after POSIX spawn file actions have completed.
5629    pub fn close_process_cloexec_fds(
5630        &mut self,
5631        requester_driver: &str,
5632        pid: u32,
5633    ) -> KernelResult<()> {
5634        self.assert_driver_owns(requester_driver, pid)?;
5635        let closed_entries = {
5636            let mut tables = lock_or_recover(&self.fd_tables);
5637            let table = tables
5638                .get_mut(pid)
5639                .ok_or_else(|| KernelError::no_such_process(pid))?;
5640            let fds = table.close_on_exec_fds();
5641            let mut closed_entries = Vec::with_capacity(fds.len());
5642            for fd in fds {
5643                let entry = table
5644                    .get(fd)
5645                    .cloned()
5646                    .expect("close-on-exec snapshot must reference an open descriptor");
5647                let closed = table.close(fd);
5648                debug_assert!(closed);
5649                closed_entries.push((entry.description, entry.filetype));
5650            }
5651            closed_entries
5652        };
5653        for (description, filetype) in closed_entries {
5654            if let Some(target) = description.lock_target() {
5655                self.file_locks.release_process_target(pid, target);
5656            }
5657            self.close_special_resource_if_needed(&description, filetype);
5658        }
5659        Ok(())
5660    }
5661
5662    pub fn fd_fcntl(
5663        &mut self,
5664        requester_driver: &str,
5665        pid: u32,
5666        fd: u32,
5667        command: u32,
5668        arg: u32,
5669    ) -> KernelResult<u32> {
5670        self.assert_driver_owns(requester_driver, pid)?;
5671        if command == F_DUPFD {
5672            {
5673                let tables = lock_or_recover(&self.fd_tables);
5674                let table = tables
5675                    .get(pid)
5676                    .ok_or_else(|| KernelError::no_such_process(pid))?;
5677                table
5678                    .get(fd)
5679                    .ok_or_else(|| KernelError::bad_file_descriptor(fd))?;
5680                if arg as usize >= table.max_fds() {
5681                    return Err(KernelError::new(
5682                        "EINVAL",
5683                        format!("fd {arg} exceeds process fd limit"),
5684                    ));
5685                }
5686            }
5687            self.resources
5688                .check_fd_allocation(&self.resource_snapshot(), 1)?;
5689        }
5690        let mut tables = lock_or_recover(&self.fd_tables);
5691        let table = tables
5692            .get_mut(pid)
5693            .ok_or_else(|| KernelError::no_such_process(pid))?;
5694        let result = table.fcntl(fd, command, arg)?;
5695        if command == F_DUPFD {
5696            self.poll_notifier.notify();
5697        }
5698        Ok(result)
5699    }
5700
5701    pub fn fd_named_pipe_peer_ready(
5702        &self,
5703        requester_driver: &str,
5704        pid: u32,
5705        fd: u32,
5706    ) -> KernelResult<bool> {
5707        self.assert_driver_owns(requester_driver, pid)?;
5708        let entry = lock_or_recover(&self.fd_tables)
5709            .get(pid)
5710            .and_then(|table| table.get(fd))
5711            .cloned()
5712            .ok_or_else(|| KernelError::bad_file_descriptor(fd))?;
5713        if entry.filetype != FILETYPE_PIPE {
5714            return Err(KernelError::new(
5715                "EINVAL",
5716                format!("fd {fd} is not a named pipe"),
5717            ));
5718        }
5719        self.pipes
5720            .named_pipe_peer_ready(entry.description.id())?
5721            .ok_or_else(|| KernelError::new("EINVAL", format!("fd {fd} is an anonymous pipe")))
5722    }
5723
5724    pub fn fd_flock(
5725        &self,
5726        requester_driver: &str,
5727        pid: u32,
5728        fd: u32,
5729        operation: u32,
5730    ) -> KernelResult<()> {
5731        self.assert_driver_owns(requester_driver, pid)?;
5732        let entry = {
5733            let tables = lock_or_recover(&self.fd_tables);
5734            tables
5735                .get(pid)
5736                .and_then(|table| table.get(fd))
5737                .cloned()
5738                .ok_or_else(|| KernelError::bad_file_descriptor(fd))?
5739        };
5740
5741        if !matches!(entry.filetype, FILETYPE_REGULAR_FILE | FILETYPE_DIRECTORY) {
5742            return Err(KernelError::new(
5743                "EBADF",
5744                format!("file descriptor {fd} does not support advisory locking"),
5745            ));
5746        }
5747
5748        let target = entry.description.lock_target().ok_or_else(|| {
5749            KernelError::new(
5750                "EBADF",
5751                format!("file descriptor {fd} is missing advisory lock metadata"),
5752            )
5753        })?;
5754        let operation = FlockOperation::from_bits(operation)?;
5755        self.file_locks
5756            .apply(entry.description.id(), target, operation)?;
5757        Ok(())
5758    }
5759
5760    // The explicit range and query fields mirror the guest fcntl lock shape.
5761    #[allow(clippy::too_many_arguments)]
5762    pub fn fd_record_lock(
5763        &self,
5764        requester_driver: &str,
5765        pid: u32,
5766        fd: u32,
5767        lock_type: RecordLockType,
5768        start: u64,
5769        length: u64,
5770        query: bool,
5771    ) -> KernelResult<Option<RecordLock>> {
5772        self.fd_record_lock_impl(
5773            requester_driver,
5774            pid,
5775            fd,
5776            lock_type,
5777            start,
5778            length,
5779            query,
5780            false,
5781        )
5782    }
5783
5784    pub fn fd_record_lock_wait(
5785        &self,
5786        requester_driver: &str,
5787        pid: u32,
5788        fd: u32,
5789        lock_type: RecordLockType,
5790        start: u64,
5791        length: u64,
5792    ) -> KernelResult<()> {
5793        self.fd_record_lock_impl(
5794            requester_driver,
5795            pid,
5796            fd,
5797            lock_type,
5798            start,
5799            length,
5800            false,
5801            true,
5802        )
5803        .map(|_| ())
5804    }
5805
5806    pub fn fd_record_lock_cancel(&self, requester_driver: &str, pid: u32) -> KernelResult<()> {
5807        self.assert_driver_owns(requester_driver, pid)?;
5808        self.file_locks.cancel_record_lock_wait(pid);
5809        Ok(())
5810    }
5811
5812    #[allow(clippy::too_many_arguments)]
5813    fn fd_record_lock_impl(
5814        &self,
5815        requester_driver: &str,
5816        pid: u32,
5817        fd: u32,
5818        lock_type: RecordLockType,
5819        start: u64,
5820        length: u64,
5821        query: bool,
5822        blocking: bool,
5823    ) -> KernelResult<Option<RecordLock>> {
5824        self.assert_driver_owns(requester_driver, pid)?;
5825        let entry = {
5826            let tables = lock_or_recover(&self.fd_tables);
5827            tables
5828                .get(pid)
5829                .and_then(|table| table.get(fd))
5830                .cloned()
5831                .ok_or_else(|| KernelError::bad_file_descriptor(fd))?
5832        };
5833        if !matches!(entry.filetype, FILETYPE_REGULAR_FILE | FILETYPE_DIRECTORY) {
5834            return Err(KernelError::new(
5835                "EBADF",
5836                format!("file descriptor {fd} does not support POSIX record locks"),
5837            ));
5838        }
5839        if query && lock_type == RecordLockType::Unlock {
5840            return Err(KernelError::new(
5841                "EINVAL",
5842                "F_GETLK requires a read or write lock type",
5843            ));
5844        }
5845        if !query {
5846            let access_mode = entry.description.flags() & 0o3;
5847            if (lock_type == RecordLockType::Read && access_mode == O_WRONLY)
5848                || (lock_type == RecordLockType::Write && access_mode == O_RDONLY)
5849            {
5850                return Err(KernelError::new(
5851                    "EBADF",
5852                    format!("file descriptor {fd} access mode is incompatible with record lock"),
5853                ));
5854            }
5855        }
5856        let target = entry.description.lock_target().ok_or_else(|| {
5857            KernelError::new(
5858                "EBADF",
5859                format!("file descriptor {fd} is missing record lock metadata"),
5860            )
5861        })?;
5862        let request = RecordLock::new(lock_type, start, length, pid)?;
5863        if query {
5864            Ok(self.file_locks.query_record_lock(target, request))
5865        } else if blocking {
5866            self.file_locks.set_blocking_record_lock(target, request)?;
5867            Ok(None)
5868        } else {
5869            self.file_locks.set_record_lock(target, request)?;
5870            Ok(None)
5871        }
5872    }
5873
5874    pub fn fd_stat(&self, requester_driver: &str, pid: u32, fd: u32) -> KernelResult<FdStat> {
5875        self.assert_driver_owns(requester_driver, pid)?;
5876        let tables = lock_or_recover(&self.fd_tables);
5877        Ok(tables
5878            .get(pid)
5879            .ok_or_else(|| KernelError::no_such_process(pid))?
5880            .stat(fd)?)
5881    }
5882
5883    /// Synchronize a descriptor's committed data and metadata. The in-memory
5884    /// VFS applies writes synchronously, so successful regular-file and
5885    /// directory syncs require no extra flush. Descriptor validation and
5886    /// Linux type errors still happen here rather than being silently ignored.
5887    pub fn fd_sync(&self, requester_driver: &str, pid: u32, fd: u32) -> KernelResult<()> {
5888        let stat = self.fd_stat(requester_driver, pid, fd)?;
5889        match stat.filetype {
5890            FILETYPE_REGULAR_FILE | FILETYPE_DIRECTORY => Ok(()),
5891            _ => Err(KernelError::new(
5892                "EINVAL",
5893                format!("file descriptor {fd} cannot be synchronized"),
5894            )),
5895        }
5896    }
5897
5898    pub fn fd_read_dir_with_types(
5899        &mut self,
5900        requester_driver: &str,
5901        pid: u32,
5902        fd: u32,
5903    ) -> KernelResult<Vec<ProcessFdDirEntry>> {
5904        let stat = self.fd_stat(requester_driver, pid, fd)?;
5905        if stat.filetype != FILETYPE_DIRECTORY {
5906            return Err(KernelError::new(
5907                "ENOTDIR",
5908                format!("file descriptor {fd} is not a directory"),
5909            ));
5910        }
5911        let description = self.description_for_fd(requester_driver, pid, fd)?;
5912        if description.detached_directory_stat().is_some() {
5913            // Linux getdents64(2) on an open directory that has since been
5914            // removed returns immediate EOF. The OFD remains valid for fstat,
5915            // but the unlinked dentry no longer enumerates even synthetic dots.
5916            return Ok(Vec::new());
5917        }
5918        let path = self.fd_path(requester_driver, pid, fd)?;
5919        let children = self.read_dir_with_types_for_process(requester_driver, pid, &path)?;
5920        self.resources
5921            .check_readdir_entries(children.len().saturating_add(2))?;
5922
5923        // fd_readdir is the Linux-like descriptor traversal surface. Unlike
5924        // path-based Node readdir, it exposes the synthetic current/parent
5925        // entries and inode identities used by libc readdir/telldir/seekdir.
5926        let current_stat = self.stat_internal(Some(pid), &path)?;
5927        let parent = parent_path(&path);
5928        let parent_stat = self.stat_internal(Some(pid), &parent)?;
5929        let mut entries = Vec::with_capacity(children.len().saturating_add(2));
5930        entries.push(ProcessFdDirEntry {
5931            name: String::from("."),
5932            ino: required_dirent_ino(&path, current_stat.ino)?,
5933            is_directory: true,
5934            is_symbolic_link: false,
5935        });
5936        entries.push(ProcessFdDirEntry {
5937            name: String::from(".."),
5938            ino: required_dirent_ino(&parent, parent_stat.ino)?,
5939            is_directory: true,
5940            is_symbolic_link: false,
5941        });
5942        for child in children {
5943            let child_path = join_child_path(&path, &child.name);
5944            let child_stat = self.lstat_internal(Some(pid), &child_path)?;
5945            entries.push(ProcessFdDirEntry {
5946                name: child.name,
5947                ino: required_dirent_ino(&child_path, child_stat.ino)?,
5948                is_directory: child.is_directory,
5949                is_symbolic_link: child.is_symbolic_link,
5950            });
5951        }
5952        Ok(entries)
5953    }
5954
5955    pub fn fd_path(&self, requester_driver: &str, pid: u32, fd: u32) -> KernelResult<String> {
5956        let description = self.description_for_fd(requester_driver, pid, fd)?;
5957        Ok(description.path())
5958    }
5959
5960    pub fn isatty(&self, requester_driver: &str, pid: u32, fd: u32) -> KernelResult<bool> {
5961        self.assert_driver_owns(requester_driver, pid)?;
5962        let entry = {
5963            let tables = lock_or_recover(&self.fd_tables);
5964            tables
5965                .get(pid)
5966                .and_then(|table| table.get(fd))
5967                .cloned()
5968                .ok_or_else(|| KernelError::bad_file_descriptor(fd))?
5969        };
5970        Ok(self.ptys.is_slave(entry.description.id()))
5971    }
5972
5973    pub fn pty_window_size(
5974        &self,
5975        requester_driver: &str,
5976        pid: u32,
5977        fd: u32,
5978    ) -> KernelResult<PtyWindowSize> {
5979        let description = self.description_for_fd(requester_driver, pid, fd)?;
5980        Ok(self.ptys.window_size(description.id())?)
5981    }
5982
5983    pub fn pty_set_discipline(
5984        &self,
5985        requester_driver: &str,
5986        pid: u32,
5987        fd: u32,
5988        config: LineDisciplineConfig,
5989    ) -> KernelResult<()> {
5990        let description = self.description_for_fd(requester_driver, pid, fd)?;
5991        self.ptys.set_discipline(description.id(), config)?;
5992        Ok(())
5993    }
5994
5995    /// Toggle PTY raw mode and, when the caller belongs to the terminal's
5996    /// foreground process group, create a generation-scoped recovery lease.
5997    /// The lease can be released during process cleanup without overwriting a
5998    /// newer terminal mutation from another process.
5999    pub fn pty_set_raw_mode(
6000        &self,
6001        requester_driver: &str,
6002        pid: u32,
6003        fd: u32,
6004        enabled: bool,
6005    ) -> KernelResult<Option<u64>> {
6006        let description = self.description_for_fd(requester_driver, pid, fd)?;
6007        let foreground_pgid = self.ptys.get_foreground_pgid(description.id())?;
6008        let process_pgid = self.processes.getpgid(pid)?;
6009        let lease_owner =
6010            (!enabled || foreground_pgid == 0 || foreground_pgid == process_pgid).then_some(pid);
6011        Ok(self
6012            .ptys
6013            .set_raw_mode(description.id(), lease_owner, enabled)?)
6014    }
6015
6016    /// Release a raw-mode recovery lease through any descriptor for the same
6017    /// PTY. `fd` normally belongs to the terminal owner because the exiting
6018    /// child may already have closed its own descriptor zero.
6019    pub fn pty_release_raw_mode(
6020        &self,
6021        requester_driver: &str,
6022        descriptor_owner_pid: u32,
6023        fd: u32,
6024        raw_mode_owner_pid: u32,
6025        generation: u64,
6026    ) -> KernelResult<bool> {
6027        self.assert_driver_owns(requester_driver, raw_mode_owner_pid)?;
6028        let description = self.description_for_fd(requester_driver, descriptor_owner_pid, fd)?;
6029        Ok(self
6030            .ptys
6031            .release_raw_mode(description.id(), raw_mode_owner_pid, generation)?)
6032    }
6033
6034    pub fn pty_set_foreground_pgid(
6035        &self,
6036        requester_driver: &str,
6037        pid: u32,
6038        fd: u32,
6039        pgid: u32,
6040    ) -> KernelResult<()> {
6041        let description = self.description_for_fd(requester_driver, pid, fd)?;
6042        let requester_sid = self.processes.getsid(pid)?;
6043        let group = self
6044            .processes
6045            .list_processes()
6046            .into_values()
6047            .find(|process| process.pgid == pgid && process.status != ProcessStatus::Exited)
6048            .ok_or_else(|| KernelError::new("ESRCH", format!("no such process group {pgid}")))?;
6049        if group.sid != requester_sid {
6050            return Err(KernelError::permission_denied(
6051                "cannot set foreground process group in different session",
6052            ));
6053        }
6054        self.ptys.set_foreground_pgid(description.id(), pgid)?;
6055        Ok(())
6056    }
6057
6058    pub fn tcgetattr(&self, requester_driver: &str, pid: u32, fd: u32) -> KernelResult<Termios> {
6059        let description = self.description_for_fd(requester_driver, pid, fd)?;
6060        Ok(self.ptys.get_termios(description.id())?)
6061    }
6062
6063    pub fn tcsetattr(
6064        &self,
6065        requester_driver: &str,
6066        pid: u32,
6067        fd: u32,
6068        termios: PartialTermios,
6069    ) -> KernelResult<()> {
6070        let description = self.description_for_fd(requester_driver, pid, fd)?;
6071        self.ptys.set_termios(description.id(), termios)?;
6072        Ok(())
6073    }
6074
6075    pub fn tcgetpgrp(&self, requester_driver: &str, pid: u32, fd: u32) -> KernelResult<u32> {
6076        let description = self.description_for_fd(requester_driver, pid, fd)?;
6077        Ok(self.ptys.get_foreground_pgid(description.id())?)
6078    }
6079
6080    pub fn pty_resize(
6081        &self,
6082        requester_driver: &str,
6083        pid: u32,
6084        fd: u32,
6085        cols: u16,
6086        rows: u16,
6087    ) -> KernelResult<()> {
6088        let description = self.description_for_fd(requester_driver, pid, fd)?;
6089        let target_pgid = self.ptys.resize(description.id(), cols, rows)?;
6090        if let Some(pgid) = target_pgid {
6091            match self.processes.kill(-(pgid as i32), SIGWINCH) {
6092                Ok(()) => {}
6093                Err(error) if error.code() == "ESRCH" => {}
6094                Err(error) => return Err(error.into()),
6095            }
6096        }
6097        Ok(())
6098    }
6099
6100    pub fn signal_process(
6101        &self,
6102        requester_driver: &str,
6103        pid: i32,
6104        signal: i32,
6105    ) -> KernelResult<()> {
6106        if pid < 0 {
6107            let pgid = pid.unsigned_abs();
6108            let members = self
6109                .processes
6110                .list_processes()
6111                .into_values()
6112                .filter(|process| process.pgid == pgid && process.status != ProcessStatus::Exited)
6113                .collect::<Vec<_>>();
6114            if members.is_empty() {
6115                self.processes.kill(pid, signal)?;
6116                return Ok(());
6117            }
6118            if let Some(process) = members
6119                .iter()
6120                .find(|process| process.driver != requester_driver)
6121            {
6122                return Err(KernelError::permission_denied(format!(
6123                    "driver \"{requester_driver}\" does not own process group {pgid} containing PID {}",
6124                    process.pid
6125                )));
6126            }
6127            self.processes.kill(pid, signal)?;
6128            return Ok(());
6129        }
6130
6131        let pid = u32::try_from(pid)
6132            .map_err(|_| KernelError::new("EINVAL", format!("invalid pid {pid}")))?;
6133        self.assert_driver_owns(requester_driver, pid)?;
6134        self.processes.kill(pid as i32, signal)?;
6135        Ok(())
6136    }
6137
6138    pub fn kill_process(&self, requester_driver: &str, pid: u32, signal: i32) -> KernelResult<()> {
6139        let pid = i32::try_from(pid)
6140            .map_err(|_| KernelError::new("EINVAL", format!("pid {pid} exceeds i32::MAX")))?;
6141        self.signal_process(requester_driver, pid, signal)
6142    }
6143
6144    pub fn setpgid(&self, requester_driver: &str, pid: u32, pgid: u32) -> KernelResult<()> {
6145        self.assert_driver_owns(requester_driver, pid)?;
6146        let target_pgid = if pgid == 0 { pid } else { pgid };
6147        if target_pgid != pid {
6148            if let Some(group_owner) =
6149                self.processes
6150                    .list_processes()
6151                    .into_values()
6152                    .find(|process| {
6153                        process.pgid == target_pgid && process.status == ProcessStatus::Running
6154                    })
6155            {
6156                if group_owner.driver != requester_driver {
6157                    return Err(KernelError::permission_denied(format!(
6158                        "driver \"{requester_driver}\" cannot join process group {target_pgid} owned by \"{}\"",
6159                        group_owner.driver
6160                    )));
6161                }
6162            }
6163        }
6164        self.processes.setpgid(pid, pgid)?;
6165        Ok(())
6166    }
6167
6168    pub fn getpgid(&self, requester_driver: &str, pid: u32) -> KernelResult<u32> {
6169        self.assert_driver_owns(requester_driver, pid)?;
6170        Ok(self.processes.getpgid(pid)?)
6171    }
6172
6173    pub fn getpid(&self, requester_driver: &str, pid: u32) -> KernelResult<u32> {
6174        self.assert_driver_owns(requester_driver, pid)?;
6175        Ok(pid)
6176    }
6177
6178    pub fn sigprocmask(
6179        &self,
6180        requester_driver: &str,
6181        pid: u32,
6182        how: SigmaskHow,
6183        set: SignalSet,
6184    ) -> KernelResult<SignalSet> {
6185        self.assert_driver_owns(requester_driver, pid)?;
6186        Ok(self.processes.sigprocmask(pid, how, set)?)
6187    }
6188
6189    pub fn sigpending(&self, requester_driver: &str, pid: u32) -> KernelResult<SignalSet> {
6190        self.assert_driver_owns(requester_driver, pid)?;
6191        Ok(self.processes.sigpending(pid)?)
6192    }
6193
6194    pub fn getppid(&self, requester_driver: &str, pid: u32) -> KernelResult<u32> {
6195        self.assert_driver_owns(requester_driver, pid)?;
6196        Ok(self.processes.getppid(pid)?)
6197    }
6198
6199    pub fn setsid(&self, requester_driver: &str, pid: u32) -> KernelResult<u32> {
6200        self.assert_driver_owns(requester_driver, pid)?;
6201        Ok(self.processes.setsid(pid)?)
6202    }
6203
6204    pub fn getsid(&self, requester_driver: &str, pid: u32) -> KernelResult<u32> {
6205        self.assert_driver_owns(requester_driver, pid)?;
6206        Ok(self.processes.getsid(pid)?)
6207    }
6208
6209    pub fn dev_fd_read_dir(&self, requester_driver: &str, pid: u32) -> KernelResult<Vec<String>> {
6210        self.assert_driver_owns(requester_driver, pid)?;
6211        let tables = lock_or_recover(&self.fd_tables);
6212        let table = tables
6213            .get(pid)
6214            .ok_or_else(|| KernelError::no_such_process(pid))?;
6215        let entry_count = table.len();
6216        self.resources.check_readdir_entries(entry_count)?;
6217        Ok(table.iter().map(|entry| entry.fd.to_string()).collect())
6218    }
6219
6220    pub fn dev_fd_stat(
6221        &mut self,
6222        requester_driver: &str,
6223        pid: u32,
6224        fd: u32,
6225    ) -> KernelResult<VirtualStat> {
6226        self.assert_driver_owns(requester_driver, pid)?;
6227        let entry = {
6228            let tables = lock_or_recover(&self.fd_tables);
6229            tables
6230                .get(pid)
6231                .and_then(|table| table.get(fd))
6232                .cloned()
6233                .ok_or_else(|| KernelError::bad_file_descriptor(fd))?
6234        };
6235
6236        if let Some(pipe_id) = self.pipes.pipe_id_for(entry.description.id()) {
6237            let metadata = self
6238                .pipes
6239                .metadata(entry.description.id())
6240                .expect("live pipe description must retain inode metadata");
6241            let mut stat = synthetic_special_file_stat(pipe_id, 0o010000 | metadata.mode, 13);
6242            stat.uid = metadata.uid;
6243            stat.gid = metadata.gid;
6244            return Ok(stat);
6245        }
6246
6247        if let Some(socket) = lock_or_recover(&self.fd_sockets).get(&entry.description.id()) {
6248            let mut stat =
6249                synthetic_special_file_stat(entry.description.id(), 0o140000 | socket.mode, 9);
6250            stat.uid = socket.uid;
6251            stat.gid = socket.gid;
6252            return Ok(stat);
6253        }
6254
6255        if self.ptys.is_pty(entry.description.id()) {
6256            return Ok(synthetic_character_device_stat(entry.description.id()));
6257        }
6258
6259        if let Some(stat) = entry.description.anonymous_stat() {
6260            return Ok(stat);
6261        }
6262        if let Some(stat) = entry.description.detached_directory_stat() {
6263            return Ok(stat);
6264        }
6265        let path = entry.description.path();
6266        if is_proc_path(&path) {
6267            return self.proc_stat_from_open_path(Some(pid), &path);
6268        }
6269
6270        Ok(self.filesystem.stat(&path)?)
6271    }
6272
6273    pub fn dispose(&mut self) -> KernelResult<()> {
6274        if self.terminated {
6275            return Ok(());
6276        }
6277
6278        dispose_kernel_vm_resources(self);
6279        Ok(())
6280    }
6281
6282    fn prepare_fd_open(
6283        &mut self,
6284        path: &str,
6285        flags: u32,
6286        mode: Option<u32>,
6287    ) -> KernelResult<(u8, Option<FileLockTarget>)> {
6288        if flags & O_CREAT != 0 && flags & O_EXCL != 0 {
6289            self.check_write_file_limits(path, 0)?;
6290            VirtualFileSystem::create_file_exclusive_with_mode(
6291                &mut self.filesystem,
6292                path,
6293                Vec::new(),
6294                mode,
6295            )?;
6296            self.update_filesystem_usage_cache_for_inode_create(path, 0);
6297            let stat = VirtualFileSystem::stat(&mut self.filesystem, path)?;
6298            return Ok((
6299                filetype_for_path(path, &stat),
6300                Some(FileLockTarget::new(stat.dev, stat.ino)),
6301            ));
6302        }
6303
6304        let exists = self.filesystem.exists(path)?;
6305        if exists {
6306            let existing_stat = VirtualFileSystem::stat(&mut self.filesystem, path)?;
6307            if existing_stat.mode & 0o170000 == 0o140000 {
6308                return Err(KernelError::new(
6309                    "ENXIO",
6310                    format!("cannot open Unix socket pathname '{path}'"),
6311                ));
6312            }
6313            if flags & O_TRUNC != 0 {
6314                let existing_size = self.current_storage_file_size(path)?;
6315                self.check_path_resize_limits_with_existing(existing_size, 0)?;
6316                VirtualFileSystem::truncate(&mut self.filesystem, path, 0)?;
6317                self.update_filesystem_usage_cache_for_resize(path, existing_size, 0);
6318            }
6319        } else if flags & O_CREAT != 0 {
6320            self.check_write_file_limits(path, 0)?;
6321            VirtualFileSystem::write_file_with_mode(&mut self.filesystem, path, Vec::new(), mode)?;
6322            self.update_filesystem_usage_cache_for_inode_create(path, 0);
6323        } else {
6324            let _ = VirtualFileSystem::stat(&mut self.filesystem, path)?;
6325            unreachable!("stat should return an error when opening a missing path");
6326        }
6327
6328        let stat = VirtualFileSystem::stat(&mut self.filesystem, path)?;
6329        Ok((
6330            filetype_for_path(path, &stat),
6331            Some(FileLockTarget::new(stat.dev, stat.ino)),
6332        ))
6333    }
6334
6335    fn validate_fd_open_flags(&mut self, pid: u32, path: &str, flags: u32) -> KernelResult<()> {
6336        if flags & O_DIRECTORY != 0 && flags & O_CREAT != 0 {
6337            return Err(KernelError::new(
6338                "EINVAL",
6339                format!("O_DIRECTORY and O_CREAT cannot be combined for '{path}'"),
6340            ));
6341        }
6342
6343        if let Some(existing_fd) = parse_dev_fd_path(path)? {
6344            let filetype = lock_or_recover(&self.fd_tables)
6345                .get(pid)
6346                .and_then(|table| table.get(existing_fd))
6347                .map(|entry| entry.filetype)
6348                .ok_or_else(|| {
6349                    KernelError::new(
6350                        "ENOENT",
6351                        format!("no such file or directory, open '{path}'"),
6352                    )
6353                })?;
6354            if flags & O_CREAT != 0 && flags & O_EXCL != 0 {
6355                return Err(KernelError::new(
6356                    "EEXIST",
6357                    format!("file already exists, open '{path}'"),
6358                ));
6359            }
6360            if flags & O_NOFOLLOW != 0 {
6361                return Err(KernelError::new(
6362                    "ELOOP",
6363                    format!("symbolic link not followed, open '{path}'"),
6364                ));
6365            }
6366            if flags & O_DIRECTORY != 0 && filetype != FILETYPE_DIRECTORY {
6367                return Err(KernelError::new(
6368                    "ENOTDIR",
6369                    format!("not a directory, open '{path}'"),
6370                ));
6371            }
6372            return Ok(());
6373        }
6374
6375        if flags & O_DIRECTORY != 0 {
6376            let stat = if flags & O_NOFOLLOW != 0 {
6377                self.lstat_internal(Some(pid), path)?
6378            } else {
6379                self.stat_internal(Some(pid), path)?
6380            };
6381            if !stat.is_directory || stat.is_symbolic_link {
6382                return Err(KernelError::new(
6383                    "ENOTDIR",
6384                    format!("not a directory, open '{path}'"),
6385                ));
6386            }
6387        } else if flags & O_NOFOLLOW != 0 && flags & (O_CREAT | O_EXCL) != (O_CREAT | O_EXCL) {
6388            match self.lstat_internal(Some(pid), path) {
6389                Ok(stat) if stat.is_symbolic_link => {
6390                    return Err(KernelError::new(
6391                        "ELOOP",
6392                        format!("symbolic link not followed, open '{path}'"),
6393                    ));
6394                }
6395                Ok(_) => {}
6396                Err(error) if error.code() == "ENOENT" && flags & O_CREAT != 0 => {}
6397                Err(error) => return Err(error),
6398            }
6399        }
6400
6401        Ok(())
6402    }
6403
6404    fn reject_unix_socket_data_path(&mut self, path: &str, code: &'static str) -> KernelResult<()> {
6405        if self
6406            .storage_stat(path)?
6407            .is_some_and(|stat| stat.mode & 0o170000 == 0o140000)
6408        {
6409            return Err(KernelError::new(
6410                code,
6411                format!("Unix socket pathname does not support file data I/O, '{path}'"),
6412            ));
6413        }
6414        Ok(())
6415    }
6416
6417    fn reject_read_only_write_path(&mut self, path: &str) -> KernelResult<()> {
6418        if is_proc_path(path) {
6419            self.filesystem
6420                .check_virtual_path(FsOperation::Write, path)
6421                .map_err(KernelError::from)?;
6422            return Err(read_only_filesystem_error(path));
6423        }
6424
6425        if is_agentos_path(path) {
6426            return Err(read_only_filesystem_error(path));
6427        }
6428
6429        Ok(())
6430    }
6431
6432    fn reject_read_only_resolved_write_path(&mut self, path: &str) -> KernelResult<()> {
6433        self.reject_read_only_write_path(path)?;
6434
6435        if let Some(resolved) = self.resolve_write_guard_path(path, true)? {
6436            if is_agentos_path(&resolved) {
6437                return Err(read_only_filesystem_error(&resolved));
6438            }
6439            if self.has_agentos_hardlink_alias(&resolved)? {
6440                return Err(read_only_filesystem_error(&resolved));
6441            }
6442        }
6443        if self.has_agentos_hardlink_alias(path)? {
6444            return Err(read_only_filesystem_error(path));
6445        }
6446
6447        Ok(())
6448    }
6449
6450    fn reject_read_only_entry_write_path(&mut self, path: &str) -> KernelResult<()> {
6451        self.reject_read_only_write_path(path)?;
6452
6453        if let Some(resolved) = self.resolve_write_guard_path(path, false)? {
6454            if is_agentos_path(&resolved) {
6455                return Err(read_only_filesystem_error(&resolved));
6456            }
6457            if self.has_agentos_hardlink_alias(&resolved)? {
6458                return Err(read_only_filesystem_error(&resolved));
6459            }
6460        }
6461        if self.has_agentos_hardlink_alias(path)? {
6462            return Err(read_only_filesystem_error(path));
6463        }
6464
6465        Ok(())
6466    }
6467
6468    fn has_agentos_hardlink_alias(&mut self, path: &str) -> KernelResult<bool> {
6469        let Some(target) = self.storage_lstat(path)? else {
6470            return Ok(false);
6471        };
6472        if target.is_directory || target.is_symbolic_link {
6473            return Ok(false);
6474        }
6475
6476        self.agentos_subtree_contains_inode("/etc/agentos", target.dev, target.ino)
6477    }
6478
6479    fn agentos_subtree_contains_inode(
6480        &mut self,
6481        path: &str,
6482        target_dev: u64,
6483        target_ino: u64,
6484    ) -> KernelResult<bool> {
6485        let Some(stat) = self.storage_lstat(path)? else {
6486            return Ok(false);
6487        };
6488        if !stat.is_directory && !stat.is_symbolic_link {
6489            return Ok(stat.dev == target_dev && stat.ino == target_ino);
6490        }
6491        if !stat.is_directory {
6492            return Ok(false);
6493        }
6494
6495        let children = self.raw_filesystem_mut().read_dir_with_types(path)?;
6496        for child in children {
6497            if child.name == "." || child.name == ".." {
6498                continue;
6499            }
6500            let child_path = join_absolute_path(path, &child.name);
6501            if self.agentos_subtree_contains_inode(&child_path, target_dev, target_ino)? {
6502                return Ok(true);
6503            }
6504        }
6505
6506        Ok(false)
6507    }
6508
6509    fn resolve_write_guard_path(
6510        &mut self,
6511        path: &str,
6512        follow_final_symlink: bool,
6513    ) -> KernelResult<Option<String>> {
6514        let normalized = normalize_path(path);
6515        if normalized == "/" {
6516            return Ok(Some(normalized));
6517        }
6518
6519        if follow_final_symlink {
6520            if let Ok(resolved) = self.filesystem.realpath(&normalized) {
6521                return Ok(Some(resolved));
6522            }
6523        }
6524
6525        let components: Vec<&str> = normalized
6526            .split('/')
6527            .filter(|component| !component.is_empty())
6528            .collect();
6529        let mut resolved_prefix = String::from("/");
6530        let mut raw_prefix = String::from("/");
6531
6532        for (index, component) in components.iter().enumerate() {
6533            let is_final = index + 1 == components.len();
6534            if is_final && !follow_final_symlink {
6535                return Ok(Some(join_absolute_path(&resolved_prefix, component)));
6536            }
6537
6538            raw_prefix = join_absolute_path(&raw_prefix, component);
6539            match self.filesystem.realpath(&raw_prefix) {
6540                Ok(resolved) => {
6541                    resolved_prefix = resolved;
6542                }
6543                Err(error) if error.code() == "ENOENT" => {
6544                    let mut resolved = resolved_prefix;
6545                    for remaining in &components[index..] {
6546                        resolved = join_absolute_path(&resolved, remaining);
6547                    }
6548                    return Ok(Some(resolved));
6549                }
6550                Err(error) => return Err(error.into()),
6551            }
6552        }
6553
6554        Ok(Some(resolved_prefix))
6555    }
6556
6557    fn populate_poll_target_revents(
6558        &self,
6559        pid: u32,
6560        targets: &mut [PollTargetEntry],
6561    ) -> KernelResult<usize> {
6562        let mut ready_count = 0;
6563        for target in targets.iter_mut() {
6564            target.revents = self.poll_target_entry(pid, target.target, target.events)?;
6565            if !target.revents.is_empty() {
6566                ready_count += 1;
6567            }
6568        }
6569
6570        Ok(ready_count)
6571    }
6572
6573    fn poll_target_entry(
6574        &self,
6575        pid: u32,
6576        target: PollTarget,
6577        requested: PollEvents,
6578    ) -> KernelResult<PollEvents> {
6579        match target {
6580            PollTarget::Fd(fd) => {
6581                let entry = {
6582                    let tables = lock_or_recover(&self.fd_tables);
6583                    tables
6584                        .get(pid)
6585                        .ok_or_else(|| KernelError::no_such_process(pid))?
6586                        .get(fd)
6587                        .cloned()
6588                };
6589                if let Some(entry) = entry {
6590                    self.poll_entry(&entry, requested)
6591                } else {
6592                    Ok(POLLNVAL)
6593                }
6594            }
6595            PollTarget::Socket(socket_id) => {
6596                let socket = self.sockets.get(socket_id);
6597                if let Some(socket) = socket {
6598                    if socket.owner_pid() != pid && socket.owner_pid() != 0 {
6599                        return Err(KernelError::permission_denied(format!(
6600                            "process {pid} does not own socket {socket_id}"
6601                        )));
6602                    }
6603                    let mut events = self.sockets.poll(socket_id, requested)?;
6604                    if events.intersects(POLLOUT)
6605                        && !self.socket_pollout_has_resource_capacity(&socket)
6606                    {
6607                        events = PollEvents::from_bits(events.bits() & !POLLOUT.bits());
6608                    }
6609                    Ok(events)
6610                } else {
6611                    Ok(POLLNVAL)
6612                }
6613            }
6614        }
6615    }
6616
6617    fn socket_pollout_has_resource_capacity(&self, socket: &SocketRecord) -> bool {
6618        #[cfg(not(target_arch = "wasm32"))]
6619        if self.sockets.has_resource_ledger() {
6620            return self.sockets.buffered_byte_capacity_available()
6621                && (socket.spec().socket_type != SocketType::Datagram
6622                    || self.sockets.datagram_capacity_available());
6623        }
6624
6625        let snapshot = self.resource_snapshot();
6626        if self
6627            .resources
6628            .limits()
6629            .max_socket_buffered_bytes
6630            .is_some_and(|limit| snapshot.socket_buffered_bytes >= limit)
6631        {
6632            return false;
6633        }
6634
6635        if socket.spec().socket_type == SocketType::Datagram
6636            && self
6637                .resources
6638                .limits()
6639                .max_socket_datagram_queue_len
6640                .is_some_and(|limit| snapshot.socket_datagram_queue_len >= limit)
6641        {
6642            return false;
6643        }
6644
6645        true
6646    }
6647
6648    fn poll_entry(
6649        &self,
6650        entry: &crate::fd_table::FdEntry,
6651        requested: PollEvents,
6652    ) -> KernelResult<PollEvents> {
6653        if let Some(socket_id) = self.fd_socket_id(&entry.description) {
6654            let socket = self
6655                .sockets
6656                .get(socket_id)
6657                .ok_or_else(|| KernelError::bad_file_descriptor(entry.fd))?;
6658            let mut events = self.sockets.poll(socket_id, requested)?;
6659            if events.intersects(POLLOUT) && !self.socket_pollout_has_resource_capacity(&socket) {
6660                events = PollEvents::from_bits(events.bits() & !POLLOUT.bits());
6661            }
6662            return Ok(events);
6663        }
6664
6665        if self.pipes.is_pipe(entry.description.id()) {
6666            return Ok(self.pipes.poll(entry.description.id(), requested)?);
6667        }
6668
6669        if self.ptys.is_pty(entry.description.id()) {
6670            return Ok(self.ptys.poll(entry.description.id(), requested)?);
6671        }
6672
6673        let access_mode = entry.description.flags() & 0b11;
6674        let mut events = PollEvents::empty();
6675        if requested.intersects(POLLIN) && access_mode != crate::fd_table::O_WRONLY {
6676            events |= POLLIN;
6677        }
6678        if requested.intersects(POLLOUT) && access_mode != crate::fd_table::O_RDONLY {
6679            events |= POLLOUT;
6680        }
6681        if entry.filetype == FILETYPE_DIRECTORY && requested.intersects(POLLOUT) {
6682            events |= POLLERR;
6683        }
6684        if self.terminated {
6685            events |= POLLHUP;
6686        }
6687        Ok(events)
6688    }
6689
6690    fn description_for_fd(
6691        &self,
6692        requester_driver: &str,
6693        pid: u32,
6694        fd: u32,
6695    ) -> KernelResult<Arc<FileDescription>> {
6696        self.assert_driver_owns(requester_driver, pid)?;
6697        lock_or_recover(&self.fd_tables)
6698            .get(pid)
6699            .and_then(|table| table.get(fd))
6700            .map(|entry| Arc::clone(&entry.description))
6701            .ok_or_else(|| KernelError::bad_file_descriptor(fd))
6702    }
6703
6704    fn fd_socket_id(&self, description: &Arc<FileDescription>) -> Option<SocketId> {
6705        lock_or_recover(&self.fd_sockets)
6706            .get(&description.id())
6707            .filter(|entry| Arc::ptr_eq(&entry.description, description))
6708            .map(|entry| entry.socket_id)
6709    }
6710
6711    fn fd_socket_id_for_fd(&self, pid: u32, fd: u32) -> KernelResult<SocketId> {
6712        let description = {
6713            let tables = lock_or_recover(&self.fd_tables);
6714            tables
6715                .get(pid)
6716                .and_then(|table| table.get(fd))
6717                .map(|entry| Arc::clone(&entry.description))
6718                .ok_or_else(|| KernelError::bad_file_descriptor(fd))?
6719        };
6720        self.fd_socket_id(&description)
6721            .ok_or_else(|| KernelError::new("ENOTSOCK", "descriptor is not a socket"))
6722    }
6723
6724    fn open_file_descriptions(&self) -> Vec<Arc<FileDescription>> {
6725        let tables = lock_or_recover(&self.fd_tables);
6726        let mut descriptions = BTreeMap::new();
6727        for pid in tables.pids() {
6728            let Some(table) = tables.get(pid) else {
6729                continue;
6730            };
6731            for entry in table.values() {
6732                descriptions
6733                    .entry(entry.description.id())
6734                    .or_insert_with(|| Arc::clone(&entry.description));
6735            }
6736        }
6737        descriptions.into_values().collect()
6738    }
6739
6740    fn prepare_anonymous_file_backing(
6741        &mut self,
6742        path: &str,
6743        stat: Option<&VirtualStat>,
6744    ) -> KernelResult<Option<OpenFileRemovalBacking>> {
6745        let descriptions = self
6746            .open_file_descriptions()
6747            .into_iter()
6748            .filter(|description| description.is_path_backed_by(path))
6749            .collect::<Vec<_>>();
6750        if descriptions.is_empty() {
6751            return Ok(None);
6752        }
6753        let mut stat = match stat {
6754            Some(stat) if !stat.is_directory && !stat.is_symbolic_link => stat.clone(),
6755            _ => return Ok(None),
6756        };
6757        if stat.nlink > 1 {
6758            if let Some(live_path) = self.find_surviving_hard_link(path, &stat)? {
6759                return Ok(Some(OpenFileRemovalBacking::LinkedAlias {
6760                    descriptions,
6761                    live_path,
6762                }));
6763            }
6764        }
6765        stat.nlink = 0;
6766        let data = self.filesystem.read_file(path)?;
6767        let backing: SharedAnonymousFile = Arc::new(Mutex::new(AnonymousFile::new(
6768            data,
6769            stat,
6770            Arc::clone(&self.anonymous_file_usage),
6771        )));
6772        Ok(Some(OpenFileRemovalBacking::Anonymous {
6773            descriptions,
6774            backing,
6775        }))
6776    }
6777
6778    fn find_surviving_hard_link(
6779        &mut self,
6780        removed_path: &str,
6781        target: &VirtualStat,
6782    ) -> KernelResult<Option<String>> {
6783        let removed_path = normalize_path(removed_path);
6784        let mut queue = VecDeque::from([(String::from("/"), 0usize)]);
6785        let mut entries = 0usize;
6786        let per_directory_limit = self.resources.max_readdir_entries().unwrap_or(usize::MAX);
6787
6788        while let Some((directory, depth)) = queue.pop_front() {
6789            self.resources.check_recursive_fs_depth(depth)?;
6790            let names = self
6791                .raw_filesystem_mut()
6792                .read_dir_limited(&directory, per_directory_limit)?;
6793            self.resources.check_readdir_entries(names.len())?;
6794            for name in names {
6795                if matches!(name.as_str(), "." | "..") {
6796                    continue;
6797                }
6798                entries = entries.saturating_add(1);
6799                self.resources.check_recursive_fs_entries(entries)?;
6800                let path = join_child_path(&directory, &name);
6801                let stat = match self.raw_filesystem_mut().lstat(&path) {
6802                    Ok(stat) => stat,
6803                    Err(error) if error.code() == "ENOENT" => continue,
6804                    Err(error) => return Err(error.into()),
6805                };
6806                if path != removed_path && stat.dev == target.dev && stat.ino == target.ino {
6807                    return Ok(Some(path));
6808                }
6809                if stat.is_directory && !stat.is_symbolic_link {
6810                    queue.push_back((path, depth.saturating_add(1)));
6811                }
6812            }
6813        }
6814        Ok(None)
6815    }
6816
6817    fn prepare_detached_directory_backing(
6818        &self,
6819        path: &str,
6820        stat: Option<&VirtualStat>,
6821    ) -> Option<(Vec<Arc<FileDescription>>, VirtualStat)> {
6822        let mut stat = stat.filter(|stat| stat.is_directory)?.clone();
6823        stat.nlink = 0;
6824        let descriptions = self
6825            .open_file_descriptions()
6826            .into_iter()
6827            .filter(|description| {
6828                description.is_path_backed_by(path)
6829                    || description
6830                        .lock_target()
6831                        .is_some_and(|target| target.ino() == stat.ino)
6832            })
6833            .collect::<Vec<_>>();
6834        (!descriptions.is_empty()).then_some((descriptions, stat))
6835    }
6836
6837    fn rename_open_file_descriptions(&self, old_path: &str, new_path: &str) {
6838        for description in self.open_file_descriptions() {
6839            description.rename_path_prefix(old_path, new_path);
6840        }
6841    }
6842
6843    fn assert_not_terminated(&self) -> KernelResult<()> {
6844        if self.terminated {
6845            Err(KernelError::disposed())
6846        } else {
6847            Ok(())
6848        }
6849    }
6850
6851    fn assert_driver_owns(&self, requester_driver: &str, pid: u32) -> KernelResult<()> {
6852        let driver_pids = lock_or_recover(&self.driver_pids);
6853        if driver_pids
6854            .get(requester_driver)
6855            .map(|pids| pids.contains(&pid))
6856            .unwrap_or(false)
6857        {
6858            return Ok(());
6859        }
6860
6861        if driver_pids.values().any(|pids| pids.contains(&pid)) {
6862            return Err(KernelError::permission_denied(format!(
6863                "driver \"{requester_driver}\" does not own PID {pid}"
6864            )));
6865        }
6866
6867        Err(KernelError::no_such_process(pid))
6868    }
6869
6870    fn cleanup_process_resources(&self, pid: u32) {
6871        cleanup_process_resources(
6872            self.fd_tables.as_ref(),
6873            &self.file_locks,
6874            &self.pipes,
6875            &self.ptys,
6876            &self.sockets,
6877            &self.fd_sockets,
6878            self.driver_pids.as_ref(),
6879            pid,
6880        );
6881    }
6882
6883    fn resolve_spawn_command(
6884        &mut self,
6885        command: &str,
6886        args: &[String],
6887        cwd: &str,
6888        parent_pid: Option<u32>,
6889    ) -> KernelResult<ResolvedSpawnCommand> {
6890        if let Some(driver) = self.commands.resolve(command).cloned() {
6891            return Ok(ResolvedSpawnCommand {
6892                command: command.to_owned(),
6893                args: args.to_vec(),
6894                driver,
6895            });
6896        }
6897
6898        let Some(path) = self.resolve_executable_path(command, cwd, parent_pid)? else {
6899            return Err(KernelError::command_not_found(command));
6900        };
6901
6902        if let Some(registered_command) = self.resolve_registered_command_path(&path) {
6903            let driver = self
6904                .commands
6905                .resolve(&registered_command)
6906                .cloned()
6907                .ok_or_else(|| KernelError::command_not_found(&registered_command))?;
6908            return Ok(ResolvedSpawnCommand {
6909                command: registered_command,
6910                args: args.to_vec(),
6911                driver,
6912            });
6913        }
6914
6915        let shebang = self
6916            .parse_shebang_command(&path)?
6917            .ok_or_else(|| KernelError::new("ENOEXEC", format!("exec format error: {path}")))?;
6918        self.resolve_shebang_command(&path, args, shebang)
6919    }
6920
6921    fn resolve_executable_path(
6922        &mut self,
6923        command: &str,
6924        cwd: &str,
6925        parent_pid: Option<u32>,
6926    ) -> KernelResult<Option<String>> {
6927        if !command.contains('/') {
6928            return Ok(None);
6929        }
6930
6931        let path = if command.starts_with('/') {
6932            normalize_path(command)
6933        } else {
6934            normalize_path(&format!("{cwd}/{command}"))
6935        };
6936        // exec(2) follows symlinks, and a symlink target may live in a different
6937        // mount (e.g. `/opt/agentos/bin/<cmd>` is its own single-symlink mount
6938        // pointing into a package tar mount). Resolve the real path before
6939        // stat-ing / reading the executable so cross-mount symlinked commands
6940        // exec their real target instead of failing to read the symlink node.
6941        let path = self.filesystem.realpath(&path).unwrap_or(path);
6942        let stat = self.filesystem.stat(&path)?;
6943        if stat.is_directory {
6944            return Err(KernelError::new(
6945                "EACCES",
6946                format!("permission denied, execute '{path}'"),
6947            ));
6948        }
6949        // Registered command projections are executable kernel objects even
6950        // when their host/package backing blob is stored as 0644. Ordinary
6951        // VFS files must still carry a real execute bit, as Linux requires.
6952        let registered = self.resolve_registered_command_path(&path).is_some();
6953        if let Some(pid) = parent_pid {
6954            if !registered {
6955                self.check_dac_access(pid, &path, DAC_EXECUTE)?;
6956            }
6957        } else if stat.mode & EXECUTABLE_PERMISSION_BITS == 0 && !registered {
6958            return Err(KernelError::new(
6959                "EACCES",
6960                format!("permission denied, execute '{path}'"),
6961            ));
6962        }
6963        Ok(Some(path))
6964    }
6965
6966    fn validate_wasm_exec_image_inner(
6967        &mut self,
6968        path: &str,
6969        cwd: &str,
6970        interpreter_depth: usize,
6971    ) -> KernelResult<()> {
6972        let resolved = self.validate_executable_path(path, cwd)?;
6973        // `/bin/<name>` and `/__agentos/commands/.../<name>` may be
6974        // kernel-owned command stubs whose backing bytes are a self-referential
6975        // launcher rather than the projected WASM blob the runner loads. Once
6976        // the exact path has resolved to a registered command, its trusted
6977        // command driver is the validated executable image; parsing the stub's
6978        // `#!` bytes would incorrectly report ELOOP for paths such as `/bin/sh`.
6979        if self.resolve_registered_command_path(&resolved).is_some() {
6980            return Ok(());
6981        }
6982        let header = self
6983            .filesystem
6984            .pread(&resolved, 0, SHEBANG_LINE_MAX_BYTES)?;
6985        if header.starts_with(b"\0asm") {
6986            return Ok(());
6987        }
6988
6989        let Some(interpreter) = linux_shebang_interpreter(&header, &resolved)? else {
6990            return Err(KernelError::new(
6991                "ENOEXEC",
6992                format!("exec format error: {resolved}"),
6993            ));
6994        };
6995        if interpreter_depth >= MAX_EXEC_INTERPRETER_DEPTH {
6996            return Err(KernelError::new(
6997                "ELOOP",
6998                format!("too many levels of symbolic links or interpreters: {resolved}"),
6999            ));
7000        }
7001
7002        self.validate_wasm_exec_image_inner(&interpreter, cwd, interpreter_depth + 1)
7003    }
7004
7005    fn resolve_registered_command_path(&self, path: &str) -> Option<String> {
7006        let normalized = normalize_path(path);
7007        for prefix in ["/bin/", "/usr/bin/", "/usr/local/bin/"] {
7008            let Some(name) = normalized.strip_prefix(prefix) else {
7009                continue;
7010            };
7011            if !name.is_empty() && !name.contains('/') && self.commands.resolve(name).is_some() {
7012                return Some(name.to_owned());
7013            }
7014        }
7015
7016        if let Some(name) = normalized
7017            .strip_prefix("/__agentos/commands/")
7018            .and_then(|suffix| suffix.rsplit('/').next())
7019        {
7020            if !name.is_empty() && !name.contains('/') && self.commands.resolve(name).is_some() {
7021                return Some(name.to_owned());
7022            }
7023        }
7024
7025        None
7026    }
7027
7028    fn parse_shebang_command(&mut self, path: &str) -> KernelResult<Option<ShebangCommand>> {
7029        let header = self.filesystem.pread(path, 0, SHEBANG_LINE_MAX_BYTES + 1)?;
7030        if !header.starts_with(b"#!") {
7031            return Ok(None);
7032        }
7033
7034        let line_end = match header.iter().position(|byte| *byte == b'\n') {
7035            Some(index) => index,
7036            None if header.len() <= SHEBANG_LINE_MAX_BYTES => header.len(),
7037            None => {
7038                return Err(KernelError::new(
7039                    "ENOEXEC",
7040                    format!("shebang line exceeds {SHEBANG_LINE_MAX_BYTES} bytes: {path}"),
7041                ));
7042            }
7043        };
7044        let line = header[2..line_end]
7045            .strip_suffix(b"\r")
7046            .unwrap_or(&header[2..line_end]);
7047        let text = std::str::from_utf8(line)
7048            .map_err(|_| KernelError::new("ENOEXEC", format!("invalid shebang line: {path}")))?;
7049        let mut parts = text.split_ascii_whitespace();
7050        let interpreter = parts
7051            .next()
7052            .ok_or_else(|| KernelError::new("ENOEXEC", format!("invalid shebang line: {path}")))?;
7053        Ok(Some(ShebangCommand {
7054            interpreter: interpreter.to_owned(),
7055            args: parts.map(ToOwned::to_owned).collect(),
7056        }))
7057    }
7058
7059    fn resolve_shebang_command(
7060        &self,
7061        path: &str,
7062        args: &[String],
7063        shebang: ShebangCommand,
7064    ) -> KernelResult<ResolvedSpawnCommand> {
7065        let mut interpreter_args = shebang.args;
7066        let interpreter = normalize_path(&shebang.interpreter);
7067        let command = if interpreter == "/usr/bin/env" || interpreter == "/bin/env" {
7068            if interpreter_args.is_empty() {
7069                return Err(KernelError::new(
7070                    "ENOENT",
7071                    format!("missing interpreter after /usr/bin/env in shebang: {path}"),
7072                ));
7073            }
7074            interpreter_args.remove(0)
7075        } else if let Some(command) = self.resolve_registered_command_path(&interpreter) {
7076            command
7077        } else if self.commands.resolve(&shebang.interpreter).is_some() {
7078            shebang.interpreter
7079        } else {
7080            return Err(KernelError::command_not_found(&shebang.interpreter));
7081        };
7082
7083        let driver = self
7084            .commands
7085            .resolve(&command)
7086            .cloned()
7087            .ok_or_else(|| KernelError::command_not_found(&command))?;
7088        let mut resolved_args = interpreter_args;
7089        resolved_args.push(path.to_owned());
7090        resolved_args.extend(args.iter().cloned());
7091        Ok(ResolvedSpawnCommand {
7092            command,
7093            args: resolved_args,
7094            driver,
7095        })
7096    }
7097
7098    fn finish_waitpid_event(&mut self, result: ProcessWaitResult) -> WaitPidEventResult {
7099        if result.event == WaitPidEvent::Exited {
7100            self.cleanup_process_resources(result.pid);
7101        }
7102        WaitPidEventResult {
7103            pid: result.pid,
7104            status: result.status,
7105            event: result.event,
7106        }
7107    }
7108
7109    fn raw_filesystem_mut(&mut self) -> &mut F {
7110        self.filesystem.inner_mut().inner_mut()
7111    }
7112
7113    fn read_file_internal(
7114        &mut self,
7115        current_pid: Option<u32>,
7116        path: &str,
7117    ) -> KernelResult<Vec<u8>> {
7118        if let Some(proc_node) = self.resolve_proc_node(path, current_pid)? {
7119            self.filesystem
7120                .check_virtual_path(FsOperation::Read, path)
7121                .map_err(KernelError::from)?;
7122            return self.proc_read_file(current_pid, &proc_node);
7123        }
7124
7125        self.reject_unix_socket_data_path(path, "ENXIO")?;
7126        Ok(self.filesystem.read_file(path)?)
7127    }
7128
7129    fn effective_recursive_fs_depth(
7130        &self,
7131        requested_max_depth: Option<usize>,
7132    ) -> KernelResult<usize> {
7133        match (requested_max_depth, self.resources.max_recursive_fs_depth()) {
7134            (Some(requested), Some(limit)) if requested > limit => Err(KernelError::new(
7135                "EINVAL",
7136                format!(
7137                    "requested recursive filesystem max depth {requested} exceeds configured limit {limit}"
7138                ),
7139            )),
7140            (Some(requested), _) => Ok(requested),
7141            (None, Some(limit)) => Ok(limit),
7142            (None, None) => Ok(usize::MAX),
7143        }
7144    }
7145
7146    fn copy_path_inner(
7147        &mut self,
7148        from: &str,
7149        to: &str,
7150        recursive: bool,
7151        depth: usize,
7152        entries: &mut usize,
7153    ) -> KernelResult<()> {
7154        self.resources.check_recursive_fs_depth(depth)?;
7155        *entries = entries.saturating_add(1);
7156        self.resources.check_recursive_fs_entries(*entries)?;
7157        let source_stat = self.lstat_internal(None, from)?;
7158
7159        if source_stat.is_symbolic_link {
7160            let target = self.read_link_internal(None, from)?;
7161            self.symlink(&target, to)?;
7162            return Ok(());
7163        }
7164
7165        if source_stat.is_directory {
7166            if !recursive {
7167                return Err(KernelError::new(
7168                    "EISDIR",
7169                    format!("illegal operation on a directory, copy '{from}'"),
7170                ));
7171            }
7172
7173            let source_root = normalize_path(from);
7174            let destination_root = normalize_path(to);
7175            if destination_root.starts_with(&(source_root.clone() + "/")) {
7176                return Err(KernelError::new(
7177                    "EINVAL",
7178                    format!("cannot copy '{from}' into its own descendant '{to}'"),
7179                ));
7180            }
7181
7182            self.mkdir(&parent_path(&destination_root), true)?;
7183            if !self.exists_internal(None, &destination_root)? {
7184                self.create_dir(&destination_root)?;
7185            }
7186            self.chmod(&destination_root, source_stat.mode)?;
7187            self.chown(&destination_root, source_stat.uid, source_stat.gid)?;
7188
7189            let names = self.read_dir_internal(None, from)?;
7190            self.resources.check_readdir_entries(names.len())?;
7191            for name in names {
7192                if matches!(name.as_str(), "." | "..") {
7193                    continue;
7194                }
7195                let child_from = join_child_path(from, &name);
7196                let child_to = join_child_path(to, &name);
7197                self.copy_path_inner(
7198                    &child_from,
7199                    &child_to,
7200                    true,
7201                    depth.saturating_add(1),
7202                    entries,
7203                )?;
7204            }
7205            return Ok(());
7206        }
7207
7208        let content = self.read_file_internal(None, from)?;
7209        self.write_file(to, content)?;
7210        self.chmod(to, source_stat.mode)?;
7211        self.chown(to, source_stat.uid, source_stat.gid)
7212    }
7213
7214    fn remove_path_inner(
7215        &mut self,
7216        path: &str,
7217        recursive: bool,
7218        depth: usize,
7219        entries: &mut usize,
7220    ) -> KernelResult<()> {
7221        self.resources.check_recursive_fs_depth(depth)?;
7222        *entries = entries.saturating_add(1);
7223        self.resources.check_recursive_fs_entries(*entries)?;
7224        let stat = self.lstat_internal(None, path)?;
7225        if stat.is_directory && !stat.is_symbolic_link {
7226            if recursive {
7227                let names = self.read_dir_internal(None, path)?;
7228                self.resources.check_readdir_entries(names.len())?;
7229                for name in names {
7230                    if matches!(name.as_str(), "." | "..") {
7231                        continue;
7232                    }
7233                    let child = join_child_path(path, &name);
7234                    self.remove_path_inner(&child, true, depth.saturating_add(1), entries)?;
7235                }
7236            }
7237            return self.remove_dir(path);
7238        }
7239
7240        self.remove_file(path)
7241    }
7242
7243    fn exists_internal(&self, current_pid: Option<u32>, path: &str) -> KernelResult<bool> {
7244        match self.resolve_proc_node(path, current_pid) {
7245            Ok(Some(_)) => {
7246                self.filesystem
7247                    .check_virtual_path(FsOperation::Read, path)
7248                    .map_err(KernelError::from)?;
7249                Ok(true)
7250            }
7251            Ok(None) => Ok(self.filesystem.exists(path)?),
7252            Err(error) if error.code() == "ENOENT" => Ok(false),
7253            Err(error) => Err(error),
7254        }
7255    }
7256
7257    fn stat_internal(&mut self, current_pid: Option<u32>, path: &str) -> KernelResult<VirtualStat> {
7258        if let Some(proc_node) = self.resolve_proc_node(path, current_pid)? {
7259            self.filesystem
7260                .check_virtual_path(FsOperation::Read, path)
7261                .map_err(KernelError::from)?;
7262            return self.proc_stat(current_pid, &proc_node);
7263        }
7264
7265        Ok(self.filesystem.stat(path)?)
7266    }
7267
7268    fn lstat_internal(&self, current_pid: Option<u32>, path: &str) -> KernelResult<VirtualStat> {
7269        if let Some(proc_node) = self.resolve_proc_node(path, current_pid)? {
7270            self.filesystem
7271                .check_virtual_path(FsOperation::Read, path)
7272                .map_err(KernelError::from)?;
7273            return self.proc_lstat(&proc_node);
7274        }
7275
7276        Ok(self.filesystem.lstat(path)?)
7277    }
7278
7279    fn read_link_internal(&self, current_pid: Option<u32>, path: &str) -> KernelResult<String> {
7280        if let Some(proc_node) = self.resolve_proc_node(path, current_pid)? {
7281            self.filesystem
7282                .check_virtual_path(FsOperation::Read, path)
7283                .map_err(KernelError::from)?;
7284            return self.proc_read_link(&proc_node);
7285        }
7286
7287        Ok(self.filesystem.read_link(path)?)
7288    }
7289
7290    fn read_dir_internal(
7291        &mut self,
7292        current_pid: Option<u32>,
7293        path: &str,
7294    ) -> KernelResult<Vec<String>> {
7295        if let Some(proc_node) = self.resolve_proc_node(path, current_pid)? {
7296            self.filesystem
7297                .check_virtual_path(FsOperation::Read, path)
7298                .map_err(KernelError::from)?;
7299            return self.proc_read_dir(current_pid, &proc_node);
7300        }
7301
7302        if let Some(limit) = self.resources.max_readdir_entries() {
7303            Ok(self.filesystem.read_dir_limited(path, limit)?)
7304        } else {
7305            Ok(self.filesystem.read_dir(path)?)
7306        }
7307    }
7308
7309    fn read_dir_with_types_internal(
7310        &mut self,
7311        current_pid: Option<u32>,
7312        path: &str,
7313    ) -> KernelResult<Vec<VirtualDirEntry>> {
7314        if let Some(proc_node) = self.resolve_proc_node(path, current_pid)? {
7315            self.filesystem
7316                .check_virtual_path(FsOperation::Read, path)
7317                .map_err(KernelError::from)?;
7318            return Ok(self
7319                .proc_read_dir(current_pid, &proc_node)?
7320                .into_iter()
7321                .map(|name| VirtualDirEntry {
7322                    name,
7323                    is_directory: false,
7324                    is_symbolic_link: false,
7325                })
7326                .collect());
7327        }
7328
7329        Ok(self.filesystem.read_dir_with_types(path)?)
7330    }
7331
7332    fn realpath_internal(&self, current_pid: Option<u32>, path: &str) -> KernelResult<String> {
7333        if let Some(proc_node) = self.resolve_proc_node(path, current_pid)? {
7334            self.filesystem
7335                .check_virtual_path(FsOperation::Read, path)
7336                .map_err(KernelError::from)?;
7337            return self.proc_realpath(current_pid, &proc_node);
7338        }
7339
7340        Ok(self.filesystem.realpath(path)?)
7341    }
7342
7343    fn resolve_proc_node(
7344        &self,
7345        path: &str,
7346        current_pid: Option<u32>,
7347    ) -> KernelResult<Option<ProcNode>> {
7348        let normalized = normalize_path(path);
7349        if !is_proc_path(&normalized) {
7350            return Ok(None);
7351        }
7352
7353        if normalized == "/proc" {
7354            return Ok(Some(ProcNode::RootDir));
7355        }
7356
7357        let suffix = normalized
7358            .strip_prefix("/proc/")
7359            .expect("proc path should have /proc prefix");
7360        let parts = suffix.split('/').collect::<Vec<_>>();
7361        if parts.is_empty() {
7362            return Ok(Some(ProcNode::RootDir));
7363        }
7364
7365        let root_node = match parts.as_slice() {
7366            ["mounts"] => Some(ProcNode::MountsFile),
7367            ["cpuinfo"] => Some(ProcNode::CpuInfoFile),
7368            ["meminfo"] => Some(ProcNode::MemInfoFile),
7369            ["loadavg"] => Some(ProcNode::LoadAvgFile),
7370            ["uptime"] => Some(ProcNode::UptimeFile),
7371            ["version"] => Some(ProcNode::VersionFile),
7372            _ => None,
7373        };
7374        if let Some(node) = root_node {
7375            return Ok(Some(node));
7376        }
7377
7378        let pid = match parts[0] {
7379            "self" => current_pid.ok_or_else(|| proc_not_found_error(&normalized))?,
7380            raw => raw
7381                .parse::<u32>()
7382                .map_err(|_| proc_not_found_error(&normalized))?,
7383        };
7384        self.proc_entry(pid)?;
7385
7386        let node = match parts.as_slice() {
7387            ["self"] => ProcNode::SelfLink { pid },
7388            [_pid] => ProcNode::PidDir { pid },
7389            [_pid, "fd"] => ProcNode::PidFdDir { pid },
7390            [_pid, "cmdline"] => ProcNode::PidCmdline { pid },
7391            [_pid, "environ"] => ProcNode::PidEnviron { pid },
7392            [_pid, "cwd"] => ProcNode::PidCwdLink { pid },
7393            [_pid, "stat"] => ProcNode::PidStatFile { pid },
7394            [_pid, "status"] => ProcNode::PidStatusFile { pid },
7395            [_pid, "fd", fd] => {
7396                let fd = fd
7397                    .parse::<u32>()
7398                    .map_err(|_| proc_not_found_error(&normalized))?;
7399                self.proc_fd_entry(pid, fd)?;
7400                ProcNode::PidFdLink { pid, fd }
7401            }
7402            _ => return Err(proc_not_found_error(&normalized)),
7403        };
7404
7405        Ok(Some(node))
7406    }
7407
7408    fn proc_entry(&self, pid: u32) -> KernelResult<crate::process_table::ProcessEntry> {
7409        self.processes
7410            .get(pid)
7411            .ok_or_else(|| proc_not_found_error(&format!("/proc/{pid}")))
7412    }
7413
7414    fn proc_fd_entry(&self, pid: u32, fd: u32) -> KernelResult<FdEntry> {
7415        lock_or_recover(&self.fd_tables)
7416            .get(pid)
7417            .and_then(|table| table.get(fd))
7418            .cloned()
7419            .ok_or_else(|| proc_not_found_error(&format!("/proc/{pid}/fd/{fd}")))
7420    }
7421
7422    fn proc_read_file(
7423        &mut self,
7424        current_pid: Option<u32>,
7425        node: &ProcNode,
7426    ) -> KernelResult<Vec<u8>> {
7427        match node {
7428            ProcNode::SelfLink { .. }
7429            | ProcNode::PidCwdLink { .. }
7430            | ProcNode::PidFdLink { .. } => {
7431                let target = self.proc_symlink_target(node)?;
7432                self.read_file_internal(current_pid, &target)
7433            }
7434            ProcNode::MountsFile => Ok(self.proc_mounts_bytes()),
7435            ProcNode::CpuInfoFile => Ok(self.proc_cpuinfo_bytes()),
7436            ProcNode::MemInfoFile => Ok(self.proc_meminfo_bytes()),
7437            ProcNode::LoadAvgFile => Ok(self.proc_loadavg_bytes()),
7438            ProcNode::UptimeFile => Ok(self.proc_uptime_bytes()),
7439            ProcNode::VersionFile => Ok(self.proc_version_bytes()),
7440            ProcNode::PidCmdline { pid } => Ok(self.proc_cmdline_bytes(*pid)),
7441            ProcNode::PidEnviron { pid } => Ok(self.proc_environ_bytes(*pid)),
7442            ProcNode::PidStatFile { pid } => Ok(self.proc_stat_bytes(*pid)),
7443            ProcNode::PidStatusFile { pid } => Ok(self.proc_status_bytes(*pid)),
7444            ProcNode::RootDir | ProcNode::PidDir { .. } | ProcNode::PidFdDir { .. } => {
7445                Err(KernelError::new(
7446                    "EISDIR",
7447                    format!(
7448                        "illegal operation on a directory, read '{}'",
7449                        self.proc_canonical_path(node)
7450                    ),
7451                ))
7452            }
7453        }
7454    }
7455
7456    fn proc_stat(
7457        &mut self,
7458        current_pid: Option<u32>,
7459        node: &ProcNode,
7460    ) -> KernelResult<VirtualStat> {
7461        match node {
7462            ProcNode::SelfLink { .. }
7463            | ProcNode::PidCwdLink { .. }
7464            | ProcNode::PidFdLink { .. } => {
7465                let target = self.proc_symlink_target(node)?;
7466                self.stat_internal(current_pid, &target)
7467            }
7468            _ => self.proc_lstat(node),
7469        }
7470    }
7471
7472    fn proc_lstat(&self, node: &ProcNode) -> KernelResult<VirtualStat> {
7473        match node {
7474            ProcNode::RootDir | ProcNode::PidDir { .. } | ProcNode::PidFdDir { .. } => {
7475                Ok(proc_dir_stat(proc_inode(node)))
7476            }
7477            ProcNode::MountsFile => Ok(proc_file_stat(
7478                proc_inode(node),
7479                self.proc_mounts_bytes().len() as u64,
7480            )),
7481            ProcNode::CpuInfoFile => Ok(proc_file_stat(
7482                proc_inode(node),
7483                self.proc_cpuinfo_bytes().len() as u64,
7484            )),
7485            ProcNode::MemInfoFile => Ok(proc_file_stat(
7486                proc_inode(node),
7487                self.proc_meminfo_bytes().len() as u64,
7488            )),
7489            ProcNode::LoadAvgFile => Ok(proc_file_stat(
7490                proc_inode(node),
7491                self.proc_loadavg_bytes().len() as u64,
7492            )),
7493            ProcNode::UptimeFile => Ok(proc_file_stat(
7494                proc_inode(node),
7495                self.proc_uptime_bytes().len() as u64,
7496            )),
7497            ProcNode::VersionFile => Ok(proc_file_stat(
7498                proc_inode(node),
7499                self.proc_version_bytes().len() as u64,
7500            )),
7501            ProcNode::PidCmdline { pid } => Ok(proc_file_stat(
7502                proc_inode(node),
7503                self.proc_cmdline_bytes(*pid).len() as u64,
7504            )),
7505            ProcNode::PidEnviron { pid } => Ok(proc_file_stat(
7506                proc_inode(node),
7507                self.proc_environ_bytes(*pid).len() as u64,
7508            )),
7509            ProcNode::PidStatFile { pid } => Ok(proc_file_stat(
7510                proc_inode(node),
7511                self.proc_stat_bytes(*pid).len() as u64,
7512            )),
7513            ProcNode::PidStatusFile { pid } => Ok(proc_file_stat(
7514                proc_inode(node),
7515                self.proc_status_bytes(*pid).len() as u64,
7516            )),
7517            ProcNode::SelfLink { .. }
7518            | ProcNode::PidCwdLink { .. }
7519            | ProcNode::PidFdLink { .. } => Ok(proc_symlink_stat(
7520                proc_inode(node),
7521                self.proc_read_link(node)?.len() as u64,
7522            )),
7523        }
7524    }
7525
7526    fn proc_read_link(&self, node: &ProcNode) -> KernelResult<String> {
7527        match node {
7528            ProcNode::SelfLink { .. }
7529            | ProcNode::PidCwdLink { .. }
7530            | ProcNode::PidFdLink { .. } => self.proc_symlink_target(node),
7531            _ => Err(KernelError::new(
7532                "EINVAL",
7533                format!(
7534                    "invalid argument, readlink '{}'",
7535                    self.proc_canonical_path(node)
7536                ),
7537            )),
7538        }
7539    }
7540
7541    fn proc_read_dir(
7542        &mut self,
7543        current_pid: Option<u32>,
7544        node: &ProcNode,
7545    ) -> KernelResult<Vec<String>> {
7546        match node {
7547            ProcNode::SelfLink { .. }
7548            | ProcNode::PidCwdLink { .. }
7549            | ProcNode::PidFdLink { .. } => {
7550                let target = self.proc_symlink_target(node)?;
7551                self.read_dir_internal(current_pid, &target)
7552            }
7553            ProcNode::RootDir => {
7554                let mut entries = self
7555                    .processes
7556                    .list_processes()
7557                    .keys()
7558                    .map(|pid| pid.to_string())
7559                    .collect::<Vec<_>>();
7560                entries.push(String::from("cpuinfo"));
7561                entries.push(String::from("loadavg"));
7562                entries.push(String::from("meminfo"));
7563                entries.push(String::from("mounts"));
7564                entries.push(String::from("self"));
7565                entries.push(String::from("uptime"));
7566                entries.push(String::from("version"));
7567                entries.sort();
7568                Ok(entries)
7569            }
7570            ProcNode::PidDir { .. } => Ok(vec![
7571                String::from("cmdline"),
7572                String::from("cwd"),
7573                String::from("environ"),
7574                String::from("fd"),
7575                String::from("stat"),
7576                String::from("status"),
7577            ]),
7578            ProcNode::PidFdDir { pid } => {
7579                let tables = lock_or_recover(&self.fd_tables);
7580                let table = tables
7581                    .get(*pid)
7582                    .ok_or_else(|| proc_not_found_error(&format!("/proc/{pid}/fd")))?;
7583                Ok(table.iter().map(|entry| entry.fd.to_string()).collect())
7584            }
7585            _ => Err(KernelError::new(
7586                "ENOTDIR",
7587                format!(
7588                    "not a directory, scandir '{}'",
7589                    self.proc_canonical_path(node)
7590                ),
7591            )),
7592        }
7593    }
7594
7595    fn proc_realpath(&self, current_pid: Option<u32>, node: &ProcNode) -> KernelResult<String> {
7596        match node {
7597            ProcNode::SelfLink { .. }
7598            | ProcNode::PidCwdLink { .. }
7599            | ProcNode::PidFdLink { .. } => {
7600                let target = self.proc_symlink_target(node)?;
7601                self.realpath_internal(current_pid, &target)
7602            }
7603            _ => Ok(self.proc_canonical_path(node)),
7604        }
7605    }
7606
7607    fn proc_symlink_target(&self, node: &ProcNode) -> KernelResult<String> {
7608        match node {
7609            ProcNode::SelfLink { pid } => Ok(format!("/proc/{pid}")),
7610            ProcNode::PidCwdLink { pid } => Ok(self.proc_entry(*pid)?.cwd),
7611            ProcNode::PidFdLink { pid, fd } => Ok(self
7612                .proc_fd_entry(*pid, *fd)?
7613                .description
7614                .proc_display_path()),
7615            _ => Err(KernelError::new(
7616                "EINVAL",
7617                format!(
7618                    "'{}' is not a symbolic link",
7619                    self.proc_canonical_path(node)
7620                ),
7621            )),
7622        }
7623    }
7624
7625    fn proc_canonical_path(&self, node: &ProcNode) -> String {
7626        match node {
7627            ProcNode::RootDir => String::from("/proc"),
7628            ProcNode::MountsFile => String::from("/proc/mounts"),
7629            ProcNode::CpuInfoFile => String::from("/proc/cpuinfo"),
7630            ProcNode::MemInfoFile => String::from("/proc/meminfo"),
7631            ProcNode::LoadAvgFile => String::from("/proc/loadavg"),
7632            ProcNode::UptimeFile => String::from("/proc/uptime"),
7633            ProcNode::VersionFile => String::from("/proc/version"),
7634            ProcNode::SelfLink { pid } => format!("/proc/{pid}"),
7635            ProcNode::PidDir { pid } => format!("/proc/{pid}"),
7636            ProcNode::PidFdDir { pid } => format!("/proc/{pid}/fd"),
7637            ProcNode::PidCmdline { pid } => format!("/proc/{pid}/cmdline"),
7638            ProcNode::PidEnviron { pid } => format!("/proc/{pid}/environ"),
7639            ProcNode::PidCwdLink { pid } => format!("/proc/{pid}/cwd"),
7640            ProcNode::PidStatFile { pid } => format!("/proc/{pid}/stat"),
7641            ProcNode::PidStatusFile { pid } => format!("/proc/{pid}/status"),
7642            ProcNode::PidFdLink { pid, fd } => format!("/proc/{pid}/fd/{fd}"),
7643        }
7644    }
7645
7646    fn proc_cmdline_bytes(&self, pid: u32) -> Vec<u8> {
7647        let entry = self
7648            .processes
7649            .get(pid)
7650            .expect("process must exist while procfs path is resolved");
7651        let mut argv = vec![entry.command];
7652        argv.extend(entry.args);
7653        null_separated_bytes(argv)
7654    }
7655
7656    fn proc_environ_bytes(&self, pid: u32) -> Vec<u8> {
7657        let entry = self
7658            .processes
7659            .get(pid)
7660            .expect("process must exist while procfs path is resolved");
7661        null_separated_bytes(
7662            entry
7663                .env
7664                .into_iter()
7665                .map(|(key, value)| format!("{key}={value}"))
7666                .collect(),
7667        )
7668    }
7669
7670    fn proc_stat_bytes(&self, pid: u32) -> Vec<u8> {
7671        let entry = self
7672            .processes
7673            .get(pid)
7674            .expect("process must exist while procfs path is resolved");
7675        let command = entry.command.replace(')', "]");
7676        let state = match entry.status {
7677            ProcessStatus::Running => 'R',
7678            ProcessStatus::Stopped => 'T',
7679            ProcessStatus::Exited => 'Z',
7680        };
7681        format!(
7682            "{pid} ({command}) {state} {ppid} {pgid} {sid} 0 0 0 0 0 0 0 0 0 0 20 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0",
7683            ppid = entry.ppid,
7684            pgid = entry.pgid,
7685            sid = entry.sid,
7686        )
7687        .into_bytes()
7688    }
7689
7690    fn proc_mounts_bytes(&self) -> Vec<u8> {
7691        let mounts = if let Some(table) =
7692            (self.filesystem.inner().inner() as &dyn Any).downcast_ref::<MountTable>()
7693        {
7694            table.get_mounts()
7695        } else {
7696            vec![MountEntry {
7697                path: String::from("/"),
7698                plugin_id: String::from("root"),
7699                guest_source: String::from("root"),
7700                guest_fstype: String::from("root"),
7701                read_only: false,
7702                access_time: crate::mount_table::AccessTimePolicy::Relatime,
7703                no_dir_atime: false,
7704            }]
7705        };
7706
7707        mounts
7708            .into_iter()
7709            .map(|mount| {
7710                let options = mount.option_string();
7711                format!(
7712                    "{source} {target} {fstype} {options} 0 0\n",
7713                    source = mount.guest_source,
7714                    target = mount.path,
7715                    fstype = mount.guest_fstype,
7716                )
7717            })
7718            .collect::<String>()
7719            .into_bytes()
7720    }
7721
7722    fn proc_cpu_count(&self) -> usize {
7723        self.resource_limits().virtual_cpu_count.unwrap_or(1)
7724    }
7725
7726    fn proc_cpuinfo_bytes(&self) -> Vec<u8> {
7727        let mut body = String::new();
7728        for processor in 0..self.proc_cpu_count() {
7729            body.push_str(&format!(
7730                "processor\t: {processor}\nmodel name\t: agentos Virtual CPU\ncpu MHz\t\t: 1000.000\nsiblings\t: 1\ncpu cores\t: 1\n\n"
7731            ));
7732        }
7733        body.into_bytes()
7734    }
7735
7736    fn proc_mem_total_bytes(&self) -> u64 {
7737        self.resource_limits()
7738            .max_wasm_memory_bytes
7739            .or(self.resource_limits().max_filesystem_bytes)
7740            .unwrap_or(DEFAULT_MAX_OPEN_FDS as u64 * 1024 * 1024)
7741    }
7742
7743    fn proc_meminfo_bytes(&self) -> Vec<u8> {
7744        let total_kb = self.proc_mem_total_bytes().div_ceil(1024);
7745        let zero_kb = 0;
7746        format!(
7747            "MemTotal:{total_kb:>8} kB\nMemFree:{total_kb:>9} kB\nMemAvailable:{total_kb:>4} kB\nBuffers:{zero_kb:>9} kB\nCached:{zero_kb:>10} kB\n"
7748        )
7749        .into_bytes()
7750    }
7751
7752    fn proc_loadavg_bytes(&self) -> Vec<u8> {
7753        let processes = self.processes.list_processes();
7754        let running = processes
7755            .values()
7756            .filter(|process| process.status == ProcessStatus::Running)
7757            .count();
7758        let total = processes.len().max(1);
7759        let last_pid = processes.keys().next_back().copied().unwrap_or(0);
7760        format!("0.00 0.00 0.00 {running}/{total} {last_pid}\n").into_bytes()
7761    }
7762
7763    fn proc_uptime_bytes(&self) -> Vec<u8> {
7764        let uptime = self.boot_instant.elapsed().as_secs_f64();
7765        format!("{uptime:.2} {uptime:.2}\n").into_bytes()
7766    }
7767
7768    fn proc_version_bytes(&self) -> Vec<u8> {
7769        format!(
7770            "Linux version 6.8.0-agentos (agentos@localhost) #1 SMP boot={}\n",
7771            self.boot_time_ms
7772        )
7773        .into_bytes()
7774    }
7775
7776    fn proc_status_bytes(&self, pid: u32) -> Vec<u8> {
7777        let entry = self
7778            .processes
7779            .get(pid)
7780            .expect("process must exist while procfs path is resolved");
7781        let (state_code, state_name) = match entry.status {
7782            ProcessStatus::Running => ('R', "running"),
7783            ProcessStatus::Stopped => ('T', "stopped"),
7784            ProcessStatus::Exited => ('Z', "zombie"),
7785        };
7786        format!(
7787            "Name:\t{name}\nState:\t{state_code} ({state_name})\nPid:\t{pid}\nPPid:\t{ppid}\nUid:\t{uid}\t{euid}\t{euid}\t{euid}\nGid:\t{gid}\t{egid}\t{egid}\t{egid}\nVmSize:\t{:>8} kB\nVmRSS:\t{:>9} kB\nThreads:\t1\n",
7788            0,
7789            0,
7790            name = entry.command,
7791            ppid = entry.ppid,
7792            uid = entry.identity.uid,
7793            euid = entry.identity.euid,
7794            gid = entry.identity.gid,
7795            egid = entry.identity.egid,
7796        )
7797        .into_bytes()
7798    }
7799
7800    fn proc_read_file_from_open_path(
7801        &mut self,
7802        current_pid: Option<u32>,
7803        path: &str,
7804    ) -> KernelResult<Vec<u8>> {
7805        let node = self
7806            .resolve_proc_node(path, current_pid)?
7807            .ok_or_else(|| proc_not_found_error(path))?;
7808        self.proc_read_file(current_pid, &node)
7809    }
7810
7811    fn proc_stat_from_open_path(
7812        &mut self,
7813        current_pid: Option<u32>,
7814        path: &str,
7815    ) -> KernelResult<VirtualStat> {
7816        let node = self
7817            .resolve_proc_node(path, current_pid)?
7818            .ok_or_else(|| proc_not_found_error(path))?;
7819        self.proc_stat(current_pid, &node)
7820    }
7821
7822    fn filesystem_usage(&mut self) -> KernelResult<FileSystemUsage> {
7823        if let Some(linked_usage) = self.filesystem_usage_cache.clone() {
7824            return Ok(FileSystemUsage {
7825                total_bytes: linked_usage
7826                    .total_bytes
7827                    .saturating_add(self.anonymous_file_usage.bytes()),
7828                inode_count: linked_usage
7829                    .inode_count
7830                    .saturating_add(self.anonymous_file_usage.inodes()),
7831            });
7832        }
7833        let filesystem = self.raw_filesystem_mut();
7834        let filesystem_any = filesystem as &mut dyn Any;
7835        let linked_usage = if let Some(mount_table) = filesystem_any.downcast_mut::<MountTable>() {
7836            mount_table.root_usage()?
7837        } else {
7838            measure_filesystem_usage(filesystem)?
7839        };
7840        self.filesystem_usage_cache = Some(linked_usage.clone());
7841        Ok(FileSystemUsage {
7842            total_bytes: linked_usage
7843                .total_bytes
7844                .saturating_add(self.anonymous_file_usage.bytes()),
7845            inode_count: linked_usage
7846                .inode_count
7847                .saturating_add(self.anonymous_file_usage.inodes()),
7848        })
7849    }
7850
7851    fn invalidate_filesystem_usage_cache(&mut self) {
7852        self.filesystem_usage_cache = None;
7853    }
7854
7855    fn path_uses_root_filesystem(&mut self, path: &str) -> bool {
7856        let filesystem = self.raw_filesystem_mut();
7857        let filesystem_any = filesystem as &mut dyn Any;
7858        filesystem_any
7859            .downcast_mut::<MountTable>()
7860            .is_none_or(|mount_table| mount_table.path_uses_root_filesystem(path))
7861    }
7862
7863    fn update_filesystem_usage_cache_for_resize(
7864        &mut self,
7865        path: &str,
7866        old_size: u64,
7867        new_size: u64,
7868    ) {
7869        if !self.path_uses_root_filesystem(path) {
7870            return;
7871        }
7872        if let Some(usage) = self.filesystem_usage_cache.as_mut() {
7873            usage.total_bytes = usage
7874                .total_bytes
7875                .saturating_sub(old_size)
7876                .saturating_add(new_size);
7877        }
7878    }
7879
7880    fn update_filesystem_usage_cache_for_write(
7881        &mut self,
7882        path: &str,
7883        existing: Option<&VirtualStat>,
7884        new_size: u64,
7885    ) {
7886        if is_storage_directory(existing) {
7887            return;
7888        }
7889
7890        if let Some(stat) = existing {
7891            self.update_filesystem_usage_cache_for_resize(path, stat.size, new_size);
7892        } else {
7893            self.update_filesystem_usage_cache_for_inode_create(path, new_size);
7894        }
7895    }
7896
7897    fn update_filesystem_usage_cache_for_inode_create(&mut self, path: &str, size: u64) {
7898        if !self.path_uses_root_filesystem(path) {
7899            return;
7900        }
7901        if let Some(usage) = self.filesystem_usage_cache.as_mut() {
7902            usage.total_bytes = usage.total_bytes.saturating_add(size);
7903            usage.inode_count = usage.inode_count.saturating_add(1);
7904        }
7905    }
7906
7907    fn update_filesystem_usage_cache_for_inode_creates(&mut self, path: &str, count: usize) {
7908        if count == 0 {
7909            return;
7910        }
7911        if !self.path_uses_root_filesystem(path) {
7912            return;
7913        }
7914        if let Some(usage) = self.filesystem_usage_cache.as_mut() {
7915            usage.inode_count = usage.inode_count.saturating_add(count);
7916        }
7917    }
7918
7919    fn update_filesystem_usage_cache_for_inode_delete(&mut self, path: &str, size: u64) {
7920        if !self.path_uses_root_filesystem(path) {
7921            return;
7922        }
7923        if let Some(usage) = self.filesystem_usage_cache.as_mut() {
7924            usage.total_bytes = usage.total_bytes.saturating_sub(size);
7925            usage.inode_count = usage.inode_count.saturating_sub(1);
7926        }
7927    }
7928
7929    fn update_filesystem_usage_cache_for_remove(
7930        &mut self,
7931        path: &str,
7932        removed: Option<&VirtualStat>,
7933    ) {
7934        let Some(stat) = removed else {
7935            return;
7936        };
7937        if stat.is_directory || stat.nlink > 1 {
7938            return;
7939        }
7940        self.update_filesystem_usage_cache_for_inode_delete(path, stat.size);
7941    }
7942
7943    fn storage_stat(&mut self, path: &str) -> KernelResult<Option<VirtualStat>> {
7944        if is_virtual_device_storage_path(path) {
7945            return Ok(None);
7946        }
7947
7948        match self.raw_filesystem_mut().stat(path) {
7949            Ok(stat) => Ok(Some(stat)),
7950            Err(error) if error.code() == "ENOENT" => Ok(None),
7951            Err(error) => Err(error.into()),
7952        }
7953    }
7954
7955    fn storage_lstat(&mut self, path: &str) -> KernelResult<Option<VirtualStat>> {
7956        if is_virtual_device_storage_path(path) {
7957            return Ok(None);
7958        }
7959
7960        match self.raw_filesystem_mut().lstat(path) {
7961            Ok(stat) => Ok(Some(stat)),
7962            Err(error) if error.code() == "ENOENT" => Ok(None),
7963            Err(error) => Err(error.into()),
7964        }
7965    }
7966
7967    fn current_storage_file_size(&mut self, path: &str) -> KernelResult<u64> {
7968        Ok(self
7969            .storage_stat(path)?
7970            .filter(|stat| !stat.is_directory)
7971            .map(|stat| stat.size)
7972            .unwrap_or(0))
7973    }
7974
7975    fn check_dac_traversal(&mut self, pid: u32, path: &str) -> KernelResult<()> {
7976        if is_proc_path(path) {
7977            return Ok(());
7978        }
7979        let identity = self
7980            .processes
7981            .get(pid)
7982            .ok_or_else(|| KernelError::no_such_process(pid))?
7983            .identity;
7984        let normalized = normalize_path(path);
7985        let components = normalized
7986            .split('/')
7987            .filter(|component| !component.is_empty())
7988            .collect::<Vec<_>>();
7989        let mut current = String::from("/");
7990        for component in components.iter().take(components.len().saturating_sub(1)) {
7991            current = join_child_path(&current, component);
7992            let stat = self.filesystem.stat(&current)?;
7993            if !stat.is_directory {
7994                return Err(KernelError::new(
7995                    "ENOTDIR",
7996                    format!("path component is not a directory: {current}"),
7997                ));
7998            }
7999            self.check_dac_mode_with_acl(&identity, &stat, DAC_EXECUTE, &current)?;
8000        }
8001        Ok(())
8002    }
8003
8004    fn check_dac_access(&mut self, pid: u32, path: &str, access: u32) -> KernelResult<()> {
8005        if is_proc_path(path) {
8006            return Ok(());
8007        }
8008        self.check_dac_traversal(pid, path)?;
8009        if access == 0 {
8010            return Ok(());
8011        }
8012        let identity = self
8013            .processes
8014            .get(pid)
8015            .ok_or_else(|| KernelError::no_such_process(pid))?
8016            .identity;
8017        let stat = self.filesystem.stat(path)?;
8018        self.check_dac_mode_with_acl(&identity, &stat, access, path)
8019    }
8020
8021    fn check_dac_mode_with_acl(
8022        &mut self,
8023        identity: &ProcessIdentity,
8024        stat: &VirtualStat,
8025        access: u32,
8026        path: &str,
8027    ) -> KernelResult<()> {
8028        // POSIX ACL USER_OBJ permissions are mirrored in the inode's owner mode
8029        // bits. Named ACL entries and the ACL mask never override the file
8030        // owner's class, so reading the ACL xattr for the owner is redundant.
8031        // Avoiding that second filesystem walk matters for host-mounted trees,
8032        // where runtimes perform thousands of metadata probes during startup.
8033        if identity.euid == 0 || identity.euid == stat.uid {
8034            return check_dac_mode(identity, stat, access, path);
8035        }
8036        let cache_key = (
8037            stat.dev,
8038            stat.ino,
8039            stat.ctime_ms,
8040            stat.ctime_nsec,
8041            stat.mode,
8042            stat.uid,
8043            stat.gid,
8044        );
8045        if self.no_posix_acl_cache.contains(&cache_key) {
8046            return check_dac_mode(identity, stat, access, path);
8047        }
8048        match self.read_posix_acl(path, POSIX_ACL_ACCESS)? {
8049            Some(acl) => acl.check_access(identity, stat, access, path),
8050            None => {
8051                // Most executable/package trees have no POSIX ACL. Cache that
8052                // negative lookup by inode metadata so repeated traversal does
8053                // not turn every stat into a second filesystem/database query.
8054                // Mutating xattr/ownership/mode operations clear the cache, and
8055                // externally changed inodes naturally produce a different key.
8056                if self.no_posix_acl_cache.len() >= 4_096 {
8057                    self.no_posix_acl_cache.clear();
8058                }
8059                self.no_posix_acl_cache.insert(cache_key);
8060                check_dac_mode(identity, stat, access, path)
8061            }
8062        }
8063    }
8064
8065    fn read_posix_acl(&mut self, path: &str, name: &str) -> KernelResult<Option<PosixAcl>> {
8066        match self.filesystem.get_xattr(path, name, true) {
8067            Ok(value) => PosixAcl::parse(&value, path).map(Some),
8068            Err(error) if matches!(error.code(), "ENODATA" | "EOPNOTSUPP") => Ok(None),
8069            Err(error) => Err(error.into()),
8070        }
8071    }
8072
8073    fn sync_access_acl_mode(&mut self, path: &str, mode: u32) -> KernelResult<()> {
8074        let Some(mut acl) = self.read_posix_acl(path, POSIX_ACL_ACCESS)? else {
8075            return Ok(());
8076        };
8077        acl.apply_mode(mode);
8078        self.filesystem
8079            .set_xattr(path, POSIX_ACL_ACCESS, acl.encode(), 2, true)?;
8080        Ok(())
8081    }
8082
8083    fn check_dac_parent_access(&mut self, pid: u32, path: &str, access: u32) -> KernelResult<()> {
8084        let mut parent = parent_path(path);
8085        loop {
8086            match self.check_dac_access(pid, &parent, access) {
8087                Err(error) if error.code() == "ENOENT" && parent != "/" => {
8088                    parent = parent_path(&parent);
8089                }
8090                result => return result,
8091            }
8092        }
8093    }
8094
8095    fn check_sticky_directory_removal(&mut self, pid: u32, path: &str) -> KernelResult<()> {
8096        let identity = self
8097            .processes
8098            .get(pid)
8099            .ok_or_else(|| KernelError::no_such_process(pid))?
8100            .identity;
8101        if identity.euid == 0 {
8102            return Ok(());
8103        }
8104        let parent = self.filesystem.stat(&parent_path(path))?;
8105        if parent.mode & 0o1000 == 0 || identity.euid == parent.uid {
8106            return Ok(());
8107        }
8108        let target = self.filesystem.lstat(path)?;
8109        if identity.euid == target.uid {
8110            Ok(())
8111        } else {
8112            Err(KernelError::new(
8113                "EPERM",
8114                format!("sticky directory prevents removing {path}"),
8115            ))
8116        }
8117    }
8118
8119    fn apply_process_creation_metadata(
8120        &mut self,
8121        pid: u32,
8122        path: &str,
8123        mode: u32,
8124        umask: u32,
8125        is_directory: bool,
8126    ) -> KernelResult<()> {
8127        let identity = self
8128            .processes
8129            .get(pid)
8130            .ok_or_else(|| KernelError::no_such_process(pid))?
8131            .identity;
8132        let parent_path = parent_path(path);
8133        let parent = self.filesystem.stat(&parent_path).map_err(|error| {
8134            KernelError::new(
8135                error.code(),
8136                format!("creation parent stat for '{parent_path}' failed: {error}"),
8137            )
8138        })?;
8139        let inherit_setgid = parent.mode & 0o2000 != 0;
8140        let gid = if inherit_setgid {
8141            parent.gid
8142        } else {
8143            identity.egid
8144        };
8145        self.filesystem
8146            .chown(path, identity.euid, gid)
8147            .map_err(|error| {
8148                KernelError::new(
8149                    error.code(),
8150                    format!("creation ownership for '{path}' failed: {error}"),
8151                )
8152            })?;
8153        let inherited_acl = self.read_posix_acl(&parent_path, POSIX_ACL_DEFAULT)?;
8154        let mut masked_mode = if let Some(acl) = inherited_acl.as_ref() {
8155            acl.restrict_to_mode(mode).mode(mode)
8156        } else {
8157            (mode & !0o777) | ((mode & 0o777) & !(umask & 0o777))
8158        };
8159        if is_directory && inherit_setgid {
8160            masked_mode |= 0o2000;
8161        }
8162        self.filesystem.chmod(path, masked_mode).map_err(|error| {
8163            KernelError::new(
8164                error.code(),
8165                format!("creation mode for '{path}' failed: {error}"),
8166            )
8167        })?;
8168        if let Some(default_acl) = inherited_acl {
8169            let access_acl = default_acl.restrict_to_mode(mode);
8170            self.filesystem
8171                .set_xattr(path, POSIX_ACL_ACCESS, access_acl.encode(), 0, true)?;
8172            if is_directory {
8173                self.filesystem.set_xattr(
8174                    path,
8175                    POSIX_ACL_DEFAULT,
8176                    default_acl.encode(),
8177                    0,
8178                    true,
8179                )?;
8180            }
8181        }
8182        Ok(())
8183    }
8184
8185    fn clear_setid_after_write(&mut self, pid: u32, path: &str) -> KernelResult<()> {
8186        let identity = self
8187            .processes
8188            .get(pid)
8189            .ok_or_else(|| KernelError::no_such_process(pid))?
8190            .identity;
8191        if identity.euid == 0 {
8192            return Ok(());
8193        }
8194        let stat = self.filesystem.stat(path)?;
8195        if stat.mode & 0o6000 != 0 {
8196            self.filesystem.chmod(path, stat.mode & !0o6000)?;
8197        }
8198        Ok(())
8199    }
8200
8201    fn missing_directory_paths(
8202        &mut self,
8203        path: &str,
8204        recursive: bool,
8205    ) -> KernelResult<Vec<String>> {
8206        let normalized = normalize_path(path);
8207        if normalized == "/" {
8208            return Ok(Vec::new());
8209        }
8210
8211        if !recursive {
8212            return Ok(if self.storage_lstat(&normalized)?.is_none() {
8213                vec![normalized]
8214            } else {
8215                Vec::new()
8216            });
8217        }
8218
8219        let mut created = Vec::new();
8220        let mut current = String::from("/");
8221        for component in normalized
8222            .split('/')
8223            .filter(|component| !component.is_empty())
8224        {
8225            current = if current == "/" {
8226                format!("/{component}")
8227            } else {
8228                format!("{current}/{component}")
8229            };
8230            if self.storage_lstat(&current)?.is_none() {
8231                created.push(current.clone());
8232            }
8233        }
8234        Ok(created)
8235    }
8236
8237    fn check_write_file_limits(&mut self, path: &str, new_size: u64) -> KernelResult<()> {
8238        let existing = self.storage_stat(path)?;
8239        self.check_write_file_limits_with_existing(path, existing.as_ref(), new_size)
8240    }
8241
8242    fn check_write_file_limits_with_existing(
8243        &mut self,
8244        path: &str,
8245        existing: Option<&VirtualStat>,
8246        new_size: u64,
8247    ) -> KernelResult<()> {
8248        if is_virtual_device_storage_path(path) {
8249            return Ok(());
8250        }
8251
8252        if let Some(existing) = existing {
8253            if is_storage_directory(Some(existing)) {
8254                return Ok(());
8255            }
8256            if new_size <= existing.size {
8257                return Ok(());
8258            }
8259
8260            let usage = self.filesystem_usage()?;
8261            self.resources.check_filesystem_usage(
8262                &usage,
8263                usage
8264                    .total_bytes
8265                    .saturating_sub(existing.size)
8266                    .saturating_add(new_size),
8267                usage.inode_count,
8268            )?;
8269            return Ok(());
8270        }
8271
8272        let usage = self.filesystem_usage()?;
8273        self.resources.check_filesystem_usage(
8274            &usage,
8275            usage.total_bytes.saturating_add(new_size),
8276            usage.inode_count.saturating_add(1),
8277        )?;
8278        Ok(())
8279    }
8280
8281    fn check_create_dir_limits(&mut self, path: &str) -> KernelResult<()> {
8282        if is_virtual_device_storage_path(path) || self.storage_lstat(path)?.is_some() {
8283            return Ok(());
8284        }
8285
8286        let parent = parent_path(path);
8287        let Some(parent_stat) = self.storage_stat(&parent)? else {
8288            return Ok(());
8289        };
8290        if !parent_stat.is_directory {
8291            return Ok(());
8292        }
8293
8294        let usage = self.filesystem_usage()?;
8295        self.resources.check_filesystem_usage(
8296            &usage,
8297            usage.total_bytes,
8298            usage.inode_count.saturating_add(1),
8299        )?;
8300        Ok(())
8301    }
8302
8303    fn check_mkdir_limits(&mut self, path: &str, recursive: bool) -> KernelResult<()> {
8304        if is_virtual_device_storage_path(path) {
8305            return Ok(());
8306        }
8307
8308        if !recursive {
8309            return self.check_create_dir_limits(path);
8310        }
8311
8312        let usage = self.filesystem_usage()?;
8313        let new_inodes = count_missing_directory_components(self.raw_filesystem_mut(), path, true)?;
8314        self.resources.check_filesystem_usage(
8315            &usage,
8316            usage.total_bytes,
8317            usage.inode_count.saturating_add(new_inodes),
8318        )?;
8319        Ok(())
8320    }
8321
8322    fn check_symlink_limits(&mut self, target: &str, link_path: &str) -> KernelResult<()> {
8323        if is_virtual_device_storage_path(link_path) || self.storage_lstat(link_path)?.is_some() {
8324            return Ok(());
8325        }
8326
8327        let parent = parent_path(link_path);
8328        let Some(parent_stat) = self.storage_stat(&parent)? else {
8329            return Ok(());
8330        };
8331        if !parent_stat.is_directory {
8332            return Ok(());
8333        }
8334
8335        let usage = self.filesystem_usage()?;
8336        self.resources.check_filesystem_usage(
8337            &usage,
8338            usage.total_bytes.saturating_add(target.len() as u64),
8339            usage.inode_count.saturating_add(1),
8340        )?;
8341        Ok(())
8342    }
8343
8344    fn check_truncate_limits_with_existing(
8345        &mut self,
8346        path: &str,
8347        existing: Option<&VirtualStat>,
8348        length: u64,
8349    ) -> KernelResult<()> {
8350        if is_virtual_device_storage_path(path) {
8351            return Ok(());
8352        }
8353
8354        let Some(existing) = existing else {
8355            return Ok(());
8356        };
8357        if is_storage_directory(Some(existing)) {
8358            return Ok(());
8359        }
8360        self.check_path_resize_limits_with_existing(existing.size, length)
8361    }
8362
8363    fn check_rename_copy_up_limits(&mut self, old_path: &str, new_path: &str) -> KernelResult<()> {
8364        let max_bytes = self.resource_limits().max_filesystem_bytes;
8365        let max_inodes = self.resource_limits().max_inode_count;
8366        let filesystem_any = self.raw_filesystem_mut() as &mut dyn Any;
8367
8368        if let Some(root) = filesystem_any.downcast_mut::<RootFileSystem>() {
8369            root.check_rename_copy_up_limits(old_path, new_path, max_bytes, max_inodes)?;
8370            return Ok(());
8371        }
8372
8373        if let Some(mount_table) = filesystem_any.downcast_mut::<MountTable>() {
8374            mount_table.check_rename_copy_up_limits(old_path, new_path, max_bytes, max_inodes)?;
8375        }
8376
8377        Ok(())
8378    }
8379
8380    fn check_path_resize_limits_with_existing(
8381        &mut self,
8382        existing_size: u64,
8383        new_size: u64,
8384    ) -> KernelResult<()> {
8385        if new_size <= existing_size {
8386            return Ok(());
8387        }
8388
8389        let usage = self.filesystem_usage()?;
8390        self.resources.check_filesystem_usage(
8391            &usage,
8392            usage
8393                .total_bytes
8394                .saturating_sub(existing_size)
8395                .saturating_add(new_size),
8396            usage.inode_count,
8397        )?;
8398        Ok(())
8399    }
8400
8401    fn blocking_read_timeout(&self) -> Option<Duration> {
8402        self.resources
8403            .limits()
8404            .max_blocking_read_ms
8405            .map(Duration::from_millis)
8406    }
8407
8408    fn close_special_resource_if_needed(&self, description: &Arc<FileDescription>, filetype: u8) {
8409        close_special_resource_if_needed(
8410            &self.file_locks,
8411            &self.pipes,
8412            &self.ptys,
8413            &self.sockets,
8414            &self.fd_sockets,
8415            description,
8416            filetype,
8417        );
8418    }
8419
8420    fn cleanup_unnamed_file_if_closed(
8421        &mut self,
8422        description: &Arc<FileDescription>,
8423    ) -> KernelResult<()> {
8424        if description.ref_count() != 0 {
8425            return Ok(());
8426        }
8427        let Some(unnamed) = self.unnamed_files.get(&description.id()).cloned() else {
8428            return Ok(());
8429        };
8430        match self.filesystem.remove_file(&unnamed.path) {
8431            Ok(()) => {
8432                self.unnamed_files.remove(&description.id());
8433                self.invalidate_filesystem_usage_cache();
8434                Ok(())
8435            }
8436            Err(error) if error.code() == "ENOENT" => {
8437                self.unnamed_files.remove(&description.id());
8438                Ok(())
8439            }
8440            Err(error) => Err(error.into()),
8441        }
8442    }
8443}
8444
8445impl KernelVm<MountTable> {
8446    fn check_mount_permissions(&self, path: &str) -> KernelResult<()> {
8447        self.filesystem
8448            .check_path(FsOperation::Write, path)
8449            .map_err(KernelError::from)?;
8450        if is_sensitive_mount_path(path) {
8451            self.filesystem
8452                .check_path(FsOperation::MountSensitive, path)
8453                .map_err(KernelError::from)?;
8454        }
8455        Ok(())
8456    }
8457
8458    pub fn mount_filesystem(
8459        &mut self,
8460        path: &str,
8461        filesystem: impl VirtualFileSystem + 'static,
8462        options: MountOptions,
8463    ) -> KernelResult<()> {
8464        self.assert_not_terminated()?;
8465        self.check_mount_permissions(path)?;
8466        self.filesystem
8467            .inner_mut()
8468            .inner_mut()
8469            .mount(path, filesystem, options)
8470            .map_err(KernelError::from)?;
8471        self.invalidate_filesystem_usage_cache();
8472        Ok(())
8473    }
8474
8475    pub fn mount_boxed_filesystem(
8476        &mut self,
8477        path: &str,
8478        filesystem: Box<dyn MountedFileSystem>,
8479        options: MountOptions,
8480    ) -> KernelResult<()> {
8481        self.assert_not_terminated()?;
8482        self.check_mount_permissions(path)?;
8483        self.filesystem
8484            .inner_mut()
8485            .inner_mut()
8486            .mount_boxed(path, filesystem, options)
8487            .map_err(KernelError::from)?;
8488        self.invalidate_filesystem_usage_cache();
8489        Ok(())
8490    }
8491
8492    pub fn unmount_filesystem(&mut self, path: &str) -> KernelResult<()> {
8493        self.assert_not_terminated()?;
8494        self.check_mount_permissions(path)?;
8495        self.filesystem
8496            .inner_mut()
8497            .inner_mut()
8498            .unmount(path)
8499            .map_err(KernelError::from)?;
8500        self.invalidate_filesystem_usage_cache();
8501        Ok(())
8502    }
8503
8504    pub fn remount_filesystem_for_process(
8505        &mut self,
8506        requester_driver: &str,
8507        pid: u32,
8508        path: &str,
8509        options: &str,
8510    ) -> KernelResult<()> {
8511        self.assert_not_terminated()?;
8512        self.assert_driver_owns(requester_driver, pid)?;
8513        if self.process_identity(requester_driver, pid)?.euid != 0 {
8514            return Err(KernelError::new(
8515                "EPERM",
8516                "remount requires effective uid 0",
8517            ));
8518        }
8519        self.check_mount_permissions(path)?;
8520        self.filesystem
8521            .inner_mut()
8522            .inner_mut()
8523            .remount(path, options)
8524            .map_err(KernelError::from)
8525    }
8526
8527    pub fn mounted_filesystems(&self) -> Vec<MountEntry> {
8528        self.filesystem.inner().inner().get_mounts()
8529    }
8530
8531    pub fn root_filesystem_mut(&mut self) -> Option<&mut RootFileSystem> {
8532        self.filesystem
8533            .inner_mut()
8534            .inner_mut()
8535            .root_virtual_filesystem_mut::<RootFileSystem>()
8536    }
8537
8538    pub fn snapshot_root_filesystem(&mut self) -> KernelResult<RootFilesystemSnapshot> {
8539        let usage = self.filesystem_usage()?;
8540        self.resources
8541            .check_filesystem_usage(&usage, usage.total_bytes, usage.inode_count)?;
8542        let root = self
8543            .root_filesystem_mut()
8544            .ok_or_else(|| KernelError::new("EINVAL", "native root filesystem is not available"))?;
8545        root.snapshot().map_err(KernelError::from)
8546    }
8547
8548    /// Snapshot the root filesystem without allowing caller-selected export
8549    /// work to materialize or return more than `max_bytes`. Raw content usage
8550    /// is checked before traversal; the encoded snapshot is checked before it
8551    /// can leave the kernel.
8552    pub fn snapshot_root_filesystem_bounded(
8553        &mut self,
8554        max_bytes: u64,
8555    ) -> KernelResult<RootFilesystemSnapshot> {
8556        if max_bytes == 0 {
8557            return Err(KernelError::new(
8558                "EINVAL",
8559                "maxBytes must be greater than zero",
8560            ));
8561        }
8562        let usage = self.filesystem_usage()?;
8563        self.resources
8564            .check_filesystem_usage(&usage, usage.total_bytes, usage.inode_count)?;
8565        if usage.total_bytes > max_bytes {
8566            return Err(KernelError::new(
8567                "EFBIG",
8568                format!(
8569                    "root filesystem export exceeds maxBytes: {} content bytes > {max_bytes}; raise maxBytes",
8570                    usage.total_bytes
8571                ),
8572            ));
8573        }
8574        let root = self
8575            .root_filesystem_mut()
8576            .ok_or_else(|| KernelError::new("EINVAL", "native root filesystem is not available"))?;
8577        let snapshot = root.snapshot().map_err(KernelError::from)?;
8578        let encoded_len = encode_snapshot(&snapshot).map_err(KernelError::from)?.len();
8579        if u64::try_from(encoded_len).unwrap_or(u64::MAX) > max_bytes {
8580            return Err(KernelError::new(
8581                "EFBIG",
8582                format!(
8583                    "root filesystem export exceeds maxBytes: {encoded_len} encoded bytes > {max_bytes}; raise maxBytes"
8584                ),
8585            ));
8586        }
8587        Ok(snapshot)
8588    }
8589}
8590
8591#[derive(Default)]
8592struct StubDriverState {
8593    exit_code: Option<i32>,
8594    on_exit: Option<ProcessExitCallback>,
8595    kill_signals: Vec<i32>,
8596}
8597
8598#[derive(Default)]
8599struct StubDriverProcess {
8600    state: Mutex<StubDriverState>,
8601    waiters: Condvar,
8602}
8603
8604impl StubDriverProcess {
8605    fn finish(&self, exit_code: i32) {
8606        let callback = {
8607            let mut state = lock_or_recover(&self.state);
8608            if state.exit_code.is_some() {
8609                return;
8610            }
8611            state.exit_code = Some(exit_code);
8612            self.waiters.notify_all();
8613            state.on_exit.clone()
8614        };
8615
8616        if let Some(callback) = callback {
8617            callback(exit_code);
8618        }
8619    }
8620
8621    fn kill_signals(&self) -> Vec<i32> {
8622        lock_or_recover(&self.state).kill_signals.clone()
8623    }
8624}
8625
8626impl DriverProcess for StubDriverProcess {
8627    fn kill(&self, signal: i32) {
8628        {
8629            let mut state = lock_or_recover(&self.state);
8630            state.kill_signals.push(signal);
8631        }
8632        if matches!(
8633            signal,
8634            crate::process_table::SIGCHLD | SIGCONT | SIGSTOP | SIGTSTP | SIGWINCH
8635        ) {
8636            return;
8637        }
8638        self.finish(128 + signal);
8639    }
8640
8641    fn wait(&self, timeout: Duration) -> Option<i32> {
8642        let state = lock_or_recover(&self.state);
8643        if let Some(code) = state.exit_code {
8644            return Some(code);
8645        }
8646
8647        let (state, _) = wait_timeout_or_recover(&self.waiters, state, timeout);
8648        state.exit_code
8649    }
8650
8651    fn set_on_exit(&self, callback: ProcessExitCallback) {
8652        let maybe_exit = {
8653            let mut state = lock_or_recover(&self.state);
8654            state.on_exit = Some(callback.clone());
8655            state.exit_code
8656        };
8657
8658        if let Some(code) = maybe_exit {
8659            callback(code);
8660        }
8661    }
8662}
8663
8664fn unix_socket_absolute_components(
8665    cwd: &str,
8666    path: &str,
8667) -> KernelResult<(String, VecDeque<String>, bool)> {
8668    if path.is_empty() {
8669        return Err(KernelError::new(
8670            "ENOENT",
8671            "Unix socket pathname must not be empty",
8672        ));
8673    }
8674    if path.as_bytes().contains(&0) {
8675        return Err(KernelError::new(
8676            "EINVAL",
8677            "Unix socket pathname contains a NUL byte",
8678        ));
8679    }
8680    // Linux copies at most PATH_MAX bytes from the caller, including the
8681    // terminating NUL. Check the raw pathname before any `..` processing so a
8682    // long spelling cannot become valid merely by normalizing shorter.
8683    if path.len() >= MAX_PATH_LENGTH {
8684        return Err(KernelError::new(
8685            "ENAMETOOLONG",
8686            format!(
8687                "Unix socket pathname is {} bytes; Linux permits at most {}",
8688                path.len(),
8689                MAX_PATH_LENGTH - 1
8690            ),
8691        ));
8692    }
8693    if !path.starts_with('/') && !cwd.starts_with('/') {
8694        return Err(KernelError::new(
8695            "EINVAL",
8696            format!("Unix socket cwd must be absolute: {cwd}"),
8697        ));
8698    }
8699
8700    let absolute = if path.starts_with('/') {
8701        path.to_owned()
8702    } else if cwd == "/" {
8703        format!("/{path}")
8704    } else {
8705        format!("{}/{path}", cwd.trim_end_matches('/'))
8706    };
8707    let trailing_slash = absolute.len() > 1 && absolute.ends_with('/');
8708    let components = absolute
8709        .split('/')
8710        .filter(|component| !component.is_empty())
8711        .map(ToOwned::to_owned)
8712        .collect();
8713    Ok((absolute, components, trailing_slash))
8714}
8715
8716fn resolve_unix_socket_components<F: VirtualFileSystem>(
8717    filesystem: &mut F,
8718    identity: &ProcessIdentity,
8719    mut remaining: VecDeque<String>,
8720    follow_final_symlink: bool,
8721    mut require_final_directory: bool,
8722) -> KernelResult<UnixSocketPathNode> {
8723    let mut resolved = Vec::<String>::new();
8724    let mut followed_symlinks = 0usize;
8725
8726    if remaining.is_empty() {
8727        let stat = filesystem.stat("/")?;
8728        if require_final_directory && !stat.is_directory {
8729            return Err(KernelError::new("ENOTDIR", "root is not a directory"));
8730        }
8731        return Ok(UnixSocketPathNode {
8732            canonical_path: String::from("/"),
8733            stat,
8734        });
8735    }
8736
8737    while let Some(component) = remaining.pop_front() {
8738        let current_path = unix_socket_components_path(&resolved);
8739        let current_stat = filesystem.stat(&current_path)?;
8740        if !current_stat.is_directory {
8741            return Err(KernelError::new(
8742                "ENOTDIR",
8743                format!("not a directory while resolving Unix socket path: {current_path}"),
8744            ));
8745        }
8746        check_unix_dac(
8747            identity,
8748            &current_stat,
8749            UNIX_DAC_SEARCH,
8750            "search",
8751            &current_path,
8752        )?;
8753
8754        match component.as_str() {
8755            "." => continue,
8756            ".." => {
8757                resolved.pop();
8758                continue;
8759            }
8760            _ => {}
8761        }
8762
8763        let candidate = join_absolute_path(&current_path, &component);
8764        let stat = filesystem.lstat(&candidate)?;
8765        let is_final = remaining.is_empty();
8766        if stat.is_symbolic_link && (!is_final || follow_final_symlink) {
8767            followed_symlinks = followed_symlinks.saturating_add(1);
8768            if followed_symlinks > MAX_UNIX_SOCKET_SYMLINKS {
8769                return Err(KernelError::new(
8770                    "ELOOP",
8771                    format!("too many symbolic links while resolving '{candidate}'"),
8772                ));
8773            }
8774            let target = filesystem.read_link(&candidate)?;
8775            if target.is_empty() {
8776                return Err(KernelError::new(
8777                    "ENOENT",
8778                    format!("empty symbolic link target while resolving '{candidate}'"),
8779                ));
8780            }
8781            if target.starts_with('/') {
8782                resolved.clear();
8783            }
8784            // A slash at the end of a symlink target carries the same
8785            // directory requirement as a slash in the caller's pathname.
8786            // Preserve it when the link supplies the final component.
8787            if target.len() > 1 && target.ends_with('/') && remaining.is_empty() {
8788                require_final_directory = true;
8789            }
8790            let target_components = target
8791                .split('/')
8792                .filter(|target_component| !target_component.is_empty())
8793                .map(ToOwned::to_owned)
8794                .collect::<Vec<_>>();
8795            for target_component in target_components.into_iter().rev() {
8796                remaining.push_front(target_component);
8797            }
8798            continue;
8799        }
8800
8801        if is_final {
8802            if require_final_directory && !stat.is_directory {
8803                return Err(KernelError::new(
8804                    "ENOTDIR",
8805                    format!("not a directory while resolving Unix socket path: {candidate}"),
8806                ));
8807            }
8808            return Ok(UnixSocketPathNode {
8809                canonical_path: candidate,
8810                stat,
8811            });
8812        }
8813        if !stat.is_directory {
8814            return Err(KernelError::new(
8815                "ENOTDIR",
8816                format!("not a directory while resolving Unix socket path: {candidate}"),
8817            ));
8818        }
8819        resolved.push(component);
8820    }
8821
8822    let canonical_path = unix_socket_components_path(&resolved);
8823    let stat = filesystem.stat(&canonical_path)?;
8824    if require_final_directory && !stat.is_directory {
8825        return Err(KernelError::new(
8826            "ENOTDIR",
8827            format!("not a directory while resolving Unix socket path: {canonical_path}"),
8828        ));
8829    }
8830    Ok(UnixSocketPathNode {
8831        canonical_path,
8832        stat,
8833    })
8834}
8835
8836fn unix_socket_components_path(components: &[String]) -> String {
8837    if components.is_empty() {
8838        String::from("/")
8839    } else {
8840        format!("/{}", components.join("/"))
8841    }
8842}
8843
8844fn check_unix_dac(
8845    identity: &ProcessIdentity,
8846    stat: &VirtualStat,
8847    requested: u32,
8848    operation: &str,
8849    path: &str,
8850) -> KernelResult<()> {
8851    // AgentOS has no fsuid/fsgid or capability mutation. Match Linux's
8852    // ordinary case with euid/egid, and model uid 0 as CAP_DAC_OVERRIDE for
8853    // the write/search checks used by AF_UNIX pathname operations.
8854    if identity.euid == 0 {
8855        return Ok(());
8856    }
8857    let shift = if identity.euid == stat.uid {
8858        6
8859    } else if identity.egid == stat.gid || identity.supplementary_gids.contains(&stat.gid) {
8860        3
8861    } else {
8862        0
8863    };
8864    let granted = (stat.mode >> shift) & 0o7;
8865    if granted & requested == requested {
8866        Ok(())
8867    } else {
8868        Err(KernelError::new(
8869            "EACCES",
8870            format!("permission denied, {operation} Unix socket path '{path}'"),
8871        ))
8872    }
8873}
8874
8875fn validate_chown_request(
8876    identity: &ProcessIdentity,
8877    stat: &VirtualStat,
8878    requested_uid: u32,
8879    requested_gid: u32,
8880    subject: &str,
8881) -> KernelResult<(u32, u32)> {
8882    const UNCHANGED_ID: u32 = u32::MAX;
8883    let next_uid = if requested_uid == UNCHANGED_ID {
8884        stat.uid
8885    } else {
8886        requested_uid
8887    };
8888    let next_gid = if requested_gid == UNCHANGED_ID {
8889        stat.gid
8890    } else {
8891        requested_gid
8892    };
8893
8894    if identity.euid == 0 || (requested_uid == UNCHANGED_ID && requested_gid == UNCHANGED_ID) {
8895        return Ok((next_uid, next_gid));
8896    }
8897    if identity.euid != stat.uid {
8898        return Err(KernelError::new(
8899            "EPERM",
8900            format!("operation not permitted, process does not own '{subject}'"),
8901        ));
8902    }
8903    if requested_uid != UNCHANGED_ID && requested_uid != stat.uid {
8904        return Err(KernelError::new(
8905            "EPERM",
8906            format!("operation not permitted, cannot change owner of '{subject}'"),
8907        ));
8908    }
8909    if requested_gid != UNCHANGED_ID
8910        && requested_gid != identity.egid
8911        && !identity.supplementary_gids.contains(&requested_gid)
8912    {
8913        return Err(KernelError::new(
8914            "EPERM",
8915            format!(
8916                "operation not permitted, gid {requested_gid} is not a process group for '{subject}'"
8917            ),
8918        ));
8919    }
8920    Ok((next_uid, next_gid))
8921}
8922
8923/// Linux clears S_ISUID on a regular file after chown, but preserves S_ISGID
8924/// when the group-execute bit is clear because that combination represents
8925/// mandatory-locking metadata rather than set-group-ID execution.
8926fn linux_chown_cleared_mode(stat: &VirtualStat) -> Option<u32> {
8927    if stat.mode & 0o170000 != 0o100000 {
8928        return None;
8929    }
8930    let mut mode = stat.mode & !0o4000;
8931    if stat.mode & 0o0010 != 0 {
8932        mode &= !0o2000;
8933    }
8934    (mode != stat.mode).then_some(mode)
8935}
8936
8937fn unix_socket_address_in_use(path: &str) -> KernelError {
8938    KernelError::new(
8939        "EADDRINUSE",
8940        format!("Unix socket pathname is already in use: {path}"),
8941    )
8942}
8943
8944impl From<VfsError> for KernelError {
8945    fn from(error: VfsError) -> Self {
8946        map_error(error.code(), error.to_string())
8947    }
8948}
8949
8950fn lock_or_recover<'a, T>(mutex: &'a Mutex<T>) -> MutexGuard<'a, T> {
8951    match mutex.lock() {
8952        Ok(guard) => guard,
8953        Err(poisoned) => poisoned.into_inner(),
8954    }
8955}
8956
8957fn wait_timeout_or_recover<'a, T>(
8958    condvar: &Condvar,
8959    guard: MutexGuard<'a, T>,
8960    timeout: Duration,
8961) -> (MutexGuard<'a, T>, WaitTimeoutResult) {
8962    match condvar.wait_timeout(guard, timeout) {
8963        Ok(result) => result,
8964        Err(poisoned) => poisoned.into_inner(),
8965    }
8966}
8967
8968fn is_sensitive_mount_path(path: &str) -> bool {
8969    let normalized = crate::vfs::normalize_path(path);
8970    normalized == "/"
8971        || normalized == "/etc"
8972        || normalized.starts_with("/etc/")
8973        || normalized == "/proc"
8974        || normalized.starts_with("/proc/")
8975}
8976
8977impl From<FdTableError> for KernelError {
8978    fn from(error: FdTableError) -> Self {
8979        map_error(error.code(), error.to_string())
8980    }
8981}
8982
8983impl From<PipeError> for KernelError {
8984    fn from(error: PipeError) -> Self {
8985        map_error(error.code(), error.to_string())
8986    }
8987}
8988
8989impl From<PtyError> for KernelError {
8990    fn from(error: PtyError) -> Self {
8991        map_error(error.code(), error.to_string())
8992    }
8993}
8994
8995impl From<ProcessTableError> for KernelError {
8996    fn from(error: ProcessTableError) -> Self {
8997        map_error(error.code(), error.to_string())
8998    }
8999}
9000
9001impl From<PermissionError> for KernelError {
9002    fn from(error: PermissionError) -> Self {
9003        map_error(error.code(), error.to_string())
9004    }
9005}
9006
9007impl From<ResourceError> for KernelError {
9008    fn from(error: ResourceError) -> Self {
9009        map_error(error.code(), error.to_string())
9010    }
9011}
9012
9013impl From<SocketTableError> for KernelError {
9014    fn from(error: SocketTableError) -> Self {
9015        map_error(error.code(), error.to_string())
9016    }
9017}
9018
9019impl From<RootFilesystemError> for KernelError {
9020    fn from(error: RootFilesystemError) -> Self {
9021        map_error("EINVAL", error.to_string())
9022    }
9023}
9024
9025fn map_dns_resolver_error(error: crate::dns::DnsResolverError) -> KernelError {
9026    let code = match error.kind() {
9027        DnsResolverErrorKind::InvalidInput => "EINVAL",
9028        DnsResolverErrorKind::NxDomain => "ENOENT",
9029        DnsResolverErrorKind::NoData => "ENODATA",
9030        DnsResolverErrorKind::LookupFailed => "EHOSTUNREACH",
9031    };
9032    map_error(code, error.to_string())
9033}
9034
9035fn map_error(code: &'static str, message: String) -> KernelError {
9036    let trimmed = strip_error_prefix(code, &message)
9037        .map(ToOwned::to_owned)
9038        .unwrap_or(message);
9039    KernelError::new(code, trimmed)
9040}
9041
9042fn strip_error_prefix<'a>(code: &str, message: &'a str) -> Option<&'a str> {
9043    let prefix = format!("{code}: ");
9044    message.strip_prefix(&prefix)
9045}
9046
9047fn parse_dev_fd_path(path: &str) -> KernelResult<Option<u32>> {
9048    let Some(raw_fd) = path.strip_prefix("/dev/fd/") else {
9049        return Ok(None);
9050    };
9051    if raw_fd.is_empty() {
9052        return Err(KernelError::new(
9053            "EBADF",
9054            format!("bad file descriptor: {path}"),
9055        ));
9056    }
9057    let fd = raw_fd
9058        .parse::<u32>()
9059        .map_err(|_| KernelError::new("EBADF", format!("bad file descriptor: {path}")))?;
9060    Ok(Some(fd))
9061}
9062
9063fn count_missing_directory_components<F: VirtualFileSystem>(
9064    filesystem: &mut F,
9065    path: &str,
9066    include_final: bool,
9067) -> VfsResult<usize> {
9068    let normalized = normalize_path(path);
9069    let parts = normalized
9070        .split('/')
9071        .filter(|part| !part.is_empty())
9072        .collect::<Vec<_>>();
9073    let limit = if include_final {
9074        parts.len()
9075    } else {
9076        parts.len().saturating_sub(1)
9077    };
9078
9079    let mut current = String::from("/");
9080    for (index, part) in parts.iter().take(limit).enumerate() {
9081        let candidate = if current == "/" {
9082            format!("/{}", part)
9083        } else {
9084            format!("{current}/{}", part)
9085        };
9086
9087        match filesystem.stat(&candidate) {
9088            Ok(stat) => {
9089                if !stat.is_directory {
9090                    return Err(VfsError::new(
9091                        "ENOTDIR",
9092                        format!("not a directory, mkdir '{candidate}'"),
9093                    ));
9094                }
9095                current = candidate;
9096            }
9097            Err(error) if error.code() == "ENOENT" => {
9098                return Ok(limit.saturating_sub(index));
9099            }
9100            Err(error) => return Err(error),
9101        }
9102    }
9103
9104    Ok(0)
9105}
9106
9107fn parent_path(path: &str) -> String {
9108    let normalized = normalize_path(path);
9109    let Some((head, _)) = normalized.rsplit_once('/') else {
9110        return String::from("/");
9111    };
9112
9113    if head.is_empty() {
9114        String::from("/")
9115    } else {
9116        String::from(head)
9117    }
9118}
9119
9120fn required_dirent_ino(path: &str, ino: u64) -> KernelResult<u64> {
9121    if ino == 0 {
9122        return Err(KernelError::new(
9123            "EIO",
9124            format!("filesystem returned an invalid zero inode for directory entry {path}"),
9125        ));
9126    }
9127    Ok(ino)
9128}
9129
9130fn join_absolute_path(parent: &str, child: &str) -> String {
9131    if parent == "/" {
9132        format!("/{child}")
9133    } else {
9134        format!("{parent}/{child}")
9135    }
9136}
9137
9138fn join_child_path(parent: &str, child: &str) -> String {
9139    normalize_path(&join_absolute_path(parent, child))
9140}
9141
9142fn is_virtual_device_storage_path(path: &str) -> bool {
9143    matches!(
9144        path,
9145        "/dev/null" | "/dev/zero" | "/dev/stdin" | "/dev/stdout" | "/dev/stderr" | "/dev/urandom"
9146    ) || path == "/dev"
9147        || path == "/dev/fd"
9148        || path == "/dev/pts"
9149        || path.starts_with("/dev/fd/")
9150        || path.starts_with("/dev/pts/")
9151}
9152
9153fn is_storage_directory(stat: Option<&VirtualStat>) -> bool {
9154    stat.is_some_and(|stat| stat.is_directory && !stat.is_symbolic_link)
9155}
9156
9157fn is_proc_path(path: &str) -> bool {
9158    let normalized = normalize_path(path);
9159    normalized == "/proc" || normalized.starts_with("/proc/")
9160}
9161
9162fn is_agentos_path(path: &str) -> bool {
9163    let normalized = normalize_path(path);
9164    normalized == "/etc/agentos" || normalized.starts_with("/etc/agentos/")
9165}
9166
9167fn open_requires_write_access(flags: u32) -> bool {
9168    flags & (O_CREAT | O_EXCL | O_TRUNC) != 0 || (flags & 0b11) != crate::fd_table::O_RDONLY
9169}
9170
9171const DAC_EXECUTE: u32 = 0o1;
9172const DAC_WRITE: u32 = 0o2;
9173const DAC_READ: u32 = 0o4;
9174const POSIX_ACL_ACCESS: &str = "system.posix_acl_access";
9175const POSIX_ACL_DEFAULT: &str = "system.posix_acl_default";
9176const POSIX_ACL_XATTR_VERSION: u32 = 2;
9177const POSIX_ACL_ENTRY_LIMIT: usize = 25;
9178const XATTR_NAME_MAX: usize = 255;
9179const ACL_USER_OBJ: u16 = 0x01;
9180const ACL_USER: u16 = 0x02;
9181const ACL_GROUP_OBJ: u16 = 0x04;
9182const ACL_GROUP: u16 = 0x08;
9183const ACL_MASK: u16 = 0x10;
9184const ACL_OTHER: u16 = 0x20;
9185const ACL_UNDEFINED_ID: u32 = u32::MAX;
9186
9187#[derive(Clone, Debug, PartialEq, Eq)]
9188struct PosixAclEntry {
9189    tag: u16,
9190    perm: u16,
9191    id: u32,
9192}
9193
9194#[derive(Clone, Debug, PartialEq, Eq)]
9195struct PosixAcl {
9196    entries: Vec<PosixAclEntry>,
9197}
9198
9199impl PosixAcl {
9200    fn parse(value: &[u8], path: &str) -> KernelResult<Self> {
9201        if value.len() < 4 || !(value.len() - 4).is_multiple_of(8) {
9202            return Err(invalid_acl(path, "invalid xattr length"));
9203        }
9204        let entry_count = (value.len() - 4) / 8;
9205        if entry_count > POSIX_ACL_ENTRY_LIMIT {
9206            return Err(KernelError::new(
9207                "E2BIG",
9208                format!(
9209                    "POSIX ACL for {path} has {entry_count} entries; limit is {POSIX_ACL_ENTRY_LIMIT}"
9210                ),
9211            ));
9212        }
9213        let version = u32::from_le_bytes(value[0..4].try_into().expect("four ACL version bytes"));
9214        if version != POSIX_ACL_XATTR_VERSION {
9215            return Err(invalid_acl(path, "unsupported xattr version"));
9216        }
9217        let entries = value[4..]
9218            .chunks_exact(8)
9219            .map(|bytes| PosixAclEntry {
9220                tag: u16::from_le_bytes([bytes[0], bytes[1]]),
9221                perm: u16::from_le_bytes([bytes[2], bytes[3]]),
9222                id: u32::from_le_bytes([bytes[4], bytes[5], bytes[6], bytes[7]]),
9223            })
9224            .collect::<Vec<_>>();
9225        let acl = Self { entries };
9226        acl.validate(path)?;
9227        Ok(acl)
9228    }
9229
9230    fn validate(&self, path: &str) -> KernelResult<()> {
9231        if self.entries.len() < 3 {
9232            return Err(invalid_acl(path, "missing required entries"));
9233        }
9234        for entry in &self.entries {
9235            if entry.perm > 0o7 {
9236                return Err(invalid_acl(path, "permission bits exceed rwx"));
9237            }
9238            let named = matches!(entry.tag, ACL_USER | ACL_GROUP);
9239            if (named && entry.id == ACL_UNDEFINED_ID) || (!named && entry.id != ACL_UNDEFINED_ID) {
9240                return Err(invalid_acl(path, "entry id does not match its tag"));
9241            }
9242        }
9243
9244        let mut index = 0;
9245        if self.entries.get(index).map(|entry| entry.tag) != Some(ACL_USER_OBJ) {
9246            return Err(invalid_acl(path, "ACL must start with user::"));
9247        }
9248        index += 1;
9249        let mut last_id = None;
9250        while self
9251            .entries
9252            .get(index)
9253            .is_some_and(|entry| entry.tag == ACL_USER)
9254        {
9255            let id = self.entries[index].id;
9256            if last_id.is_some_and(|last| id <= last) {
9257                return Err(invalid_acl(path, "named users are not strictly sorted"));
9258            }
9259            last_id = Some(id);
9260            index += 1;
9261        }
9262        if self.entries.get(index).map(|entry| entry.tag) != Some(ACL_GROUP_OBJ) {
9263            return Err(invalid_acl(path, "ACL is missing group::"));
9264        }
9265        index += 1;
9266        last_id = None;
9267        while self
9268            .entries
9269            .get(index)
9270            .is_some_and(|entry| entry.tag == ACL_GROUP)
9271        {
9272            let id = self.entries[index].id;
9273            if last_id.is_some_and(|last| id <= last) {
9274                return Err(invalid_acl(path, "named groups are not strictly sorted"));
9275            }
9276            last_id = Some(id);
9277            index += 1;
9278        }
9279        let has_named = self
9280            .entries
9281            .iter()
9282            .any(|entry| matches!(entry.tag, ACL_USER | ACL_GROUP));
9283        let has_mask = self
9284            .entries
9285            .get(index)
9286            .is_some_and(|entry| entry.tag == ACL_MASK);
9287        if has_mask {
9288            index += 1;
9289        }
9290        if has_named && !has_mask {
9291            return Err(invalid_acl(path, "named entries require a mask"));
9292        }
9293        if self.entries.get(index).map(|entry| entry.tag) != Some(ACL_OTHER)
9294            || index + 1 != self.entries.len()
9295        {
9296            return Err(invalid_acl(path, "ACL must end with other::"));
9297        }
9298        Ok(())
9299    }
9300
9301    fn encode(&self) -> Vec<u8> {
9302        let mut value = Vec::with_capacity(4 + self.entries.len() * 8);
9303        value.extend_from_slice(&POSIX_ACL_XATTR_VERSION.to_le_bytes());
9304        for entry in &self.entries {
9305            value.extend_from_slice(&entry.tag.to_le_bytes());
9306            value.extend_from_slice(&entry.perm.to_le_bytes());
9307            value.extend_from_slice(&entry.id.to_le_bytes());
9308        }
9309        value
9310    }
9311
9312    fn entry(&self, tag: u16) -> &PosixAclEntry {
9313        self.entries
9314            .iter()
9315            .find(|entry| entry.tag == tag)
9316            .expect("validated ACL required entry")
9317    }
9318
9319    fn mask(&self) -> u32 {
9320        self.entries
9321            .iter()
9322            .find(|entry| entry.tag == ACL_MASK)
9323            .map_or(0o7, |entry| u32::from(entry.perm))
9324    }
9325
9326    fn mode(&self, original_mode: u32) -> u32 {
9327        let owner = u32::from(self.entry(ACL_USER_OBJ).perm);
9328        let group = self
9329            .entries
9330            .iter()
9331            .find(|entry| entry.tag == ACL_MASK)
9332            .unwrap_or_else(|| self.entry(ACL_GROUP_OBJ));
9333        let other = u32::from(self.entry(ACL_OTHER).perm);
9334        (original_mode & !0o777) | (owner << 6) | (u32::from(group.perm) << 3) | other
9335    }
9336
9337    fn apply_mode(&mut self, mode: u32) {
9338        let has_mask = self.entries.iter().any(|entry| entry.tag == ACL_MASK);
9339        for entry in &mut self.entries {
9340            let permissions = match entry.tag {
9341                ACL_USER_OBJ => Some((mode >> 6) & 0o7),
9342                ACL_MASK => Some((mode >> 3) & 0o7),
9343                ACL_GROUP_OBJ if !has_mask => Some((mode >> 3) & 0o7),
9344                ACL_OTHER => Some(mode & 0o7),
9345                _ => None,
9346            };
9347            if let Some(permissions) = permissions {
9348                entry.perm = permissions as u16;
9349            }
9350        }
9351    }
9352
9353    fn restrict_to_mode(&self, mode: u32) -> Self {
9354        let mut acl = self.clone();
9355        let has_mask = acl.entries.iter().any(|entry| entry.tag == ACL_MASK);
9356        for entry in &mut acl.entries {
9357            let restriction = match entry.tag {
9358                ACL_USER_OBJ => Some((mode >> 6) & 0o7),
9359                ACL_MASK => Some((mode >> 3) & 0o7),
9360                ACL_GROUP_OBJ if !has_mask => Some((mode >> 3) & 0o7),
9361                ACL_OTHER => Some(mode & 0o7),
9362                _ => None,
9363            };
9364            if let Some(restriction) = restriction {
9365                entry.perm &= restriction as u16;
9366            }
9367        }
9368        acl
9369    }
9370
9371    fn check_access(
9372        &self,
9373        identity: &ProcessIdentity,
9374        stat: &VirtualStat,
9375        access: u32,
9376        path: &str,
9377    ) -> KernelResult<()> {
9378        let mask = self.mask();
9379        let granted = if identity.euid == stat.uid {
9380            u32::from(self.entry(ACL_USER_OBJ).perm)
9381        } else if let Some(entry) = self
9382            .entries
9383            .iter()
9384            .find(|entry| entry.tag == ACL_USER && entry.id == identity.euid)
9385        {
9386            u32::from(entry.perm) & mask
9387        } else {
9388            let in_group = |gid| identity.egid == gid || identity.supplementary_gids.contains(&gid);
9389            let mut matched = false;
9390            let mut group_permissions = 0;
9391            if in_group(stat.gid) {
9392                matched = true;
9393                group_permissions |= u32::from(self.entry(ACL_GROUP_OBJ).perm);
9394            }
9395            for entry in self.entries.iter().filter(|entry| entry.tag == ACL_GROUP) {
9396                if in_group(entry.id) {
9397                    matched = true;
9398                    group_permissions |= u32::from(entry.perm);
9399                }
9400            }
9401            if matched {
9402                group_permissions & mask
9403            } else {
9404                u32::from(self.entry(ACL_OTHER).perm)
9405            }
9406        };
9407        if granted & access == access {
9408            Ok(())
9409        } else {
9410            Err(KernelError::new(
9411                "EACCES",
9412                format!(
9413                    "ACL permission denied: {path} requires {access:o}, granted={granted:o}, euid={}, egid={}",
9414                    identity.euid, identity.egid
9415                ),
9416            ))
9417        }
9418    }
9419}
9420
9421fn invalid_acl(path: &str, reason: &str) -> KernelError {
9422    KernelError::new("EINVAL", format!("invalid POSIX ACL for {path}: {reason}"))
9423}
9424
9425fn check_xattr_namespace(
9426    identity: &ProcessIdentity,
9427    name: &str,
9428    write: bool,
9429    path: &str,
9430) -> KernelResult<()> {
9431    if name.is_empty() || name.len() > XATTR_NAME_MAX {
9432        return Err(KernelError::new(
9433            "EINVAL",
9434            format!(
9435                "extended attribute name for {path} is {} bytes; maximum is {XATTR_NAME_MAX}",
9436                name.len()
9437            ),
9438        ));
9439    }
9440    let supported = name.starts_with("user.")
9441        || name.starts_with("trusted.")
9442        || name.starts_with("security.")
9443        || name == "system.posix_acl_access"
9444        || name == "system.posix_acl_default";
9445    if !supported {
9446        return Err(KernelError::new(
9447            "EOPNOTSUPP",
9448            format!("unsupported extended attribute namespace for {name} on {path}"),
9449        ));
9450    }
9451    if identity.euid != 0 && (name.starts_with("trusted.") || name.starts_with("security.")) {
9452        return Err(KernelError::permission_denied(format!(
9453            "{} {name} requires root privileges on {path}",
9454            if write { "modifying" } else { "reading" }
9455        )));
9456    }
9457    Ok(())
9458}
9459
9460fn check_xattr_inode_write_policy(stat: &VirtualStat, name: &str, path: &str) -> KernelResult<()> {
9461    if name.starts_with("user.") && !stat.is_directory && stat.mode & 0o170000 != S_IFREG {
9462        return Err(KernelError::new(
9463            "EPERM",
9464            format!("user extended attributes require a regular file or directory: {path}"),
9465        ));
9466    }
9467    Ok(())
9468}
9469
9470fn check_dac_mode(
9471    identity: &ProcessIdentity,
9472    stat: &VirtualStat,
9473    access: u32,
9474    path: &str,
9475) -> KernelResult<()> {
9476    if identity.euid == 0 {
9477        if access & DAC_EXECUTE != 0 && !stat.is_directory && stat.mode & 0o111 == 0 {
9478            return Err(KernelError::new(
9479                "EACCES",
9480                format!("execute permission denied: {path}"),
9481            ));
9482        }
9483        return Ok(());
9484    }
9485    let shift = if identity.euid == stat.uid {
9486        6
9487    } else if identity.egid == stat.gid || identity.supplementary_gids.contains(&stat.gid) {
9488        3
9489    } else {
9490        0
9491    };
9492    let granted = (stat.mode >> shift) & 0o7;
9493    if granted & access == access {
9494        Ok(())
9495    } else {
9496        Err(KernelError::new(
9497            "EACCES",
9498            format!(
9499                "permission denied: {path} requires {access:o}, mode={:o}, euid={}, egid={}",
9500                stat.mode & 0o7777,
9501                identity.euid,
9502                identity.egid
9503            ),
9504        ))
9505    }
9506}
9507
9508fn credential_transition_denied(operation: &str, id: u32) -> KernelError {
9509    KernelError::new(
9510        "EPERM",
9511        format!("{operation} is not permitted for credential id {id}"),
9512    )
9513}
9514
9515fn checked_write_end(offset: u64, len: usize) -> KernelResult<u64> {
9516    offset
9517        .checked_add(len as u64)
9518        .ok_or_else(|| KernelError::new("EINVAL", "write offset out of range"))
9519}
9520
9521fn check_direct_io_alignment(flags: u32, offset: u64, len: usize) -> KernelResult<()> {
9522    const DIRECT_IO_ALIGNMENT: u64 = 512;
9523    if flags & O_DIRECT == 0 {
9524        return Ok(());
9525    }
9526    if !offset.is_multiple_of(DIRECT_IO_ALIGNMENT)
9527        || !(len as u64).is_multiple_of(DIRECT_IO_ALIGNMENT)
9528    {
9529        return Err(KernelError::new(
9530            "EINVAL",
9531            format!("O_DIRECT I/O requires {DIRECT_IO_ALIGNMENT}-byte aligned offset and length"),
9532        ));
9533    }
9534    Ok(())
9535}
9536
9537fn filetype_for_path(path: &str, stat: &VirtualStat) -> u8 {
9538    if stat.is_directory {
9539        FILETYPE_DIRECTORY
9540    } else if stat.mode & 0o170000 == 0o140000 {
9541        FILETYPE_SOCKET_STREAM
9542    } else if path.starts_with("/dev/") {
9543        FILETYPE_CHARACTER_DEVICE
9544    } else if stat.is_symbolic_link {
9545        FILETYPE_SYMBOLIC_LINK
9546    } else {
9547        FILETYPE_REGULAR_FILE
9548    }
9549}
9550
9551fn synthetic_character_device_stat(ino: u64) -> VirtualStat {
9552    synthetic_special_file_stat(ino, 0o020666, 2)
9553}
9554
9555fn synthetic_special_file_stat(ino: u64, mode: u32, dev: u64) -> VirtualStat {
9556    let now = now_ms();
9557    VirtualStat {
9558        mode,
9559        size: 0,
9560        blocks: 0,
9561        dev,
9562        rdev: 0,
9563        is_directory: false,
9564        is_symbolic_link: false,
9565        atime_ms: now,
9566        atime_nsec: 0,
9567        mtime_ms: now,
9568        mtime_nsec: 0,
9569        ctime_ms: now,
9570        ctime_nsec: 0,
9571        birthtime_ms: now,
9572        ino,
9573        nlink: 1,
9574        uid: 0,
9575        gid: 0,
9576    }
9577}
9578
9579fn proc_dir_stat(ino: u64) -> VirtualStat {
9580    let now = now_ms();
9581    VirtualStat {
9582        mode: S_IFDIR | 0o555,
9583        size: 0,
9584        blocks: 0,
9585        dev: 3,
9586        rdev: 0,
9587        is_directory: true,
9588        is_symbolic_link: false,
9589        atime_ms: now,
9590        atime_nsec: 0,
9591        mtime_ms: now,
9592        mtime_nsec: 0,
9593        ctime_ms: now,
9594        ctime_nsec: 0,
9595        birthtime_ms: now,
9596        ino,
9597        nlink: 2,
9598        uid: 0,
9599        gid: 0,
9600    }
9601}
9602
9603fn proc_file_stat(ino: u64, size: u64) -> VirtualStat {
9604    let now = now_ms();
9605    VirtualStat {
9606        mode: S_IFREG | 0o444,
9607        size,
9608        blocks: if size == 0 { 0 } else { size.div_ceil(512) },
9609        dev: 3,
9610        rdev: 0,
9611        is_directory: false,
9612        is_symbolic_link: false,
9613        atime_ms: now,
9614        atime_nsec: 0,
9615        mtime_ms: now,
9616        mtime_nsec: 0,
9617        ctime_ms: now,
9618        ctime_nsec: 0,
9619        birthtime_ms: now,
9620        ino,
9621        nlink: 1,
9622        uid: 0,
9623        gid: 0,
9624    }
9625}
9626
9627fn proc_symlink_stat(ino: u64, size: u64) -> VirtualStat {
9628    let now = now_ms();
9629    VirtualStat {
9630        mode: S_IFLNK | 0o777,
9631        size,
9632        blocks: if size == 0 { 0 } else { size.div_ceil(512) },
9633        dev: 3,
9634        rdev: 0,
9635        is_directory: false,
9636        is_symbolic_link: true,
9637        atime_ms: now,
9638        atime_nsec: 0,
9639        mtime_ms: now,
9640        mtime_nsec: 0,
9641        ctime_ms: now,
9642        ctime_nsec: 0,
9643        birthtime_ms: now,
9644        ino,
9645        nlink: 1,
9646        uid: 0,
9647        gid: 0,
9648    }
9649}
9650
9651fn proc_filetype(node: &ProcNode) -> u8 {
9652    match node {
9653        ProcNode::RootDir | ProcNode::PidDir { .. } | ProcNode::PidFdDir { .. } => {
9654            FILETYPE_DIRECTORY
9655        }
9656        ProcNode::SelfLink { .. } | ProcNode::PidCwdLink { .. } | ProcNode::PidFdLink { .. } => {
9657            FILETYPE_SYMBOLIC_LINK
9658        }
9659        ProcNode::MountsFile
9660        | ProcNode::CpuInfoFile
9661        | ProcNode::MemInfoFile
9662        | ProcNode::LoadAvgFile
9663        | ProcNode::UptimeFile
9664        | ProcNode::VersionFile
9665        | ProcNode::PidCmdline { .. }
9666        | ProcNode::PidEnviron { .. }
9667        | ProcNode::PidStatFile { .. }
9668        | ProcNode::PidStatusFile { .. } => FILETYPE_REGULAR_FILE,
9669    }
9670}
9671
9672fn proc_inode(node: &ProcNode) -> u64 {
9673    match node {
9674        ProcNode::RootDir => 0xfffe_0001,
9675        ProcNode::MountsFile => 0xfffe_0002,
9676        ProcNode::CpuInfoFile => 0xfffe_0003,
9677        ProcNode::MemInfoFile => 0xfffe_0004,
9678        ProcNode::LoadAvgFile => 0xfffe_0005,
9679        ProcNode::UptimeFile => 0xfffe_0006,
9680        ProcNode::VersionFile => 0xfffe_0007,
9681        ProcNode::SelfLink { pid } => 0xfffe_1000 + u64::from(*pid),
9682        ProcNode::PidDir { pid } => 0xfffe_2000 + u64::from(*pid),
9683        ProcNode::PidFdDir { pid } => 0xfffe_3000 + u64::from(*pid),
9684        ProcNode::PidCmdline { pid } => 0xfffe_4000 + u64::from(*pid),
9685        ProcNode::PidEnviron { pid } => 0xfffe_5000 + u64::from(*pid),
9686        ProcNode::PidCwdLink { pid } => 0xfffe_6000 + u64::from(*pid),
9687        ProcNode::PidStatFile { pid } => 0xfffe_7000 + u64::from(*pid),
9688        ProcNode::PidStatusFile { pid } => 0xfffe_8000 + u64::from(*pid),
9689        ProcNode::PidFdLink { pid, fd } => 0xffff_0000 + ((u64::from(*pid)) << 8) + u64::from(*fd),
9690    }
9691}
9692
9693fn null_separated_bytes(parts: Vec<String>) -> Vec<u8> {
9694    if parts.is_empty() {
9695        return Vec::new();
9696    }
9697
9698    let mut bytes = parts.join("\0").into_bytes();
9699    bytes.push(0);
9700    bytes
9701}
9702
9703fn proc_not_found_error(path: &str) -> KernelError {
9704    KernelError::new(
9705        "ENOENT",
9706        format!("no such file or directory, stat '{path}'"),
9707    )
9708}
9709
9710fn read_only_filesystem_error(path: &str) -> KernelError {
9711    KernelError::new("EROFS", format!("read-only filesystem: {path}"))
9712}
9713
9714fn now_ms() -> u64 {
9715    SystemTime::now()
9716        .duration_since(UNIX_EPOCH)
9717        .unwrap_or_default()
9718        .as_millis() as u64
9719}
9720
9721impl<F> Drop for KernelVm<F> {
9722    fn drop(&mut self) {
9723        if !self.terminated {
9724            dispose_kernel_vm_resources(self);
9725        }
9726    }
9727}
9728
9729#[cfg(test)]
9730mod tests {
9731    use super::*;
9732    use crate::fd_table::{FD_CLOEXEC, F_GETFD, F_SETFD, O_RDONLY};
9733    use crate::process_table::SIGTERM;
9734    use crate::vfs::MemoryFileSystem;
9735    use std::panic::{catch_unwind, AssertUnwindSafe};
9736    use std::thread;
9737
9738    fn kernel_with_process() -> (KernelVm<MemoryFileSystem>, KernelProcessHandle) {
9739        let mut config = KernelVmConfig::new("vm-fd-socket-test");
9740        config.permissions = Permissions::allow_all();
9741        let mut kernel = KernelVm::new(MemoryFileSystem::new(), config);
9742        kernel
9743            .register_driver(CommandDriver::new("wasm", ["socket-test"]))
9744            .expect("register wasm driver");
9745        let process = kernel
9746            .spawn_process(
9747                "socket-test",
9748                Vec::new(),
9749                SpawnOptions {
9750                    requester_driver: Some(String::from("wasm")),
9751                    ..SpawnOptions::default()
9752                },
9753            )
9754            .expect("spawn socket test process");
9755        (kernel, process)
9756    }
9757
9758    #[test]
9759    fn fd_socketpair_preserves_messages_and_transfers_descriptions() {
9760        let (mut kernel, process) = kernel_with_process();
9761        let pid = process.pid();
9762        let (left, right) = kernel
9763            .fd_socketpair("wasm", pid, SocketType::Stream, true, false)
9764            .expect("create stream socketpair");
9765        assert_ne!(left, right);
9766        assert_eq!(
9767            kernel
9768                .fd_stat("wasm", pid, left)
9769                .expect("stat socket")
9770                .filetype,
9771            FILETYPE_SOCKET_STREAM
9772        );
9773        assert_ne!(
9774            kernel
9775                .fd_stat("wasm", pid, left)
9776                .expect("stat socket")
9777                .flags
9778                & O_NONBLOCK,
9779            0
9780        );
9781
9782        kernel
9783            .fd_write("wasm", pid, left, b"hello")
9784            .expect("write socketpair");
9785        assert_eq!(
9786            kernel
9787                .fd_read("wasm", pid, right, 32)
9788                .expect("read socketpair"),
9789            b"hello"
9790        );
9791
9792        let (pipe_read, pipe_write) = kernel.open_pipe("wasm", pid).expect("open pipe");
9793        kernel
9794            .fd_socket_sendmsg("wasm", pid, left, b"x", &[pipe_read])
9795            .expect("send pipe description");
9796        let received = kernel
9797            .fd_socket_recvmsg("wasm", pid, right, 1, 1, true, false, false, false)
9798            .expect("receive rights")
9799            .expect("message available");
9800        assert_eq!(received.payload, b"x");
9801        assert!(!received.payload_truncated);
9802        assert!(!received.control_truncated);
9803        assert_eq!(received.rights.len(), 1);
9804        let passed_read = match received.rights[0] {
9805            ReceivedFdRight::Fd(fd) => fd,
9806            ReceivedFdRight::Opaque(_) => panic!("expected transferred pipe fd"),
9807        };
9808        assert_eq!(
9809            kernel
9810                .fd_fcntl("wasm", pid, passed_read, F_GETFD, 0)
9811                .expect("get received fd flags"),
9812            FD_CLOEXEC
9813        );
9814
9815        kernel
9816            .fd_close("wasm", pid, pipe_read)
9817            .expect("close original pipe read end");
9818        kernel
9819            .fd_write("wasm", pid, pipe_write, b"through-rights")
9820            .expect("write pipe");
9821        assert_eq!(
9822            kernel
9823                .fd_read("wasm", pid, passed_read, 64)
9824                .expect("read transferred pipe"),
9825            b"through-rights"
9826        );
9827
9828        for fd in [passed_read, pipe_write, left, right] {
9829            kernel.fd_close("wasm", pid, fd).expect("close fd");
9830        }
9831        assert_eq!(kernel.sockets.snapshot().sockets, 0);
9832        assert!(lock_or_recover(&kernel.fd_sockets).is_empty());
9833    }
9834
9835    #[test]
9836    fn closed_socket_identity_cannot_poison_reused_regular_fd() {
9837        let (mut kernel, process) = kernel_with_process();
9838        let pid = process.pid();
9839        let (left, right) = kernel
9840            .fd_socketpair("wasm", pid, SocketType::Stream, false, false)
9841            .expect("create socketpair");
9842        let stale_socket_entry = {
9843            let tables = lock_or_recover(&kernel.fd_tables);
9844            let description = &tables.get(pid).unwrap().get(left).unwrap().description;
9845            lock_or_recover(&kernel.fd_sockets)
9846                .get(&description.id())
9847                .cloned()
9848                .expect("registered socket description")
9849        };
9850        kernel
9851            .fd_close("wasm", pid, left)
9852            .expect("close left socket");
9853        kernel
9854            .fd_close("wasm", pid, right)
9855            .expect("close right socket");
9856        assert!(lock_or_recover(&kernel.fd_sockets).is_empty());
9857
9858        kernel
9859            .write_file("/regular", b"regular-data".to_vec())
9860            .expect("seed regular file");
9861        let regular_fd = kernel
9862            .fd_open("wasm", pid, "/regular", O_RDONLY, None)
9863            .expect("open regular file after socket close");
9864        let regular_description = {
9865            let tables = lock_or_recover(&kernel.fd_tables);
9866            Arc::clone(
9867                &tables
9868                    .get(pid)
9869                    .unwrap()
9870                    .get(regular_fd)
9871                    .unwrap()
9872                    .description,
9873            )
9874        };
9875        assert_ne!(
9876            regular_description.id(),
9877            stale_socket_entry.description.id(),
9878            "open-file-description ids must be globally unique"
9879        );
9880
9881        // Defense in depth: even a corrupt/stale numeric registry key cannot
9882        // classify a different description as a socket.
9883        lock_or_recover(&kernel.fd_sockets).insert(regular_description.id(), stale_socket_entry);
9884        assert_eq!(
9885            kernel
9886                .fd_read("wasm", pid, regular_fd, 32)
9887                .expect("read regular file despite stale socket key"),
9888            b"regular-data"
9889        );
9890        kernel
9891            .fd_close("wasm", pid, regular_fd)
9892            .expect("close regular file");
9893        assert!(lock_or_recover(&kernel.fd_sockets).is_empty());
9894    }
9895
9896    #[test]
9897    fn fd_socketpair_datagram_reads_one_truncated_message_at_a_time() {
9898        let (mut kernel, process) = kernel_with_process();
9899        let pid = process.pid();
9900        let (left, right) = kernel
9901            .fd_socketpair("wasm", pid, SocketType::Datagram, false, false)
9902            .expect("create datagram socketpair");
9903        kernel
9904            .fd_write("wasm", pid, left, b"abcd")
9905            .expect("write first datagram");
9906        kernel
9907            .fd_write("wasm", pid, left, b"ef")
9908            .expect("write second datagram");
9909        let truncated = kernel
9910            .fd_socket_recvmsg("wasm", pid, right, 2, 0, false, false, false, false)
9911            .unwrap()
9912            .unwrap();
9913        assert_eq!(truncated.payload, b"ab");
9914        assert!(truncated.payload_truncated);
9915        assert_eq!(truncated.full_length, 4);
9916        assert_eq!(kernel.fd_read("wasm", pid, right, 8).unwrap(), b"ef");
9917    }
9918
9919    #[test]
9920    fn fd_socketpair_peek_duplicates_rights_without_consuming_message() {
9921        let (mut kernel, process) = kernel_with_process();
9922        let pid = process.pid();
9923        let (left, right) = kernel
9924            .fd_socketpair("wasm", pid, SocketType::Stream, false, false)
9925            .unwrap();
9926        let (pipe_read, pipe_write) = kernel.open_pipe("wasm", pid).unwrap();
9927        kernel
9928            .fd_socket_sendmsg("wasm", pid, left, b"hello", &[pipe_read])
9929            .unwrap();
9930
9931        let peeked = kernel
9932            .fd_socket_recvmsg("wasm", pid, right, 2, 1, false, true, false, false)
9933            .unwrap()
9934            .unwrap();
9935        assert_eq!(peeked.payload, b"he");
9936        assert_eq!(peeked.full_length, 2);
9937        let peeked_fd = match peeked.rights[0] {
9938            ReceivedFdRight::Fd(fd) => fd,
9939            ReceivedFdRight::Opaque(_) => panic!("expected fd right"),
9940        };
9941
9942        let consumed = kernel
9943            .fd_socket_recvmsg("wasm", pid, right, 5, 1, false, false, false, true)
9944            .unwrap()
9945            .unwrap();
9946        assert_eq!(consumed.payload, b"hello");
9947        let consumed_fd = match consumed.rights[0] {
9948            ReceivedFdRight::Fd(fd) => fd,
9949            ReceivedFdRight::Opaque(_) => panic!("expected fd right"),
9950        };
9951        kernel.fd_close("wasm", pid, pipe_read).unwrap();
9952        kernel.fd_write("wasm", pid, pipe_write, b"ab").unwrap();
9953        assert_eq!(kernel.fd_read("wasm", pid, peeked_fd, 1).unwrap(), b"a");
9954        assert_eq!(kernel.fd_read("wasm", pid, consumed_fd, 1).unwrap(), b"b");
9955        for fd in [peeked_fd, consumed_fd, pipe_write, left, right] {
9956            kernel.fd_close("wasm", pid, fd).unwrap();
9957        }
9958    }
9959
9960    #[test]
9961    fn exact_fd_transfer_install_preserves_description_identity_and_offset() {
9962        let (mut kernel, parent) = kernel_with_process();
9963        let parent_pid = parent.pid();
9964        kernel.write_file("/shared", b"abc").unwrap();
9965        let source_fd = kernel
9966            .fd_open("wasm", parent_pid, "/shared", O_RDONLY, None)
9967            .unwrap();
9968        kernel.fd_seek("wasm", parent_pid, source_fd, 1, 0).unwrap();
9969        let transfer = kernel.fd_transfer("wasm", parent_pid, source_fd).unwrap();
9970
9971        let child = kernel
9972            .spawn_process(
9973                "socket-test",
9974                Vec::new(),
9975                SpawnOptions {
9976                    requester_driver: Some(String::from("wasm")),
9977                    parent_pid: Some(parent_pid),
9978                    ..SpawnOptions::default()
9979                },
9980            )
9981            .unwrap();
9982        kernel
9983            .fd_install_transfer_at("wasm", child.pid(), 9, 0, &transfer)
9984            .unwrap();
9985        assert_eq!(
9986            kernel
9987                .fd_transfer("wasm", child.pid(), 9)
9988                .unwrap()
9989                .description_id(),
9990            transfer.description_id()
9991        );
9992        assert_eq!(kernel.fd_read("wasm", child.pid(), 9, 1).unwrap(), b"b");
9993        assert_eq!(
9994            kernel.fd_read("wasm", parent_pid, source_fd, 1).unwrap(),
9995            b"c"
9996        );
9997
9998        child.finish(0);
9999        parent.finish(0);
10000        kernel.waitpid(child.pid()).unwrap();
10001        kernel.waitpid(parent_pid).unwrap();
10002    }
10003
10004    #[test]
10005    fn dev_fd_stat_reports_linux_pipe_and_socket_modes() {
10006        let (mut kernel, process) = kernel_with_process();
10007        let pid = process.pid();
10008        let (pipe_read, pipe_write) = kernel.open_pipe("wasm", pid).unwrap();
10009        let (socket_left, socket_right) = kernel
10010            .fd_socketpair("wasm", pid, SocketType::Stream, false, false)
10011            .unwrap();
10012
10013        for fd in [pipe_read, pipe_write] {
10014            let stat = kernel.dev_fd_stat("wasm", pid, fd).unwrap();
10015            assert_eq!(stat.mode, 0o010600);
10016            assert_eq!(stat.nlink, 1);
10017            assert_eq!(stat.size, 0);
10018        }
10019        let read_stat = kernel.dev_fd_stat("wasm", pid, pipe_read).unwrap();
10020        let write_stat = kernel.dev_fd_stat("wasm", pid, pipe_write).unwrap();
10021        assert_eq!(read_stat.dev, write_stat.dev);
10022        assert_eq!(read_stat.ino, write_stat.ino);
10023        for fd in [socket_left, socket_right] {
10024            let stat = kernel.dev_fd_stat("wasm", pid, fd).unwrap();
10025            assert_eq!(stat.mode, 0o140777);
10026            assert_eq!(stat.nlink, 1);
10027            assert_eq!(stat.size, 0);
10028        }
10029    }
10030
10031    #[test]
10032    fn adopted_kernel_socket_lives_while_queued_transfer_guard_exists() {
10033        let (mut kernel, process) = kernel_with_process();
10034        let pid = process.pid();
10035        let socket_id = kernel
10036            .socket_create("wasm", pid, SocketSpec::tcp())
10037            .expect("create transferable socket");
10038        let guard = kernel
10039            .fd_adopt_socket_transfer("wasm", pid, socket_id, O_NONBLOCK)
10040            .expect("retain socket description");
10041        assert_eq!(
10042            kernel.sockets.get(socket_id).unwrap().owner_pid(),
10043            0,
10044            "description-owned sockets must survive sender process cleanup"
10045        );
10046
10047        let (left, right) = kernel
10048            .fd_socketpair("wasm", pid, SocketType::Stream, false, false)
10049            .expect("create rights channel");
10050        kernel
10051            .fd_socket_sendmsg_transfers(
10052                "wasm",
10053                pid,
10054                left,
10055                b"x",
10056                &[FdTransferRequest::Opaque(Arc::new(guard))],
10057            )
10058            .expect("queue socket guard");
10059        kernel.fd_close("wasm", pid, left).unwrap();
10060        kernel.fd_close("wasm", pid, right).unwrap();
10061        assert!(
10062            kernel.sockets.get(socket_id).is_none(),
10063            "discarding the queued right must release and prune the adopted socket"
10064        );
10065    }
10066
10067    #[test]
10068    fn adopting_socket_for_transfer_does_not_require_a_free_sender_fd() {
10069        let mut config = KernelVmConfig::new("vm-full-fd-transfer-test");
10070        config.permissions = Permissions::allow_all();
10071        config.resources.max_open_fds = Some(3);
10072        let mut kernel = KernelVm::new(MemoryFileSystem::new(), config);
10073        kernel
10074            .register_driver(CommandDriver::new("wasm", ["socket-test"]))
10075            .unwrap();
10076        let process = kernel
10077            .spawn_process(
10078                "socket-test",
10079                Vec::new(),
10080                SpawnOptions {
10081                    requester_driver: Some(String::from("wasm")),
10082                    ..SpawnOptions::default()
10083                },
10084            )
10085            .unwrap();
10086        let pid = process.pid();
10087        let socket_id = kernel
10088            .socket_create("wasm", pid, SocketSpec::tcp())
10089            .unwrap();
10090
10091        let guard = kernel
10092            .fd_adopt_socket_transfer("wasm", pid, socket_id, 0)
10093            .expect("SCM_RIGHTS must not need a temporary fd in a full sender table");
10094        assert_eq!(kernel.fd_snapshot("wasm", pid).unwrap().len(), 3);
10095        assert_eq!(kernel.sockets.get(socket_id).unwrap().owner_pid(), 0);
10096        drop(guard);
10097    }
10098
10099    struct RetainedKernelResources {
10100        process: KernelProcessHandle,
10101        fd_tables: Arc<Mutex<FdTableManager>>,
10102        pipes: PipeManager,
10103        ptys: PtyManager,
10104        sockets: SocketTable,
10105        driver_pids: Arc<Mutex<BTreeMap<String, BTreeSet<u32>>>>,
10106    }
10107
10108    fn kernel_with_live_resources() -> (KernelVm<MemoryFileSystem>, RetainedKernelResources) {
10109        let mut config = KernelVmConfig::new("vm-drop-resources");
10110        config.permissions = Permissions::allow_all();
10111        let mut kernel = KernelVm::new(MemoryFileSystem::new(), config);
10112        kernel
10113            .register_driver(CommandDriver::new("shell", ["sh"]))
10114            .expect("register shell");
10115
10116        let process = kernel
10117            .spawn_process(
10118                "sh",
10119                Vec::new(),
10120                SpawnOptions {
10121                    requester_driver: Some(String::from("shell")),
10122                    ..SpawnOptions::default()
10123                },
10124            )
10125            .expect("spawn shell");
10126        let _ = kernel.open_pipe("shell", process.pid()).expect("open pipe");
10127        let _ = kernel.open_pty("shell", process.pid()).expect("open pty");
10128        let socket = kernel
10129            .socket_create("shell", process.pid(), SocketSpec::tcp())
10130            .expect("create socket");
10131        kernel
10132            .socket_set_state("shell", process.pid(), socket, SocketState::Listening)
10133            .expect("mark listener");
10134
10135        let retained = RetainedKernelResources {
10136            process: process.clone(),
10137            fd_tables: Arc::clone(&kernel.fd_tables),
10138            pipes: kernel.pipes.clone(),
10139            ptys: kernel.ptys.clone(),
10140            sockets: kernel.sockets.clone(),
10141            driver_pids: Arc::clone(&kernel.driver_pids),
10142        };
10143
10144        assert_eq!(lock_or_recover(retained.fd_tables.as_ref()).len(), 1);
10145        assert_eq!(retained.pipes.pipe_count(), 1);
10146        assert_eq!(retained.ptys.pty_count(), 1);
10147        assert_eq!(retained.sockets.snapshot().sockets, 1);
10148
10149        (kernel, retained)
10150    }
10151
10152    fn recursive_fs_kernel() -> KernelVm<MemoryFileSystem> {
10153        let mut config = KernelVmConfig::new("vm-recursive-fs");
10154        config.permissions = Permissions::allow_all();
10155        KernelVm::new(MemoryFileSystem::new(), config)
10156    }
10157
10158    #[test]
10159    fn exec_process_replaces_image_without_replacing_linux_process_state() {
10160        let mut config = KernelVmConfig::new("vm-exec-process-state");
10161        config.permissions = Permissions::allow_all();
10162        config.env = BTreeMap::from([(String::from("INHERITED"), String::from("old"))]);
10163        let mut kernel = KernelVm::new(MemoryFileSystem::new(), config);
10164        kernel
10165            .register_driver(CommandDriver::new("runtime", ["old", "new"]))
10166            .expect("register runtime commands");
10167
10168        let parent = kernel
10169            .spawn_process(
10170                "old",
10171                Vec::new(),
10172                SpawnOptions {
10173                    requester_driver: Some(String::from("runtime")),
10174                    ..SpawnOptions::default()
10175                },
10176            )
10177            .expect("spawn parent");
10178        let child = kernel
10179            .spawn_process(
10180                "old",
10181                vec![String::from("old-argv")],
10182                SpawnOptions {
10183                    requester_driver: Some(String::from("runtime")),
10184                    parent_pid: Some(parent.pid()),
10185                    cwd: Some(String::from("/before-exec")),
10186                    env: BTreeMap::from([(String::from("STALE"), String::from("value"))]),
10187                },
10188            )
10189            .expect("spawn child");
10190        kernel
10191            .setpgid("runtime", child.pid(), child.pid())
10192            .expect("put child in its own process group");
10193        kernel
10194            .umask("runtime", child.pid(), Some(0o077))
10195            .expect("set process umask");
10196        let blocked = SignalSet::from_signal(SIGTERM).expect("signal set");
10197        kernel
10198            .sigprocmask("runtime", child.pid(), SigmaskHow::Block, blocked)
10199            .expect("block signal");
10200        kernel
10201            .kill_process("runtime", child.pid(), SIGTERM)
10202            .expect("queue blocked signal");
10203
10204        let (preserved_fd, cloexec_fd) = kernel
10205            .open_pipe("runtime", child.pid())
10206            .expect("open process pipe");
10207        kernel
10208            .fd_fcntl("runtime", child.pid(), cloexec_fd, F_SETFD, FD_CLOEXEC)
10209            .expect("mark close-on-exec fd");
10210        let (forwarded_cloexec_fd, forwarded_peer_fd) = kernel
10211            .open_pipe("runtime", child.pid())
10212            .expect("open runner-forwarded process pipe");
10213
10214        let before = kernel.processes.get(child.pid()).expect("child entry");
10215        let replacement_env = BTreeMap::from([(String::from("ONLY"), String::from("new"))]);
10216        kernel
10217            .mkdir("/literal", true)
10218            .expect("create literal executable directory");
10219        kernel
10220            .write_file("/literal/not-executable", b"wasm".to_vec())
10221            .expect("create non-executable replacement");
10222        kernel
10223            .chmod("/literal/not-executable", 0o644)
10224            .expect("clear replacement execute bits");
10225        let error = kernel
10226            .exec_process_retaining_internal_fds(
10227                "runtime",
10228                child.pid(),
10229                "new",
10230                vec![String::new(), String::from("argument")],
10231                replacement_env.clone(),
10232                String::from("/must-not-change-cwd"),
10233                &[],
10234                &[forwarded_cloexec_fd],
10235                Some("/literal/not-executable"),
10236            )
10237            .expect_err("pathname validation must fail before exec commits");
10238        assert_eq!(error.code(), "EACCES");
10239        assert_eq!(
10240            kernel.processes.get(child.pid()).expect("child entry"),
10241            before,
10242            "a pre-commit failure must not change process metadata"
10243        );
10244        kernel
10245            .fd_stat("runtime", child.pid(), cloexec_fd)
10246            .expect("a pre-commit failure must not close CLOEXEC descriptors");
10247        kernel
10248            .fd_stat("runtime", child.pid(), forwarded_cloexec_fd)
10249            .expect("a pre-commit failure must not close forwarded descriptors");
10250        kernel
10251            .write_file("/literal/new", b"wasm".to_vec())
10252            .expect("create executable replacement");
10253        kernel
10254            .chmod("/literal/new", 0o755)
10255            .expect("mark replacement executable");
10256        kernel
10257            .exec_process_retaining_internal_fds(
10258                "runtime",
10259                child.pid(),
10260                "new",
10261                vec![String::new(), String::from("argument")],
10262                replacement_env.clone(),
10263                String::from("/must-not-change-cwd"),
10264                &[],
10265                &[forwarded_cloexec_fd],
10266                Some("/literal/new"),
10267            )
10268            .expect("replace process image");
10269
10270        let after = kernel.processes.get(child.pid()).expect("exec child entry");
10271        assert_eq!(after.pid, before.pid);
10272        assert_eq!(after.ppid, before.ppid);
10273        assert_eq!(after.pgid, before.pgid);
10274        assert_eq!(after.sid, before.sid);
10275        assert_eq!(after.identity, before.identity);
10276        assert_eq!(after.umask, before.umask);
10277        assert_eq!(after.cwd, before.cwd, "execve must preserve cwd");
10278        assert_eq!(after.command, "");
10279        assert_eq!(after.args, vec![String::from("argument")]);
10280        assert_eq!(
10281            kernel
10282                .read_file_for_process(
10283                    "runtime",
10284                    child.pid(),
10285                    &format!("/proc/{}/cmdline", child.pid()),
10286                )
10287                .expect("read post-exec cmdline"),
10288            b"\0argument\0".to_vec(),
10289            "procfs cmdline must contain argv exactly once, including empty argv0"
10290        );
10291        assert_eq!(after.env, replacement_env, "envp must replace, not overlay");
10292        assert_eq!(
10293            kernel
10294                .sigprocmask(
10295                    "runtime",
10296                    child.pid(),
10297                    SigmaskHow::Block,
10298                    SignalSet::empty(),
10299                )
10300                .expect("read signal mask"),
10301            blocked,
10302            "blocked signal mask must survive exec"
10303        );
10304        assert!(
10305            kernel
10306                .sigpending("runtime", child.pid())
10307                .expect("read pending signals")
10308                .contains(SIGTERM),
10309            "pending signals must survive exec"
10310        );
10311        kernel
10312            .fd_stat("runtime", child.pid(), preserved_fd)
10313            .expect("non-CLOEXEC fd must survive");
10314        assert_eq!(
10315            kernel
10316                .fd_stat("runtime", child.pid(), cloexec_fd)
10317                .expect_err("CLOEXEC fd must close")
10318                .code(),
10319            "EBADF"
10320        );
10321        assert_eq!(
10322            kernel
10323                .fd_stat("runtime", child.pid(), forwarded_cloexec_fd)
10324                .expect_err("runner-forwarded CLOEXEC fd must close")
10325                .code(),
10326            "EBADF"
10327        );
10328        kernel
10329            .fd_stat("runtime", child.pid(), forwarded_peer_fd)
10330            .expect("unmarked peer of runner-forwarded fd must survive");
10331    }
10332
10333    #[test]
10334    fn validate_executable_path_matches_linux_path_errors_and_symlinks() {
10335        let mut config = KernelVmConfig::new("vm-exec-path-errors");
10336        config.permissions = Permissions::allow_all();
10337        let mut kernel = KernelVm::new(MemoryFileSystem::new(), config);
10338
10339        assert_eq!(
10340            kernel
10341                .validate_executable_path("/missing", "/")
10342                .expect_err("missing image must fail")
10343                .code(),
10344            "ENOENT"
10345        );
10346
10347        kernel
10348            .write_file("/plain", b"data".to_vec())
10349            .expect("create plain file");
10350        assert_eq!(
10351            kernel
10352                .validate_executable_path("/plain/child", "/")
10353                .expect_err("non-directory path component must fail")
10354                .code(),
10355            "ENOTDIR"
10356        );
10357
10358        kernel.mkdir("/directory", true).expect("create directory");
10359        assert_eq!(
10360            kernel
10361                .validate_executable_path("/directory", "/")
10362                .expect_err("directory image must fail")
10363                .code(),
10364            "EACCES"
10365        );
10366        assert_eq!(
10367            kernel
10368                .validate_executable_path("/plain", "/")
10369                .expect_err("non-executable image must fail")
10370                .code(),
10371            "EACCES"
10372        );
10373
10374        kernel
10375            .chmod("/plain", 0o755)
10376            .expect("mark target executable");
10377        kernel
10378            .symlink("/plain", "/image-link")
10379            .expect("create executable symlink");
10380        assert_eq!(
10381            kernel
10382                .validate_executable_path("/image-link", "/")
10383                .expect("exec must follow final symlink"),
10384            "/plain"
10385        );
10386
10387        kernel
10388            .symlink("/loop-b", "/loop-a")
10389            .expect("create first loop link");
10390        kernel
10391            .symlink("/loop-a", "/loop-b")
10392            .expect("create second loop link");
10393        assert_eq!(
10394            kernel
10395                .validate_executable_path("/loop-a", "/")
10396                .expect_err("symlink loop must fail")
10397                .code(),
10398            "ELOOP"
10399        );
10400    }
10401
10402    #[test]
10403    fn validate_wasm_exec_image_follows_linux_shebang_chain() {
10404        let mut config = KernelVmConfig::new("vm-exec-shebang");
10405        config.permissions = Permissions::allow_all();
10406        let mut kernel = KernelVm::new(MemoryFileSystem::new(), config);
10407
10408        kernel
10409            .register_driver(CommandDriver::new("shell", ["sh"]))
10410            .expect("register projected shell command");
10411        kernel
10412            .mkdir("/bin", true)
10413            .expect("create command directory");
10414        kernel
10415            .write_file("/bin/sh", b"#!/bin/sh\n".to_vec())
10416            .expect("write self-referential registered command stub");
10417        kernel
10418            .write_file("/registered-script", b"#!/bin/sh\n".to_vec())
10419            .expect("write registered-interpreter script");
10420        kernel
10421            .chmod("/registered-script", 0o755)
10422            .expect("mark registered-interpreter script executable");
10423        kernel
10424            .validate_wasm_exec_image("/registered-script", "/")
10425            .expect("registered command stub must resolve as a runtime image");
10426
10427        kernel
10428            .write_file("/interpreter.wasm", b"\0asm\x01\0\0\0".to_vec())
10429            .expect("write WASM interpreter");
10430        kernel
10431            .chmod("/interpreter.wasm", 0o755)
10432            .expect("mark interpreter executable");
10433        kernel
10434            .write_file(
10435                "/script",
10436                b"#!/interpreter.wasm one optional argument\necho ignored\n".to_vec(),
10437            )
10438            .expect("write executable script");
10439        kernel
10440            .chmod("/script", 0o755)
10441            .expect("mark script executable");
10442        kernel
10443            .validate_wasm_exec_image("/script", "/")
10444            .expect("WASM interpreter chain must validate");
10445
10446        kernel
10447            .write_file("/missing-interpreter", b"#!/absent\n".to_vec())
10448            .expect("write missing-interpreter script");
10449        kernel
10450            .chmod("/missing-interpreter", 0o755)
10451            .expect("mark missing-interpreter script executable");
10452        assert_eq!(
10453            kernel
10454                .validate_wasm_exec_image("/missing-interpreter", "/")
10455                .expect_err("missing interpreter must fail")
10456                .code(),
10457            "ENOENT"
10458        );
10459
10460        kernel
10461            .write_file("/not-executable", b"\0asm\x01\0\0\0".to_vec())
10462            .expect("write non-executable interpreter");
10463        kernel
10464            .write_file("/denied-script", b"#!/not-executable\n".to_vec())
10465            .expect("write denied-interpreter script");
10466        kernel
10467            .chmod("/denied-script", 0o755)
10468            .expect("mark denied-interpreter script executable");
10469        assert_eq!(
10470            kernel
10471                .validate_wasm_exec_image("/denied-script", "/")
10472                .expect_err("non-executable interpreter must fail")
10473                .code(),
10474            "EACCES"
10475        );
10476
10477        for depth in 0..=MAX_EXEC_INTERPRETER_DEPTH {
10478            let path = format!("/recursive-{depth}");
10479            let next = format!("/recursive-{}", depth + 1);
10480            kernel
10481                .write_file(&path, format!("#!{next}\n").into_bytes())
10482                .expect("write recursive interpreter");
10483            kernel
10484                .chmod(&path, 0o755)
10485                .expect("mark recursive interpreter executable");
10486        }
10487        assert_eq!(
10488            kernel
10489                .validate_wasm_exec_image("/recursive-0", "/")
10490                .expect_err("interpreter recursion must be bounded")
10491                .code(),
10492            "ELOOP"
10493        );
10494    }
10495
10496    #[test]
10497    fn recursive_copy_preserves_tree_metadata_and_symlinks() {
10498        let mut kernel = recursive_fs_kernel();
10499        kernel
10500            .mkdir("/src/nested", true)
10501            .expect("create source dirs");
10502        kernel
10503            .write_file("/src/nested/file.txt", b"hello".to_vec())
10504            .expect("write source file");
10505        kernel
10506            .chmod("/src/nested/file.txt", 0o640)
10507            .expect("chmod source file");
10508        kernel
10509            .chown("/src/nested/file.txt", 42, 43)
10510            .expect("chown source file");
10511        kernel
10512            .symlink("../nested/file.txt", "/src/link")
10513            .expect("create source symlink");
10514
10515        kernel
10516            .copy_path("/src", "/dst", true)
10517            .expect("recursive copy");
10518
10519        assert_eq!(
10520            kernel
10521                .read_file("/dst/nested/file.txt")
10522                .expect("read copied"),
10523            b"hello".to_vec()
10524        );
10525        let copied = kernel.lstat("/dst/nested/file.txt").expect("stat copied");
10526        assert_eq!(copied.mode & 0o777, 0o640);
10527        assert_eq!((copied.uid, copied.gid), (42, 43));
10528        let link = kernel.lstat("/dst/link").expect("lstat copied link");
10529        assert!(link.is_symbolic_link);
10530        assert_eq!(
10531            kernel.read_link("/dst/link").expect("read copied link"),
10532            "../nested/file.txt"
10533        );
10534    }
10535
10536    #[test]
10537    fn recursive_remove_deletes_subtree_but_does_not_follow_symlinks() {
10538        let mut kernel = recursive_fs_kernel();
10539        kernel.mkdir("/tree/dir", true).expect("create tree");
10540        kernel
10541            .write_file("/tree/dir/file.txt", b"tree".to_vec())
10542            .expect("write tree file");
10543        kernel
10544            .write_file("/outside.txt", b"outside".to_vec())
10545            .expect("write outside file");
10546        kernel
10547            .symlink("/outside.txt", "/tree/link-out")
10548            .expect("create symlink out of tree");
10549
10550        kernel.remove_path("/tree", true).expect("recursive remove");
10551
10552        assert!(!kernel.exists("/tree").expect("tree existence"));
10553        assert_eq!(
10554            kernel.read_file("/outside.txt").expect("outside survives"),
10555            b"outside".to_vec()
10556        );
10557    }
10558
10559    #[test]
10560    fn read_dir_recursive_respects_user_depth_and_reports_types() {
10561        let mut kernel = recursive_fs_kernel();
10562        kernel.mkdir("/root/a/b", true).expect("create deep tree");
10563        kernel
10564            .write_file("/root/a/file.txt", b"x".to_vec())
10565            .expect("write file");
10566        kernel
10567            .symlink("a/file.txt", "/root/link")
10568            .expect("create link");
10569
10570        let entries = kernel
10571            .read_dir_recursive("/root", Some(0))
10572            .expect("recursive listing");
10573        assert_eq!(entries.len(), 2);
10574        assert!(entries
10575            .iter()
10576            .any(|entry| entry.path == "/root/a" && entry.is_directory));
10577        assert!(entries
10578            .iter()
10579            .any(|entry| entry.path == "/root/link" && entry.is_symbolic_link));
10580        assert!(!entries.iter().any(|entry| entry.path == "/root/a/file.txt"));
10581    }
10582
10583    #[test]
10584    fn recursive_ops_enforce_depth_and_entry_bounds() {
10585        let mut depth_config = KernelVmConfig::new("vm-recursive-depth-limit");
10586        depth_config.permissions = Permissions::allow_all();
10587        depth_config.resources = ResourceLimits {
10588            max_recursive_fs_depth: Some(1),
10589            ..ResourceLimits::default()
10590        };
10591        let mut depth_kernel = KernelVm::new(MemoryFileSystem::new(), depth_config);
10592        depth_kernel
10593            .mkdir("/root/a/b", true)
10594            .expect("create deep tree");
10595
10596        let error = depth_kernel
10597            .copy_path("/root", "/copy", true)
10598            .expect_err("copy should hit depth limit");
10599        assert_eq!(error.code(), "ENOMEM");
10600        assert!(error.to_string().contains("depth 2"));
10601
10602        let mut entry_config = KernelVmConfig::new("vm-recursive-entry-limit");
10603        entry_config.permissions = Permissions::allow_all();
10604        entry_config.resources = ResourceLimits {
10605            max_recursive_fs_entries: Some(2),
10606            ..ResourceLimits::default()
10607        };
10608        let mut entry_kernel = KernelVm::new(MemoryFileSystem::new(), entry_config);
10609        entry_kernel.mkdir("/root", true).expect("create root");
10610        entry_kernel
10611            .write_file("/root/a.txt", b"a".to_vec())
10612            .expect("write a");
10613        entry_kernel
10614            .write_file("/root/b.txt", b"b".to_vec())
10615            .expect("write b");
10616        entry_kernel
10617            .write_file("/root/c.txt", b"c".to_vec())
10618            .expect("write c");
10619
10620        let error = entry_kernel
10621            .read_dir_recursive("/root", None)
10622            .expect_err("listing should hit entry limit");
10623        assert_eq!(error.code(), "ENOMEM");
10624        assert!(error.to_string().contains("3 entries"));
10625    }
10626
10627    fn assert_kernel_drop_released_resources(retained: &RetainedKernelResources) {
10628        assert_eq!(retained.process.wait(Duration::from_millis(50)), Some(143));
10629        assert_eq!(retained.process.kill_signals(), vec![15]);
10630        assert!(
10631            lock_or_recover(retained.fd_tables.as_ref()).is_empty(),
10632            "kernel drop should remove fd tables"
10633        );
10634        assert_eq!(
10635            retained.pipes.pipe_count(),
10636            0,
10637            "kernel drop should close pipes"
10638        );
10639        assert_eq!(
10640            retained.ptys.pty_count(),
10641            0,
10642            "kernel drop should close PTYs"
10643        );
10644        assert_eq!(
10645            retained.sockets.snapshot().sockets,
10646            0,
10647            "kernel drop should reclaim sockets"
10648        );
10649        assert!(
10650            lock_or_recover(retained.driver_pids.as_ref()).is_empty(),
10651            "kernel drop should clear driver-owned pid tracking"
10652        );
10653    }
10654
10655    #[test]
10656    fn setpgid_rejects_joining_a_process_group_owned_by_another_driver() {
10657        let kernel = KernelVm::new(MemoryFileSystem::new(), KernelVmConfig::new("vm-setpgid"));
10658
10659        let leader_pid = kernel.processes.allocate_pid().expect("allocate pid");
10660        kernel.processes.register(
10661            leader_pid,
10662            String::from("driver-a"),
10663            String::from("sh"),
10664            Vec::new(),
10665            ProcessContext {
10666                pid: leader_pid,
10667                ppid: 0,
10668                env: BTreeMap::new(),
10669                cwd: String::from("/"),
10670                umask: DEFAULT_PROCESS_UMASK,
10671                fds: Default::default(),
10672                identity: ProcessIdentity::default(),
10673                blocked_signals: SignalSet::empty(),
10674                pending_signals: SignalSet::empty(),
10675            },
10676            Arc::new(StubDriverProcess::default()),
10677        );
10678
10679        let peer_pid = kernel.processes.allocate_pid().expect("allocate pid");
10680        kernel.processes.register(
10681            peer_pid,
10682            String::from("driver-b"),
10683            String::from("sh"),
10684            Vec::new(),
10685            ProcessContext {
10686                pid: peer_pid,
10687                ppid: leader_pid,
10688                env: BTreeMap::new(),
10689                cwd: String::from("/"),
10690                umask: DEFAULT_PROCESS_UMASK,
10691                fds: Default::default(),
10692                identity: ProcessIdentity::default(),
10693                blocked_signals: SignalSet::empty(),
10694                pending_signals: SignalSet::empty(),
10695            },
10696            Arc::new(StubDriverProcess::default()),
10697        );
10698
10699        lock_or_recover(&kernel.driver_pids)
10700            .entry(String::from("driver-a"))
10701            .or_default()
10702            .insert(leader_pid);
10703        lock_or_recover(&kernel.driver_pids)
10704            .entry(String::from("driver-b"))
10705            .or_default()
10706            .insert(peer_pid);
10707
10708        let error = kernel
10709            .setpgid("driver-b", peer_pid, leader_pid)
10710            .expect_err("cross-driver process-group join should be denied");
10711        assert_eq!(error.code(), "EPERM");
10712    }
10713
10714    #[test]
10715    fn sigprocmask_and_sigpending_require_process_ownership() {
10716        let mut kernel = KernelVm::new(MemoryFileSystem::new(), KernelVmConfig::new("vm-sigmask"));
10717        let process = kernel
10718            .register_process(
10719                String::from("driver-a"),
10720                String::from("sleep"),
10721                Vec::new(),
10722                ProcessContext {
10723                    pid: 0,
10724                    ppid: 0,
10725                    env: BTreeMap::new(),
10726                    cwd: String::from("/"),
10727                    umask: DEFAULT_PROCESS_UMASK,
10728                    fds: Default::default(),
10729                    identity: ProcessIdentity::default(),
10730                    blocked_signals: SignalSet::empty(),
10731                    pending_signals: SignalSet::empty(),
10732                },
10733                None,
10734                None,
10735                false,
10736            )
10737            .expect("create virtual process");
10738        let mask =
10739            SignalSet::from_signal(crate::process_table::SIGCHLD).expect("SIGCHLD should be valid");
10740
10741        let previous = kernel
10742            .sigprocmask("driver-a", process.pid(), SigmaskHow::Block, mask)
10743            .expect("owner should update signal mask");
10744        assert_eq!(previous, SignalSet::empty());
10745        assert_eq!(
10746            kernel
10747                .sigpending("driver-a", process.pid())
10748                .expect("owner should read pending signals"),
10749            SignalSet::empty()
10750        );
10751
10752        let error = kernel
10753            .sigprocmask("driver-b", process.pid(), SigmaskHow::Block, mask)
10754            .expect_err("foreign driver should be rejected");
10755        assert_eq!(error.code(), "EPERM");
10756        let error = kernel
10757            .sigpending("driver-b", process.pid())
10758            .expect_err("foreign driver should be rejected");
10759        assert_eq!(error.code(), "EPERM");
10760    }
10761
10762    #[test]
10763    fn cleanup_process_resources_blocks_concurrent_dup2_until_pipe_cleanup_finishes() {
10764        let fd_tables = Arc::new(Mutex::new(FdTableManager::new()));
10765        let file_locks = FileLockManager::new();
10766        let pipes = PipeManager::new();
10767        let ptys = PtyManager::new();
10768        let sockets = SocketTable::new();
10769        let fd_sockets = Arc::new(Mutex::new(BTreeMap::new()));
10770        let driver_pids = Arc::new(Mutex::new(BTreeMap::from([(
10771            String::from("driver"),
10772            BTreeSet::from([41]),
10773        )])));
10774        let pipe = pipes.create_pipe();
10775
10776        {
10777            let mut tables = lock_or_recover(fd_tables.as_ref());
10778            let table = tables.create(41);
10779            table
10780                .open_with(
10781                    Arc::clone(&pipe.read.description),
10782                    pipe.read.filetype,
10783                    Some(10),
10784                )
10785                .expect("open pipe read end");
10786            table
10787                .open_with(
10788                    Arc::clone(&pipe.write.description),
10789                    pipe.write.filetype,
10790                    Some(11),
10791                )
10792                .expect("open pipe write end");
10793        }
10794
10795        let hook_state = Arc::new((Mutex::new((false, false)), Condvar::new()));
10796        let hook_state_for_cleanup = Arc::clone(&hook_state);
10797        set_cleanup_process_resources_test_hook(Some(Arc::new(move || {
10798            let (state, wake) = &*hook_state_for_cleanup;
10799            let mut state = lock_or_recover(state);
10800            state.0 = true;
10801            wake.notify_all();
10802            while !state.1 {
10803                state = wake.wait(state).expect("wait for cleanup release");
10804            }
10805        })));
10806
10807        let fd_tables_for_cleanup = Arc::clone(&fd_tables);
10808        let pipes_for_cleanup = pipes.clone();
10809        let driver_pids_for_cleanup = Arc::clone(&driver_pids);
10810        let cleanup_thread = thread::spawn(move || {
10811            cleanup_process_resources(
10812                fd_tables_for_cleanup.as_ref(),
10813                &file_locks,
10814                &pipes_for_cleanup,
10815                &ptys,
10816                &sockets,
10817                &fd_sockets,
10818                driver_pids_for_cleanup.as_ref(),
10819                41,
10820            );
10821        });
10822
10823        {
10824            let (state, wake) = &*hook_state;
10825            let mut state = lock_or_recover(state);
10826            while !state.0 {
10827                state = wake.wait(state).expect("wait for cleanup hook");
10828            }
10829        }
10830
10831        let fd_tables_for_dup = Arc::clone(&fd_tables);
10832        let dup_thread = thread::spawn(move || {
10833            let mut tables = lock_or_recover(fd_tables_for_dup.as_ref());
10834            let Some(table) = tables.get_mut(41) else {
10835                return Err(String::from("ESRCH"));
10836            };
10837            table.dup2(10, 12).map_err(|error| error.code().to_string())
10838        });
10839
10840        {
10841            let (state, wake) = &*hook_state;
10842            let mut state = lock_or_recover(state);
10843            state.1 = true;
10844            wake.notify_all();
10845        }
10846
10847        cleanup_thread.join().expect("cleanup thread should finish");
10848        let dup_result = dup_thread.join().expect("dup thread should finish");
10849        set_cleanup_process_resources_test_hook(None);
10850
10851        assert_eq!(dup_result, Err(String::from("ESRCH")));
10852        assert!(
10853            lock_or_recover(fd_tables.as_ref()).get(41).is_none(),
10854            "cleanup should remove the process FD table"
10855        );
10856        assert_eq!(pipes.pipe_count(), 0, "pipe cleanup should not leak");
10857        assert!(
10858            lock_or_recover(driver_pids.as_ref())
10859                .get("driver")
10860                .is_none_or(|pids| pids.is_empty()),
10861            "driver ownership should be cleared"
10862        );
10863    }
10864
10865    #[test]
10866    fn drop_disposes_live_kernel_vm_resources() {
10867        let (kernel, retained) = kernel_with_live_resources();
10868        drop(kernel);
10869        assert_kernel_drop_released_resources(&retained);
10870    }
10871
10872    #[test]
10873    fn drop_during_panic_still_disposes_live_kernel_vm_resources() {
10874        let retained = Arc::new(Mutex::new(None::<RetainedKernelResources>));
10875        let retained_for_panic = Arc::clone(&retained);
10876
10877        let panic_result = catch_unwind(AssertUnwindSafe(move || {
10878            let (kernel, resources) = kernel_with_live_resources();
10879            *lock_or_recover(retained_for_panic.as_ref()) = Some(resources);
10880            let _kernel = kernel;
10881            panic!("intentional panic to exercise KernelVm::drop");
10882        }));
10883
10884        assert!(panic_result.is_err(), "panic should be observed");
10885        let retained = lock_or_recover(retained.as_ref())
10886            .take()
10887            .expect("panic path should retain resources for assertions");
10888        assert_kernel_drop_released_resources(&retained);
10889    }
10890}