Skip to main content

agentos_kernel/
kernel.rs

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