Skip to main content

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