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    FdEntry, FdStat, FdTableError, FdTableManager, FileDescription, FileLockManager,
11    FileLockTarget, FlockOperation, ProcessFdTable, FILETYPE_CHARACTER_DEVICE, FILETYPE_DIRECTORY,
12    FILETYPE_PIPE, FILETYPE_REGULAR_FILE, FILETYPE_SYMBOLIC_LINK, F_DUPFD, O_APPEND, O_CREAT,
13    O_EXCL, O_NONBLOCK, O_TRUNC,
14};
15use crate::mount_table::{MountEntry, MountOptions, MountTable, MountedFileSystem};
16use crate::network_policy::format_tcp_resource;
17use crate::permissions::{
18    check_command_execution, check_network_access, FsOperation, NetworkOperation, PermissionError,
19    PermissionedFileSystem, Permissions,
20};
21use crate::pipe_manager::{PipeError, PipeManager};
22use crate::poll::{
23    PollEvents, PollFd, PollNotifier, PollResult, PollTarget, PollTargetEntry, PollTargetResult,
24    POLLERR, POLLHUP, POLLIN, POLLNVAL, POLLOUT,
25};
26use crate::process_table::{
27    DriverProcess, ProcessContext, ProcessExitCallback, ProcessInfo, ProcessStatus, ProcessTable,
28    ProcessTableError, ProcessWaitResult, SigmaskHow, SignalSet, DEFAULT_PROCESS_UMASK, SIGCONT,
29    SIGPIPE, SIGSTOP, SIGTSTP, SIGWINCH,
30};
31use crate::pty::{
32    LineDisciplineConfig, PartialTermios, PtyError, PtyManager, PtyWindowSize, Termios,
33};
34use crate::resource_accounting::{
35    measure_filesystem_usage, FileSystemUsage, ResourceAccountant, ResourceError, ResourceLimits,
36    ResourceSnapshot, DEFAULT_MAX_OPEN_FDS,
37};
38use crate::root_fs::{RootFileSystem, RootFilesystemError, RootFilesystemSnapshot};
39use crate::socket_table::{
40    DatagramSocketOption, InetSocketAddress, ReceivedDatagram, SocketId, SocketMulticastMembership,
41    SocketReadiness, SocketRecord, SocketShutdown, SocketSpec, SocketState, SocketTable,
42    SocketTableError, SocketType,
43};
44use crate::user::{ProcessIdentity, UserConfig, UserManager};
45use crate::vfs::{
46    normalize_path, VfsError, VfsResult, VirtualDirEntry, VirtualFileSystem, VirtualStat,
47    VirtualTimeSpec, VirtualUtimeSpec,
48};
49use hickory_proto::rr::RecordType;
50use std::any::Any;
51use std::collections::{BTreeMap, BTreeSet, VecDeque};
52use std::error::Error;
53use std::fmt;
54#[cfg(test)]
55use std::sync::OnceLock;
56use std::sync::{Arc, Condvar, Mutex, MutexGuard, WaitTimeoutResult};
57use std::time::Duration;
58use web_time::{Instant, SystemTime, UNIX_EPOCH};
59
60pub type KernelResult<T> = Result<T, KernelError>;
61pub use crate::process_table::{ProcessWaitEvent as WaitPidEvent, WaitPidFlags};
62
63pub const SEEK_SET: u8 = 0;
64pub const SEEK_CUR: u8 = 1;
65pub const SEEK_END: u8 = 2;
66const EXECUTABLE_PERMISSION_BITS: u32 = 0o111;
67const SHEBANG_LINE_MAX_BYTES: usize = 256;
68
69#[derive(Debug, Clone, PartialEq, Eq)]
70pub struct KernelError {
71    code: &'static str,
72    message: String,
73}
74
75impl KernelError {
76    pub fn code(&self) -> &'static str {
77        self.code
78    }
79
80    fn new(code: &'static str, message: impl Into<String>) -> Self {
81        Self {
82            code,
83            message: message.into(),
84        }
85    }
86
87    fn disposed() -> Self {
88        Self::new("EINVAL", "kernel VM is disposed")
89    }
90
91    fn no_such_process(pid: u32) -> Self {
92        Self::new("ESRCH", format!("no such process {pid}"))
93    }
94
95    fn bad_file_descriptor(fd: u32) -> Self {
96        Self::new("EBADF", format!("bad file descriptor {fd}"))
97    }
98
99    fn permission_denied(message: impl Into<String>) -> Self {
100        Self::new("EPERM", message)
101    }
102
103    fn command_not_found(command: &str) -> Self {
104        Self::new("ENOENT", format!("command not found: {command}"))
105    }
106}
107
108impl fmt::Display for KernelError {
109    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
110        write!(f, "{}: {}", self.code, self.message)
111    }
112}
113
114impl Error for KernelError {}
115
116#[derive(Clone)]
117pub struct KernelVmConfig {
118    pub vm_id: String,
119    pub env: BTreeMap<String, String>,
120    pub cwd: String,
121    pub user: UserConfig,
122    pub permissions: Permissions,
123    pub loopback_exempt_ports: BTreeSet<u16>,
124    pub dns: DnsConfig,
125    pub dns_resolver: SharedDnsResolver,
126    pub resources: ResourceLimits,
127    pub zombie_ttl: Duration,
128}
129
130impl KernelVmConfig {
131    pub fn new(vm_id: impl Into<String>) -> Self {
132        Self {
133            vm_id: vm_id.into(),
134            env: BTreeMap::new(),
135            cwd: String::from("/workspace"),
136            user: UserConfig::default(),
137            permissions: Permissions::default(),
138            loopback_exempt_ports: BTreeSet::new(),
139            dns: DnsConfig::default(),
140            dns_resolver: Arc::new(HickoryDnsResolver::default()),
141            resources: ResourceLimits::default(),
142            zombie_ttl: Duration::from_secs(60),
143        }
144    }
145}
146
147#[derive(Debug, Clone, Default)]
148pub struct SpawnOptions {
149    pub requester_driver: Option<String>,
150    pub parent_pid: Option<u32>,
151    pub env: BTreeMap<String, String>,
152    pub cwd: Option<String>,
153}
154
155#[derive(Debug, Clone, Default, PartialEq, Eq)]
156pub struct VirtualProcessOptions {
157    pub parent_pid: Option<u32>,
158    pub env: BTreeMap<String, String>,
159    pub cwd: Option<String>,
160}
161
162#[derive(Debug, Clone, Default, PartialEq, Eq)]
163pub struct ExecOptions {
164    pub requester_driver: Option<String>,
165    pub parent_pid: Option<u32>,
166    pub env: BTreeMap<String, String>,
167    pub cwd: Option<String>,
168}
169
170#[derive(Debug, Clone, PartialEq, Eq)]
171pub struct RecursiveDirEntry {
172    pub path: String,
173    pub is_directory: bool,
174    pub is_symbolic_link: bool,
175    pub size: u64,
176}
177
178#[derive(Debug, Clone, Default, PartialEq, Eq)]
179pub struct OpenShellOptions {
180    pub requester_driver: Option<String>,
181    pub command: Option<String>,
182    pub args: Vec<String>,
183    pub env: BTreeMap<String, String>,
184    pub cwd: Option<String>,
185}
186
187#[derive(Debug, Clone, PartialEq, Eq)]
188pub struct WaitPidResult {
189    pub pid: u32,
190    pub status: i32,
191}
192
193#[derive(Debug, Clone, PartialEq, Eq)]
194pub struct WaitPidEventResult {
195    pub pid: u32,
196    pub status: i32,
197    pub event: WaitPidEvent,
198}
199
200#[derive(Debug, Clone)]
201struct ResolvedSpawnCommand {
202    command: String,
203    args: Vec<String>,
204    driver: CommandDriver,
205}
206
207#[derive(Debug, Clone)]
208struct ShebangCommand {
209    interpreter: String,
210    args: Vec<String>,
211}
212
213#[derive(Clone)]
214pub struct KernelProcessHandle {
215    pid: u32,
216    driver: String,
217    process: Arc<StubDriverProcess>,
218}
219
220impl fmt::Debug for KernelProcessHandle {
221    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
222        f.debug_struct("KernelProcessHandle")
223            .field("pid", &self.pid)
224            .field("driver", &self.driver)
225            .finish_non_exhaustive()
226    }
227}
228
229impl KernelProcessHandle {
230    pub fn pid(&self) -> u32 {
231        self.pid
232    }
233
234    pub fn driver(&self) -> &str {
235        &self.driver
236    }
237
238    pub fn finish(&self, exit_code: i32) {
239        self.process.finish(exit_code);
240    }
241
242    pub fn kill(&self, signal: i32) {
243        self.process.kill(signal);
244    }
245
246    pub fn wait(&self, timeout: Duration) -> Option<i32> {
247        self.process.wait(timeout)
248    }
249
250    pub fn kill_signals(&self) -> Vec<i32> {
251        self.process.kill_signals()
252    }
253}
254
255#[derive(Debug, Clone)]
256pub struct OpenShellHandle {
257    process: KernelProcessHandle,
258    master_fd: u32,
259    slave_fd: u32,
260    pty_path: String,
261}
262
263impl OpenShellHandle {
264    pub fn process(&self) -> &KernelProcessHandle {
265        &self.process
266    }
267
268    pub fn pid(&self) -> u32 {
269        self.process.pid()
270    }
271
272    pub fn master_fd(&self) -> u32 {
273        self.master_fd
274    }
275
276    pub fn slave_fd(&self) -> u32 {
277        self.slave_fd
278    }
279
280    pub fn pty_path(&self) -> &str {
281        &self.pty_path
282    }
283}
284
285pub struct KernelVm<F> {
286    vm_id: String,
287    boot_time_ms: u64,
288    boot_instant: Instant,
289    filesystem: PermissionedFileSystem<DeviceLayer<F>>,
290    permissions: Permissions,
291    loopback_exempt_ports: BTreeSet<u16>,
292    dns: DnsConfig,
293    dns_resolver: SharedDnsResolver,
294    env: BTreeMap<String, String>,
295    cwd: String,
296    commands: CommandRegistry,
297    fd_tables: Arc<Mutex<FdTableManager>>,
298    processes: ProcessTable,
299    pipes: PipeManager,
300    ptys: PtyManager,
301    sockets: SocketTable,
302    poll_notifier: PollNotifier,
303    users: UserManager,
304    resources: ResourceAccountant,
305    filesystem_usage_cache: Option<FileSystemUsage>,
306    file_locks: FileLockManager,
307    driver_pids: Arc<Mutex<BTreeMap<String, BTreeSet<u32>>>>,
308    terminated: bool,
309}
310
311fn cleanup_process_resources(
312    fd_tables: &Mutex<FdTableManager>,
313    file_locks: &FileLockManager,
314    pipes: &PipeManager,
315    ptys: &PtyManager,
316    sockets: &SocketTable,
317    driver_pids: &Mutex<BTreeMap<String, BTreeSet<u32>>>,
318    pid: u32,
319) {
320    let mut cleanup = Vec::new();
321    {
322        let mut tables = lock_or_recover(fd_tables);
323        let descriptors = tables
324            .get(pid)
325            .map(|table| {
326                table
327                    .iter()
328                    .map(|entry| (entry.fd, Arc::clone(&entry.description), entry.filetype))
329                    .collect::<Vec<_>>()
330            })
331            .unwrap_or_default();
332
333        cleanup_process_resources_test_hook();
334
335        if let Some(table) = tables.get_mut(pid) {
336            for (fd, description, filetype) in &descriptors {
337                table.close(*fd);
338                cleanup.push((Arc::clone(description), *filetype));
339            }
340        }
341        tables.remove(pid);
342    }
343
344    for (description, filetype) in cleanup {
345        close_special_resource_if_needed(file_locks, pipes, ptys, &description, filetype);
346    }
347
348    sockets.remove_all_for_pid(pid);
349
350    let mut owners = lock_or_recover(driver_pids);
351    for pids in owners.values_mut() {
352        pids.remove(&pid);
353    }
354}
355
356fn dispose_kernel_vm_resources<F>(kernel: &mut KernelVm<F>) {
357    kernel.processes.terminate_all();
358    let pids = lock_or_recover(&kernel.fd_tables).pids();
359    for pid in pids {
360        cleanup_process_resources(
361            kernel.fd_tables.as_ref(),
362            &kernel.file_locks,
363            &kernel.pipes,
364            &kernel.ptys,
365            &kernel.sockets,
366            kernel.driver_pids.as_ref(),
367            pid,
368        );
369    }
370    lock_or_recover(&kernel.driver_pids).clear();
371    kernel.terminated = true;
372}
373
374#[cfg(test)]
375type CleanupProcessResourcesHook = Arc<dyn Fn() + Send + Sync + 'static>;
376
377#[cfg(test)]
378fn cleanup_process_resources_test_hook() {
379    let hook = lock_or_recover(cleanup_process_resources_test_hook_slot()).clone();
380    if let Some(hook) = hook {
381        hook();
382    }
383}
384
385#[cfg(not(test))]
386fn cleanup_process_resources_test_hook() {}
387
388#[cfg(test)]
389fn cleanup_process_resources_test_hook_slot() -> &'static Mutex<Option<CleanupProcessResourcesHook>>
390{
391    static HOOK: OnceLock<Mutex<Option<CleanupProcessResourcesHook>>> = OnceLock::new();
392    HOOK.get_or_init(|| Mutex::new(None))
393}
394
395#[cfg(test)]
396fn set_cleanup_process_resources_test_hook(hook: Option<CleanupProcessResourcesHook>) {
397    *lock_or_recover(cleanup_process_resources_test_hook_slot()) = hook;
398}
399
400fn close_special_resource_if_needed(
401    file_locks: &FileLockManager,
402    pipes: &PipeManager,
403    ptys: &PtyManager,
404    description: &Arc<FileDescription>,
405    filetype: u8,
406) {
407    if description.ref_count() != 0 {
408        return;
409    }
410
411    file_locks.release_owner(description.id());
412
413    if filetype == FILETYPE_PIPE && pipes.is_pipe(description.id()) {
414        pipes.close(description.id());
415    }
416
417    if ptys.is_pty(description.id()) {
418        ptys.close(description.id());
419    }
420}
421
422#[derive(Debug, Clone, PartialEq, Eq)]
423enum ProcNode {
424    RootDir,
425    MountsFile,
426    CpuInfoFile,
427    MemInfoFile,
428    LoadAvgFile,
429    UptimeFile,
430    VersionFile,
431    SelfLink { pid: u32 },
432    PidDir { pid: u32 },
433    PidFdDir { pid: u32 },
434    PidCmdline { pid: u32 },
435    PidEnviron { pid: u32 },
436    PidCwdLink { pid: u32 },
437    PidStatFile { pid: u32 },
438    PidStatusFile { pid: u32 },
439    PidFdLink { pid: u32, fd: u32 },
440}
441
442impl<F: VirtualFileSystem + 'static> KernelVm<F> {
443    pub fn new(filesystem: F, config: KernelVmConfig) -> Self {
444        let vm_id = config.vm_id;
445        let boot_time_ms = now_ms();
446        let boot_instant = Instant::now();
447        let permissions = config.permissions.clone();
448        let users = UserManager::from_config(config.user);
449        let process_table = ProcessTable::with_zombie_ttl(config.zombie_ttl);
450        let process_table_for_pty = process_table.clone();
451        let fd_tables = Arc::new(Mutex::new(FdTableManager::with_max_fds(
452            config
453                .resources
454                .max_open_fds
455                .unwrap_or(DEFAULT_MAX_OPEN_FDS),
456        )));
457        let file_locks = FileLockManager::new();
458        let driver_pids = Arc::new(Mutex::new(BTreeMap::new()));
459        let poll_notifier = PollNotifier::default();
460        let pipes = PipeManager::with_notifier(poll_notifier.clone());
461        let ptys = PtyManager::with_signal_handler_and_notifier(
462            Arc::new(move |pgid, signal| {
463                let _ = process_table_for_pty.kill(-(pgid as i32), signal);
464            }),
465            poll_notifier.clone(),
466        );
467        let sockets = SocketTable::new();
468
469        let fd_tables_for_exit = Arc::clone(&fd_tables);
470        let file_locks_for_exit = file_locks.clone();
471        let driver_pids_for_exit = Arc::clone(&driver_pids);
472        let pipes_for_exit = pipes.clone();
473        let ptys_for_exit = ptys.clone();
474        let sockets_for_exit = sockets.clone();
475        process_table.set_on_process_exit(Some(Arc::new(move |pid| {
476            cleanup_process_resources(
477                fd_tables_for_exit.as_ref(),
478                &file_locks_for_exit,
479                &pipes_for_exit,
480                &ptys_for_exit,
481                &sockets_for_exit,
482                driver_pids_for_exit.as_ref(),
483                pid,
484            );
485        })));
486
487        let filesystem = PermissionedFileSystem::new(
488            create_device_layer(filesystem),
489            vm_id.clone(),
490            permissions.clone(),
491        );
492        // Usage accounting is kernel-internal: the cache is populated lazily by
493        // `filesystem_usage()` through the RAW filesystem so no guest-attributable
494        // permission check fires at construction (or ever) for quota bookkeeping.
495        let filesystem_usage_cache = None;
496
497        Self {
498            vm_id: vm_id.clone(),
499            boot_time_ms,
500            boot_instant,
501            filesystem,
502            permissions,
503            loopback_exempt_ports: config.loopback_exempt_ports,
504            dns: config.dns,
505            dns_resolver: config.dns_resolver,
506            env: config.env,
507            cwd: config.cwd,
508            commands: CommandRegistry::new(),
509            fd_tables,
510            processes: process_table,
511            pipes,
512            ptys,
513            sockets,
514            poll_notifier,
515            users,
516            resources: ResourceAccountant::new(config.resources),
517            filesystem_usage_cache,
518            file_locks,
519            driver_pids,
520            terminated: false,
521        }
522    }
523
524    pub fn vm_id(&self) -> &str {
525        &self.vm_id
526    }
527
528    pub fn state(&self) -> LifecycleState {
529        if self.terminated {
530            LifecycleState::Terminated
531        } else if self.processes.running_count() > 0 {
532            LifecycleState::Busy
533        } else {
534            LifecycleState::Ready
535        }
536    }
537
538    pub fn commands(&self) -> BTreeMap<String, String> {
539        self.commands.list()
540    }
541
542    pub fn filesystem(&self) -> &PermissionedFileSystem<DeviceLayer<F>> {
543        &self.filesystem
544    }
545
546    pub fn filesystem_mut(&mut self) -> &mut PermissionedFileSystem<DeviceLayer<F>> {
547        &mut self.filesystem
548    }
549
550    pub fn user_manager(&self) -> &UserManager {
551        &self.users
552    }
553
554    pub fn environment(&self) -> &BTreeMap<String, String> {
555        &self.env
556    }
557
558    pub fn process_identity(
559        &self,
560        requester_driver: &str,
561        pid: u32,
562    ) -> KernelResult<ProcessIdentity> {
563        self.assert_driver_owns(requester_driver, pid)?;
564        Ok(self
565            .processes
566            .get(pid)
567            .ok_or_else(|| KernelError::no_such_process(pid))?
568            .identity)
569    }
570
571    pub fn user_profile(&self) -> UserManager {
572        self.users.clone()
573    }
574
575    pub fn getuid(&self, requester_driver: &str, pid: u32) -> KernelResult<u32> {
576        Ok(self.process_identity(requester_driver, pid)?.uid)
577    }
578
579    pub fn getgid(&self, requester_driver: &str, pid: u32) -> KernelResult<u32> {
580        Ok(self.process_identity(requester_driver, pid)?.gid)
581    }
582
583    pub fn geteuid(&self, requester_driver: &str, pid: u32) -> KernelResult<u32> {
584        Ok(self.process_identity(requester_driver, pid)?.euid)
585    }
586
587    pub fn getegid(&self, requester_driver: &str, pid: u32) -> KernelResult<u32> {
588        Ok(self.process_identity(requester_driver, pid)?.egid)
589    }
590
591    pub fn getgroups(&self, requester_driver: &str, pid: u32) -> KernelResult<Vec<u32>> {
592        Ok(self
593            .process_identity(requester_driver, pid)?
594            .supplementary_gids)
595    }
596
597    pub fn getpwuid(&self, uid: u32) -> KernelResult<String> {
598        self.users
599            .getpwuid(uid)
600            .ok_or_else(|| KernelError::new("ENOENT", format!("unknown uid {uid}")))
601    }
602
603    pub fn getgrgid(&self, gid: u32) -> KernelResult<String> {
604        self.users
605            .getgrgid(gid)
606            .ok_or_else(|| KernelError::new("ENOENT", format!("unknown gid {gid}")))
607    }
608
609    pub fn resource_snapshot(&self) -> ResourceSnapshot {
610        let fd_tables = lock_or_recover(&self.fd_tables);
611        self.resources.snapshot(
612            &self.processes,
613            &fd_tables,
614            &self.pipes,
615            &self.ptys,
616            &self.sockets,
617        )
618    }
619
620    pub fn resource_limits(&self) -> &ResourceLimits {
621        self.resources.limits()
622    }
623
624    pub fn set_permissions(&mut self, permissions: Permissions) {
625        self.filesystem.set_permissions(permissions.clone());
626        self.permissions = permissions;
627    }
628
629    pub fn set_loopback_exempt_ports(&mut self, ports: BTreeSet<u16>) {
630        self.loopback_exempt_ports = ports;
631    }
632
633    pub fn extend_loopback_exempt_ports(&mut self, ports: impl IntoIterator<Item = u16>) {
634        self.loopback_exempt_ports.extend(ports);
635    }
636
637    pub fn resolve_dns(
638        &self,
639        hostname: &str,
640        policy: DnsLookupPolicy,
641    ) -> KernelResult<DnsResolution> {
642        self.assert_not_terminated()?;
643        if matches!(policy, DnsLookupPolicy::CheckPermissions) {
644            let resource = format_dns_resource(hostname).map_err(map_dns_resolver_error)?;
645            check_network_access(
646                &self.vm_id,
647                &self.permissions,
648                NetworkOperation::Dns,
649                &resource,
650            )?;
651        }
652
653        resolve_dns(&self.dns, self.dns_resolver.as_ref(), hostname).map_err(map_dns_resolver_error)
654    }
655
656    pub fn resolve_dns_records(
657        &self,
658        hostname: &str,
659        record_type: RecordType,
660        policy: DnsLookupPolicy,
661    ) -> KernelResult<DnsRecordResolution> {
662        self.assert_not_terminated()?;
663        if matches!(policy, DnsLookupPolicy::CheckPermissions) {
664            let resource = format_dns_resource(hostname).map_err(map_dns_resolver_error)?;
665            check_network_access(
666                &self.vm_id,
667                &self.permissions,
668                NetworkOperation::Dns,
669                &resource,
670            )?;
671        }
672
673        resolve_dns_records(&self.dns, self.dns_resolver.as_ref(), hostname, record_type)
674            .map_err(map_dns_resolver_error)
675    }
676
677    pub fn register_driver(&mut self, driver: CommandDriver) -> KernelResult<()> {
678        self.assert_not_terminated()?;
679        let driver_name = driver.name().to_owned();
680        let populate_driver = driver.clone();
681        self.commands.register(driver)?;
682        lock_or_recover(&self.driver_pids)
683            .entry(driver_name)
684            .or_default();
685        self.commands
686            .populate_driver_bin(&mut self.filesystem, &populate_driver)?;
687        Ok(())
688    }
689
690    pub fn exec(
691        &mut self,
692        command: &str,
693        options: ExecOptions,
694    ) -> KernelResult<KernelProcessHandle> {
695        self.spawn_process(
696            "sh",
697            vec![String::from("-c"), String::from(command)],
698            SpawnOptions {
699                requester_driver: options.requester_driver,
700                parent_pid: options.parent_pid,
701                env: options.env,
702                cwd: options.cwd,
703            },
704        )
705    }
706
707    pub fn open_shell(&mut self, options: OpenShellOptions) -> KernelResult<OpenShellHandle> {
708        let command = options.command.unwrap_or_else(|| String::from("sh"));
709        let requester_driver = options.requester_driver.clone();
710        let process = self.spawn_process(
711            &command,
712            options.args,
713            SpawnOptions {
714                requester_driver: requester_driver.clone(),
715                parent_pid: None,
716                env: options.env,
717                cwd: options.cwd,
718            },
719        )?;
720        let owner = requester_driver.as_deref().unwrap_or(process.driver());
721        let (master_fd, slave_fd, pty_path) = self.open_pty(owner, process.pid())?;
722        self.setpgid(owner, process.pid(), process.pid())?;
723        self.pty_set_foreground_pgid(owner, process.pid(), master_fd, process.pid())?;
724        Ok(OpenShellHandle {
725            process,
726            master_fd,
727            slave_fd,
728            pty_path,
729        })
730    }
731
732    pub fn read_file(&mut self, path: &str) -> KernelResult<Vec<u8>> {
733        self.assert_not_terminated()?;
734        self.read_file_internal(None, path)
735    }
736
737    pub fn pread_file(&mut self, path: &str, offset: u64, length: usize) -> KernelResult<Vec<u8>> {
738        self.assert_not_terminated()?;
739        self.resources.check_pread_length(length)?;
740        Ok(VirtualFileSystem::pread(
741            &mut self.filesystem,
742            path,
743            offset,
744            length,
745        )?)
746    }
747
748    pub fn read_file_for_process(
749        &mut self,
750        requester_driver: &str,
751        pid: u32,
752        path: &str,
753    ) -> KernelResult<Vec<u8>> {
754        self.assert_not_terminated()?;
755        self.assert_driver_owns(requester_driver, pid)?;
756        self.read_file_internal(Some(pid), path)
757    }
758
759    pub fn write_file(&mut self, path: &str, content: impl Into<Vec<u8>>) -> KernelResult<()> {
760        self.assert_not_terminated()?;
761        self.reject_read_only_resolved_write_path(path)?;
762        let content = content.into();
763        let new_size = content.len() as u64;
764        let existing = self.storage_stat(path)?;
765        self.check_write_file_limits_with_existing(path, existing.as_ref(), new_size)?;
766        self.filesystem.write_file(path, content)?;
767        self.update_filesystem_usage_cache_for_write(existing.as_ref(), new_size);
768        Ok(())
769    }
770
771    /// Writes `content` at `offset` within an existing file, growing (and
772    /// zero-filling) it as needed. This is the positional counterpart to
773    /// [`Self::pread_file`]: it lets a descriptor-based caller (the shared WASI
774    /// runner over the browser wire, which has no kernel fd offsets) write a
775    /// region without the lossy, non-atomic read-modify-write it would
776    /// otherwise have to do client-side. Enforcement matches `write_file`:
777    /// read-only paths are rejected and the resulting file size is charged
778    /// against the resource limits before the write.
779    pub fn pwrite_file(
780        &mut self,
781        path: &str,
782        offset: u64,
783        content: impl Into<Vec<u8>>,
784    ) -> KernelResult<()> {
785        self.assert_not_terminated()?;
786        self.reject_read_only_resolved_write_path(path)?;
787        let content = content.into();
788        let existing = self.storage_stat(path)?;
789        let existing_size = existing.as_ref().map(|stat| stat.size).unwrap_or(0);
790        let end = offset.saturating_add(content.len() as u64);
791        self.check_write_file_limits_with_existing(
792            path,
793            existing.as_ref(),
794            existing_size.max(end),
795        )?;
796        self.filesystem.pwrite(path, content, offset)?;
797        self.update_filesystem_usage_cache_for_write(existing.as_ref(), existing_size.max(end));
798        Ok(())
799    }
800
801    pub fn write_file_for_process(
802        &mut self,
803        requester_driver: &str,
804        pid: u32,
805        path: &str,
806        content: impl Into<Vec<u8>>,
807        mode: Option<u32>,
808    ) -> KernelResult<()> {
809        self.assert_not_terminated()?;
810        self.assert_driver_owns(requester_driver, pid)?;
811        let existed = self.exists_internal(Some(pid), path)?;
812        let content = content.into();
813        let new_size = content.len() as u64;
814        self.reject_read_only_resolved_write_path(path)?;
815        let existing = self.storage_stat(path)?;
816        self.check_write_file_limits_with_existing(path, existing.as_ref(), new_size)?;
817        VirtualFileSystem::write_file_with_mode(&mut self.filesystem, path, content, mode)?;
818        self.update_filesystem_usage_cache_for_write(existing.as_ref(), new_size);
819        if !existed {
820            let umask = self.processes.get_umask(pid)?;
821            self.apply_creation_mode(path, mode.unwrap_or(0o666), umask)?;
822        }
823        Ok(())
824    }
825
826    pub fn create_dir(&mut self, path: &str) -> KernelResult<()> {
827        self.assert_not_terminated()?;
828        self.reject_read_only_entry_write_path(path)?;
829        self.check_create_dir_limits(path)?;
830        self.filesystem.create_dir(path)?;
831        self.update_filesystem_usage_cache_for_inode_create(0);
832        Ok(())
833    }
834
835    pub fn create_dir_for_process(
836        &mut self,
837        requester_driver: &str,
838        pid: u32,
839        path: &str,
840        mode: Option<u32>,
841    ) -> KernelResult<()> {
842        self.assert_not_terminated()?;
843        self.assert_driver_owns(requester_driver, pid)?;
844        let existed = self.exists_internal(Some(pid), path)?;
845        self.reject_read_only_entry_write_path(path)?;
846        self.check_create_dir_limits(path)?;
847        VirtualFileSystem::create_dir_with_mode(&mut self.filesystem, path, mode)?;
848        self.update_filesystem_usage_cache_for_inode_create(0);
849        if !existed {
850            let umask = self.processes.get_umask(pid)?;
851            self.apply_creation_mode(path, mode.unwrap_or(0o777), umask)?;
852        }
853        Ok(())
854    }
855
856    pub fn mkdir(&mut self, path: &str, recursive: bool) -> KernelResult<()> {
857        self.assert_not_terminated()?;
858        self.reject_read_only_entry_write_path(path)?;
859        let created_paths = self.missing_directory_paths(path, recursive)?;
860        self.check_mkdir_limits(path, recursive)?;
861        self.filesystem.mkdir(path, recursive)?;
862        self.update_filesystem_usage_cache_for_inode_creates(created_paths.len());
863        Ok(())
864    }
865
866    pub fn mkdir_for_process(
867        &mut self,
868        requester_driver: &str,
869        pid: u32,
870        path: &str,
871        recursive: bool,
872        mode: Option<u32>,
873    ) -> KernelResult<()> {
874        self.assert_not_terminated()?;
875        self.assert_driver_owns(requester_driver, pid)?;
876        let created_paths = self.missing_directory_paths(path, recursive)?;
877        self.reject_read_only_entry_write_path(path)?;
878        self.check_mkdir_limits(path, recursive)?;
879        VirtualFileSystem::mkdir_with_mode(&mut self.filesystem, path, recursive, mode)?;
880        if !created_paths.is_empty() {
881            let umask = self.processes.get_umask(pid)?;
882            let mode = mode.unwrap_or(0o777);
883            for created_path in &created_paths {
884                self.apply_creation_mode(created_path, mode, umask)?;
885            }
886        }
887        self.update_filesystem_usage_cache_for_inode_creates(created_paths.len());
888        Ok(())
889    }
890
891    pub fn umask(
892        &self,
893        requester_driver: &str,
894        pid: u32,
895        new_mask: Option<u32>,
896    ) -> KernelResult<u32> {
897        self.assert_driver_owns(requester_driver, pid)?;
898        match new_mask {
899            Some(mask) => Ok(self.processes.set_umask(pid, mask)?),
900            None => Ok(self.processes.get_umask(pid)?),
901        }
902    }
903
904    pub fn exists(&self, path: &str) -> KernelResult<bool> {
905        self.assert_not_terminated()?;
906        self.exists_internal(None, path)
907    }
908
909    pub fn exists_for_process(
910        &self,
911        requester_driver: &str,
912        pid: u32,
913        path: &str,
914    ) -> KernelResult<bool> {
915        self.assert_not_terminated()?;
916        self.assert_driver_owns(requester_driver, pid)?;
917        self.exists_internal(Some(pid), path)
918    }
919
920    pub fn stat(&mut self, path: &str) -> KernelResult<VirtualStat> {
921        self.assert_not_terminated()?;
922        self.stat_internal(None, path)
923    }
924
925    pub fn stat_for_process(
926        &mut self,
927        requester_driver: &str,
928        pid: u32,
929        path: &str,
930    ) -> KernelResult<VirtualStat> {
931        self.assert_not_terminated()?;
932        self.assert_driver_owns(requester_driver, pid)?;
933        self.stat_internal(Some(pid), path)
934    }
935
936    pub fn lstat(&self, path: &str) -> KernelResult<VirtualStat> {
937        self.assert_not_terminated()?;
938        self.lstat_internal(None, path)
939    }
940
941    pub fn lstat_for_process(
942        &self,
943        requester_driver: &str,
944        pid: u32,
945        path: &str,
946    ) -> KernelResult<VirtualStat> {
947        self.assert_not_terminated()?;
948        self.assert_driver_owns(requester_driver, pid)?;
949        self.lstat_internal(Some(pid), path)
950    }
951
952    pub fn read_link(&self, path: &str) -> KernelResult<String> {
953        self.assert_not_terminated()?;
954        self.read_link_internal(None, path)
955    }
956
957    pub fn read_link_for_process(
958        &self,
959        requester_driver: &str,
960        pid: u32,
961        path: &str,
962    ) -> KernelResult<String> {
963        self.assert_not_terminated()?;
964        self.assert_driver_owns(requester_driver, pid)?;
965        self.read_link_internal(Some(pid), path)
966    }
967
968    pub fn read_dir(&mut self, path: &str) -> KernelResult<Vec<String>> {
969        self.assert_not_terminated()?;
970        let entries = self.read_dir_internal(None, path)?;
971        self.resources.check_readdir_entries(entries.len())?;
972        Ok(entries)
973    }
974
975    pub fn read_dir_for_process(
976        &mut self,
977        requester_driver: &str,
978        pid: u32,
979        path: &str,
980    ) -> KernelResult<Vec<String>> {
981        self.assert_not_terminated()?;
982        self.assert_driver_owns(requester_driver, pid)?;
983        let entries = self.read_dir_internal(Some(pid), path)?;
984        self.resources.check_readdir_entries(entries.len())?;
985        Ok(entries)
986    }
987
988    pub fn read_dir_with_types_for_process(
989        &mut self,
990        requester_driver: &str,
991        pid: u32,
992        path: &str,
993    ) -> KernelResult<Vec<VirtualDirEntry>> {
994        self.assert_not_terminated()?;
995        self.assert_driver_owns(requester_driver, pid)?;
996        let entries = self.read_dir_with_types_internal(Some(pid), path)?;
997        self.resources.check_readdir_entries(entries.len())?;
998        Ok(entries)
999    }
1000
1001    /// Lists a directory with each child's file type in one call. This is the
1002    /// typed counterpart to [`Self::read_dir`]: it lets a descriptor-based caller
1003    /// recover Dirent kinds (`readdir({ withFileTypes })`) without an extra
1004    /// `lstat` round-trip per entry. Reuses `read_dir_internal` so proc, the
1005    /// readdir-entry limit, and read-permission checks behave identically; the
1006    /// per-entry `lstat` is in-process (no wire hops).
1007    pub fn read_dir_with_types(&mut self, path: &str) -> KernelResult<Vec<VirtualDirEntry>> {
1008        self.assert_not_terminated()?;
1009        let names = self.read_dir_internal(None, path)?;
1010        self.resources.check_readdir_entries(names.len())?;
1011        let mut entries = Vec::with_capacity(names.len());
1012        for name in names {
1013            let child = normalize_path(&format!("{path}/{name}"));
1014            let stat = self.lstat_internal(None, &child)?;
1015            entries.push(VirtualDirEntry {
1016                name,
1017                is_directory: stat.is_directory,
1018                is_symbolic_link: stat.is_symbolic_link,
1019            });
1020        }
1021        Ok(entries)
1022    }
1023
1024    pub fn read_dir_recursive(
1025        &mut self,
1026        path: &str,
1027        max_depth: Option<usize>,
1028    ) -> KernelResult<Vec<RecursiveDirEntry>> {
1029        self.assert_not_terminated()?;
1030        let depth_limit = self.effective_recursive_fs_depth(max_depth)?;
1031        let caller_limited = max_depth.is_some();
1032        let mut entries = Vec::new();
1033        let mut queue = VecDeque::from([(normalize_path(path), 0usize)]);
1034
1035        while let Some((dir_path, depth)) = queue.pop_front() {
1036            self.resources.check_recursive_fs_depth(depth)?;
1037            let names = self.read_dir_internal(None, &dir_path)?;
1038            self.resources.check_readdir_entries(names.len())?;
1039
1040            for name in names {
1041                if matches!(name.as_str(), "." | "..") {
1042                    continue;
1043                }
1044                let child = join_child_path(&dir_path, &name);
1045                let stat = self.lstat_internal(None, &child)?;
1046                let entry = RecursiveDirEntry {
1047                    path: child.clone(),
1048                    is_directory: stat.is_directory,
1049                    is_symbolic_link: stat.is_symbolic_link,
1050                    size: stat.size,
1051                };
1052                entries.push(entry);
1053                self.resources.check_recursive_fs_entries(entries.len())?;
1054
1055                if stat.is_directory && !stat.is_symbolic_link {
1056                    let child_depth = depth.saturating_add(1);
1057                    if child_depth <= depth_limit {
1058                        queue.push_back((child, child_depth));
1059                    } else if !caller_limited {
1060                        self.resources.check_recursive_fs_depth(child_depth)?;
1061                    }
1062                }
1063            }
1064        }
1065
1066        Ok(entries)
1067    }
1068
1069    pub fn copy_path(&mut self, from: &str, to: &str, recursive: bool) -> KernelResult<()> {
1070        self.assert_not_terminated()?;
1071        let mut entries = 0usize;
1072        self.copy_path_inner(from, to, recursive, 0, &mut entries)?;
1073        Ok(())
1074    }
1075
1076    pub fn remove_path(&mut self, path: &str, recursive: bool) -> KernelResult<()> {
1077        self.assert_not_terminated()?;
1078        let mut entries = 0usize;
1079        self.remove_path_inner(path, recursive, 0, &mut entries)
1080    }
1081
1082    pub fn move_path(&mut self, from: &str, to: &str) -> KernelResult<()> {
1083        self.assert_not_terminated()?;
1084        match self.rename(from, to) {
1085            Ok(()) => Ok(()),
1086            Err(error) if error.code() == "EXDEV" => {
1087                self.copy_path(from, to, true)?;
1088                self.remove_path(from, true)
1089            }
1090            Err(error) => Err(error),
1091        }
1092    }
1093
1094    pub fn remove_file(&mut self, path: &str) -> KernelResult<()> {
1095        self.assert_not_terminated()?;
1096        self.reject_read_only_entry_write_path(path)?;
1097        let removed = self.storage_lstat(path)?;
1098        self.filesystem.remove_file(path)?;
1099        self.update_filesystem_usage_cache_for_remove(removed.as_ref());
1100        Ok(())
1101    }
1102
1103    pub fn remove_dir(&mut self, path: &str) -> KernelResult<()> {
1104        self.assert_not_terminated()?;
1105        self.reject_read_only_entry_write_path(path)?;
1106        let removed = self.storage_lstat(path)?;
1107        self.filesystem.remove_dir(path)?;
1108        if removed.as_ref().is_some_and(|stat| stat.is_directory) {
1109            self.update_filesystem_usage_cache_for_inode_delete(0);
1110        }
1111        Ok(())
1112    }
1113
1114    pub fn rename(&mut self, old_path: &str, new_path: &str) -> KernelResult<()> {
1115        self.assert_not_terminated()?;
1116        self.reject_read_only_entry_write_path(old_path)?;
1117        self.reject_read_only_entry_write_path(new_path)?;
1118        self.check_rename_copy_up_limits(old_path, new_path)?;
1119        self.filesystem.rename(old_path, new_path)?;
1120        // Rename can be a pure metadata move, a destination replacement, or an
1121        // overlay copy-up/removal with hard-link aliasing. Drop the cached root
1122        // usage because the local byte/inode delta is not knowable here.
1123        self.invalidate_filesystem_usage_cache();
1124        Ok(())
1125    }
1126
1127    pub fn realpath(&self, path: &str) -> KernelResult<String> {
1128        self.assert_not_terminated()?;
1129        self.realpath_internal(None, path)
1130    }
1131
1132    pub fn realpath_for_process(
1133        &self,
1134        requester_driver: &str,
1135        pid: u32,
1136        path: &str,
1137    ) -> KernelResult<String> {
1138        self.assert_not_terminated()?;
1139        self.assert_driver_owns(requester_driver, pid)?;
1140        self.realpath_internal(Some(pid), path)
1141    }
1142
1143    pub fn symlink(&mut self, target: &str, link_path: &str) -> KernelResult<()> {
1144        self.assert_not_terminated()?;
1145        if is_proc_path(target) {
1146            self.filesystem
1147                .check_virtual_path(FsOperation::Write, link_path)
1148                .map_err(KernelError::from)?;
1149            return Err(read_only_filesystem_error(link_path));
1150        }
1151        self.reject_read_only_entry_write_path(link_path)?;
1152        self.check_symlink_limits(target, link_path)?;
1153        self.filesystem.symlink(target, link_path)?;
1154        self.update_filesystem_usage_cache_for_inode_create(target.len() as u64);
1155        Ok(())
1156    }
1157
1158    pub fn chmod(&mut self, path: &str, mode: u32) -> KernelResult<()> {
1159        self.assert_not_terminated()?;
1160        self.reject_read_only_resolved_write_path(path)?;
1161        Ok(self.filesystem.chmod(path, mode)?)
1162    }
1163
1164    pub fn link(&mut self, old_path: &str, new_path: &str) -> KernelResult<()> {
1165        self.assert_not_terminated()?;
1166        if is_proc_path(old_path) {
1167            self.filesystem
1168                .check_virtual_path(FsOperation::Write, new_path)
1169                .map_err(KernelError::from)?;
1170            return Err(read_only_filesystem_error(new_path));
1171        }
1172        self.reject_read_only_resolved_write_path(old_path)?;
1173        self.reject_read_only_entry_write_path(new_path)?;
1174        self.filesystem.link(old_path, new_path)?;
1175        // Hard-link creation makes another directory entry for an already
1176        // reachable inode, so measured root usage is unchanged.
1177        Ok(())
1178    }
1179
1180    pub fn chown(&mut self, path: &str, uid: u32, gid: u32) -> KernelResult<()> {
1181        self.assert_not_terminated()?;
1182        self.reject_read_only_resolved_write_path(path)?;
1183        Ok(self.filesystem.chown(path, uid, gid)?)
1184    }
1185
1186    pub fn utimes(&mut self, path: &str, atime_ms: u64, mtime_ms: u64) -> KernelResult<()> {
1187        self.utimes_spec(
1188            path,
1189            VirtualUtimeSpec::Set(VirtualTimeSpec::from_millis(atime_ms)),
1190            VirtualUtimeSpec::Set(VirtualTimeSpec::from_millis(mtime_ms)),
1191        )
1192    }
1193
1194    pub fn utimes_spec(
1195        &mut self,
1196        path: &str,
1197        atime: VirtualUtimeSpec,
1198        mtime: VirtualUtimeSpec,
1199    ) -> KernelResult<()> {
1200        self.assert_not_terminated()?;
1201        self.reject_read_only_resolved_write_path(path)?;
1202        Ok(self.filesystem.utimes_spec(path, atime, mtime, true)?)
1203    }
1204
1205    pub fn lutimes(
1206        &mut self,
1207        path: &str,
1208        atime: VirtualUtimeSpec,
1209        mtime: VirtualUtimeSpec,
1210    ) -> KernelResult<()> {
1211        self.assert_not_terminated()?;
1212        self.reject_read_only_entry_write_path(path)?;
1213        Ok(self.filesystem.utimes_spec(path, atime, mtime, false)?)
1214    }
1215
1216    pub fn futimes(
1217        &mut self,
1218        requester_driver: &str,
1219        pid: u32,
1220        fd: u32,
1221        atime: VirtualUtimeSpec,
1222        mtime: VirtualUtimeSpec,
1223    ) -> KernelResult<()> {
1224        self.assert_not_terminated()?;
1225        let path = self
1226            .description_for_fd(requester_driver, pid, fd)?
1227            .path()
1228            .to_owned();
1229        self.reject_read_only_resolved_write_path(&path)?;
1230        Ok(self.filesystem.utimes_spec(&path, atime, mtime, true)?)
1231    }
1232
1233    pub fn truncate(&mut self, path: &str, length: u64) -> KernelResult<()> {
1234        self.assert_not_terminated()?;
1235        self.reject_read_only_resolved_write_path(path)?;
1236        let existing = self.storage_stat(path)?;
1237        self.check_truncate_limits_with_existing(path, existing.as_ref(), length)?;
1238        self.filesystem.truncate(path, length)?;
1239        self.update_filesystem_usage_cache_for_write(existing.as_ref(), length);
1240        Ok(())
1241    }
1242
1243    pub fn list_processes(&self) -> BTreeMap<u32, ProcessInfo> {
1244        self.processes.list_processes()
1245    }
1246
1247    pub fn zombie_timer_count(&self) -> usize {
1248        self.processes.zombie_timer_count()
1249    }
1250
1251    pub fn spawn_process(
1252        &mut self,
1253        command: &str,
1254        args: Vec<String>,
1255        options: SpawnOptions,
1256    ) -> KernelResult<KernelProcessHandle> {
1257        self.assert_not_terminated()?;
1258        if let (Some(requester), Some(parent_pid)) =
1259            (options.requester_driver.as_deref(), options.parent_pid)
1260        {
1261            self.assert_driver_owns(requester, parent_pid)?;
1262        }
1263
1264        let cwd = options.cwd.clone().unwrap_or_else(|| self.cwd.clone());
1265        let resolved = self.resolve_spawn_command(command, &args, &cwd)?;
1266
1267        self.resources
1268            .check_process_argv_bytes(&resolved.command, &resolved.args)?;
1269        self.resources
1270            .check_process_env_bytes(&self.env, &options.env)?;
1271
1272        let mut env = self.env.clone();
1273        env.extend(options.env.clone());
1274        check_command_execution(
1275            &self.vm_id,
1276            &self.permissions,
1277            &resolved.command,
1278            &resolved.args,
1279            Some(&cwd),
1280            &env,
1281        )?;
1282
1283        let inherited_fds = {
1284            let tables = lock_or_recover(&self.fd_tables);
1285            options
1286                .parent_pid
1287                .and_then(|pid| tables.get(pid).map(ProcessFdTable::len))
1288                .unwrap_or(3)
1289        };
1290        self.resources
1291            .check_process_spawn(&self.resource_snapshot(), inherited_fds)?;
1292
1293        self.register_process(
1294            resolved.driver.name().to_owned(),
1295            resolved.command,
1296            resolved.args,
1297            ProcessContext {
1298                pid: 0,
1299                ppid: options.parent_pid.unwrap_or(0),
1300                env,
1301                cwd,
1302                umask: DEFAULT_PROCESS_UMASK,
1303                fds: Default::default(),
1304                identity: self.users.identity(),
1305                blocked_signals: SignalSet::empty(),
1306                pending_signals: SignalSet::empty(),
1307            },
1308            options.requester_driver.as_deref(),
1309        )
1310    }
1311
1312    pub fn create_virtual_process(
1313        &mut self,
1314        requester_driver: &str,
1315        driver: &str,
1316        command: &str,
1317        args: Vec<String>,
1318        options: VirtualProcessOptions,
1319    ) -> KernelResult<KernelProcessHandle> {
1320        self.assert_not_terminated()?;
1321        if let Some(parent_pid) = options.parent_pid {
1322            self.assert_driver_owns(requester_driver, parent_pid)?;
1323        }
1324
1325        let cwd = options.cwd.clone().unwrap_or_else(|| self.cwd.clone());
1326        self.resources.check_process_argv_bytes(command, &args)?;
1327        self.resources
1328            .check_process_env_bytes(&self.env, &options.env)?;
1329
1330        let mut env = self.env.clone();
1331        env.extend(options.env.clone());
1332        check_command_execution(
1333            &self.vm_id,
1334            &self.permissions,
1335            command,
1336            &args,
1337            Some(&cwd),
1338            &env,
1339        )?;
1340
1341        let inherited_fds = {
1342            let tables = lock_or_recover(&self.fd_tables);
1343            options
1344                .parent_pid
1345                .and_then(|pid| tables.get(pid).map(ProcessFdTable::len))
1346                .unwrap_or(3)
1347        };
1348        self.resources
1349            .check_process_spawn(&self.resource_snapshot(), inherited_fds)?;
1350
1351        self.register_process(
1352            String::from(driver),
1353            String::from(command),
1354            args,
1355            ProcessContext {
1356                pid: 0,
1357                ppid: options.parent_pid.unwrap_or(0),
1358                env,
1359                cwd,
1360                umask: DEFAULT_PROCESS_UMASK,
1361                fds: Default::default(),
1362                identity: self.users.identity(),
1363                blocked_signals: SignalSet::empty(),
1364                pending_signals: SignalSet::empty(),
1365            },
1366            Some(requester_driver),
1367        )
1368    }
1369
1370    pub fn read_process_stdin(
1371        &mut self,
1372        requester_driver: &str,
1373        pid: u32,
1374        length: usize,
1375        timeout: Option<Duration>,
1376    ) -> KernelResult<Option<Vec<u8>>> {
1377        self.fd_read_with_timeout_result(requester_driver, pid, 0, length, timeout)
1378    }
1379
1380    pub fn write_process_stdout(
1381        &mut self,
1382        requester_driver: &str,
1383        pid: u32,
1384        data: &[u8],
1385    ) -> KernelResult<usize> {
1386        self.fd_write(requester_driver, pid, 1, data)
1387    }
1388
1389    pub fn write_process_stderr(
1390        &mut self,
1391        requester_driver: &str,
1392        pid: u32,
1393        data: &[u8],
1394    ) -> KernelResult<usize> {
1395        self.fd_write(requester_driver, pid, 2, data)
1396    }
1397
1398    pub fn exit_process(
1399        &mut self,
1400        requester_driver: &str,
1401        pid: u32,
1402        exit_code: i32,
1403    ) -> KernelResult<()> {
1404        self.assert_driver_owns(requester_driver, pid)?;
1405        self.processes.mark_exited(pid, exit_code);
1406        Ok(())
1407    }
1408
1409    fn register_process(
1410        &mut self,
1411        driver_name: String,
1412        command: String,
1413        args: Vec<String>,
1414        mut ctx: ProcessContext,
1415        requester_driver: Option<&str>,
1416    ) -> KernelResult<KernelProcessHandle> {
1417        let pid = self.processes.allocate_pid()?;
1418        ctx.pid = pid;
1419
1420        {
1421            let mut tables = lock_or_recover(&self.fd_tables);
1422            if ctx.ppid != 0 {
1423                let parent_pid = ctx.ppid;
1424                tables.fork(parent_pid, pid);
1425            } else {
1426                tables.create(pid);
1427            }
1428        }
1429
1430        let process = Arc::new(StubDriverProcess::default());
1431        self.processes.register(
1432            pid,
1433            driver_name.clone(),
1434            command,
1435            args,
1436            ctx,
1437            process.clone(),
1438        );
1439
1440        let mut owners = lock_or_recover(&self.driver_pids);
1441        owners.entry(driver_name.clone()).or_default().insert(pid);
1442        if let Some(requester) = requester_driver {
1443            owners
1444                .entry(String::from(requester))
1445                .or_default()
1446                .insert(pid);
1447        }
1448
1449        Ok(KernelProcessHandle {
1450            pid,
1451            driver: driver_name,
1452            process,
1453        })
1454    }
1455
1456    pub fn waitpid(&mut self, pid: u32) -> KernelResult<WaitPidResult> {
1457        let (pid, status) = self.processes.waitpid(pid)?;
1458        self.cleanup_process_resources(pid);
1459        Ok(WaitPidResult { pid, status })
1460    }
1461
1462    pub fn waitpid_with_options(
1463        &mut self,
1464        requester_driver: &str,
1465        waiter_pid: u32,
1466        pid: i32,
1467        flags: WaitPidFlags,
1468    ) -> KernelResult<Option<WaitPidEventResult>> {
1469        self.assert_driver_owns(requester_driver, waiter_pid)?;
1470        let result = self.processes.waitpid_for(waiter_pid, pid, flags)?;
1471        Ok(result.map(|result| self.finish_waitpid_event(result)))
1472    }
1473
1474    pub fn wait_and_reap(&mut self, pid: u32) -> KernelResult<(u32, i32)> {
1475        let result = self.waitpid(pid)?;
1476        Ok((result.pid, result.status))
1477    }
1478
1479    pub fn open_pipe(&mut self, requester_driver: &str, pid: u32) -> KernelResult<(u32, u32)> {
1480        self.assert_not_terminated()?;
1481        self.assert_driver_owns(requester_driver, pid)?;
1482        self.resources
1483            .check_pipe_allocation(&self.resource_snapshot())?;
1484        let mut tables = lock_or_recover(&self.fd_tables);
1485        let table = tables
1486            .get_mut(pid)
1487            .ok_or_else(|| KernelError::no_such_process(pid))?;
1488        Ok(self.pipes.create_pipe_fds(table)?)
1489    }
1490
1491    pub fn open_pty(
1492        &mut self,
1493        requester_driver: &str,
1494        pid: u32,
1495    ) -> KernelResult<(u32, u32, String)> {
1496        self.assert_not_terminated()?;
1497        self.assert_driver_owns(requester_driver, pid)?;
1498        self.resources
1499            .check_pty_allocation(&self.resource_snapshot())?;
1500        let mut tables = lock_or_recover(&self.fd_tables);
1501        let table = tables
1502            .get_mut(pid)
1503            .ok_or_else(|| KernelError::no_such_process(pid))?;
1504        Ok(self.ptys.create_pty_fds(table)?)
1505    }
1506
1507    pub fn socket_create(
1508        &mut self,
1509        requester_driver: &str,
1510        pid: u32,
1511        spec: SocketSpec,
1512    ) -> KernelResult<SocketId> {
1513        self.assert_not_terminated()?;
1514        self.assert_driver_owns(requester_driver, pid)?;
1515        self.resources
1516            .check_socket_allocation(&self.resource_snapshot())?;
1517        Ok(self.sockets.allocate(pid, spec).id())
1518    }
1519
1520    pub fn set_socket_readiness_sink<S>(&mut self, sink: Option<S>)
1521    where
1522        S: Fn(SocketReadiness) + Send + Sync + 'static,
1523    {
1524        self.sockets.set_readiness_sink(sink);
1525    }
1526
1527    pub fn socket_get(&self, socket_id: SocketId) -> Option<SocketRecord> {
1528        self.sockets.get(socket_id)
1529    }
1530
1531    pub fn socket_records_for_pid(&self, pid: u32) -> Vec<SocketRecord> {
1532        self.sockets.records_for_owner(pid)
1533    }
1534
1535    pub fn socket_bind_inet(
1536        &mut self,
1537        requester_driver: &str,
1538        pid: u32,
1539        socket_id: SocketId,
1540        address: InetSocketAddress,
1541    ) -> KernelResult<()> {
1542        self.assert_not_terminated()?;
1543        self.assert_driver_owns(requester_driver, pid)?;
1544        let existing = self
1545            .sockets
1546            .get(socket_id)
1547            .ok_or_else(|| KernelError::new("ENOENT", format!("no such socket {socket_id}")))?;
1548        if existing.owner_pid() != pid {
1549            return Err(KernelError::permission_denied(format!(
1550                "process {pid} does not own socket {socket_id}"
1551            )));
1552        }
1553        check_network_access(
1554            &self.vm_id,
1555            &self.permissions,
1556            NetworkOperation::Listen,
1557            &format_tcp_resource(address.host(), address.port()),
1558        )?;
1559
1560        self.sockets.bind_inet(socket_id, address)?;
1561        self.poll_notifier.notify();
1562        Ok(())
1563    }
1564
1565    pub fn socket_bind_unix(
1566        &mut self,
1567        requester_driver: &str,
1568        pid: u32,
1569        socket_id: SocketId,
1570        path: impl Into<String>,
1571    ) -> KernelResult<()> {
1572        self.assert_not_terminated()?;
1573        self.assert_driver_owns(requester_driver, pid)?;
1574        let existing = self
1575            .sockets
1576            .get(socket_id)
1577            .ok_or_else(|| KernelError::new("ENOENT", format!("no such socket {socket_id}")))?;
1578        if existing.owner_pid() != pid {
1579            return Err(KernelError::permission_denied(format!(
1580                "process {pid} does not own socket {socket_id}"
1581            )));
1582        }
1583
1584        self.sockets
1585            .bind_unix(socket_id, normalize_path(&path.into()))?;
1586        self.poll_notifier.notify();
1587        Ok(())
1588    }
1589
1590    pub fn socket_listen(
1591        &mut self,
1592        requester_driver: &str,
1593        pid: u32,
1594        socket_id: SocketId,
1595        backlog: usize,
1596    ) -> KernelResult<()> {
1597        self.assert_not_terminated()?;
1598        self.assert_driver_owns(requester_driver, pid)?;
1599        let existing = self
1600            .sockets
1601            .get(socket_id)
1602            .ok_or_else(|| KernelError::new("ENOENT", format!("no such socket {socket_id}")))?;
1603        if existing.owner_pid() != pid {
1604            return Err(KernelError::permission_denied(format!(
1605                "process {pid} does not own socket {socket_id}"
1606            )));
1607        }
1608        if let Some(address) = existing.local_address() {
1609            check_network_access(
1610                &self.vm_id,
1611                &self.permissions,
1612                NetworkOperation::Listen,
1613                &format_tcp_resource(address.host(), address.port()),
1614            )?;
1615        }
1616
1617        self.sockets.listen(socket_id, backlog)?;
1618        self.poll_notifier.notify();
1619        Ok(())
1620    }
1621
1622    pub fn socket_queue_incoming_tcp_connection(
1623        &mut self,
1624        requester_driver: &str,
1625        pid: u32,
1626        listener_socket_id: SocketId,
1627        peer_address: InetSocketAddress,
1628    ) -> KernelResult<()> {
1629        self.assert_not_terminated()?;
1630        self.assert_driver_owns(requester_driver, pid)?;
1631        let existing = self.sockets.get(listener_socket_id).ok_or_else(|| {
1632            KernelError::new("ENOENT", format!("no such socket {listener_socket_id}"))
1633        })?;
1634        if existing.owner_pid() != pid {
1635            return Err(KernelError::permission_denied(format!(
1636                "process {pid} does not own socket {listener_socket_id}"
1637            )));
1638        }
1639
1640        self.sockets
1641            .enqueue_incoming_tcp_connection(listener_socket_id, peer_address)?;
1642        self.poll_notifier.notify();
1643        Ok(())
1644    }
1645
1646    pub fn socket_accept(
1647        &mut self,
1648        requester_driver: &str,
1649        pid: u32,
1650        listener_socket_id: SocketId,
1651    ) -> KernelResult<SocketId> {
1652        self.assert_not_terminated()?;
1653        self.assert_driver_owns(requester_driver, pid)?;
1654        let existing = self.sockets.get(listener_socket_id).ok_or_else(|| {
1655            KernelError::new("ENOENT", format!("no such socket {listener_socket_id}"))
1656        })?;
1657        if existing.owner_pid() != pid {
1658            return Err(KernelError::permission_denied(format!(
1659                "process {pid} does not own socket {listener_socket_id}"
1660            )));
1661        }
1662
1663        let snapshot = self.resource_snapshot();
1664        self.resources.check_socket_allocation(&snapshot)?;
1665        self.resources.check_socket_state_transition(
1666            &snapshot,
1667            SocketState::Created,
1668            SocketState::Connected,
1669        )?;
1670
1671        let socket_id = self.sockets.accept(listener_socket_id)?.id();
1672        self.poll_notifier.notify();
1673        Ok(socket_id)
1674    }
1675
1676    pub fn socket_connect_pair(
1677        &mut self,
1678        requester_driver: &str,
1679        pid: u32,
1680        socket_id: SocketId,
1681        peer_socket_id: SocketId,
1682    ) -> KernelResult<()> {
1683        self.assert_not_terminated()?;
1684        self.assert_driver_owns(requester_driver, pid)?;
1685        let existing = self
1686            .sockets
1687            .get(socket_id)
1688            .ok_or_else(|| KernelError::new("ENOENT", format!("no such socket {socket_id}")))?;
1689        if existing.owner_pid() != pid {
1690            return Err(KernelError::permission_denied(format!(
1691                "process {pid} does not own socket {socket_id}"
1692            )));
1693        }
1694
1695        let peer = self.sockets.get(peer_socket_id).ok_or_else(|| {
1696            KernelError::new("ENOENT", format!("no such socket {peer_socket_id}"))
1697        })?;
1698        self.assert_driver_owns(requester_driver, peer.owner_pid())?;
1699
1700        let mut snapshot = self.resource_snapshot();
1701        for current_state in [existing.state(), peer.state()] {
1702            self.resources.check_socket_state_transition(
1703                &snapshot,
1704                current_state,
1705                SocketState::Connected,
1706            )?;
1707            if !current_state.counts_as_connection() {
1708                snapshot.socket_connections = snapshot.socket_connections.saturating_add(1);
1709            }
1710        }
1711
1712        self.sockets.connect_pair(socket_id, peer_socket_id)?;
1713        self.poll_notifier.notify();
1714        Ok(())
1715    }
1716
1717    pub fn socket_connect_unix(
1718        &mut self,
1719        requester_driver: &str,
1720        pid: u32,
1721        socket_id: SocketId,
1722        target_path: impl Into<String>,
1723    ) -> KernelResult<()> {
1724        self.assert_not_terminated()?;
1725        self.assert_driver_owns(requester_driver, pid)?;
1726        let existing = self
1727            .sockets
1728            .get(socket_id)
1729            .ok_or_else(|| KernelError::new("ENOENT", format!("no such socket {socket_id}")))?;
1730        if existing.owner_pid() != pid {
1731            return Err(KernelError::permission_denied(format!(
1732                "process {pid} does not own socket {socket_id}"
1733            )));
1734        }
1735
1736        let target_path = normalize_path(&target_path.into());
1737        self.sockets
1738            .find_bound_unix_socket(&target_path)
1739            .ok_or_else(|| {
1740                KernelError::new(
1741                    "ECONNREFUSED",
1742                    format!("no listening socket bound at path {target_path}"),
1743                )
1744            })?;
1745
1746        let mut snapshot = self.resource_snapshot();
1747        self.resources.check_socket_allocation(&snapshot)?;
1748        for current_state in [existing.state(), SocketState::Created] {
1749            self.resources.check_socket_state_transition(
1750                &snapshot,
1751                current_state,
1752                SocketState::Connected,
1753            )?;
1754            if !current_state.counts_as_connection() {
1755                snapshot.socket_connections = snapshot.socket_connections.saturating_add(1);
1756            }
1757        }
1758
1759        self.sockets
1760            .connect_to_bound_unix_stream(socket_id, target_path)?;
1761        self.poll_notifier.notify();
1762        Ok(())
1763    }
1764
1765    pub fn socket_connect_inet_loopback(
1766        &mut self,
1767        requester_driver: &str,
1768        pid: u32,
1769        socket_id: SocketId,
1770        target_address: InetSocketAddress,
1771    ) -> KernelResult<()> {
1772        self.assert_not_terminated()?;
1773        self.assert_driver_owns(requester_driver, pid)?;
1774        let existing = self
1775            .sockets
1776            .get(socket_id)
1777            .ok_or_else(|| KernelError::new("ENOENT", format!("no such socket {socket_id}")))?;
1778        if existing.owner_pid() != pid {
1779            return Err(KernelError::permission_denied(format!(
1780                "process {pid} does not own socket {socket_id}"
1781            )));
1782        }
1783        check_network_access(
1784            &self.vm_id,
1785            &self.permissions,
1786            NetworkOperation::Http,
1787            &format_tcp_resource(target_address.host(), target_address.port()),
1788        )?;
1789        self.check_loopback_port_allowed(
1790            SocketSpec::tcp(),
1791            &target_address,
1792            "TCP loopback connect",
1793        )?;
1794
1795        self.sockets
1796            .find_bound_inet_socket(SocketSpec::tcp(), &target_address)
1797            .ok_or_else(|| {
1798                KernelError::new(
1799                    "ECONNREFUSED",
1800                    format!(
1801                        "no listening socket bound at {}:{}",
1802                        target_address.host(),
1803                        target_address.port()
1804                    ),
1805                )
1806            })?;
1807
1808        let mut snapshot = self.resource_snapshot();
1809        self.resources.check_socket_allocation(&snapshot)?;
1810        for current_state in [existing.state(), SocketState::Created] {
1811            self.resources.check_socket_state_transition(
1812                &snapshot,
1813                current_state,
1814                SocketState::Connected,
1815            )?;
1816            if !current_state.counts_as_connection() {
1817                snapshot.socket_connections = snapshot.socket_connections.saturating_add(1);
1818            }
1819        }
1820
1821        self.sockets
1822            .connect_to_bound_inet_stream(socket_id, target_address)?;
1823        self.poll_notifier.notify();
1824        Ok(())
1825    }
1826
1827    pub fn socket_send_to_inet_loopback(
1828        &mut self,
1829        requester_driver: &str,
1830        pid: u32,
1831        socket_id: SocketId,
1832        target_address: InetSocketAddress,
1833        data: &[u8],
1834    ) -> KernelResult<usize> {
1835        self.assert_not_terminated()?;
1836        self.assert_driver_owns(requester_driver, pid)?;
1837        let existing = self
1838            .sockets
1839            .get(socket_id)
1840            .ok_or_else(|| KernelError::new("ENOENT", format!("no such socket {socket_id}")))?;
1841        if existing.owner_pid() != pid {
1842            return Err(KernelError::permission_denied(format!(
1843                "process {pid} does not own socket {socket_id}"
1844            )));
1845        }
1846        if existing.spec() != SocketSpec::udp()
1847            || existing.state() != SocketState::Bound
1848            || existing.local_address().is_none()
1849        {
1850            self.sockets
1851                .check_send_to_bound_udp_socket(socket_id, target_address.clone())?;
1852        }
1853        check_network_access(
1854            &self.vm_id,
1855            &self.permissions,
1856            NetworkOperation::Http,
1857            &format_tcp_resource(target_address.host(), target_address.port()),
1858        )?;
1859        self.check_loopback_port_allowed(SocketSpec::udp(), &target_address, "UDP loopback send")?;
1860
1861        self.sockets
1862            .check_send_to_bound_udp_socket(socket_id, target_address.clone())?;
1863        self.resources
1864            .check_socket_datagram_enqueue(&self.resource_snapshot(), data.len())?;
1865        let written = self
1866            .sockets
1867            .send_to_bound_udp_socket(socket_id, target_address, data)?;
1868        if written > 0 {
1869            self.poll_notifier.notify();
1870        }
1871        Ok(written)
1872    }
1873
1874    fn check_loopback_port_allowed(
1875        &self,
1876        spec: SocketSpec,
1877        target_address: &InetSocketAddress,
1878        operation: &str,
1879    ) -> KernelResult<()> {
1880        if self
1881            .sockets
1882            .find_bound_inet_socket(spec, target_address)
1883            .is_some()
1884            || self.loopback_exempt_ports.contains(&target_address.port())
1885        {
1886            return Ok(());
1887        }
1888
1889        Err(KernelError::permission_denied(format!(
1890            "{operation} to {}:{} is not owned by this VM and is not loopback-exempt",
1891            target_address.host(),
1892            target_address.port()
1893        )))
1894    }
1895
1896    pub fn socket_recv_datagram(
1897        &mut self,
1898        requester_driver: &str,
1899        pid: u32,
1900        socket_id: SocketId,
1901        max_bytes: usize,
1902    ) -> KernelResult<Option<ReceivedDatagram>> {
1903        self.assert_not_terminated()?;
1904        self.assert_driver_owns(requester_driver, pid)?;
1905        let existing = self
1906            .sockets
1907            .get(socket_id)
1908            .ok_or_else(|| KernelError::new("ENOENT", format!("no such socket {socket_id}")))?;
1909        if existing.owner_pid() != pid {
1910            return Err(KernelError::permission_denied(format!(
1911                "process {pid} does not own socket {socket_id}"
1912            )));
1913        }
1914
1915        let result = self.sockets.recv_datagram(socket_id, max_bytes)?;
1916        if result.is_some() {
1917            self.poll_notifier.notify();
1918        }
1919        Ok(result)
1920    }
1921
1922    pub fn socket_set_datagram_option(
1923        &mut self,
1924        requester_driver: &str,
1925        pid: u32,
1926        socket_id: SocketId,
1927        option: DatagramSocketOption,
1928        enabled: bool,
1929    ) -> KernelResult<()> {
1930        self.assert_not_terminated()?;
1931        self.assert_driver_owns(requester_driver, pid)?;
1932        let existing = self
1933            .sockets
1934            .get(socket_id)
1935            .ok_or_else(|| KernelError::new("ENOENT", format!("no such socket {socket_id}")))?;
1936        if existing.owner_pid() != pid {
1937            return Err(KernelError::permission_denied(format!(
1938                "process {pid} does not own socket {socket_id}"
1939            )));
1940        }
1941
1942        self.sockets
1943            .set_datagram_socket_option(socket_id, option, enabled)?;
1944        self.poll_notifier.notify();
1945        Ok(())
1946    }
1947
1948    pub fn socket_add_membership(
1949        &mut self,
1950        requester_driver: &str,
1951        pid: u32,
1952        socket_id: SocketId,
1953        membership: SocketMulticastMembership,
1954    ) -> KernelResult<()> {
1955        self.assert_not_terminated()?;
1956        self.assert_driver_owns(requester_driver, pid)?;
1957        let existing = self
1958            .sockets
1959            .get(socket_id)
1960            .ok_or_else(|| KernelError::new("ENOENT", format!("no such socket {socket_id}")))?;
1961        if existing.owner_pid() != pid {
1962            return Err(KernelError::permission_denied(format!(
1963                "process {pid} does not own socket {socket_id}"
1964            )));
1965        }
1966
1967        self.sockets
1968            .add_multicast_membership(socket_id, membership)?;
1969        self.poll_notifier.notify();
1970        Ok(())
1971    }
1972
1973    pub fn socket_drop_membership(
1974        &mut self,
1975        requester_driver: &str,
1976        pid: u32,
1977        socket_id: SocketId,
1978        membership: SocketMulticastMembership,
1979    ) -> KernelResult<()> {
1980        self.assert_not_terminated()?;
1981        self.assert_driver_owns(requester_driver, pid)?;
1982        let existing = self
1983            .sockets
1984            .get(socket_id)
1985            .ok_or_else(|| KernelError::new("ENOENT", format!("no such socket {socket_id}")))?;
1986        if existing.owner_pid() != pid {
1987            return Err(KernelError::permission_denied(format!(
1988                "process {pid} does not own socket {socket_id}"
1989            )));
1990        }
1991
1992        self.sockets
1993            .drop_multicast_membership(socket_id, membership)?;
1994        self.poll_notifier.notify();
1995        Ok(())
1996    }
1997
1998    pub fn socket_set_state(
1999        &mut self,
2000        requester_driver: &str,
2001        pid: u32,
2002        socket_id: SocketId,
2003        state: SocketState,
2004    ) -> KernelResult<()> {
2005        self.assert_not_terminated()?;
2006        self.assert_driver_owns(requester_driver, pid)?;
2007        let existing = self
2008            .sockets
2009            .get(socket_id)
2010            .ok_or_else(|| KernelError::new("ENOENT", format!("no such socket {socket_id}")))?;
2011        if existing.owner_pid() != pid {
2012            return Err(KernelError::permission_denied(format!(
2013                "process {pid} does not own socket {socket_id}"
2014            )));
2015        }
2016
2017        self.resources.check_socket_state_transition(
2018            &self.resource_snapshot(),
2019            existing.state(),
2020            state,
2021        )?;
2022        self.sockets.update_state(socket_id, state)?;
2023        self.poll_notifier.notify();
2024        Ok(())
2025    }
2026
2027    pub fn socket_write(
2028        &mut self,
2029        requester_driver: &str,
2030        pid: u32,
2031        socket_id: SocketId,
2032        data: &[u8],
2033    ) -> KernelResult<usize> {
2034        self.assert_not_terminated()?;
2035        self.assert_driver_owns(requester_driver, pid)?;
2036        let existing = self
2037            .sockets
2038            .get(socket_id)
2039            .ok_or_else(|| KernelError::new("ENOENT", format!("no such socket {socket_id}")))?;
2040        if existing.owner_pid() != pid {
2041            return Err(KernelError::permission_denied(format!(
2042                "process {pid} does not own socket {socket_id}"
2043            )));
2044        }
2045
2046        self.sockets.check_write(socket_id)?;
2047        self.resources
2048            .check_socket_buffer_growth(&self.resource_snapshot(), data.len())?;
2049        let written = self.sockets.write(socket_id, data)?;
2050        if written > 0 {
2051            self.poll_notifier.notify();
2052        }
2053        Ok(written)
2054    }
2055
2056    pub fn socket_read(
2057        &mut self,
2058        requester_driver: &str,
2059        pid: u32,
2060        socket_id: SocketId,
2061        max_bytes: usize,
2062    ) -> KernelResult<Option<Vec<u8>>> {
2063        self.assert_not_terminated()?;
2064        self.assert_driver_owns(requester_driver, pid)?;
2065        let existing = self
2066            .sockets
2067            .get(socket_id)
2068            .ok_or_else(|| KernelError::new("ENOENT", format!("no such socket {socket_id}")))?;
2069        if existing.owner_pid() != pid {
2070            return Err(KernelError::permission_denied(format!(
2071                "process {pid} does not own socket {socket_id}"
2072            )));
2073        }
2074
2075        let result = self.sockets.read(socket_id, max_bytes)?;
2076        if result.is_some() {
2077            self.poll_notifier.notify();
2078        }
2079        Ok(result)
2080    }
2081
2082    pub fn socket_shutdown(
2083        &mut self,
2084        requester_driver: &str,
2085        pid: u32,
2086        socket_id: SocketId,
2087        how: SocketShutdown,
2088    ) -> KernelResult<()> {
2089        self.assert_not_terminated()?;
2090        self.assert_driver_owns(requester_driver, pid)?;
2091        let existing = self
2092            .sockets
2093            .get(socket_id)
2094            .ok_or_else(|| KernelError::new("ENOENT", format!("no such socket {socket_id}")))?;
2095        if existing.owner_pid() != pid {
2096            return Err(KernelError::permission_denied(format!(
2097                "process {pid} does not own socket {socket_id}"
2098            )));
2099        }
2100
2101        self.sockets.shutdown(socket_id, how)?;
2102        self.poll_notifier.notify();
2103        Ok(())
2104    }
2105
2106    pub fn socket_close(
2107        &mut self,
2108        requester_driver: &str,
2109        pid: u32,
2110        socket_id: SocketId,
2111    ) -> KernelResult<()> {
2112        self.assert_not_terminated()?;
2113        self.assert_driver_owns(requester_driver, pid)?;
2114        let existing = self
2115            .sockets
2116            .get(socket_id)
2117            .ok_or_else(|| KernelError::new("ENOENT", format!("no such socket {socket_id}")))?;
2118        if existing.owner_pid() != pid {
2119            return Err(KernelError::permission_denied(format!(
2120                "process {pid} does not own socket {socket_id}"
2121            )));
2122        }
2123
2124        self.sockets.remove(socket_id)?;
2125        self.poll_notifier.notify();
2126        Ok(())
2127    }
2128
2129    pub fn fd_open(
2130        &mut self,
2131        requester_driver: &str,
2132        pid: u32,
2133        path: &str,
2134        flags: u32,
2135        mode: Option<u32>,
2136    ) -> KernelResult<u32> {
2137        self.assert_not_terminated()?;
2138        self.assert_driver_owns(requester_driver, pid)?;
2139        if let Some(existing_fd) = parse_dev_fd_path(path)? {
2140            {
2141                let tables = lock_or_recover(&self.fd_tables);
2142                let table = tables
2143                    .get(pid)
2144                    .ok_or_else(|| KernelError::no_such_process(pid))?;
2145                table
2146                    .get(existing_fd)
2147                    .ok_or_else(|| KernelError::bad_file_descriptor(existing_fd))?;
2148            }
2149            self.resources
2150                .check_fd_allocation(&self.resource_snapshot(), 1)?;
2151            let mut tables = lock_or_recover(&self.fd_tables);
2152            let table = tables
2153                .get_mut(pid)
2154                .ok_or_else(|| KernelError::no_such_process(pid))?;
2155            let entry = table
2156                .get(existing_fd)
2157                .cloned()
2158                .ok_or_else(|| KernelError::bad_file_descriptor(existing_fd))?;
2159            return Ok(table.dup_with_status_flags(
2160                existing_fd,
2161                Some(entry.status_flags | (flags & O_NONBLOCK)),
2162            )?);
2163        }
2164
2165        if let Some(proc_node) = self.resolve_proc_node(path, Some(pid))? {
2166            if open_requires_write_access(flags) {
2167                self.filesystem
2168                    .check_virtual_path(FsOperation::Write, path)
2169                    .map_err(KernelError::from)?;
2170                return Err(read_only_filesystem_error(path));
2171            }
2172
2173            if matches!(
2174                proc_node,
2175                ProcNode::SelfLink { .. }
2176                    | ProcNode::PidCwdLink { .. }
2177                    | ProcNode::PidFdLink { .. }
2178            ) {
2179                let target = self.proc_symlink_target(&proc_node)?;
2180                return self.fd_open(requester_driver, pid, &target, flags, mode);
2181            }
2182
2183            self.filesystem
2184                .check_virtual_path(FsOperation::Read, path)
2185                .map_err(KernelError::from)?;
2186            self.resources
2187                .check_fd_allocation(&self.resource_snapshot(), 1)?;
2188            let mut tables = lock_or_recover(&self.fd_tables);
2189            let table = tables
2190                .get_mut(pid)
2191                .ok_or_else(|| KernelError::no_such_process(pid))?;
2192            return Ok(table.open_with_details(
2193                &self.proc_canonical_path(&proc_node),
2194                flags,
2195                proc_filetype(&proc_node),
2196                None,
2197            )?);
2198        }
2199
2200        if open_requires_write_access(flags) {
2201            self.reject_read_only_resolved_write_path(path)?;
2202        }
2203        let existed = if flags & O_CREAT != 0 {
2204            self.exists_internal(Some(pid), path)?
2205        } else {
2206            false
2207        };
2208        let (filetype, lock_target) = self.prepare_fd_open(path, flags, mode)?;
2209        if flags & O_CREAT != 0 && !existed {
2210            let umask = self.processes.get_umask(pid)?;
2211            self.apply_creation_mode(path, mode.unwrap_or(0o666), umask)?;
2212        }
2213        self.resources
2214            .check_fd_allocation(&self.resource_snapshot(), 1)?;
2215        let mut tables = lock_or_recover(&self.fd_tables);
2216        let table = tables
2217            .get_mut(pid)
2218            .ok_or_else(|| KernelError::no_such_process(pid))?;
2219        Ok(table.open_with_details(path, flags, filetype, lock_target)?)
2220    }
2221
2222    pub fn fd_read(
2223        &mut self,
2224        requester_driver: &str,
2225        pid: u32,
2226        fd: u32,
2227        length: usize,
2228    ) -> KernelResult<Vec<u8>> {
2229        Ok(self
2230            .fd_read_with_timeout_result(requester_driver, pid, fd, length, None)?
2231            .unwrap_or_default())
2232    }
2233
2234    pub fn fd_read_with_timeout_result(
2235        &mut self,
2236        requester_driver: &str,
2237        pid: u32,
2238        fd: u32,
2239        length: usize,
2240        timeout: Option<Duration>,
2241    ) -> KernelResult<Option<Vec<u8>>> {
2242        self.assert_driver_owns(requester_driver, pid)?;
2243        let entry = {
2244            let tables = lock_or_recover(&self.fd_tables);
2245            tables
2246                .get(pid)
2247                .and_then(|table| table.get(fd))
2248                .cloned()
2249                .ok_or_else(|| KernelError::bad_file_descriptor(fd))?
2250        };
2251
2252        if self.pipes.is_pipe(entry.description.id()) {
2253            return Ok(self.pipes.read_with_timeout(
2254                entry.description.id(),
2255                length,
2256                if entry.status_flags & O_NONBLOCK != 0 {
2257                    Some(Duration::ZERO)
2258                } else {
2259                    timeout.or_else(|| self.blocking_read_timeout())
2260                },
2261            )?);
2262        }
2263
2264        if self.ptys.is_pty(entry.description.id()) {
2265            return Ok(self.ptys.read_with_timeout(
2266                entry.description.id(),
2267                length,
2268                if entry.status_flags & O_NONBLOCK != 0 {
2269                    Some(Duration::ZERO)
2270                } else {
2271                    timeout.or_else(|| self.blocking_read_timeout())
2272                },
2273            )?);
2274        }
2275
2276        self.resources.check_pread_length(length)?;
2277
2278        if is_proc_path(entry.description.path()) {
2279            let bytes = self.proc_read_file_from_open_path(Some(pid), entry.description.path())?;
2280            let start = entry.description.cursor() as usize;
2281            let end = start.saturating_add(length).min(bytes.len());
2282            let chunk = if start >= bytes.len() {
2283                Vec::new()
2284            } else {
2285                bytes[start..end].to_vec()
2286            };
2287            entry.description.set_cursor(
2288                entry
2289                    .description
2290                    .cursor()
2291                    .saturating_add(chunk.len() as u64),
2292            );
2293            return Ok(Some(chunk));
2294        }
2295
2296        let cursor = entry.description.cursor();
2297        let bytes = VirtualFileSystem::pread(
2298            &mut self.filesystem,
2299            entry.description.path(),
2300            cursor,
2301            length,
2302        )?;
2303        entry
2304            .description
2305            .set_cursor(cursor.saturating_add(bytes.len() as u64));
2306        Ok(Some(bytes))
2307    }
2308
2309    pub fn fd_write(
2310        &mut self,
2311        requester_driver: &str,
2312        pid: u32,
2313        fd: u32,
2314        data: &[u8],
2315    ) -> KernelResult<usize> {
2316        self.assert_driver_owns(requester_driver, pid)?;
2317        self.resources.check_fd_write_size(data.len())?;
2318        let entry = {
2319            let tables = lock_or_recover(&self.fd_tables);
2320            tables
2321                .get(pid)
2322                .and_then(|table| table.get(fd))
2323                .cloned()
2324                .ok_or_else(|| KernelError::bad_file_descriptor(fd))?
2325        };
2326
2327        if self.pipes.is_pipe(entry.description.id()) {
2328            return match self.pipes.write_with_mode(
2329                entry.description.id(),
2330                data,
2331                entry.status_flags & O_NONBLOCK != 0,
2332            ) {
2333                Ok(bytes) => Ok(bytes),
2334                Err(error) => {
2335                    if error.code() == "EPIPE" {
2336                        self.processes.kill(pid as i32, SIGPIPE)?;
2337                    }
2338                    Err(error.into())
2339                }
2340            };
2341        }
2342
2343        if self.ptys.is_pty(entry.description.id()) {
2344            return Ok(self.ptys.write(entry.description.id(), data)?);
2345        }
2346
2347        self.reject_read_only_resolved_write_path(entry.description.path())?;
2348
2349        let path = entry.description.path().to_owned();
2350        if is_virtual_device_storage_path(&path) {
2351            VirtualFileSystem::write_file(&mut self.filesystem, &path, data.to_vec())?;
2352            let cursor = entry.description.cursor();
2353            entry
2354                .description
2355                .set_cursor(cursor.saturating_add(data.len() as u64));
2356            return Ok(data.len());
2357        }
2358        let current_size = self.current_storage_file_size(&path)?;
2359        let cursor = entry.description.cursor();
2360        if entry.description.flags() & O_APPEND != 0 {
2361            let required_size = current_size.max(checked_write_end(current_size, data.len())?);
2362            self.check_path_resize_limits(&path, required_size)?;
2363            let new_len = VirtualFileSystem::append_file(&mut self.filesystem, &path, data)?;
2364            self.update_filesystem_usage_cache_for_resize(current_size, new_len);
2365            entry.description.set_cursor(new_len);
2366            return Ok(data.len());
2367        }
2368
2369        let required_size = current_size.max(checked_write_end(cursor, data.len())?);
2370        self.check_path_resize_limits(&path, required_size)?;
2371        VirtualFileSystem::pwrite(&mut self.filesystem, &path, data, cursor)?;
2372        self.update_filesystem_usage_cache_for_resize(current_size, required_size);
2373        entry
2374            .description
2375            .set_cursor(cursor.saturating_add(data.len() as u64));
2376        Ok(data.len())
2377    }
2378
2379    pub fn poll_fds(
2380        &self,
2381        requester_driver: &str,
2382        pid: u32,
2383        fds: Vec<PollFd>,
2384        timeout_ms: i32,
2385    ) -> KernelResult<PollResult> {
2386        let targets = fds
2387            .into_iter()
2388            .map(|poll_fd| PollTargetEntry::fd(poll_fd.fd, poll_fd.events))
2389            .collect::<Vec<_>>();
2390        let result = self.poll_targets(requester_driver, pid, targets, timeout_ms)?;
2391        Ok(PollResult {
2392            ready_count: result.ready_count,
2393            fds: result
2394                .targets
2395                .into_iter()
2396                .map(|target| match target.target {
2397                    PollTarget::Fd(fd) => PollFd {
2398                        fd,
2399                        events: target.events,
2400                        revents: target.revents,
2401                    },
2402                    PollTarget::Socket(_) => unreachable!("fd poll should only include fd targets"),
2403                })
2404                .collect(),
2405        })
2406    }
2407
2408    /// A cloneable, Send handle for waiting on kernel poll-state changes off
2409    /// the kernel owner's thread. Pair with a zero-timeout `poll_fds` /
2410    /// `fd_read_with_timeout_result` re-check on the owning thread.
2411    pub fn poll_wait_handle(&self) -> crate::poll::PollWaitHandle {
2412        crate::poll::PollWaitHandle::new(self.poll_notifier.clone())
2413    }
2414
2415    pub fn poll_targets(
2416        &self,
2417        requester_driver: &str,
2418        pid: u32,
2419        mut targets: Vec<PollTargetEntry>,
2420        timeout_ms: i32,
2421    ) -> KernelResult<PollTargetResult> {
2422        self.assert_driver_owns(requester_driver, pid)?;
2423        if timeout_ms < -1 {
2424            return Err(KernelError::new(
2425                "EINVAL",
2426                format!("invalid poll timeout {timeout_ms}"),
2427            ));
2428        }
2429
2430        let timeout = if timeout_ms < 0 {
2431            None
2432        } else {
2433            Some(Duration::from_millis(timeout_ms as u64))
2434        };
2435        let deadline = timeout.map(|duration| Instant::now() + duration);
2436
2437        loop {
2438            let observed_generation = self.poll_notifier.snapshot();
2439            let ready_count = self.populate_poll_target_revents(pid, &mut targets)?;
2440            if ready_count > 0 || matches!(timeout, Some(duration) if duration.is_zero()) {
2441                return Ok(PollTargetResult {
2442                    ready_count,
2443                    targets,
2444                });
2445            }
2446
2447            let remaining = deadline.map(|target| target.saturating_duration_since(Instant::now()));
2448            if matches!(remaining, Some(duration) if duration.is_zero()) {
2449                return Ok(PollTargetResult {
2450                    ready_count,
2451                    targets,
2452                });
2453            }
2454
2455            if !self
2456                .poll_notifier
2457                .wait_for_change(observed_generation, remaining)
2458            {
2459                return Ok(PollTargetResult {
2460                    ready_count,
2461                    targets,
2462                });
2463            }
2464        }
2465    }
2466
2467    pub fn fd_seek(
2468        &mut self,
2469        requester_driver: &str,
2470        pid: u32,
2471        fd: u32,
2472        offset: i64,
2473        whence: u8,
2474    ) -> KernelResult<u64> {
2475        self.assert_driver_owns(requester_driver, pid)?;
2476        let entry = {
2477            let tables = lock_or_recover(&self.fd_tables);
2478            tables
2479                .get(pid)
2480                .and_then(|table| table.get(fd))
2481                .cloned()
2482                .ok_or_else(|| KernelError::bad_file_descriptor(fd))?
2483        };
2484
2485        if self.pipes.is_pipe(entry.description.id()) || self.ptys.is_pty(entry.description.id()) {
2486            return Err(KernelError::new("ESPIPE", "illegal seek"));
2487        }
2488
2489        let base = match whence {
2490            SEEK_SET => 0_i128,
2491            SEEK_CUR => i128::from(entry.description.cursor()),
2492            SEEK_END => {
2493                let size = if is_proc_path(entry.description.path()) {
2494                    self.proc_stat_from_open_path(Some(pid), entry.description.path())?
2495                        .size
2496                } else {
2497                    self.filesystem.stat(entry.description.path())?.size
2498                };
2499                i128::from(size)
2500            }
2501            _ => {
2502                return Err(KernelError::new(
2503                    "EINVAL",
2504                    format!("invalid whence {whence}"),
2505                ));
2506            }
2507        };
2508        let next = base + i128::from(offset);
2509        if next < 0 {
2510            return Err(KernelError::new("EINVAL", "negative seek position"));
2511        }
2512        let next = u64::try_from(next)
2513            .map_err(|_| KernelError::new("EINVAL", "seek position out of range"))?;
2514        entry.description.set_cursor(next);
2515        Ok(next)
2516    }
2517
2518    pub fn fd_pread(
2519        &mut self,
2520        requester_driver: &str,
2521        pid: u32,
2522        fd: u32,
2523        length: usize,
2524        offset: u64,
2525    ) -> KernelResult<Vec<u8>> {
2526        self.assert_driver_owns(requester_driver, pid)?;
2527        self.resources.check_pread_length(length)?;
2528        let entry = {
2529            let tables = lock_or_recover(&self.fd_tables);
2530            tables
2531                .get(pid)
2532                .and_then(|table| table.get(fd))
2533                .cloned()
2534                .ok_or_else(|| KernelError::bad_file_descriptor(fd))?
2535        };
2536
2537        if self.pipes.is_pipe(entry.description.id()) || self.ptys.is_pty(entry.description.id()) {
2538            return Err(KernelError::new("ESPIPE", "illegal seek"));
2539        }
2540
2541        if is_proc_path(entry.description.path()) {
2542            let bytes = self.proc_read_file_from_open_path(Some(pid), entry.description.path())?;
2543            let start = usize::try_from(offset)
2544                .map_err(|_| KernelError::new("EINVAL", "pread offset out of range"))?;
2545            let end = start.saturating_add(length).min(bytes.len());
2546            return Ok(if start >= bytes.len() {
2547                Vec::new()
2548            } else {
2549                bytes[start..end].to_vec()
2550            });
2551        }
2552
2553        Ok(VirtualFileSystem::pread(
2554            &mut self.filesystem,
2555            entry.description.path(),
2556            offset,
2557            length,
2558        )?)
2559    }
2560
2561    pub fn fd_pwrite(
2562        &mut self,
2563        requester_driver: &str,
2564        pid: u32,
2565        fd: u32,
2566        data: &[u8],
2567        offset: u64,
2568    ) -> KernelResult<usize> {
2569        self.assert_driver_owns(requester_driver, pid)?;
2570        self.resources.check_fd_write_size(data.len())?;
2571        let entry = {
2572            let tables = lock_or_recover(&self.fd_tables);
2573            tables
2574                .get(pid)
2575                .and_then(|table| table.get(fd))
2576                .cloned()
2577                .ok_or_else(|| KernelError::bad_file_descriptor(fd))?
2578        };
2579
2580        if self.pipes.is_pipe(entry.description.id()) || self.ptys.is_pty(entry.description.id()) {
2581            return Err(KernelError::new("ESPIPE", "illegal seek"));
2582        }
2583
2584        self.reject_read_only_resolved_write_path(entry.description.path())?;
2585
2586        let current_size = self.current_storage_file_size(entry.description.path())?;
2587        let required_size = current_size.max(checked_write_end(offset, data.len())?);
2588        self.check_path_resize_limits(entry.description.path(), required_size)?;
2589        VirtualFileSystem::pwrite(
2590            &mut self.filesystem,
2591            entry.description.path(),
2592            data.to_vec(),
2593            offset,
2594        )?;
2595        self.update_filesystem_usage_cache_for_resize(current_size, required_size);
2596        Ok(data.len())
2597    }
2598
2599    pub fn fd_dup(&mut self, requester_driver: &str, pid: u32, fd: u32) -> KernelResult<u32> {
2600        self.assert_driver_owns(requester_driver, pid)?;
2601        {
2602            let tables = lock_or_recover(&self.fd_tables);
2603            let table = tables
2604                .get(pid)
2605                .ok_or_else(|| KernelError::no_such_process(pid))?;
2606            table
2607                .get(fd)
2608                .ok_or_else(|| KernelError::bad_file_descriptor(fd))?;
2609        }
2610        self.resources
2611            .check_fd_allocation(&self.resource_snapshot(), 1)?;
2612        let mut tables = lock_or_recover(&self.fd_tables);
2613        let table = tables
2614            .get_mut(pid)
2615            .ok_or_else(|| KernelError::no_such_process(pid))?;
2616        Ok(table.dup(fd)?)
2617    }
2618
2619    pub fn fd_dup2(
2620        &mut self,
2621        requester_driver: &str,
2622        pid: u32,
2623        old_fd: u32,
2624        new_fd: u32,
2625    ) -> KernelResult<()> {
2626        self.assert_driver_owns(requester_driver, pid)?;
2627        let (replaced, needs_fd_growth) = {
2628            let tables = lock_or_recover(&self.fd_tables);
2629            let table = tables
2630                .get(pid)
2631                .ok_or_else(|| KernelError::no_such_process(pid))?;
2632            table
2633                .get(old_fd)
2634                .ok_or_else(|| KernelError::bad_file_descriptor(old_fd))?;
2635            let replaced = if old_fd == new_fd {
2636                None
2637            } else {
2638                table.get(new_fd).cloned()
2639            };
2640            if new_fd as usize >= table.max_fds() {
2641                return Err(KernelError::bad_file_descriptor(new_fd));
2642            }
2643            let needs_fd_growth = old_fd != new_fd && replaced.is_none();
2644            (replaced, needs_fd_growth)
2645        };
2646        if needs_fd_growth {
2647            self.resources
2648                .check_fd_allocation(&self.resource_snapshot(), 1)?;
2649        }
2650        {
2651            let mut tables = lock_or_recover(&self.fd_tables);
2652            let table = tables
2653                .get_mut(pid)
2654                .ok_or_else(|| KernelError::no_such_process(pid))?;
2655            table.dup2(old_fd, new_fd)?;
2656        }
2657
2658        if let Some(entry) = replaced {
2659            self.close_special_resource_if_needed(&entry.description, entry.filetype);
2660        }
2661        Ok(())
2662    }
2663
2664    pub fn fd_close(&mut self, requester_driver: &str, pid: u32, fd: u32) -> KernelResult<()> {
2665        self.assert_driver_owns(requester_driver, pid)?;
2666        let (description, filetype) = {
2667            let mut tables = lock_or_recover(&self.fd_tables);
2668            let table = tables
2669                .get_mut(pid)
2670                .ok_or_else(|| KernelError::no_such_process(pid))?;
2671            let entry = table
2672                .get(fd)
2673                .cloned()
2674                .ok_or_else(|| KernelError::bad_file_descriptor(fd))?;
2675            table.close(fd);
2676            (entry.description, entry.filetype)
2677        };
2678        self.close_special_resource_if_needed(&description, filetype);
2679        Ok(())
2680    }
2681
2682    pub fn fd_fcntl(
2683        &mut self,
2684        requester_driver: &str,
2685        pid: u32,
2686        fd: u32,
2687        command: u32,
2688        arg: u32,
2689    ) -> KernelResult<u32> {
2690        self.assert_driver_owns(requester_driver, pid)?;
2691        if command == F_DUPFD {
2692            {
2693                let tables = lock_or_recover(&self.fd_tables);
2694                let table = tables
2695                    .get(pid)
2696                    .ok_or_else(|| KernelError::no_such_process(pid))?;
2697                table
2698                    .get(fd)
2699                    .ok_or_else(|| KernelError::bad_file_descriptor(fd))?;
2700                if arg as usize >= table.max_fds() {
2701                    return Err(KernelError::new(
2702                        "EINVAL",
2703                        format!("fd {arg} exceeds process fd limit"),
2704                    ));
2705                }
2706            }
2707            self.resources
2708                .check_fd_allocation(&self.resource_snapshot(), 1)?;
2709        }
2710        let mut tables = lock_or_recover(&self.fd_tables);
2711        let table = tables
2712            .get_mut(pid)
2713            .ok_or_else(|| KernelError::no_such_process(pid))?;
2714        let result = table.fcntl(fd, command, arg)?;
2715        if command == F_DUPFD {
2716            self.poll_notifier.notify();
2717        }
2718        Ok(result)
2719    }
2720
2721    pub fn fd_flock(
2722        &self,
2723        requester_driver: &str,
2724        pid: u32,
2725        fd: u32,
2726        operation: u32,
2727    ) -> KernelResult<()> {
2728        self.assert_driver_owns(requester_driver, pid)?;
2729        let entry = {
2730            let tables = lock_or_recover(&self.fd_tables);
2731            tables
2732                .get(pid)
2733                .and_then(|table| table.get(fd))
2734                .cloned()
2735                .ok_or_else(|| KernelError::bad_file_descriptor(fd))?
2736        };
2737
2738        if entry.filetype != FILETYPE_REGULAR_FILE {
2739            return Err(KernelError::new(
2740                "EBADF",
2741                format!("file descriptor {fd} does not support advisory locking"),
2742            ));
2743        }
2744
2745        let target = entry.description.lock_target().ok_or_else(|| {
2746            KernelError::new(
2747                "EBADF",
2748                format!("file descriptor {fd} is missing advisory lock metadata"),
2749            )
2750        })?;
2751        let operation = FlockOperation::from_bits(operation)?;
2752        self.file_locks
2753            .apply(entry.description.id(), target, operation)?;
2754        Ok(())
2755    }
2756
2757    pub fn fd_stat(&self, requester_driver: &str, pid: u32, fd: u32) -> KernelResult<FdStat> {
2758        self.assert_driver_owns(requester_driver, pid)?;
2759        let tables = lock_or_recover(&self.fd_tables);
2760        Ok(tables
2761            .get(pid)
2762            .ok_or_else(|| KernelError::no_such_process(pid))?
2763            .stat(fd)?)
2764    }
2765
2766    pub fn fd_path(&self, requester_driver: &str, pid: u32, fd: u32) -> KernelResult<String> {
2767        let description = self.description_for_fd(requester_driver, pid, fd)?;
2768        Ok(description.path().to_owned())
2769    }
2770
2771    pub fn isatty(&self, requester_driver: &str, pid: u32, fd: u32) -> KernelResult<bool> {
2772        self.assert_driver_owns(requester_driver, pid)?;
2773        let entry = {
2774            let tables = lock_or_recover(&self.fd_tables);
2775            tables
2776                .get(pid)
2777                .and_then(|table| table.get(fd))
2778                .cloned()
2779                .ok_or_else(|| KernelError::bad_file_descriptor(fd))?
2780        };
2781        Ok(self.ptys.is_slave(entry.description.id()))
2782    }
2783
2784    pub fn pty_window_size(
2785        &self,
2786        requester_driver: &str,
2787        pid: u32,
2788        fd: u32,
2789    ) -> KernelResult<PtyWindowSize> {
2790        let description = self.description_for_fd(requester_driver, pid, fd)?;
2791        Ok(self.ptys.window_size(description.id())?)
2792    }
2793
2794    pub fn pty_set_discipline(
2795        &self,
2796        requester_driver: &str,
2797        pid: u32,
2798        fd: u32,
2799        config: LineDisciplineConfig,
2800    ) -> KernelResult<()> {
2801        let description = self.description_for_fd(requester_driver, pid, fd)?;
2802        self.ptys.set_discipline(description.id(), config)?;
2803        Ok(())
2804    }
2805
2806    /// Toggle PTY raw mode and, when the caller belongs to the terminal's
2807    /// foreground process group, create a generation-scoped recovery lease.
2808    /// The lease can be released during process cleanup without overwriting a
2809    /// newer terminal mutation from another process.
2810    pub fn pty_set_raw_mode(
2811        &self,
2812        requester_driver: &str,
2813        pid: u32,
2814        fd: u32,
2815        enabled: bool,
2816    ) -> KernelResult<Option<u64>> {
2817        let description = self.description_for_fd(requester_driver, pid, fd)?;
2818        let foreground_pgid = self.ptys.get_foreground_pgid(description.id())?;
2819        let process_pgid = self.processes.getpgid(pid)?;
2820        let lease_owner =
2821            (!enabled || foreground_pgid == 0 || foreground_pgid == process_pgid).then_some(pid);
2822        Ok(self
2823            .ptys
2824            .set_raw_mode(description.id(), lease_owner, enabled)?)
2825    }
2826
2827    /// Release a raw-mode recovery lease through any descriptor for the same
2828    /// PTY. `fd` normally belongs to the terminal owner because the exiting
2829    /// child may already have closed its own descriptor zero.
2830    pub fn pty_release_raw_mode(
2831        &self,
2832        requester_driver: &str,
2833        descriptor_owner_pid: u32,
2834        fd: u32,
2835        raw_mode_owner_pid: u32,
2836        generation: u64,
2837    ) -> KernelResult<bool> {
2838        self.assert_driver_owns(requester_driver, raw_mode_owner_pid)?;
2839        let description = self.description_for_fd(requester_driver, descriptor_owner_pid, fd)?;
2840        Ok(self
2841            .ptys
2842            .release_raw_mode(description.id(), raw_mode_owner_pid, generation)?)
2843    }
2844
2845    pub fn pty_set_foreground_pgid(
2846        &self,
2847        requester_driver: &str,
2848        pid: u32,
2849        fd: u32,
2850        pgid: u32,
2851    ) -> KernelResult<()> {
2852        let description = self.description_for_fd(requester_driver, pid, fd)?;
2853        let requester_sid = self.processes.getsid(pid)?;
2854        let group = self
2855            .processes
2856            .list_processes()
2857            .into_values()
2858            .find(|process| process.pgid == pgid && process.status != ProcessStatus::Exited)
2859            .ok_or_else(|| KernelError::new("ESRCH", format!("no such process group {pgid}")))?;
2860        if group.sid != requester_sid {
2861            return Err(KernelError::permission_denied(
2862                "cannot set foreground process group in different session",
2863            ));
2864        }
2865        self.ptys.set_foreground_pgid(description.id(), pgid)?;
2866        Ok(())
2867    }
2868
2869    pub fn tcgetattr(&self, requester_driver: &str, pid: u32, fd: u32) -> KernelResult<Termios> {
2870        let description = self.description_for_fd(requester_driver, pid, fd)?;
2871        Ok(self.ptys.get_termios(description.id())?)
2872    }
2873
2874    pub fn tcsetattr(
2875        &self,
2876        requester_driver: &str,
2877        pid: u32,
2878        fd: u32,
2879        termios: PartialTermios,
2880    ) -> KernelResult<()> {
2881        let description = self.description_for_fd(requester_driver, pid, fd)?;
2882        self.ptys.set_termios(description.id(), termios)?;
2883        Ok(())
2884    }
2885
2886    pub fn tcgetpgrp(&self, requester_driver: &str, pid: u32, fd: u32) -> KernelResult<u32> {
2887        let description = self.description_for_fd(requester_driver, pid, fd)?;
2888        Ok(self.ptys.get_foreground_pgid(description.id())?)
2889    }
2890
2891    pub fn pty_resize(
2892        &self,
2893        requester_driver: &str,
2894        pid: u32,
2895        fd: u32,
2896        cols: u16,
2897        rows: u16,
2898    ) -> KernelResult<()> {
2899        let description = self.description_for_fd(requester_driver, pid, fd)?;
2900        let target_pgid = self.ptys.resize(description.id(), cols, rows)?;
2901        if let Some(pgid) = target_pgid {
2902            match self.processes.kill(-(pgid as i32), SIGWINCH) {
2903                Ok(()) => {}
2904                Err(error) if error.code() == "ESRCH" => {}
2905                Err(error) => return Err(error.into()),
2906            }
2907        }
2908        Ok(())
2909    }
2910
2911    pub fn signal_process(
2912        &self,
2913        requester_driver: &str,
2914        pid: i32,
2915        signal: i32,
2916    ) -> KernelResult<()> {
2917        if pid < 0 {
2918            let pgid = pid.unsigned_abs();
2919            let members = self
2920                .processes
2921                .list_processes()
2922                .into_values()
2923                .filter(|process| process.pgid == pgid && process.status != ProcessStatus::Exited)
2924                .collect::<Vec<_>>();
2925            if members.is_empty() {
2926                self.processes.kill(pid, signal)?;
2927                return Ok(());
2928            }
2929            if let Some(process) = members
2930                .iter()
2931                .find(|process| process.driver != requester_driver)
2932            {
2933                return Err(KernelError::permission_denied(format!(
2934                    "driver \"{requester_driver}\" does not own process group {pgid} containing PID {}",
2935                    process.pid
2936                )));
2937            }
2938            self.processes.kill(pid, signal)?;
2939            return Ok(());
2940        }
2941
2942        let pid = u32::try_from(pid)
2943            .map_err(|_| KernelError::new("EINVAL", format!("invalid pid {pid}")))?;
2944        self.assert_driver_owns(requester_driver, pid)?;
2945        self.processes.kill(pid as i32, signal)?;
2946        Ok(())
2947    }
2948
2949    pub fn kill_process(&self, requester_driver: &str, pid: u32, signal: i32) -> KernelResult<()> {
2950        let pid = i32::try_from(pid)
2951            .map_err(|_| KernelError::new("EINVAL", format!("pid {pid} exceeds i32::MAX")))?;
2952        self.signal_process(requester_driver, pid, signal)
2953    }
2954
2955    pub fn setpgid(&self, requester_driver: &str, pid: u32, pgid: u32) -> KernelResult<()> {
2956        self.assert_driver_owns(requester_driver, pid)?;
2957        let target_pgid = if pgid == 0 { pid } else { pgid };
2958        if target_pgid != pid {
2959            if let Some(group_owner) =
2960                self.processes
2961                    .list_processes()
2962                    .into_values()
2963                    .find(|process| {
2964                        process.pgid == target_pgid && process.status == ProcessStatus::Running
2965                    })
2966            {
2967                if group_owner.driver != requester_driver {
2968                    return Err(KernelError::permission_denied(format!(
2969                        "driver \"{requester_driver}\" cannot join process group {target_pgid} owned by \"{}\"",
2970                        group_owner.driver
2971                    )));
2972                }
2973            }
2974        }
2975        self.processes.setpgid(pid, pgid)?;
2976        Ok(())
2977    }
2978
2979    pub fn getpgid(&self, requester_driver: &str, pid: u32) -> KernelResult<u32> {
2980        self.assert_driver_owns(requester_driver, pid)?;
2981        Ok(self.processes.getpgid(pid)?)
2982    }
2983
2984    pub fn getpid(&self, requester_driver: &str, pid: u32) -> KernelResult<u32> {
2985        self.assert_driver_owns(requester_driver, pid)?;
2986        Ok(pid)
2987    }
2988
2989    pub fn sigprocmask(
2990        &self,
2991        requester_driver: &str,
2992        pid: u32,
2993        how: SigmaskHow,
2994        set: SignalSet,
2995    ) -> KernelResult<SignalSet> {
2996        self.assert_driver_owns(requester_driver, pid)?;
2997        Ok(self.processes.sigprocmask(pid, how, set)?)
2998    }
2999
3000    pub fn sigpending(&self, requester_driver: &str, pid: u32) -> KernelResult<SignalSet> {
3001        self.assert_driver_owns(requester_driver, pid)?;
3002        Ok(self.processes.sigpending(pid)?)
3003    }
3004
3005    pub fn getppid(&self, requester_driver: &str, pid: u32) -> KernelResult<u32> {
3006        self.assert_driver_owns(requester_driver, pid)?;
3007        Ok(self.processes.getppid(pid)?)
3008    }
3009
3010    pub fn setsid(&self, requester_driver: &str, pid: u32) -> KernelResult<u32> {
3011        self.assert_driver_owns(requester_driver, pid)?;
3012        Ok(self.processes.setsid(pid)?)
3013    }
3014
3015    pub fn getsid(&self, requester_driver: &str, pid: u32) -> KernelResult<u32> {
3016        self.assert_driver_owns(requester_driver, pid)?;
3017        Ok(self.processes.getsid(pid)?)
3018    }
3019
3020    pub fn dev_fd_read_dir(&self, requester_driver: &str, pid: u32) -> KernelResult<Vec<String>> {
3021        self.assert_driver_owns(requester_driver, pid)?;
3022        let tables = lock_or_recover(&self.fd_tables);
3023        let table = tables
3024            .get(pid)
3025            .ok_or_else(|| KernelError::no_such_process(pid))?;
3026        let entry_count = table.len();
3027        self.resources.check_readdir_entries(entry_count)?;
3028        Ok(table.iter().map(|entry| entry.fd.to_string()).collect())
3029    }
3030
3031    pub fn dev_fd_stat(
3032        &mut self,
3033        requester_driver: &str,
3034        pid: u32,
3035        fd: u32,
3036    ) -> KernelResult<VirtualStat> {
3037        self.assert_driver_owns(requester_driver, pid)?;
3038        let entry = {
3039            let tables = lock_or_recover(&self.fd_tables);
3040            tables
3041                .get(pid)
3042                .and_then(|table| table.get(fd))
3043                .cloned()
3044                .ok_or_else(|| KernelError::bad_file_descriptor(fd))?
3045        };
3046
3047        if self.pipes.is_pipe(entry.description.id()) || self.ptys.is_pty(entry.description.id()) {
3048            return Ok(synthetic_character_device_stat(entry.description.id()));
3049        }
3050
3051        if is_proc_path(entry.description.path()) {
3052            return self.proc_stat_from_open_path(Some(pid), entry.description.path());
3053        }
3054
3055        Ok(self.filesystem.stat(entry.description.path())?)
3056    }
3057
3058    pub fn dispose(&mut self) -> KernelResult<()> {
3059        if self.terminated {
3060            return Ok(());
3061        }
3062
3063        dispose_kernel_vm_resources(self);
3064        Ok(())
3065    }
3066
3067    fn prepare_fd_open(
3068        &mut self,
3069        path: &str,
3070        flags: u32,
3071        mode: Option<u32>,
3072    ) -> KernelResult<(u8, Option<FileLockTarget>)> {
3073        if open_requires_write_access(flags) {
3074            self.reject_read_only_resolved_write_path(path)?;
3075        }
3076
3077        if flags & O_CREAT != 0 && flags & O_EXCL != 0 {
3078            self.check_write_file_limits(path, 0)?;
3079            VirtualFileSystem::create_file_exclusive_with_mode(
3080                &mut self.filesystem,
3081                path,
3082                Vec::new(),
3083                mode,
3084            )?;
3085            self.update_filesystem_usage_cache_for_inode_create(0);
3086            let stat = VirtualFileSystem::stat(&mut self.filesystem, path)?;
3087            return Ok((
3088                filetype_for_path(path, &stat),
3089                Some(FileLockTarget::new(stat.ino)),
3090            ));
3091        }
3092
3093        let exists = self.filesystem.exists(path)?;
3094        if exists {
3095            if flags & O_TRUNC != 0 {
3096                let existing_size = self.current_storage_file_size(path)?;
3097                self.check_path_resize_limits_with_existing(existing_size, 0)?;
3098                VirtualFileSystem::truncate(&mut self.filesystem, path, 0)?;
3099                self.update_filesystem_usage_cache_for_resize(existing_size, 0);
3100            }
3101        } else if flags & O_CREAT != 0 {
3102            self.check_write_file_limits(path, 0)?;
3103            VirtualFileSystem::write_file_with_mode(&mut self.filesystem, path, Vec::new(), mode)?;
3104            self.update_filesystem_usage_cache_for_inode_create(0);
3105        } else {
3106            let _ = VirtualFileSystem::stat(&mut self.filesystem, path)?;
3107            unreachable!("stat should return an error when opening a missing path");
3108        }
3109
3110        let stat = VirtualFileSystem::stat(&mut self.filesystem, path)?;
3111        Ok((
3112            filetype_for_path(path, &stat),
3113            Some(FileLockTarget::new(stat.ino)),
3114        ))
3115    }
3116
3117    fn reject_read_only_write_path(&mut self, path: &str) -> KernelResult<()> {
3118        if is_proc_path(path) {
3119            self.filesystem
3120                .check_virtual_path(FsOperation::Write, path)
3121                .map_err(KernelError::from)?;
3122            return Err(read_only_filesystem_error(path));
3123        }
3124
3125        if is_agentos_path(path) {
3126            return Err(read_only_filesystem_error(path));
3127        }
3128
3129        Ok(())
3130    }
3131
3132    fn reject_read_only_resolved_write_path(&mut self, path: &str) -> KernelResult<()> {
3133        self.reject_read_only_write_path(path)?;
3134
3135        if let Some(resolved) = self.resolve_write_guard_path(path, true)? {
3136            if is_agentos_path(&resolved) {
3137                return Err(read_only_filesystem_error(&resolved));
3138            }
3139            if self.has_agentos_hardlink_alias(&resolved)? {
3140                return Err(read_only_filesystem_error(&resolved));
3141            }
3142        }
3143        if self.has_agentos_hardlink_alias(path)? {
3144            return Err(read_only_filesystem_error(path));
3145        }
3146
3147        Ok(())
3148    }
3149
3150    fn reject_read_only_entry_write_path(&mut self, path: &str) -> KernelResult<()> {
3151        self.reject_read_only_write_path(path)?;
3152
3153        if let Some(resolved) = self.resolve_write_guard_path(path, false)? {
3154            if is_agentos_path(&resolved) {
3155                return Err(read_only_filesystem_error(&resolved));
3156            }
3157            if self.has_agentos_hardlink_alias(&resolved)? {
3158                return Err(read_only_filesystem_error(&resolved));
3159            }
3160        }
3161        if self.has_agentos_hardlink_alias(path)? {
3162            return Err(read_only_filesystem_error(path));
3163        }
3164
3165        Ok(())
3166    }
3167
3168    fn has_agentos_hardlink_alias(&mut self, path: &str) -> KernelResult<bool> {
3169        let Some(target) = self.storage_lstat(path)? else {
3170            return Ok(false);
3171        };
3172        if target.is_directory || target.is_symbolic_link {
3173            return Ok(false);
3174        }
3175
3176        self.agentos_subtree_contains_inode("/etc/agentos", target.dev, target.ino)
3177    }
3178
3179    fn agentos_subtree_contains_inode(
3180        &mut self,
3181        path: &str,
3182        target_dev: u64,
3183        target_ino: u64,
3184    ) -> KernelResult<bool> {
3185        let Some(stat) = self.storage_lstat(path)? else {
3186            return Ok(false);
3187        };
3188        if !stat.is_directory && !stat.is_symbolic_link {
3189            return Ok(stat.dev == target_dev && stat.ino == target_ino);
3190        }
3191        if !stat.is_directory {
3192            return Ok(false);
3193        }
3194
3195        let children = self.raw_filesystem_mut().read_dir_with_types(path)?;
3196        for child in children {
3197            if child.name == "." || child.name == ".." {
3198                continue;
3199            }
3200            let child_path = join_absolute_path(path, &child.name);
3201            if self.agentos_subtree_contains_inode(&child_path, target_dev, target_ino)? {
3202                return Ok(true);
3203            }
3204        }
3205
3206        Ok(false)
3207    }
3208
3209    fn resolve_write_guard_path(
3210        &mut self,
3211        path: &str,
3212        follow_final_symlink: bool,
3213    ) -> KernelResult<Option<String>> {
3214        let normalized = normalize_path(path);
3215        if normalized == "/" {
3216            return Ok(Some(normalized));
3217        }
3218
3219        if follow_final_symlink {
3220            if let Ok(resolved) = self.filesystem.realpath(&normalized) {
3221                return Ok(Some(resolved));
3222            }
3223        }
3224
3225        let components: Vec<&str> = normalized
3226            .split('/')
3227            .filter(|component| !component.is_empty())
3228            .collect();
3229        let mut resolved_prefix = String::from("/");
3230        let mut raw_prefix = String::from("/");
3231
3232        for (index, component) in components.iter().enumerate() {
3233            let is_final = index + 1 == components.len();
3234            if is_final && !follow_final_symlink {
3235                return Ok(Some(join_absolute_path(&resolved_prefix, component)));
3236            }
3237
3238            raw_prefix = join_absolute_path(&raw_prefix, component);
3239            match self.filesystem.realpath(&raw_prefix) {
3240                Ok(resolved) => {
3241                    resolved_prefix = resolved;
3242                }
3243                Err(error) if error.code() == "ENOENT" => {
3244                    let mut resolved = resolved_prefix;
3245                    for remaining in &components[index..] {
3246                        resolved = join_absolute_path(&resolved, remaining);
3247                    }
3248                    return Ok(Some(resolved));
3249                }
3250                Err(error) => return Err(error.into()),
3251            }
3252        }
3253
3254        Ok(Some(resolved_prefix))
3255    }
3256
3257    fn populate_poll_target_revents(
3258        &self,
3259        pid: u32,
3260        targets: &mut [PollTargetEntry],
3261    ) -> KernelResult<usize> {
3262        let mut ready_count = 0;
3263        for target in targets.iter_mut() {
3264            target.revents = self.poll_target_entry(pid, target.target, target.events)?;
3265            if !target.revents.is_empty() {
3266                ready_count += 1;
3267            }
3268        }
3269
3270        Ok(ready_count)
3271    }
3272
3273    fn poll_target_entry(
3274        &self,
3275        pid: u32,
3276        target: PollTarget,
3277        requested: PollEvents,
3278    ) -> KernelResult<PollEvents> {
3279        match target {
3280            PollTarget::Fd(fd) => {
3281                let entry = {
3282                    let tables = lock_or_recover(&self.fd_tables);
3283                    tables
3284                        .get(pid)
3285                        .ok_or_else(|| KernelError::no_such_process(pid))?
3286                        .get(fd)
3287                        .cloned()
3288                };
3289                if let Some(entry) = entry {
3290                    self.poll_entry(&entry, requested)
3291                } else {
3292                    Ok(POLLNVAL)
3293                }
3294            }
3295            PollTarget::Socket(socket_id) => {
3296                let socket = self.sockets.get(socket_id);
3297                if let Some(socket) = socket {
3298                    if socket.owner_pid() != pid {
3299                        return Err(KernelError::permission_denied(format!(
3300                            "process {pid} does not own socket {socket_id}"
3301                        )));
3302                    }
3303                    let mut events = self.sockets.poll(socket_id, requested)?;
3304                    if events.intersects(POLLOUT)
3305                        && !self.socket_pollout_has_resource_capacity(&socket)
3306                    {
3307                        events = PollEvents::from_bits(events.bits() & !POLLOUT.bits());
3308                    }
3309                    Ok(events)
3310                } else {
3311                    Ok(POLLNVAL)
3312                }
3313            }
3314        }
3315    }
3316
3317    fn socket_pollout_has_resource_capacity(&self, socket: &SocketRecord) -> bool {
3318        let snapshot = self.resource_snapshot();
3319        if self
3320            .resources
3321            .limits()
3322            .max_socket_buffered_bytes
3323            .is_some_and(|limit| snapshot.socket_buffered_bytes >= limit)
3324        {
3325            return false;
3326        }
3327
3328        if socket.spec().socket_type == SocketType::Datagram
3329            && self
3330                .resources
3331                .limits()
3332                .max_socket_datagram_queue_len
3333                .is_some_and(|limit| snapshot.socket_datagram_queue_len >= limit)
3334        {
3335            return false;
3336        }
3337
3338        true
3339    }
3340
3341    fn poll_entry(
3342        &self,
3343        entry: &crate::fd_table::FdEntry,
3344        requested: PollEvents,
3345    ) -> KernelResult<PollEvents> {
3346        if self.pipes.is_pipe(entry.description.id()) {
3347            return Ok(self.pipes.poll(entry.description.id(), requested)?);
3348        }
3349
3350        if self.ptys.is_pty(entry.description.id()) {
3351            return Ok(self.ptys.poll(entry.description.id(), requested)?);
3352        }
3353
3354        let access_mode = entry.description.flags() & 0b11;
3355        let mut events = PollEvents::empty();
3356        if requested.intersects(POLLIN) && access_mode != crate::fd_table::O_WRONLY {
3357            events |= POLLIN;
3358        }
3359        if requested.intersects(POLLOUT) && access_mode != crate::fd_table::O_RDONLY {
3360            events |= POLLOUT;
3361        }
3362        if entry.filetype == FILETYPE_DIRECTORY && requested.intersects(POLLOUT) {
3363            events |= POLLERR;
3364        }
3365        if self.terminated {
3366            events |= POLLHUP;
3367        }
3368        Ok(events)
3369    }
3370
3371    fn description_for_fd(
3372        &self,
3373        requester_driver: &str,
3374        pid: u32,
3375        fd: u32,
3376    ) -> KernelResult<Arc<FileDescription>> {
3377        self.assert_driver_owns(requester_driver, pid)?;
3378        lock_or_recover(&self.fd_tables)
3379            .get(pid)
3380            .and_then(|table| table.get(fd))
3381            .map(|entry| Arc::clone(&entry.description))
3382            .ok_or_else(|| KernelError::bad_file_descriptor(fd))
3383    }
3384
3385    fn assert_not_terminated(&self) -> KernelResult<()> {
3386        if self.terminated {
3387            Err(KernelError::disposed())
3388        } else {
3389            Ok(())
3390        }
3391    }
3392
3393    fn assert_driver_owns(&self, requester_driver: &str, pid: u32) -> KernelResult<()> {
3394        let driver_pids = lock_or_recover(&self.driver_pids);
3395        if driver_pids
3396            .get(requester_driver)
3397            .map(|pids| pids.contains(&pid))
3398            .unwrap_or(false)
3399        {
3400            return Ok(());
3401        }
3402
3403        if driver_pids.values().any(|pids| pids.contains(&pid)) {
3404            return Err(KernelError::permission_denied(format!(
3405                "driver \"{requester_driver}\" does not own PID {pid}"
3406            )));
3407        }
3408
3409        Err(KernelError::no_such_process(pid))
3410    }
3411
3412    fn cleanup_process_resources(&self, pid: u32) {
3413        cleanup_process_resources(
3414            self.fd_tables.as_ref(),
3415            &self.file_locks,
3416            &self.pipes,
3417            &self.ptys,
3418            &self.sockets,
3419            self.driver_pids.as_ref(),
3420            pid,
3421        );
3422    }
3423
3424    fn resolve_spawn_command(
3425        &mut self,
3426        command: &str,
3427        args: &[String],
3428        cwd: &str,
3429    ) -> KernelResult<ResolvedSpawnCommand> {
3430        if let Some(driver) = self.commands.resolve(command).cloned() {
3431            return Ok(ResolvedSpawnCommand {
3432                command: command.to_owned(),
3433                args: args.to_vec(),
3434                driver,
3435            });
3436        }
3437
3438        let Some(path) = self.resolve_executable_path(command, cwd)? else {
3439            return Err(KernelError::command_not_found(command));
3440        };
3441
3442        if let Some(registered_command) = self.resolve_registered_command_path(&path) {
3443            let driver = self
3444                .commands
3445                .resolve(&registered_command)
3446                .cloned()
3447                .ok_or_else(|| KernelError::command_not_found(&registered_command))?;
3448            return Ok(ResolvedSpawnCommand {
3449                command: registered_command,
3450                args: args.to_vec(),
3451                driver,
3452            });
3453        }
3454
3455        let shebang = self
3456            .parse_shebang_command(&path)?
3457            .ok_or_else(|| KernelError::new("ENOEXEC", format!("exec format error: {path}")))?;
3458        self.resolve_shebang_command(&path, args, shebang)
3459    }
3460
3461    fn resolve_executable_path(
3462        &mut self,
3463        command: &str,
3464        cwd: &str,
3465    ) -> KernelResult<Option<String>> {
3466        if !command.contains('/') {
3467            return Ok(None);
3468        }
3469
3470        let path = if command.starts_with('/') {
3471            normalize_path(command)
3472        } else {
3473            normalize_path(&format!("{cwd}/{command}"))
3474        };
3475        // exec(2) follows symlinks, and a symlink target may live in a different
3476        // mount (e.g. `/opt/agentos/bin/<cmd>` is its own single-symlink mount
3477        // pointing into a package tar mount). Resolve the real path before
3478        // stat-ing / reading the executable so cross-mount symlinked commands
3479        // exec their real target instead of failing to read the symlink node.
3480        let path = self.filesystem.realpath(&path).unwrap_or(path);
3481        let stat = self.filesystem.stat(&path)?;
3482        if stat.is_directory {
3483            return Err(KernelError::new(
3484                "EACCES",
3485                format!("permission denied, execute '{path}'"),
3486            ));
3487        }
3488        if stat.mode & EXECUTABLE_PERMISSION_BITS == 0 {
3489            return Err(KernelError::new(
3490                "EACCES",
3491                format!("permission denied, execute '{path}'"),
3492            ));
3493        }
3494        Ok(Some(path))
3495    }
3496
3497    fn resolve_registered_command_path(&self, path: &str) -> Option<String> {
3498        let normalized = normalize_path(path);
3499        for prefix in ["/bin/", "/usr/bin/", "/usr/local/bin/"] {
3500            let Some(name) = normalized.strip_prefix(prefix) else {
3501                continue;
3502            };
3503            if !name.is_empty() && !name.contains('/') && self.commands.resolve(name).is_some() {
3504                return Some(name.to_owned());
3505            }
3506        }
3507
3508        if let Some(name) = normalized
3509            .strip_prefix("/__secure_exec/commands/")
3510            .and_then(|suffix| suffix.rsplit('/').next())
3511        {
3512            if !name.is_empty() && !name.contains('/') && self.commands.resolve(name).is_some() {
3513                return Some(name.to_owned());
3514            }
3515        }
3516
3517        None
3518    }
3519
3520    fn parse_shebang_command(&mut self, path: &str) -> KernelResult<Option<ShebangCommand>> {
3521        let header = self.filesystem.pread(path, 0, SHEBANG_LINE_MAX_BYTES + 1)?;
3522        if !header.starts_with(b"#!") {
3523            return Ok(None);
3524        }
3525
3526        let line_end = match header.iter().position(|byte| *byte == b'\n') {
3527            Some(index) => index,
3528            None if header.len() <= SHEBANG_LINE_MAX_BYTES => header.len(),
3529            None => {
3530                return Err(KernelError::new(
3531                    "ENOEXEC",
3532                    format!("shebang line exceeds {SHEBANG_LINE_MAX_BYTES} bytes: {path}"),
3533                ));
3534            }
3535        };
3536        let line = header[2..line_end]
3537            .strip_suffix(b"\r")
3538            .unwrap_or(&header[2..line_end]);
3539        let text = std::str::from_utf8(line)
3540            .map_err(|_| KernelError::new("ENOEXEC", format!("invalid shebang line: {path}")))?;
3541        let mut parts = text.split_ascii_whitespace();
3542        let interpreter = parts
3543            .next()
3544            .ok_or_else(|| KernelError::new("ENOEXEC", format!("invalid shebang line: {path}")))?;
3545        Ok(Some(ShebangCommand {
3546            interpreter: interpreter.to_owned(),
3547            args: parts.map(ToOwned::to_owned).collect(),
3548        }))
3549    }
3550
3551    fn resolve_shebang_command(
3552        &self,
3553        path: &str,
3554        args: &[String],
3555        shebang: ShebangCommand,
3556    ) -> KernelResult<ResolvedSpawnCommand> {
3557        let mut interpreter_args = shebang.args;
3558        let interpreter = normalize_path(&shebang.interpreter);
3559        let command = if interpreter == "/usr/bin/env" || interpreter == "/bin/env" {
3560            if interpreter_args.is_empty() {
3561                return Err(KernelError::new(
3562                    "ENOENT",
3563                    format!("missing interpreter after /usr/bin/env in shebang: {path}"),
3564                ));
3565            }
3566            interpreter_args.remove(0)
3567        } else if let Some(command) = self.resolve_registered_command_path(&interpreter) {
3568            command
3569        } else if self.commands.resolve(&shebang.interpreter).is_some() {
3570            shebang.interpreter
3571        } else {
3572            return Err(KernelError::command_not_found(&shebang.interpreter));
3573        };
3574
3575        let driver = self
3576            .commands
3577            .resolve(&command)
3578            .cloned()
3579            .ok_or_else(|| KernelError::command_not_found(&command))?;
3580        let mut resolved_args = interpreter_args;
3581        resolved_args.push(path.to_owned());
3582        resolved_args.extend(args.iter().cloned());
3583        Ok(ResolvedSpawnCommand {
3584            command,
3585            args: resolved_args,
3586            driver,
3587        })
3588    }
3589
3590    fn finish_waitpid_event(&mut self, result: ProcessWaitResult) -> WaitPidEventResult {
3591        if result.event == WaitPidEvent::Exited {
3592            self.cleanup_process_resources(result.pid);
3593        }
3594        WaitPidEventResult {
3595            pid: result.pid,
3596            status: result.status,
3597            event: result.event,
3598        }
3599    }
3600
3601    fn raw_filesystem_mut(&mut self) -> &mut F {
3602        self.filesystem.inner_mut().inner_mut()
3603    }
3604
3605    fn read_file_internal(
3606        &mut self,
3607        current_pid: Option<u32>,
3608        path: &str,
3609    ) -> KernelResult<Vec<u8>> {
3610        if let Some(proc_node) = self.resolve_proc_node(path, current_pid)? {
3611            self.filesystem
3612                .check_virtual_path(FsOperation::Read, path)
3613                .map_err(KernelError::from)?;
3614            return self.proc_read_file(current_pid, &proc_node);
3615        }
3616
3617        Ok(self.filesystem.read_file(path)?)
3618    }
3619
3620    fn effective_recursive_fs_depth(
3621        &self,
3622        requested_max_depth: Option<usize>,
3623    ) -> KernelResult<usize> {
3624        match (requested_max_depth, self.resources.max_recursive_fs_depth()) {
3625            (Some(requested), Some(limit)) if requested > limit => Err(KernelError::new(
3626                "EINVAL",
3627                format!(
3628                    "requested recursive filesystem max depth {requested} exceeds configured limit {limit}"
3629                ),
3630            )),
3631            (Some(requested), _) => Ok(requested),
3632            (None, Some(limit)) => Ok(limit),
3633            (None, None) => Ok(usize::MAX),
3634        }
3635    }
3636
3637    fn copy_path_inner(
3638        &mut self,
3639        from: &str,
3640        to: &str,
3641        recursive: bool,
3642        depth: usize,
3643        entries: &mut usize,
3644    ) -> KernelResult<()> {
3645        self.resources.check_recursive_fs_depth(depth)?;
3646        *entries = entries.saturating_add(1);
3647        self.resources.check_recursive_fs_entries(*entries)?;
3648        let source_stat = self.lstat_internal(None, from)?;
3649
3650        if source_stat.is_symbolic_link {
3651            let target = self.read_link_internal(None, from)?;
3652            self.symlink(&target, to)?;
3653            return Ok(());
3654        }
3655
3656        if source_stat.is_directory {
3657            if !recursive {
3658                return Err(KernelError::new(
3659                    "EISDIR",
3660                    format!("illegal operation on a directory, copy '{from}'"),
3661                ));
3662            }
3663
3664            let source_root = normalize_path(from);
3665            let destination_root = normalize_path(to);
3666            if destination_root.starts_with(&(source_root.clone() + "/")) {
3667                return Err(KernelError::new(
3668                    "EINVAL",
3669                    format!("cannot copy '{from}' into its own descendant '{to}'"),
3670                ));
3671            }
3672
3673            self.mkdir(&parent_path(&destination_root), true)?;
3674            if !self.exists_internal(None, &destination_root)? {
3675                self.create_dir(&destination_root)?;
3676            }
3677            self.chmod(&destination_root, source_stat.mode)?;
3678            self.chown(&destination_root, source_stat.uid, source_stat.gid)?;
3679
3680            let names = self.read_dir_internal(None, from)?;
3681            self.resources.check_readdir_entries(names.len())?;
3682            for name in names {
3683                if matches!(name.as_str(), "." | "..") {
3684                    continue;
3685                }
3686                let child_from = join_child_path(from, &name);
3687                let child_to = join_child_path(to, &name);
3688                self.copy_path_inner(
3689                    &child_from,
3690                    &child_to,
3691                    true,
3692                    depth.saturating_add(1),
3693                    entries,
3694                )?;
3695            }
3696            return Ok(());
3697        }
3698
3699        let content = self.read_file_internal(None, from)?;
3700        self.write_file(to, content)?;
3701        self.chmod(to, source_stat.mode)?;
3702        self.chown(to, source_stat.uid, source_stat.gid)
3703    }
3704
3705    fn remove_path_inner(
3706        &mut self,
3707        path: &str,
3708        recursive: bool,
3709        depth: usize,
3710        entries: &mut usize,
3711    ) -> KernelResult<()> {
3712        self.resources.check_recursive_fs_depth(depth)?;
3713        *entries = entries.saturating_add(1);
3714        self.resources.check_recursive_fs_entries(*entries)?;
3715        let stat = self.lstat_internal(None, path)?;
3716        if stat.is_directory && !stat.is_symbolic_link {
3717            if recursive {
3718                let names = self.read_dir_internal(None, path)?;
3719                self.resources.check_readdir_entries(names.len())?;
3720                for name in names {
3721                    if matches!(name.as_str(), "." | "..") {
3722                        continue;
3723                    }
3724                    let child = join_child_path(path, &name);
3725                    self.remove_path_inner(&child, true, depth.saturating_add(1), entries)?;
3726                }
3727            }
3728            return self.remove_dir(path);
3729        }
3730
3731        self.remove_file(path)
3732    }
3733
3734    fn exists_internal(&self, current_pid: Option<u32>, path: &str) -> KernelResult<bool> {
3735        match self.resolve_proc_node(path, current_pid) {
3736            Ok(Some(_)) => {
3737                self.filesystem
3738                    .check_virtual_path(FsOperation::Read, path)
3739                    .map_err(KernelError::from)?;
3740                Ok(true)
3741            }
3742            Ok(None) => Ok(self.filesystem.exists(path)?),
3743            Err(error) if error.code() == "ENOENT" => Ok(false),
3744            Err(error) => Err(error),
3745        }
3746    }
3747
3748    fn stat_internal(&mut self, current_pid: Option<u32>, path: &str) -> KernelResult<VirtualStat> {
3749        if let Some(proc_node) = self.resolve_proc_node(path, current_pid)? {
3750            self.filesystem
3751                .check_virtual_path(FsOperation::Read, path)
3752                .map_err(KernelError::from)?;
3753            return self.proc_stat(current_pid, &proc_node);
3754        }
3755
3756        Ok(self.filesystem.stat(path)?)
3757    }
3758
3759    fn lstat_internal(&self, current_pid: Option<u32>, path: &str) -> KernelResult<VirtualStat> {
3760        if let Some(proc_node) = self.resolve_proc_node(path, current_pid)? {
3761            self.filesystem
3762                .check_virtual_path(FsOperation::Read, path)
3763                .map_err(KernelError::from)?;
3764            return self.proc_lstat(&proc_node);
3765        }
3766
3767        Ok(self.filesystem.lstat(path)?)
3768    }
3769
3770    fn read_link_internal(&self, current_pid: Option<u32>, path: &str) -> KernelResult<String> {
3771        if let Some(proc_node) = self.resolve_proc_node(path, current_pid)? {
3772            self.filesystem
3773                .check_virtual_path(FsOperation::Read, path)
3774                .map_err(KernelError::from)?;
3775            return self.proc_read_link(&proc_node);
3776        }
3777
3778        Ok(self.filesystem.read_link(path)?)
3779    }
3780
3781    fn read_dir_internal(
3782        &mut self,
3783        current_pid: Option<u32>,
3784        path: &str,
3785    ) -> KernelResult<Vec<String>> {
3786        if let Some(proc_node) = self.resolve_proc_node(path, current_pid)? {
3787            self.filesystem
3788                .check_virtual_path(FsOperation::Read, path)
3789                .map_err(KernelError::from)?;
3790            return self.proc_read_dir(current_pid, &proc_node);
3791        }
3792
3793        if let Some(limit) = self.resources.max_readdir_entries() {
3794            Ok(self.filesystem.read_dir_limited(path, limit)?)
3795        } else {
3796            Ok(self.filesystem.read_dir(path)?)
3797        }
3798    }
3799
3800    fn read_dir_with_types_internal(
3801        &mut self,
3802        current_pid: Option<u32>,
3803        path: &str,
3804    ) -> KernelResult<Vec<VirtualDirEntry>> {
3805        if let Some(proc_node) = self.resolve_proc_node(path, current_pid)? {
3806            self.filesystem
3807                .check_virtual_path(FsOperation::Read, path)
3808                .map_err(KernelError::from)?;
3809            return Ok(self
3810                .proc_read_dir(current_pid, &proc_node)?
3811                .into_iter()
3812                .map(|name| VirtualDirEntry {
3813                    name,
3814                    is_directory: false,
3815                    is_symbolic_link: false,
3816                })
3817                .collect());
3818        }
3819
3820        Ok(self.filesystem.read_dir_with_types(path)?)
3821    }
3822
3823    fn realpath_internal(&self, current_pid: Option<u32>, path: &str) -> KernelResult<String> {
3824        if let Some(proc_node) = self.resolve_proc_node(path, current_pid)? {
3825            self.filesystem
3826                .check_virtual_path(FsOperation::Read, path)
3827                .map_err(KernelError::from)?;
3828            return self.proc_realpath(current_pid, &proc_node);
3829        }
3830
3831        Ok(self.filesystem.realpath(path)?)
3832    }
3833
3834    fn resolve_proc_node(
3835        &self,
3836        path: &str,
3837        current_pid: Option<u32>,
3838    ) -> KernelResult<Option<ProcNode>> {
3839        let normalized = normalize_path(path);
3840        if !is_proc_path(&normalized) {
3841            return Ok(None);
3842        }
3843
3844        if normalized == "/proc" {
3845            return Ok(Some(ProcNode::RootDir));
3846        }
3847
3848        let suffix = normalized
3849            .strip_prefix("/proc/")
3850            .expect("proc path should have /proc prefix");
3851        let parts = suffix.split('/').collect::<Vec<_>>();
3852        if parts.is_empty() {
3853            return Ok(Some(ProcNode::RootDir));
3854        }
3855
3856        let root_node = match parts.as_slice() {
3857            ["mounts"] => Some(ProcNode::MountsFile),
3858            ["cpuinfo"] => Some(ProcNode::CpuInfoFile),
3859            ["meminfo"] => Some(ProcNode::MemInfoFile),
3860            ["loadavg"] => Some(ProcNode::LoadAvgFile),
3861            ["uptime"] => Some(ProcNode::UptimeFile),
3862            ["version"] => Some(ProcNode::VersionFile),
3863            _ => None,
3864        };
3865        if let Some(node) = root_node {
3866            return Ok(Some(node));
3867        }
3868
3869        let pid = match parts[0] {
3870            "self" => current_pid.ok_or_else(|| proc_not_found_error(&normalized))?,
3871            raw => raw
3872                .parse::<u32>()
3873                .map_err(|_| proc_not_found_error(&normalized))?,
3874        };
3875        self.proc_entry(pid)?;
3876
3877        let node = match parts.as_slice() {
3878            ["self"] => ProcNode::SelfLink { pid },
3879            [_pid] => ProcNode::PidDir { pid },
3880            [_pid, "fd"] => ProcNode::PidFdDir { pid },
3881            [_pid, "cmdline"] => ProcNode::PidCmdline { pid },
3882            [_pid, "environ"] => ProcNode::PidEnviron { pid },
3883            [_pid, "cwd"] => ProcNode::PidCwdLink { pid },
3884            [_pid, "stat"] => ProcNode::PidStatFile { pid },
3885            [_pid, "status"] => ProcNode::PidStatusFile { pid },
3886            [_pid, "fd", fd] => {
3887                let fd = fd
3888                    .parse::<u32>()
3889                    .map_err(|_| proc_not_found_error(&normalized))?;
3890                self.proc_fd_entry(pid, fd)?;
3891                ProcNode::PidFdLink { pid, fd }
3892            }
3893            _ => return Err(proc_not_found_error(&normalized)),
3894        };
3895
3896        Ok(Some(node))
3897    }
3898
3899    fn proc_entry(&self, pid: u32) -> KernelResult<crate::process_table::ProcessEntry> {
3900        self.processes
3901            .get(pid)
3902            .ok_or_else(|| proc_not_found_error(&format!("/proc/{pid}")))
3903    }
3904
3905    fn proc_fd_entry(&self, pid: u32, fd: u32) -> KernelResult<FdEntry> {
3906        lock_or_recover(&self.fd_tables)
3907            .get(pid)
3908            .and_then(|table| table.get(fd))
3909            .cloned()
3910            .ok_or_else(|| proc_not_found_error(&format!("/proc/{pid}/fd/{fd}")))
3911    }
3912
3913    fn proc_read_file(
3914        &mut self,
3915        current_pid: Option<u32>,
3916        node: &ProcNode,
3917    ) -> KernelResult<Vec<u8>> {
3918        match node {
3919            ProcNode::SelfLink { .. }
3920            | ProcNode::PidCwdLink { .. }
3921            | ProcNode::PidFdLink { .. } => {
3922                let target = self.proc_symlink_target(node)?;
3923                self.read_file_internal(current_pid, &target)
3924            }
3925            ProcNode::MountsFile => Ok(self.proc_mounts_bytes()),
3926            ProcNode::CpuInfoFile => Ok(self.proc_cpuinfo_bytes()),
3927            ProcNode::MemInfoFile => Ok(self.proc_meminfo_bytes()),
3928            ProcNode::LoadAvgFile => Ok(self.proc_loadavg_bytes()),
3929            ProcNode::UptimeFile => Ok(self.proc_uptime_bytes()),
3930            ProcNode::VersionFile => Ok(self.proc_version_bytes()),
3931            ProcNode::PidCmdline { pid } => Ok(self.proc_cmdline_bytes(*pid)),
3932            ProcNode::PidEnviron { pid } => Ok(self.proc_environ_bytes(*pid)),
3933            ProcNode::PidStatFile { pid } => Ok(self.proc_stat_bytes(*pid)),
3934            ProcNode::PidStatusFile { pid } => Ok(self.proc_status_bytes(*pid)),
3935            ProcNode::RootDir | ProcNode::PidDir { .. } | ProcNode::PidFdDir { .. } => {
3936                Err(KernelError::new(
3937                    "EISDIR",
3938                    format!(
3939                        "illegal operation on a directory, read '{}'",
3940                        self.proc_canonical_path(node)
3941                    ),
3942                ))
3943            }
3944        }
3945    }
3946
3947    fn proc_stat(
3948        &mut self,
3949        current_pid: Option<u32>,
3950        node: &ProcNode,
3951    ) -> KernelResult<VirtualStat> {
3952        match node {
3953            ProcNode::SelfLink { .. }
3954            | ProcNode::PidCwdLink { .. }
3955            | ProcNode::PidFdLink { .. } => {
3956                let target = self.proc_symlink_target(node)?;
3957                self.stat_internal(current_pid, &target)
3958            }
3959            _ => self.proc_lstat(node),
3960        }
3961    }
3962
3963    fn proc_lstat(&self, node: &ProcNode) -> KernelResult<VirtualStat> {
3964        match node {
3965            ProcNode::RootDir | ProcNode::PidDir { .. } | ProcNode::PidFdDir { .. } => {
3966                Ok(proc_dir_stat(proc_inode(node)))
3967            }
3968            ProcNode::MountsFile => Ok(proc_file_stat(
3969                proc_inode(node),
3970                self.proc_mounts_bytes().len() as u64,
3971            )),
3972            ProcNode::CpuInfoFile => Ok(proc_file_stat(
3973                proc_inode(node),
3974                self.proc_cpuinfo_bytes().len() as u64,
3975            )),
3976            ProcNode::MemInfoFile => Ok(proc_file_stat(
3977                proc_inode(node),
3978                self.proc_meminfo_bytes().len() as u64,
3979            )),
3980            ProcNode::LoadAvgFile => Ok(proc_file_stat(
3981                proc_inode(node),
3982                self.proc_loadavg_bytes().len() as u64,
3983            )),
3984            ProcNode::UptimeFile => Ok(proc_file_stat(
3985                proc_inode(node),
3986                self.proc_uptime_bytes().len() as u64,
3987            )),
3988            ProcNode::VersionFile => Ok(proc_file_stat(
3989                proc_inode(node),
3990                self.proc_version_bytes().len() as u64,
3991            )),
3992            ProcNode::PidCmdline { pid } => Ok(proc_file_stat(
3993                proc_inode(node),
3994                self.proc_cmdline_bytes(*pid).len() as u64,
3995            )),
3996            ProcNode::PidEnviron { pid } => Ok(proc_file_stat(
3997                proc_inode(node),
3998                self.proc_environ_bytes(*pid).len() as u64,
3999            )),
4000            ProcNode::PidStatFile { pid } => Ok(proc_file_stat(
4001                proc_inode(node),
4002                self.proc_stat_bytes(*pid).len() as u64,
4003            )),
4004            ProcNode::PidStatusFile { pid } => Ok(proc_file_stat(
4005                proc_inode(node),
4006                self.proc_status_bytes(*pid).len() as u64,
4007            )),
4008            ProcNode::SelfLink { .. }
4009            | ProcNode::PidCwdLink { .. }
4010            | ProcNode::PidFdLink { .. } => Ok(proc_symlink_stat(
4011                proc_inode(node),
4012                self.proc_read_link(node)?.len() as u64,
4013            )),
4014        }
4015    }
4016
4017    fn proc_read_link(&self, node: &ProcNode) -> KernelResult<String> {
4018        match node {
4019            ProcNode::SelfLink { .. }
4020            | ProcNode::PidCwdLink { .. }
4021            | ProcNode::PidFdLink { .. } => self.proc_symlink_target(node),
4022            _ => Err(KernelError::new(
4023                "EINVAL",
4024                format!(
4025                    "invalid argument, readlink '{}'",
4026                    self.proc_canonical_path(node)
4027                ),
4028            )),
4029        }
4030    }
4031
4032    fn proc_read_dir(
4033        &mut self,
4034        current_pid: Option<u32>,
4035        node: &ProcNode,
4036    ) -> KernelResult<Vec<String>> {
4037        match node {
4038            ProcNode::SelfLink { .. }
4039            | ProcNode::PidCwdLink { .. }
4040            | ProcNode::PidFdLink { .. } => {
4041                let target = self.proc_symlink_target(node)?;
4042                self.read_dir_internal(current_pid, &target)
4043            }
4044            ProcNode::RootDir => {
4045                let mut entries = self
4046                    .processes
4047                    .list_processes()
4048                    .keys()
4049                    .map(|pid| pid.to_string())
4050                    .collect::<Vec<_>>();
4051                entries.push(String::from("cpuinfo"));
4052                entries.push(String::from("loadavg"));
4053                entries.push(String::from("meminfo"));
4054                entries.push(String::from("mounts"));
4055                entries.push(String::from("self"));
4056                entries.push(String::from("uptime"));
4057                entries.push(String::from("version"));
4058                entries.sort();
4059                Ok(entries)
4060            }
4061            ProcNode::PidDir { .. } => Ok(vec![
4062                String::from("cmdline"),
4063                String::from("cwd"),
4064                String::from("environ"),
4065                String::from("fd"),
4066                String::from("stat"),
4067                String::from("status"),
4068            ]),
4069            ProcNode::PidFdDir { pid } => {
4070                let tables = lock_or_recover(&self.fd_tables);
4071                let table = tables
4072                    .get(*pid)
4073                    .ok_or_else(|| proc_not_found_error(&format!("/proc/{pid}/fd")))?;
4074                Ok(table.iter().map(|entry| entry.fd.to_string()).collect())
4075            }
4076            _ => Err(KernelError::new(
4077                "ENOTDIR",
4078                format!(
4079                    "not a directory, scandir '{}'",
4080                    self.proc_canonical_path(node)
4081                ),
4082            )),
4083        }
4084    }
4085
4086    fn proc_realpath(&self, current_pid: Option<u32>, node: &ProcNode) -> KernelResult<String> {
4087        match node {
4088            ProcNode::SelfLink { .. }
4089            | ProcNode::PidCwdLink { .. }
4090            | ProcNode::PidFdLink { .. } => {
4091                let target = self.proc_symlink_target(node)?;
4092                self.realpath_internal(current_pid, &target)
4093            }
4094            _ => Ok(self.proc_canonical_path(node)),
4095        }
4096    }
4097
4098    fn proc_symlink_target(&self, node: &ProcNode) -> KernelResult<String> {
4099        match node {
4100            ProcNode::SelfLink { pid } => Ok(format!("/proc/{pid}")),
4101            ProcNode::PidCwdLink { pid } => Ok(self.proc_entry(*pid)?.cwd),
4102            ProcNode::PidFdLink { pid, fd } => {
4103                Ok(self.proc_fd_entry(*pid, *fd)?.description.path().to_owned())
4104            }
4105            _ => Err(KernelError::new(
4106                "EINVAL",
4107                format!(
4108                    "'{}' is not a symbolic link",
4109                    self.proc_canonical_path(node)
4110                ),
4111            )),
4112        }
4113    }
4114
4115    fn proc_canonical_path(&self, node: &ProcNode) -> String {
4116        match node {
4117            ProcNode::RootDir => String::from("/proc"),
4118            ProcNode::MountsFile => String::from("/proc/mounts"),
4119            ProcNode::CpuInfoFile => String::from("/proc/cpuinfo"),
4120            ProcNode::MemInfoFile => String::from("/proc/meminfo"),
4121            ProcNode::LoadAvgFile => String::from("/proc/loadavg"),
4122            ProcNode::UptimeFile => String::from("/proc/uptime"),
4123            ProcNode::VersionFile => String::from("/proc/version"),
4124            ProcNode::SelfLink { pid } => format!("/proc/{pid}"),
4125            ProcNode::PidDir { pid } => format!("/proc/{pid}"),
4126            ProcNode::PidFdDir { pid } => format!("/proc/{pid}/fd"),
4127            ProcNode::PidCmdline { pid } => format!("/proc/{pid}/cmdline"),
4128            ProcNode::PidEnviron { pid } => format!("/proc/{pid}/environ"),
4129            ProcNode::PidCwdLink { pid } => format!("/proc/{pid}/cwd"),
4130            ProcNode::PidStatFile { pid } => format!("/proc/{pid}/stat"),
4131            ProcNode::PidStatusFile { pid } => format!("/proc/{pid}/status"),
4132            ProcNode::PidFdLink { pid, fd } => format!("/proc/{pid}/fd/{fd}"),
4133        }
4134    }
4135
4136    fn proc_cmdline_bytes(&self, pid: u32) -> Vec<u8> {
4137        let entry = self
4138            .processes
4139            .get(pid)
4140            .expect("process must exist while procfs path is resolved");
4141        let mut argv = vec![entry.command];
4142        argv.extend(entry.args);
4143        null_separated_bytes(argv)
4144    }
4145
4146    fn proc_environ_bytes(&self, pid: u32) -> Vec<u8> {
4147        let entry = self
4148            .processes
4149            .get(pid)
4150            .expect("process must exist while procfs path is resolved");
4151        null_separated_bytes(
4152            entry
4153                .env
4154                .into_iter()
4155                .map(|(key, value)| format!("{key}={value}"))
4156                .collect(),
4157        )
4158    }
4159
4160    fn proc_stat_bytes(&self, pid: u32) -> Vec<u8> {
4161        let entry = self
4162            .processes
4163            .get(pid)
4164            .expect("process must exist while procfs path is resolved");
4165        let command = entry.command.replace(')', "]");
4166        let state = match entry.status {
4167            ProcessStatus::Running => 'R',
4168            ProcessStatus::Stopped => 'T',
4169            ProcessStatus::Exited => 'Z',
4170        };
4171        format!(
4172            "{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",
4173            ppid = entry.ppid,
4174            pgid = entry.pgid,
4175            sid = entry.sid,
4176        )
4177        .into_bytes()
4178    }
4179
4180    fn proc_mounts_bytes(&self) -> Vec<u8> {
4181        let mounts = if let Some(table) =
4182            (self.filesystem.inner().inner() as &dyn Any).downcast_ref::<MountTable>()
4183        {
4184            table.get_mounts()
4185        } else {
4186            vec![MountEntry {
4187                path: String::from("/"),
4188                plugin_id: String::from("root"),
4189                read_only: false,
4190            }]
4191        };
4192
4193        mounts
4194            .into_iter()
4195            .map(|mount| {
4196                let options = if mount.read_only { "ro" } else { "rw" };
4197                format!(
4198                    "{source} {target} {fstype} {options} 0 0\n",
4199                    source = mount.plugin_id,
4200                    target = mount.path,
4201                    fstype = mount.plugin_id,
4202                )
4203            })
4204            .collect::<String>()
4205            .into_bytes()
4206    }
4207
4208    fn proc_cpu_count(&self) -> usize {
4209        self.resource_limits().virtual_cpu_count.unwrap_or(1)
4210    }
4211
4212    fn proc_cpuinfo_bytes(&self) -> Vec<u8> {
4213        let mut body = String::new();
4214        for processor in 0..self.proc_cpu_count() {
4215            body.push_str(&format!(
4216                "processor\t: {processor}\nmodel name\t: secure-exec Virtual CPU\ncpu MHz\t\t: 1000.000\nsiblings\t: 1\ncpu cores\t: 1\n\n"
4217            ));
4218        }
4219        body.into_bytes()
4220    }
4221
4222    fn proc_mem_total_bytes(&self) -> u64 {
4223        self.resource_limits()
4224            .max_wasm_memory_bytes
4225            .or(self.resource_limits().max_filesystem_bytes)
4226            .unwrap_or(DEFAULT_MAX_OPEN_FDS as u64 * 1024 * 1024)
4227    }
4228
4229    fn proc_meminfo_bytes(&self) -> Vec<u8> {
4230        let total_kb = self.proc_mem_total_bytes().div_ceil(1024);
4231        let zero_kb = 0;
4232        format!(
4233            "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"
4234        )
4235        .into_bytes()
4236    }
4237
4238    fn proc_loadavg_bytes(&self) -> Vec<u8> {
4239        let processes = self.processes.list_processes();
4240        let running = processes
4241            .values()
4242            .filter(|process| process.status == ProcessStatus::Running)
4243            .count();
4244        let total = processes.len().max(1);
4245        let last_pid = processes.keys().next_back().copied().unwrap_or(0);
4246        format!("0.00 0.00 0.00 {running}/{total} {last_pid}\n").into_bytes()
4247    }
4248
4249    fn proc_uptime_bytes(&self) -> Vec<u8> {
4250        let uptime = self.boot_instant.elapsed().as_secs_f64();
4251        format!("{uptime:.2} {uptime:.2}\n").into_bytes()
4252    }
4253
4254    fn proc_version_bytes(&self) -> Vec<u8> {
4255        format!(
4256            "Linux version 6.8.0-agentos (agentos@localhost) #1 SMP boot={}\n",
4257            self.boot_time_ms
4258        )
4259        .into_bytes()
4260    }
4261
4262    fn proc_status_bytes(&self, pid: u32) -> Vec<u8> {
4263        let entry = self
4264            .processes
4265            .get(pid)
4266            .expect("process must exist while procfs path is resolved");
4267        let (state_code, state_name) = match entry.status {
4268            ProcessStatus::Running => ('R', "running"),
4269            ProcessStatus::Stopped => ('T', "stopped"),
4270            ProcessStatus::Exited => ('Z', "zombie"),
4271        };
4272        format!(
4273            "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",
4274            0,
4275            0,
4276            name = entry.command,
4277            ppid = entry.ppid,
4278            uid = entry.identity.uid,
4279            euid = entry.identity.euid,
4280            gid = entry.identity.gid,
4281            egid = entry.identity.egid,
4282        )
4283        .into_bytes()
4284    }
4285
4286    fn proc_read_file_from_open_path(
4287        &mut self,
4288        current_pid: Option<u32>,
4289        path: &str,
4290    ) -> KernelResult<Vec<u8>> {
4291        let node = self
4292            .resolve_proc_node(path, current_pid)?
4293            .ok_or_else(|| proc_not_found_error(path))?;
4294        self.proc_read_file(current_pid, &node)
4295    }
4296
4297    fn proc_stat_from_open_path(
4298        &mut self,
4299        current_pid: Option<u32>,
4300        path: &str,
4301    ) -> KernelResult<VirtualStat> {
4302        let node = self
4303            .resolve_proc_node(path, current_pid)?
4304            .ok_or_else(|| proc_not_found_error(path))?;
4305        self.proc_stat(current_pid, &node)
4306    }
4307
4308    fn filesystem_usage(&mut self) -> KernelResult<FileSystemUsage> {
4309        if let Some(usage) = self.filesystem_usage_cache.clone() {
4310            return Ok(usage);
4311        }
4312        let filesystem = self.raw_filesystem_mut();
4313        let filesystem_any = filesystem as &mut dyn Any;
4314        let usage = if let Some(mount_table) = filesystem_any.downcast_mut::<MountTable>() {
4315            mount_table.root_usage()?
4316        } else {
4317            measure_filesystem_usage(filesystem)?
4318        };
4319        self.filesystem_usage_cache = Some(usage.clone());
4320        Ok(usage)
4321    }
4322
4323    fn invalidate_filesystem_usage_cache(&mut self) {
4324        self.filesystem_usage_cache = None;
4325    }
4326
4327    fn update_filesystem_usage_cache_for_resize(&mut self, old_size: u64, new_size: u64) {
4328        if let Some(usage) = self.filesystem_usage_cache.as_mut() {
4329            usage.total_bytes = usage
4330                .total_bytes
4331                .saturating_sub(old_size)
4332                .saturating_add(new_size);
4333        }
4334    }
4335
4336    fn update_filesystem_usage_cache_for_write(
4337        &mut self,
4338        existing: Option<&VirtualStat>,
4339        new_size: u64,
4340    ) {
4341        if is_storage_directory(existing) {
4342            return;
4343        }
4344
4345        if let Some(stat) = existing {
4346            self.update_filesystem_usage_cache_for_resize(stat.size, new_size);
4347        } else {
4348            self.update_filesystem_usage_cache_for_inode_create(new_size);
4349        }
4350    }
4351
4352    fn update_filesystem_usage_cache_for_inode_create(&mut self, size: u64) {
4353        if let Some(usage) = self.filesystem_usage_cache.as_mut() {
4354            usage.total_bytes = usage.total_bytes.saturating_add(size);
4355            usage.inode_count = usage.inode_count.saturating_add(1);
4356        }
4357    }
4358
4359    fn update_filesystem_usage_cache_for_inode_creates(&mut self, count: usize) {
4360        if count == 0 {
4361            return;
4362        }
4363        if let Some(usage) = self.filesystem_usage_cache.as_mut() {
4364            usage.inode_count = usage.inode_count.saturating_add(count);
4365        }
4366    }
4367
4368    fn update_filesystem_usage_cache_for_inode_delete(&mut self, size: u64) {
4369        if let Some(usage) = self.filesystem_usage_cache.as_mut() {
4370            usage.total_bytes = usage.total_bytes.saturating_sub(size);
4371            usage.inode_count = usage.inode_count.saturating_sub(1);
4372        }
4373    }
4374
4375    fn update_filesystem_usage_cache_for_remove(&mut self, removed: Option<&VirtualStat>) {
4376        let Some(stat) = removed else {
4377            return;
4378        };
4379        if stat.is_directory || stat.nlink > 1 {
4380            return;
4381        }
4382        self.update_filesystem_usage_cache_for_inode_delete(stat.size);
4383    }
4384
4385    fn storage_stat(&mut self, path: &str) -> KernelResult<Option<VirtualStat>> {
4386        if is_virtual_device_storage_path(path) {
4387            return Ok(None);
4388        }
4389
4390        match self.raw_filesystem_mut().stat(path) {
4391            Ok(stat) => Ok(Some(stat)),
4392            Err(error) if error.code() == "ENOENT" => Ok(None),
4393            Err(error) => Err(error.into()),
4394        }
4395    }
4396
4397    fn storage_lstat(&mut self, path: &str) -> KernelResult<Option<VirtualStat>> {
4398        if is_virtual_device_storage_path(path) {
4399            return Ok(None);
4400        }
4401
4402        match self.raw_filesystem_mut().lstat(path) {
4403            Ok(stat) => Ok(Some(stat)),
4404            Err(error) if error.code() == "ENOENT" => Ok(None),
4405            Err(error) => Err(error.into()),
4406        }
4407    }
4408
4409    fn current_storage_file_size(&mut self, path: &str) -> KernelResult<u64> {
4410        Ok(self
4411            .storage_stat(path)?
4412            .filter(|stat| !stat.is_directory)
4413            .map(|stat| stat.size)
4414            .unwrap_or(0))
4415    }
4416
4417    fn apply_creation_mode(&mut self, path: &str, mode: u32, umask: u32) -> KernelResult<()> {
4418        let masked_mode = (mode & !0o777) | ((mode & 0o777) & !(umask & 0o777));
4419        Ok(self.filesystem.chmod(path, masked_mode)?)
4420    }
4421
4422    fn missing_directory_paths(
4423        &mut self,
4424        path: &str,
4425        recursive: bool,
4426    ) -> KernelResult<Vec<String>> {
4427        let normalized = normalize_path(path);
4428        if normalized == "/" {
4429            return Ok(Vec::new());
4430        }
4431
4432        if !recursive {
4433            return Ok(if self.storage_lstat(&normalized)?.is_none() {
4434                vec![normalized]
4435            } else {
4436                Vec::new()
4437            });
4438        }
4439
4440        let mut created = Vec::new();
4441        let mut current = String::from("/");
4442        for component in normalized
4443            .split('/')
4444            .filter(|component| !component.is_empty())
4445        {
4446            current = if current == "/" {
4447                format!("/{component}")
4448            } else {
4449                format!("{current}/{component}")
4450            };
4451            if self.storage_lstat(&current)?.is_none() {
4452                created.push(current.clone());
4453            }
4454        }
4455        Ok(created)
4456    }
4457
4458    fn check_write_file_limits(&mut self, path: &str, new_size: u64) -> KernelResult<()> {
4459        let existing = self.storage_stat(path)?;
4460        self.check_write_file_limits_with_existing(path, existing.as_ref(), new_size)
4461    }
4462
4463    fn check_write_file_limits_with_existing(
4464        &mut self,
4465        path: &str,
4466        existing: Option<&VirtualStat>,
4467        new_size: u64,
4468    ) -> KernelResult<()> {
4469        if is_virtual_device_storage_path(path) {
4470            return Ok(());
4471        }
4472
4473        if let Some(existing) = existing {
4474            if is_storage_directory(Some(existing)) {
4475                return Ok(());
4476            }
4477            if new_size <= existing.size {
4478                return Ok(());
4479            }
4480
4481            let usage = self.filesystem_usage()?;
4482            self.resources.check_filesystem_usage(
4483                &usage,
4484                usage
4485                    .total_bytes
4486                    .saturating_sub(existing.size)
4487                    .saturating_add(new_size),
4488                usage.inode_count,
4489            )?;
4490            return Ok(());
4491        }
4492
4493        let usage = self.filesystem_usage()?;
4494        self.resources.check_filesystem_usage(
4495            &usage,
4496            usage.total_bytes.saturating_add(new_size),
4497            usage.inode_count.saturating_add(1),
4498        )?;
4499        Ok(())
4500    }
4501
4502    fn check_create_dir_limits(&mut self, path: &str) -> KernelResult<()> {
4503        if is_virtual_device_storage_path(path) || self.storage_lstat(path)?.is_some() {
4504            return Ok(());
4505        }
4506
4507        let parent = parent_path(path);
4508        let Some(parent_stat) = self.storage_stat(&parent)? else {
4509            return Ok(());
4510        };
4511        if !parent_stat.is_directory {
4512            return Ok(());
4513        }
4514
4515        let usage = self.filesystem_usage()?;
4516        self.resources.check_filesystem_usage(
4517            &usage,
4518            usage.total_bytes,
4519            usage.inode_count.saturating_add(1),
4520        )?;
4521        Ok(())
4522    }
4523
4524    fn check_mkdir_limits(&mut self, path: &str, recursive: bool) -> KernelResult<()> {
4525        if is_virtual_device_storage_path(path) {
4526            return Ok(());
4527        }
4528
4529        if !recursive {
4530            return self.check_create_dir_limits(path);
4531        }
4532
4533        let usage = self.filesystem_usage()?;
4534        let new_inodes = count_missing_directory_components(self.raw_filesystem_mut(), path, true)?;
4535        self.resources.check_filesystem_usage(
4536            &usage,
4537            usage.total_bytes,
4538            usage.inode_count.saturating_add(new_inodes),
4539        )?;
4540        Ok(())
4541    }
4542
4543    fn check_symlink_limits(&mut self, target: &str, link_path: &str) -> KernelResult<()> {
4544        if is_virtual_device_storage_path(link_path) || self.storage_lstat(link_path)?.is_some() {
4545            return Ok(());
4546        }
4547
4548        let parent = parent_path(link_path);
4549        let Some(parent_stat) = self.storage_stat(&parent)? else {
4550            return Ok(());
4551        };
4552        if !parent_stat.is_directory {
4553            return Ok(());
4554        }
4555
4556        let usage = self.filesystem_usage()?;
4557        self.resources.check_filesystem_usage(
4558            &usage,
4559            usage.total_bytes.saturating_add(target.len() as u64),
4560            usage.inode_count.saturating_add(1),
4561        )?;
4562        Ok(())
4563    }
4564
4565    fn check_truncate_limits_with_existing(
4566        &mut self,
4567        path: &str,
4568        existing: Option<&VirtualStat>,
4569        length: u64,
4570    ) -> KernelResult<()> {
4571        if is_virtual_device_storage_path(path) {
4572            return Ok(());
4573        }
4574
4575        let Some(existing) = existing else {
4576            return Ok(());
4577        };
4578        if is_storage_directory(Some(existing)) {
4579            return Ok(());
4580        }
4581        self.check_path_resize_limits_with_existing(existing.size, length)
4582    }
4583
4584    fn check_rename_copy_up_limits(&mut self, old_path: &str, new_path: &str) -> KernelResult<()> {
4585        let max_bytes = self.resource_limits().max_filesystem_bytes;
4586        let max_inodes = self.resource_limits().max_inode_count;
4587        let filesystem_any = self.raw_filesystem_mut() as &mut dyn Any;
4588
4589        if let Some(root) = filesystem_any.downcast_mut::<RootFileSystem>() {
4590            root.check_rename_copy_up_limits(old_path, new_path, max_bytes, max_inodes)?;
4591            return Ok(());
4592        }
4593
4594        if let Some(mount_table) = filesystem_any.downcast_mut::<MountTable>() {
4595            mount_table.check_rename_copy_up_limits(old_path, new_path, max_bytes, max_inodes)?;
4596        }
4597
4598        Ok(())
4599    }
4600
4601    fn check_path_resize_limits(&mut self, path: &str, new_size: u64) -> KernelResult<()> {
4602        if is_virtual_device_storage_path(path) {
4603            return Ok(());
4604        }
4605
4606        let Some(existing) = self.storage_stat(path)? else {
4607            return Ok(());
4608        };
4609        if existing.is_directory {
4610            return Ok(());
4611        }
4612        self.check_path_resize_limits_with_existing(existing.size, new_size)
4613    }
4614
4615    fn check_path_resize_limits_with_existing(
4616        &mut self,
4617        existing_size: u64,
4618        new_size: u64,
4619    ) -> KernelResult<()> {
4620        if new_size <= existing_size {
4621            return Ok(());
4622        }
4623
4624        let usage = self.filesystem_usage()?;
4625        self.resources.check_filesystem_usage(
4626            &usage,
4627            usage
4628                .total_bytes
4629                .saturating_sub(existing_size)
4630                .saturating_add(new_size),
4631            usage.inode_count,
4632        )?;
4633        Ok(())
4634    }
4635
4636    fn blocking_read_timeout(&self) -> Option<Duration> {
4637        self.resources
4638            .limits()
4639            .max_blocking_read_ms
4640            .map(Duration::from_millis)
4641    }
4642
4643    fn close_special_resource_if_needed(&self, description: &Arc<FileDescription>, filetype: u8) {
4644        close_special_resource_if_needed(
4645            &self.file_locks,
4646            &self.pipes,
4647            &self.ptys,
4648            description,
4649            filetype,
4650        );
4651    }
4652}
4653
4654impl KernelVm<MountTable> {
4655    fn check_mount_permissions(&self, path: &str) -> KernelResult<()> {
4656        self.filesystem
4657            .check_path(FsOperation::Write, path)
4658            .map_err(KernelError::from)?;
4659        if is_sensitive_mount_path(path) {
4660            self.filesystem
4661                .check_path(FsOperation::MountSensitive, path)
4662                .map_err(KernelError::from)?;
4663        }
4664        Ok(())
4665    }
4666
4667    pub fn mount_filesystem(
4668        &mut self,
4669        path: &str,
4670        filesystem: impl VirtualFileSystem + 'static,
4671        options: MountOptions,
4672    ) -> KernelResult<()> {
4673        self.assert_not_terminated()?;
4674        self.check_mount_permissions(path)?;
4675        self.filesystem
4676            .inner_mut()
4677            .inner_mut()
4678            .mount(path, filesystem, options)
4679            .map_err(KernelError::from)
4680    }
4681
4682    pub fn mount_boxed_filesystem(
4683        &mut self,
4684        path: &str,
4685        filesystem: Box<dyn MountedFileSystem>,
4686        options: MountOptions,
4687    ) -> KernelResult<()> {
4688        self.assert_not_terminated()?;
4689        self.check_mount_permissions(path)?;
4690        self.filesystem
4691            .inner_mut()
4692            .inner_mut()
4693            .mount_boxed(path, filesystem, options)
4694            .map_err(KernelError::from)
4695    }
4696
4697    pub fn unmount_filesystem(&mut self, path: &str) -> KernelResult<()> {
4698        self.assert_not_terminated()?;
4699        self.check_mount_permissions(path)?;
4700        self.filesystem
4701            .inner_mut()
4702            .inner_mut()
4703            .unmount(path)
4704            .map_err(KernelError::from)
4705    }
4706
4707    pub fn mounted_filesystems(&self) -> Vec<MountEntry> {
4708        self.filesystem.inner().inner().get_mounts()
4709    }
4710
4711    pub fn root_filesystem_mut(&mut self) -> Option<&mut RootFileSystem> {
4712        self.filesystem
4713            .inner_mut()
4714            .inner_mut()
4715            .root_virtual_filesystem_mut::<RootFileSystem>()
4716    }
4717
4718    pub fn snapshot_root_filesystem(&mut self) -> KernelResult<RootFilesystemSnapshot> {
4719        let usage = self.filesystem_usage()?;
4720        self.resources
4721            .check_filesystem_usage(&usage, usage.total_bytes, usage.inode_count)?;
4722        let root = self
4723            .root_filesystem_mut()
4724            .ok_or_else(|| KernelError::new("EINVAL", "native root filesystem is not available"))?;
4725        root.snapshot().map_err(KernelError::from)
4726    }
4727}
4728
4729#[derive(Default)]
4730struct StubDriverState {
4731    exit_code: Option<i32>,
4732    on_exit: Option<ProcessExitCallback>,
4733    kill_signals: Vec<i32>,
4734}
4735
4736#[derive(Default)]
4737struct StubDriverProcess {
4738    state: Mutex<StubDriverState>,
4739    waiters: Condvar,
4740}
4741
4742impl StubDriverProcess {
4743    fn finish(&self, exit_code: i32) {
4744        let callback = {
4745            let mut state = lock_or_recover(&self.state);
4746            if state.exit_code.is_some() {
4747                return;
4748            }
4749            state.exit_code = Some(exit_code);
4750            self.waiters.notify_all();
4751            state.on_exit.clone()
4752        };
4753
4754        if let Some(callback) = callback {
4755            callback(exit_code);
4756        }
4757    }
4758
4759    fn kill_signals(&self) -> Vec<i32> {
4760        lock_or_recover(&self.state).kill_signals.clone()
4761    }
4762}
4763
4764impl DriverProcess for StubDriverProcess {
4765    fn kill(&self, signal: i32) {
4766        {
4767            let mut state = lock_or_recover(&self.state);
4768            state.kill_signals.push(signal);
4769        }
4770        if matches!(
4771            signal,
4772            crate::process_table::SIGCHLD | SIGCONT | SIGSTOP | SIGTSTP | SIGWINCH
4773        ) {
4774            return;
4775        }
4776        self.finish(128 + signal);
4777    }
4778
4779    fn wait(&self, timeout: Duration) -> Option<i32> {
4780        let state = lock_or_recover(&self.state);
4781        if let Some(code) = state.exit_code {
4782            return Some(code);
4783        }
4784
4785        let (state, _) = wait_timeout_or_recover(&self.waiters, state, timeout);
4786        state.exit_code
4787    }
4788
4789    fn set_on_exit(&self, callback: ProcessExitCallback) {
4790        let maybe_exit = {
4791            let mut state = lock_or_recover(&self.state);
4792            state.on_exit = Some(callback.clone());
4793            state.exit_code
4794        };
4795
4796        if let Some(code) = maybe_exit {
4797            callback(code);
4798        }
4799    }
4800}
4801
4802impl From<VfsError> for KernelError {
4803    fn from(error: VfsError) -> Self {
4804        map_error(error.code(), error.to_string())
4805    }
4806}
4807
4808fn lock_or_recover<'a, T>(mutex: &'a Mutex<T>) -> MutexGuard<'a, T> {
4809    match mutex.lock() {
4810        Ok(guard) => guard,
4811        Err(poisoned) => poisoned.into_inner(),
4812    }
4813}
4814
4815fn wait_timeout_or_recover<'a, T>(
4816    condvar: &Condvar,
4817    guard: MutexGuard<'a, T>,
4818    timeout: Duration,
4819) -> (MutexGuard<'a, T>, WaitTimeoutResult) {
4820    match condvar.wait_timeout(guard, timeout) {
4821        Ok(result) => result,
4822        Err(poisoned) => poisoned.into_inner(),
4823    }
4824}
4825
4826fn is_sensitive_mount_path(path: &str) -> bool {
4827    let normalized = crate::vfs::normalize_path(path);
4828    normalized == "/"
4829        || normalized == "/etc"
4830        || normalized.starts_with("/etc/")
4831        || normalized == "/proc"
4832        || normalized.starts_with("/proc/")
4833}
4834
4835impl From<FdTableError> for KernelError {
4836    fn from(error: FdTableError) -> Self {
4837        map_error(error.code(), error.to_string())
4838    }
4839}
4840
4841impl From<PipeError> for KernelError {
4842    fn from(error: PipeError) -> Self {
4843        map_error(error.code(), error.to_string())
4844    }
4845}
4846
4847impl From<PtyError> for KernelError {
4848    fn from(error: PtyError) -> Self {
4849        map_error(error.code(), error.to_string())
4850    }
4851}
4852
4853impl From<ProcessTableError> for KernelError {
4854    fn from(error: ProcessTableError) -> Self {
4855        map_error(error.code(), error.to_string())
4856    }
4857}
4858
4859impl From<PermissionError> for KernelError {
4860    fn from(error: PermissionError) -> Self {
4861        map_error(error.code(), error.to_string())
4862    }
4863}
4864
4865impl From<ResourceError> for KernelError {
4866    fn from(error: ResourceError) -> Self {
4867        map_error(error.code(), error.to_string())
4868    }
4869}
4870
4871impl From<SocketTableError> for KernelError {
4872    fn from(error: SocketTableError) -> Self {
4873        map_error(error.code(), error.to_string())
4874    }
4875}
4876
4877impl From<RootFilesystemError> for KernelError {
4878    fn from(error: RootFilesystemError) -> Self {
4879        map_error("EINVAL", error.to_string())
4880    }
4881}
4882
4883fn map_dns_resolver_error(error: crate::dns::DnsResolverError) -> KernelError {
4884    let code = match error.kind() {
4885        DnsResolverErrorKind::InvalidInput => "EINVAL",
4886        DnsResolverErrorKind::LookupFailed => "EHOSTUNREACH",
4887    };
4888    map_error(code, error.to_string())
4889}
4890
4891fn map_error(code: &'static str, message: String) -> KernelError {
4892    let trimmed = strip_error_prefix(code, &message)
4893        .map(ToOwned::to_owned)
4894        .unwrap_or(message);
4895    KernelError::new(code, trimmed)
4896}
4897
4898fn strip_error_prefix<'a>(code: &str, message: &'a str) -> Option<&'a str> {
4899    let prefix = format!("{code}: ");
4900    message.strip_prefix(&prefix)
4901}
4902
4903fn parse_dev_fd_path(path: &str) -> KernelResult<Option<u32>> {
4904    let Some(raw_fd) = path.strip_prefix("/dev/fd/") else {
4905        return Ok(None);
4906    };
4907    if raw_fd.is_empty() {
4908        return Err(KernelError::new(
4909            "EBADF",
4910            format!("bad file descriptor: {path}"),
4911        ));
4912    }
4913    let fd = raw_fd
4914        .parse::<u32>()
4915        .map_err(|_| KernelError::new("EBADF", format!("bad file descriptor: {path}")))?;
4916    Ok(Some(fd))
4917}
4918
4919fn count_missing_directory_components<F: VirtualFileSystem>(
4920    filesystem: &mut F,
4921    path: &str,
4922    include_final: bool,
4923) -> VfsResult<usize> {
4924    let normalized = normalize_path(path);
4925    let parts = normalized
4926        .split('/')
4927        .filter(|part| !part.is_empty())
4928        .collect::<Vec<_>>();
4929    let limit = if include_final {
4930        parts.len()
4931    } else {
4932        parts.len().saturating_sub(1)
4933    };
4934
4935    let mut current = String::from("/");
4936    for (index, part) in parts.iter().take(limit).enumerate() {
4937        let candidate = if current == "/" {
4938            format!("/{}", part)
4939        } else {
4940            format!("{current}/{}", part)
4941        };
4942
4943        match filesystem.stat(&candidate) {
4944            Ok(stat) => {
4945                if !stat.is_directory {
4946                    return Err(VfsError::new(
4947                        "ENOTDIR",
4948                        format!("not a directory, mkdir '{candidate}'"),
4949                    ));
4950                }
4951                current = candidate;
4952            }
4953            Err(error) if error.code() == "ENOENT" => {
4954                return Ok(limit.saturating_sub(index));
4955            }
4956            Err(error) => return Err(error),
4957        }
4958    }
4959
4960    Ok(0)
4961}
4962
4963fn parent_path(path: &str) -> String {
4964    let normalized = normalize_path(path);
4965    let Some((head, _)) = normalized.rsplit_once('/') else {
4966        return String::from("/");
4967    };
4968
4969    if head.is_empty() {
4970        String::from("/")
4971    } else {
4972        String::from(head)
4973    }
4974}
4975
4976fn join_absolute_path(parent: &str, child: &str) -> String {
4977    if parent == "/" {
4978        format!("/{child}")
4979    } else {
4980        format!("{parent}/{child}")
4981    }
4982}
4983
4984fn join_child_path(parent: &str, child: &str) -> String {
4985    normalize_path(&join_absolute_path(parent, child))
4986}
4987
4988fn is_virtual_device_storage_path(path: &str) -> bool {
4989    matches!(
4990        path,
4991        "/dev/null" | "/dev/zero" | "/dev/stdin" | "/dev/stdout" | "/dev/stderr" | "/dev/urandom"
4992    ) || path == "/dev"
4993        || path == "/dev/fd"
4994        || path == "/dev/pts"
4995        || path.starts_with("/dev/fd/")
4996        || path.starts_with("/dev/pts/")
4997}
4998
4999fn is_storage_directory(stat: Option<&VirtualStat>) -> bool {
5000    stat.is_some_and(|stat| stat.is_directory && !stat.is_symbolic_link)
5001}
5002
5003fn is_proc_path(path: &str) -> bool {
5004    let normalized = normalize_path(path);
5005    normalized == "/proc" || normalized.starts_with("/proc/")
5006}
5007
5008fn is_agentos_path(path: &str) -> bool {
5009    let normalized = normalize_path(path);
5010    normalized == "/etc/agentos" || normalized.starts_with("/etc/agentos/")
5011}
5012
5013fn open_requires_write_access(flags: u32) -> bool {
5014    flags & (O_CREAT | O_EXCL | O_TRUNC) != 0 || (flags & 0b11) != crate::fd_table::O_RDONLY
5015}
5016
5017fn checked_write_end(offset: u64, len: usize) -> KernelResult<u64> {
5018    offset
5019        .checked_add(len as u64)
5020        .ok_or_else(|| KernelError::new("EINVAL", "write offset out of range"))
5021}
5022
5023fn filetype_for_path(path: &str, stat: &VirtualStat) -> u8 {
5024    if stat.is_directory {
5025        FILETYPE_DIRECTORY
5026    } else if path.starts_with("/dev/") {
5027        FILETYPE_CHARACTER_DEVICE
5028    } else if stat.is_symbolic_link {
5029        FILETYPE_SYMBOLIC_LINK
5030    } else {
5031        FILETYPE_REGULAR_FILE
5032    }
5033}
5034
5035fn synthetic_character_device_stat(ino: u64) -> VirtualStat {
5036    let now = now_ms();
5037    VirtualStat {
5038        mode: 0o666,
5039        size: 0,
5040        blocks: 0,
5041        dev: 2,
5042        rdev: 0,
5043        is_directory: false,
5044        is_symbolic_link: false,
5045        atime_ms: now,
5046        atime_nsec: 0,
5047        mtime_ms: now,
5048        mtime_nsec: 0,
5049        ctime_ms: now,
5050        ctime_nsec: 0,
5051        birthtime_ms: now,
5052        ino,
5053        nlink: 1,
5054        uid: 0,
5055        gid: 0,
5056    }
5057}
5058
5059fn proc_dir_stat(ino: u64) -> VirtualStat {
5060    let now = now_ms();
5061    VirtualStat {
5062        mode: 0o555,
5063        size: 0,
5064        blocks: 0,
5065        dev: 3,
5066        rdev: 0,
5067        is_directory: true,
5068        is_symbolic_link: false,
5069        atime_ms: now,
5070        atime_nsec: 0,
5071        mtime_ms: now,
5072        mtime_nsec: 0,
5073        ctime_ms: now,
5074        ctime_nsec: 0,
5075        birthtime_ms: now,
5076        ino,
5077        nlink: 2,
5078        uid: 0,
5079        gid: 0,
5080    }
5081}
5082
5083fn proc_file_stat(ino: u64, size: u64) -> VirtualStat {
5084    let now = now_ms();
5085    VirtualStat {
5086        mode: 0o444,
5087        size,
5088        blocks: if size == 0 { 0 } else { size.div_ceil(512) },
5089        dev: 3,
5090        rdev: 0,
5091        is_directory: false,
5092        is_symbolic_link: false,
5093        atime_ms: now,
5094        atime_nsec: 0,
5095        mtime_ms: now,
5096        mtime_nsec: 0,
5097        ctime_ms: now,
5098        ctime_nsec: 0,
5099        birthtime_ms: now,
5100        ino,
5101        nlink: 1,
5102        uid: 0,
5103        gid: 0,
5104    }
5105}
5106
5107fn proc_symlink_stat(ino: u64, size: u64) -> VirtualStat {
5108    let now = now_ms();
5109    VirtualStat {
5110        mode: 0o777,
5111        size,
5112        blocks: if size == 0 { 0 } else { size.div_ceil(512) },
5113        dev: 3,
5114        rdev: 0,
5115        is_directory: false,
5116        is_symbolic_link: true,
5117        atime_ms: now,
5118        atime_nsec: 0,
5119        mtime_ms: now,
5120        mtime_nsec: 0,
5121        ctime_ms: now,
5122        ctime_nsec: 0,
5123        birthtime_ms: now,
5124        ino,
5125        nlink: 1,
5126        uid: 0,
5127        gid: 0,
5128    }
5129}
5130
5131fn proc_filetype(node: &ProcNode) -> u8 {
5132    match node {
5133        ProcNode::RootDir | ProcNode::PidDir { .. } | ProcNode::PidFdDir { .. } => {
5134            FILETYPE_DIRECTORY
5135        }
5136        ProcNode::SelfLink { .. } | ProcNode::PidCwdLink { .. } | ProcNode::PidFdLink { .. } => {
5137            FILETYPE_SYMBOLIC_LINK
5138        }
5139        ProcNode::MountsFile
5140        | ProcNode::CpuInfoFile
5141        | ProcNode::MemInfoFile
5142        | ProcNode::LoadAvgFile
5143        | ProcNode::UptimeFile
5144        | ProcNode::VersionFile
5145        | ProcNode::PidCmdline { .. }
5146        | ProcNode::PidEnviron { .. }
5147        | ProcNode::PidStatFile { .. }
5148        | ProcNode::PidStatusFile { .. } => FILETYPE_REGULAR_FILE,
5149    }
5150}
5151
5152fn proc_inode(node: &ProcNode) -> u64 {
5153    match node {
5154        ProcNode::RootDir => 0xfffe_0001,
5155        ProcNode::MountsFile => 0xfffe_0002,
5156        ProcNode::CpuInfoFile => 0xfffe_0003,
5157        ProcNode::MemInfoFile => 0xfffe_0004,
5158        ProcNode::LoadAvgFile => 0xfffe_0005,
5159        ProcNode::UptimeFile => 0xfffe_0006,
5160        ProcNode::VersionFile => 0xfffe_0007,
5161        ProcNode::SelfLink { pid } => 0xfffe_1000 + u64::from(*pid),
5162        ProcNode::PidDir { pid } => 0xfffe_2000 + u64::from(*pid),
5163        ProcNode::PidFdDir { pid } => 0xfffe_3000 + u64::from(*pid),
5164        ProcNode::PidCmdline { pid } => 0xfffe_4000 + u64::from(*pid),
5165        ProcNode::PidEnviron { pid } => 0xfffe_5000 + u64::from(*pid),
5166        ProcNode::PidCwdLink { pid } => 0xfffe_6000 + u64::from(*pid),
5167        ProcNode::PidStatFile { pid } => 0xfffe_7000 + u64::from(*pid),
5168        ProcNode::PidStatusFile { pid } => 0xfffe_8000 + u64::from(*pid),
5169        ProcNode::PidFdLink { pid, fd } => 0xffff_0000 + ((u64::from(*pid)) << 8) + u64::from(*fd),
5170    }
5171}
5172
5173fn null_separated_bytes(parts: Vec<String>) -> Vec<u8> {
5174    if parts.is_empty() {
5175        return Vec::new();
5176    }
5177
5178    let mut bytes = parts.join("\0").into_bytes();
5179    bytes.push(0);
5180    bytes
5181}
5182
5183fn proc_not_found_error(path: &str) -> KernelError {
5184    KernelError::new(
5185        "ENOENT",
5186        format!("no such file or directory, stat '{path}'"),
5187    )
5188}
5189
5190fn read_only_filesystem_error(path: &str) -> KernelError {
5191    KernelError::new("EROFS", format!("read-only filesystem: {path}"))
5192}
5193
5194fn now_ms() -> u64 {
5195    SystemTime::now()
5196        .duration_since(UNIX_EPOCH)
5197        .unwrap_or_default()
5198        .as_millis() as u64
5199}
5200
5201impl<F> Drop for KernelVm<F> {
5202    fn drop(&mut self) {
5203        if !self.terminated {
5204            dispose_kernel_vm_resources(self);
5205        }
5206    }
5207}
5208
5209#[cfg(test)]
5210mod tests {
5211    use super::*;
5212    use crate::vfs::MemoryFileSystem;
5213    use std::panic::{catch_unwind, AssertUnwindSafe};
5214    use std::thread;
5215
5216    struct RetainedKernelResources {
5217        process: KernelProcessHandle,
5218        fd_tables: Arc<Mutex<FdTableManager>>,
5219        pipes: PipeManager,
5220        ptys: PtyManager,
5221        sockets: SocketTable,
5222        driver_pids: Arc<Mutex<BTreeMap<String, BTreeSet<u32>>>>,
5223    }
5224
5225    fn kernel_with_live_resources() -> (KernelVm<MemoryFileSystem>, RetainedKernelResources) {
5226        let mut config = KernelVmConfig::new("vm-drop-resources");
5227        config.permissions = Permissions::allow_all();
5228        let mut kernel = KernelVm::new(MemoryFileSystem::new(), config);
5229        kernel
5230            .register_driver(CommandDriver::new("shell", ["sh"]))
5231            .expect("register shell");
5232
5233        let process = kernel
5234            .spawn_process(
5235                "sh",
5236                Vec::new(),
5237                SpawnOptions {
5238                    requester_driver: Some(String::from("shell")),
5239                    ..SpawnOptions::default()
5240                },
5241            )
5242            .expect("spawn shell");
5243        let _ = kernel.open_pipe("shell", process.pid()).expect("open pipe");
5244        let _ = kernel.open_pty("shell", process.pid()).expect("open pty");
5245        let socket = kernel
5246            .socket_create("shell", process.pid(), SocketSpec::tcp())
5247            .expect("create socket");
5248        kernel
5249            .socket_set_state("shell", process.pid(), socket, SocketState::Listening)
5250            .expect("mark listener");
5251
5252        let retained = RetainedKernelResources {
5253            process: process.clone(),
5254            fd_tables: Arc::clone(&kernel.fd_tables),
5255            pipes: kernel.pipes.clone(),
5256            ptys: kernel.ptys.clone(),
5257            sockets: kernel.sockets.clone(),
5258            driver_pids: Arc::clone(&kernel.driver_pids),
5259        };
5260
5261        assert_eq!(lock_or_recover(retained.fd_tables.as_ref()).len(), 1);
5262        assert_eq!(retained.pipes.pipe_count(), 1);
5263        assert_eq!(retained.ptys.pty_count(), 1);
5264        assert_eq!(retained.sockets.snapshot().sockets, 1);
5265
5266        (kernel, retained)
5267    }
5268
5269    fn recursive_fs_kernel() -> KernelVm<MemoryFileSystem> {
5270        let mut config = KernelVmConfig::new("vm-recursive-fs");
5271        config.permissions = Permissions::allow_all();
5272        KernelVm::new(MemoryFileSystem::new(), config)
5273    }
5274
5275    #[test]
5276    fn recursive_copy_preserves_tree_metadata_and_symlinks() {
5277        let mut kernel = recursive_fs_kernel();
5278        kernel
5279            .mkdir("/src/nested", true)
5280            .expect("create source dirs");
5281        kernel
5282            .write_file("/src/nested/file.txt", b"hello".to_vec())
5283            .expect("write source file");
5284        kernel
5285            .chmod("/src/nested/file.txt", 0o640)
5286            .expect("chmod source file");
5287        kernel
5288            .chown("/src/nested/file.txt", 42, 43)
5289            .expect("chown source file");
5290        kernel
5291            .symlink("../nested/file.txt", "/src/link")
5292            .expect("create source symlink");
5293
5294        kernel
5295            .copy_path("/src", "/dst", true)
5296            .expect("recursive copy");
5297
5298        assert_eq!(
5299            kernel
5300                .read_file("/dst/nested/file.txt")
5301                .expect("read copied"),
5302            b"hello".to_vec()
5303        );
5304        let copied = kernel.lstat("/dst/nested/file.txt").expect("stat copied");
5305        assert_eq!(copied.mode & 0o777, 0o640);
5306        assert_eq!((copied.uid, copied.gid), (42, 43));
5307        let link = kernel.lstat("/dst/link").expect("lstat copied link");
5308        assert!(link.is_symbolic_link);
5309        assert_eq!(
5310            kernel.read_link("/dst/link").expect("read copied link"),
5311            "../nested/file.txt"
5312        );
5313    }
5314
5315    #[test]
5316    fn recursive_remove_deletes_subtree_but_does_not_follow_symlinks() {
5317        let mut kernel = recursive_fs_kernel();
5318        kernel.mkdir("/tree/dir", true).expect("create tree");
5319        kernel
5320            .write_file("/tree/dir/file.txt", b"tree".to_vec())
5321            .expect("write tree file");
5322        kernel
5323            .write_file("/outside.txt", b"outside".to_vec())
5324            .expect("write outside file");
5325        kernel
5326            .symlink("/outside.txt", "/tree/link-out")
5327            .expect("create symlink out of tree");
5328
5329        kernel.remove_path("/tree", true).expect("recursive remove");
5330
5331        assert!(!kernel.exists("/tree").expect("tree existence"));
5332        assert_eq!(
5333            kernel.read_file("/outside.txt").expect("outside survives"),
5334            b"outside".to_vec()
5335        );
5336    }
5337
5338    #[test]
5339    fn read_dir_recursive_respects_user_depth_and_reports_types() {
5340        let mut kernel = recursive_fs_kernel();
5341        kernel.mkdir("/root/a/b", true).expect("create deep tree");
5342        kernel
5343            .write_file("/root/a/file.txt", b"x".to_vec())
5344            .expect("write file");
5345        kernel
5346            .symlink("a/file.txt", "/root/link")
5347            .expect("create link");
5348
5349        let entries = kernel
5350            .read_dir_recursive("/root", Some(0))
5351            .expect("recursive listing");
5352        assert_eq!(entries.len(), 2);
5353        assert!(entries
5354            .iter()
5355            .any(|entry| entry.path == "/root/a" && entry.is_directory));
5356        assert!(entries
5357            .iter()
5358            .any(|entry| entry.path == "/root/link" && entry.is_symbolic_link));
5359        assert!(!entries.iter().any(|entry| entry.path == "/root/a/file.txt"));
5360    }
5361
5362    #[test]
5363    fn recursive_ops_enforce_depth_and_entry_bounds() {
5364        let mut depth_config = KernelVmConfig::new("vm-recursive-depth-limit");
5365        depth_config.permissions = Permissions::allow_all();
5366        depth_config.resources = ResourceLimits {
5367            max_recursive_fs_depth: Some(1),
5368            ..ResourceLimits::default()
5369        };
5370        let mut depth_kernel = KernelVm::new(MemoryFileSystem::new(), depth_config);
5371        depth_kernel
5372            .mkdir("/root/a/b", true)
5373            .expect("create deep tree");
5374
5375        let error = depth_kernel
5376            .copy_path("/root", "/copy", true)
5377            .expect_err("copy should hit depth limit");
5378        assert_eq!(error.code(), "ENOMEM");
5379        assert!(error.to_string().contains("depth 2"));
5380
5381        let mut entry_config = KernelVmConfig::new("vm-recursive-entry-limit");
5382        entry_config.permissions = Permissions::allow_all();
5383        entry_config.resources = ResourceLimits {
5384            max_recursive_fs_entries: Some(2),
5385            ..ResourceLimits::default()
5386        };
5387        let mut entry_kernel = KernelVm::new(MemoryFileSystem::new(), entry_config);
5388        entry_kernel.mkdir("/root", true).expect("create root");
5389        entry_kernel
5390            .write_file("/root/a.txt", b"a".to_vec())
5391            .expect("write a");
5392        entry_kernel
5393            .write_file("/root/b.txt", b"b".to_vec())
5394            .expect("write b");
5395        entry_kernel
5396            .write_file("/root/c.txt", b"c".to_vec())
5397            .expect("write c");
5398
5399        let error = entry_kernel
5400            .read_dir_recursive("/root", None)
5401            .expect_err("listing should hit entry limit");
5402        assert_eq!(error.code(), "ENOMEM");
5403        assert!(error.to_string().contains("3 entries"));
5404    }
5405
5406    fn assert_kernel_drop_released_resources(retained: &RetainedKernelResources) {
5407        assert_eq!(retained.process.wait(Duration::from_millis(50)), Some(143));
5408        assert_eq!(retained.process.kill_signals(), vec![15]);
5409        assert!(
5410            lock_or_recover(retained.fd_tables.as_ref()).is_empty(),
5411            "kernel drop should remove fd tables"
5412        );
5413        assert_eq!(
5414            retained.pipes.pipe_count(),
5415            0,
5416            "kernel drop should close pipes"
5417        );
5418        assert_eq!(
5419            retained.ptys.pty_count(),
5420            0,
5421            "kernel drop should close PTYs"
5422        );
5423        assert_eq!(
5424            retained.sockets.snapshot().sockets,
5425            0,
5426            "kernel drop should reclaim sockets"
5427        );
5428        assert!(
5429            lock_or_recover(retained.driver_pids.as_ref()).is_empty(),
5430            "kernel drop should clear driver-owned pid tracking"
5431        );
5432    }
5433
5434    #[test]
5435    fn setpgid_rejects_joining_a_process_group_owned_by_another_driver() {
5436        let kernel = KernelVm::new(MemoryFileSystem::new(), KernelVmConfig::new("vm-setpgid"));
5437
5438        let leader_pid = kernel.processes.allocate_pid().expect("allocate pid");
5439        kernel.processes.register(
5440            leader_pid,
5441            String::from("driver-a"),
5442            String::from("sh"),
5443            Vec::new(),
5444            ProcessContext {
5445                pid: leader_pid,
5446                ppid: 0,
5447                env: BTreeMap::new(),
5448                cwd: String::from("/"),
5449                umask: DEFAULT_PROCESS_UMASK,
5450                fds: Default::default(),
5451                identity: ProcessIdentity::default(),
5452                blocked_signals: SignalSet::empty(),
5453                pending_signals: SignalSet::empty(),
5454            },
5455            Arc::new(StubDriverProcess::default()),
5456        );
5457
5458        let peer_pid = kernel.processes.allocate_pid().expect("allocate pid");
5459        kernel.processes.register(
5460            peer_pid,
5461            String::from("driver-b"),
5462            String::from("sh"),
5463            Vec::new(),
5464            ProcessContext {
5465                pid: peer_pid,
5466                ppid: leader_pid,
5467                env: BTreeMap::new(),
5468                cwd: String::from("/"),
5469                umask: DEFAULT_PROCESS_UMASK,
5470                fds: Default::default(),
5471                identity: ProcessIdentity::default(),
5472                blocked_signals: SignalSet::empty(),
5473                pending_signals: SignalSet::empty(),
5474            },
5475            Arc::new(StubDriverProcess::default()),
5476        );
5477
5478        lock_or_recover(&kernel.driver_pids)
5479            .entry(String::from("driver-a"))
5480            .or_default()
5481            .insert(leader_pid);
5482        lock_or_recover(&kernel.driver_pids)
5483            .entry(String::from("driver-b"))
5484            .or_default()
5485            .insert(peer_pid);
5486
5487        let error = kernel
5488            .setpgid("driver-b", peer_pid, leader_pid)
5489            .expect_err("cross-driver process-group join should be denied");
5490        assert_eq!(error.code(), "EPERM");
5491    }
5492
5493    #[test]
5494    fn sigprocmask_and_sigpending_require_process_ownership() {
5495        let mut kernel = KernelVm::new(MemoryFileSystem::new(), KernelVmConfig::new("vm-sigmask"));
5496        let process = kernel
5497            .register_process(
5498                String::from("driver-a"),
5499                String::from("sleep"),
5500                Vec::new(),
5501                ProcessContext {
5502                    pid: 0,
5503                    ppid: 0,
5504                    env: BTreeMap::new(),
5505                    cwd: String::from("/"),
5506                    umask: DEFAULT_PROCESS_UMASK,
5507                    fds: Default::default(),
5508                    identity: ProcessIdentity::default(),
5509                    blocked_signals: SignalSet::empty(),
5510                    pending_signals: SignalSet::empty(),
5511                },
5512                None,
5513            )
5514            .expect("create virtual process");
5515        let mask =
5516            SignalSet::from_signal(crate::process_table::SIGCHLD).expect("SIGCHLD should be valid");
5517
5518        let previous = kernel
5519            .sigprocmask("driver-a", process.pid(), SigmaskHow::Block, mask)
5520            .expect("owner should update signal mask");
5521        assert_eq!(previous, SignalSet::empty());
5522        assert_eq!(
5523            kernel
5524                .sigpending("driver-a", process.pid())
5525                .expect("owner should read pending signals"),
5526            SignalSet::empty()
5527        );
5528
5529        let error = kernel
5530            .sigprocmask("driver-b", process.pid(), SigmaskHow::Block, mask)
5531            .expect_err("foreign driver should be rejected");
5532        assert_eq!(error.code(), "EPERM");
5533        let error = kernel
5534            .sigpending("driver-b", process.pid())
5535            .expect_err("foreign driver should be rejected");
5536        assert_eq!(error.code(), "EPERM");
5537    }
5538
5539    #[test]
5540    fn cleanup_process_resources_blocks_concurrent_dup2_until_pipe_cleanup_finishes() {
5541        let fd_tables = Arc::new(Mutex::new(FdTableManager::new()));
5542        let file_locks = FileLockManager::new();
5543        let pipes = PipeManager::new();
5544        let ptys = PtyManager::new();
5545        let sockets = SocketTable::new();
5546        let driver_pids = Arc::new(Mutex::new(BTreeMap::from([(
5547            String::from("driver"),
5548            BTreeSet::from([41]),
5549        )])));
5550        let pipe = pipes.create_pipe();
5551
5552        {
5553            let mut tables = lock_or_recover(fd_tables.as_ref());
5554            let table = tables.create(41);
5555            table
5556                .open_with(
5557                    Arc::clone(&pipe.read.description),
5558                    pipe.read.filetype,
5559                    Some(10),
5560                )
5561                .expect("open pipe read end");
5562            table
5563                .open_with(
5564                    Arc::clone(&pipe.write.description),
5565                    pipe.write.filetype,
5566                    Some(11),
5567                )
5568                .expect("open pipe write end");
5569        }
5570
5571        let hook_state = Arc::new((Mutex::new((false, false)), Condvar::new()));
5572        let hook_state_for_cleanup = Arc::clone(&hook_state);
5573        set_cleanup_process_resources_test_hook(Some(Arc::new(move || {
5574            let (state, wake) = &*hook_state_for_cleanup;
5575            let mut state = lock_or_recover(state);
5576            state.0 = true;
5577            wake.notify_all();
5578            while !state.1 {
5579                state = wake.wait(state).expect("wait for cleanup release");
5580            }
5581        })));
5582
5583        let fd_tables_for_cleanup = Arc::clone(&fd_tables);
5584        let pipes_for_cleanup = pipes.clone();
5585        let driver_pids_for_cleanup = Arc::clone(&driver_pids);
5586        let cleanup_thread = thread::spawn(move || {
5587            cleanup_process_resources(
5588                fd_tables_for_cleanup.as_ref(),
5589                &file_locks,
5590                &pipes_for_cleanup,
5591                &ptys,
5592                &sockets,
5593                driver_pids_for_cleanup.as_ref(),
5594                41,
5595            );
5596        });
5597
5598        {
5599            let (state, wake) = &*hook_state;
5600            let mut state = lock_or_recover(state);
5601            while !state.0 {
5602                state = wake.wait(state).expect("wait for cleanup hook");
5603            }
5604        }
5605
5606        let fd_tables_for_dup = Arc::clone(&fd_tables);
5607        let dup_thread = thread::spawn(move || {
5608            let mut tables = lock_or_recover(fd_tables_for_dup.as_ref());
5609            let Some(table) = tables.get_mut(41) else {
5610                return Err(String::from("ESRCH"));
5611            };
5612            table.dup2(10, 12).map_err(|error| error.code().to_string())
5613        });
5614
5615        {
5616            let (state, wake) = &*hook_state;
5617            let mut state = lock_or_recover(state);
5618            state.1 = true;
5619            wake.notify_all();
5620        }
5621
5622        cleanup_thread.join().expect("cleanup thread should finish");
5623        let dup_result = dup_thread.join().expect("dup thread should finish");
5624        set_cleanup_process_resources_test_hook(None);
5625
5626        assert_eq!(dup_result, Err(String::from("ESRCH")));
5627        assert!(
5628            lock_or_recover(fd_tables.as_ref()).get(41).is_none(),
5629            "cleanup should remove the process FD table"
5630        );
5631        assert_eq!(pipes.pipe_count(), 0, "pipe cleanup should not leak");
5632        assert!(
5633            lock_or_recover(driver_pids.as_ref())
5634                .get("driver")
5635                .is_none_or(|pids| pids.is_empty()),
5636            "driver ownership should be cleared"
5637        );
5638    }
5639
5640    #[test]
5641    fn drop_disposes_live_kernel_vm_resources() {
5642        let (kernel, retained) = kernel_with_live_resources();
5643        drop(kernel);
5644        assert_kernel_drop_released_resources(&retained);
5645    }
5646
5647    #[test]
5648    fn drop_during_panic_still_disposes_live_kernel_vm_resources() {
5649        let retained = Arc::new(Mutex::new(None::<RetainedKernelResources>));
5650        let retained_for_panic = Arc::clone(&retained);
5651
5652        let panic_result = catch_unwind(AssertUnwindSafe(move || {
5653            let (kernel, resources) = kernel_with_live_resources();
5654            *lock_or_recover(retained_for_panic.as_ref()) = Some(resources);
5655            let _kernel = kernel;
5656            panic!("intentional panic to exercise KernelVm::drop");
5657        }));
5658
5659        assert!(panic_result.is_err(), "panic should be observed");
5660        let retained = lock_or_recover(retained.as_ref())
5661            .take()
5662            .expect("panic path should retain resources for assertions");
5663        assert_kernel_drop_released_resources(&retained);
5664    }
5665}