Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions crates/bridge/bridge-contract.json
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,7 @@
],
"returnType": "unknown",
"names": [
"process.chdir",
"process.cpuUsage",
"process.fcntlLock",
"process.flock",
Expand Down
21 changes: 21 additions & 0 deletions crates/kernel/src/kernel.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1673,6 +1673,27 @@ impl<F: VirtualFileSystem + 'static> KernelVm<F> {
}
}

/// The process working directory, which relative path lookup, child
/// spawns and `/proc/<pid>/cwd` read.
pub fn cwd(&self, requester_driver: &str, pid: u32) -> KernelResult<String> {
self.assert_driver_owns(requester_driver, pid)?;
Ok(self.processes.get_cwd(pid)?)
}

/// Records a new working directory after the caller has resolved `cwd` to
/// an existing directory, as `chdir(2)` does.
pub fn set_cwd(&mut self, requester_driver: &str, pid: u32, cwd: &str) -> KernelResult<()> {
self.assert_not_terminated()?;
self.assert_driver_owns(requester_driver, pid)?;
if !cwd.starts_with('/') {
return Err(KernelError::new(
"EINVAL",
format!("process cwd must be absolute, got {cwd:?}"),
));
}
Ok(self.processes.set_cwd(pid, normalize_path(cwd))?)
}

pub fn exists(&self, path: &str) -> KernelResult<bool> {
self.assert_not_terminated()?;
self.exists_internal(None, path)
Expand Down
16 changes: 16 additions & 0 deletions crates/kernel/src/process_table.rs
Original file line number Diff line number Diff line change
Expand Up @@ -852,6 +852,22 @@ impl ProcessTable {
.ok_or_else(|| ProcessTableError::no_such_process(pid))
}

pub fn get_cwd(&self, pid: u32) -> ProcessResult<String> {
self.get(pid)
.map(|entry| entry.cwd)
.ok_or_else(|| ProcessTableError::no_such_process(pid))
}

pub fn set_cwd(&self, pid: u32, cwd: String) -> ProcessResult<()> {
let mut state = self.inner.lock_state();
let record = state
.entries
.get_mut(&pid)
.ok_or_else(|| ProcessTableError::no_such_process(pid))?;
record.entry.cwd = cwd;
Ok(())
}

pub fn get_umask(&self, pid: u32) -> ProcessResult<u32> {
self.get(pid)
.map(|entry| entry.umask)
Expand Down
42 changes: 42 additions & 0 deletions crates/native-sidecar/src/execution/child_process.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2320,6 +2320,34 @@ pub(super) fn apply_posix_spawn_file_actions(
))
}

/// A child starts in its parent's current directory and resolves a relative
/// `cwd` option against it, as `chdir` after `fork` does. The parent's
/// `guest_cwd` is its launch-time host mapping anchor, which `process.chdir`
/// does not move, so the current directory comes from the kernel.
fn start_child_in_parent_cwd(
vm: &VmState,
parent: &ActiveProcess,
request: &mut JavascriptChildProcessSpawnRequest,
) -> Result<(), SidecarError> {
let current = vm
.kernel
.cwd(EXECUTION_DRIVER_NAME, parent.kernel_pid)
.map_err(kernel_error)?;
match request.options.cwd.as_deref() {
Some(cwd) if !cwd.starts_with('/') => {
request.options.cwd = Some(normalize_path(&format!(
"{}/{cwd}",
current.trim_end_matches('/')
)));
}
None if current != normalize_path(&parent.guest_cwd) => {
request.options.cwd = Some(current);
}
_ => {}
}
Ok(())
}

pub(super) fn resolve_posix_spawn_action_path(cwd: &str, action_path: &str) -> String {
if action_path.starts_with('/') {
normalize_path(action_path)
Expand Down Expand Up @@ -6029,6 +6057,20 @@ where
let requested_pgid = spawn_attributes.process_group;
let current_process_label =
NativeSidecar::<B>::child_process_path_label(process_id, current_process_path);
{
let vm = vms.get(vm_id).ok_or_else(|| missing_vm_error(vm_id))?;
let root = vm
.active_processes
.get(process_id)
.ok_or_else(|| missing_process_error(vm_id, process_id))?;
let parent = NativeSidecar::<B>::active_process_by_path(root, current_process_path)
.ok_or_else(|| {
SidecarError::InvalidState(format!(
"unknown child process path {current_process_label} during spawn cwd resolution"
))
})?;
start_child_in_parent_cwd(&vm, parent, &mut request)?;
}
let parent_sync_roots = {
let vm = vms.get(vm_id).ok_or_else(|| missing_vm_error(vm_id))?;
let root = vm
Expand Down
30 changes: 26 additions & 4 deletions crates/native-sidecar/src/execution/javascript/rpc.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use super::super::*;
use crate::filesystem::{
javascript_sync_rpc_path_arg, remove_process_shadow_path, rename_process_shadow_path,
javascript_fs_stat_value, javascript_sync_rpc_path_arg, remove_process_shadow_path,
rename_process_shadow_path,
};
use agentos_kernel::vfs::{VirtualTimeSpec, VirtualUtimeSpec};

Expand Down Expand Up @@ -641,8 +642,13 @@ where
}
if request.raw_bytes_args.contains_key(&usize::MAX) && request.method == "fs.readFileRangeSync"
{
let path =
javascript_sync_rpc_path_arg(process, &request.args, 0, "filesystem ranged read path")?;
let path = javascript_sync_rpc_path_arg(
kernel,
process,
&request.args,
0,
"filesystem ranged read path",
)?;
let offset =
javascript_sync_rpc_arg_u64(&request.args, 1, "filesystem ranged read offset")?;
let length = usize::try_from(javascript_sync_rpc_arg_u64(
Expand Down Expand Up @@ -2056,6 +2062,22 @@ where
"action": "default",
}))
}
"process.chdir" => {
let path =
javascript_sync_rpc_path_arg(kernel, process, &request.args, 0, "chdir path")?;
let stat = javascript_fs_stat_value(kernel, process, process.kernel_pid, &path)?;
if stat.get("isDirectory") != Some(&Value::Bool(true)) {
return Err(SidecarError::Errno {
errno: "ENOTDIR",
message: format!("not a directory: {path}"),
});
}
kernel
.set_cwd(EXECUTION_DRIVER_NAME, process.kernel_pid, &path)
.and_then(|()| kernel.cwd(EXECUTION_DRIVER_NAME, process.kernel_pid))
.map(Value::String)
.map_err(kernel_error)
}
"process.umask" => {
let new_mask = javascript_sync_rpc_arg_u32_optional(&request.args, 0, "process umask")?;
kernel
Expand Down Expand Up @@ -2342,7 +2364,7 @@ fn mirror_process_chmod_to_host(
request: &JavascriptSyncRpcRequest,
) -> Result<(), SidecarError> {
let guest_path =
javascript_sync_rpc_path_arg(process, &request.args, 0, "filesystem chmod path")?;
javascript_sync_rpc_path_arg(kernel, process, &request.args, 0, "filesystem chmod path")?;
let guest_path = if guest_path.starts_with('/') {
normalize_path(&guest_path)
} else {
Expand Down
Loading
Loading