mirror of
https://github.com/herdrdev/herdr.git
synced 2026-11-06 00:06:18 +00:00
+2
-2
@@ -3793,14 +3793,14 @@ impl PaneRuntime {
|
||||
}
|
||||
|
||||
let pid = self.child_pid.load(Ordering::Relaxed);
|
||||
crate::platform::process_cwd(pid)
|
||||
crate::platform::pane_process_cwd(pid)
|
||||
}
|
||||
|
||||
pub fn cwd_for_persistence(&self) -> Option<std::path::PathBuf> {
|
||||
let pid = self.child_pid.load(Ordering::Acquire);
|
||||
let exited = self.cwd_process_exited.load(Ordering::Acquire);
|
||||
if let Some(cwd) = (!exited)
|
||||
.then(|| crate::platform::process_cwd(pid))
|
||||
.then(|| crate::platform::pane_process_cwd(pid))
|
||||
.flatten()
|
||||
.filter(|cwd| cwd.is_absolute())
|
||||
{
|
||||
|
||||
@@ -414,6 +414,9 @@ mod windows;
|
||||
#[cfg(target_os = "windows")]
|
||||
pub use windows::*;
|
||||
|
||||
#[cfg(not(windows))]
|
||||
pub(crate) use process_cwd as pane_process_cwd;
|
||||
|
||||
#[cfg(not(any(target_os = "linux", target_os = "macos", target_os = "windows")))]
|
||||
mod fallback;
|
||||
#[cfg(not(any(target_os = "linux", target_os = "macos", target_os = "windows")))]
|
||||
|
||||
+188
-29
@@ -446,6 +446,11 @@ pub(crate) fn set_default_plugin_pane_pwd(
|
||||
) {
|
||||
}
|
||||
|
||||
#[cfg(target_pointer_width = "64")]
|
||||
use windows_sys::{
|
||||
Wdk::System::Threading::ProcessWow64Information, Win32::System::Kernel::STRING32,
|
||||
};
|
||||
|
||||
use windows_sys::{
|
||||
Wdk::System::Threading::ProcessCommandLineInformation,
|
||||
Wdk::System::Threading::{NtQueryInformationProcess, ProcessBasicInformation},
|
||||
@@ -1580,12 +1585,80 @@ pub fn foreground_process_group_id(child_pid: u32) -> Option<u32> {
|
||||
|
||||
pub fn process_cwd(pid: u32) -> Option<PathBuf> {
|
||||
let process = ProcessHandle::open(pid, PROCESS_QUERY_LIMITED_INFORMATION | PROCESS_VM_READ)?;
|
||||
let process_parameters = read_process_parameters(process.0)?;
|
||||
read_unicode_string(process.0, process_parameters.current_directory.dos_path)
|
||||
process_cwd_from_handle(process.0)
|
||||
}
|
||||
|
||||
pub(crate) fn pane_process_cwd(child_pid: u32) -> Option<PathBuf> {
|
||||
let process = ProcessHandle::open(
|
||||
child_pid,
|
||||
PROCESS_QUERY_LIMITED_INFORMATION | PROCESS_VM_READ,
|
||||
)?;
|
||||
let executable = PathBuf::from(process_executable_path(process.0)?);
|
||||
if executable.with_extension("shim").is_file() {
|
||||
// A Scoop shim keeps its launch cwd while the real shell can change directories.
|
||||
// Never replace the saved shell directory with that launcher directory on exit.
|
||||
let snapshot = cached_foreground_processes();
|
||||
return process_cwd(shim_shell_pid(child_pid, &snapshot)?);
|
||||
}
|
||||
process_cwd_from_handle(process.0)
|
||||
}
|
||||
|
||||
fn process_cwd_from_handle(process: HANDLE) -> Option<PathBuf> {
|
||||
read_unicode_string(process, process_cwd_descriptor(process)?)
|
||||
.map(PathBuf::from)
|
||||
.filter(|path| path.is_absolute())
|
||||
}
|
||||
|
||||
fn process_cwd_descriptor(process: HANDLE) -> Option<UNICODE_STRING> {
|
||||
#[cfg(target_pointer_width = "64")]
|
||||
{
|
||||
let mut peb32_address = 0_usize;
|
||||
// SAFETY: the output is a writable ULONG_PTR with its exact buffer size.
|
||||
let status = unsafe {
|
||||
NtQueryInformationProcess(
|
||||
process,
|
||||
ProcessWow64Information,
|
||||
(&mut peb32_address as *mut usize).cast(),
|
||||
size_of::<usize>() as u32,
|
||||
null_mut(),
|
||||
)
|
||||
};
|
||||
if status != STATUS_SUCCESS as NTSTATUS {
|
||||
return None;
|
||||
}
|
||||
if peb32_address != 0 {
|
||||
// WoW64's native PEB can report C:\Windows while the x86 shell uses
|
||||
// another directory. Read its own pointer-width layout instead.
|
||||
let peb = read_process_value::<Peb32>(process, peb32_address as *const c_void)?;
|
||||
let parameters = read_process_value::<ProcessCwdParameters32>(
|
||||
process,
|
||||
peb.process_parameters as usize as *const c_void,
|
||||
)?;
|
||||
let cwd = parameters.current_directory;
|
||||
return Some(UNICODE_STRING {
|
||||
Length: cwd.Length,
|
||||
MaximumLength: cwd.MaximumLength,
|
||||
Buffer: cwd.Buffer as usize as *mut u16,
|
||||
});
|
||||
}
|
||||
}
|
||||
Some(read_process_parameters(process)?.current_directory.dos_path)
|
||||
}
|
||||
|
||||
fn shim_shell_pid(child_pid: u32, snapshot: &ProcessSnapshot) -> Option<u32> {
|
||||
snapshot.entry(child_pid)?;
|
||||
let mut shells = snapshot
|
||||
.children_by_parent
|
||||
.get(&child_pid)?
|
||||
.iter()
|
||||
.filter_map(|&index| {
|
||||
let entry = &snapshot.entries[index];
|
||||
super::is_pane_shell_process_name(&entry.name).then_some(entry.pid)
|
||||
});
|
||||
let shell_pid = shells.next()?;
|
||||
shells.next().is_none().then_some(shell_pid)
|
||||
}
|
||||
|
||||
fn select_pane_foreground_job_cached(shell_pid: u32) -> Option<ForegroundJob> {
|
||||
let snapshot = cached_foreground_processes();
|
||||
let (job, retry_with_fresh_snapshot) =
|
||||
@@ -2864,6 +2937,24 @@ struct Peb {
|
||||
process_parameters: *mut RtlUserProcessParameters,
|
||||
}
|
||||
|
||||
// Prefixes of the x86 PEB and RTL_USER_PROCESS_PARAMETERS through the fields
|
||||
// needed for cwd; remote pointers must stay 32-bit on a 64-bit reader.
|
||||
#[cfg(target_pointer_width = "64")]
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Copy)]
|
||||
struct Peb32 {
|
||||
reserved: [u32; 4],
|
||||
process_parameters: u32,
|
||||
}
|
||||
|
||||
#[cfg(target_pointer_width = "64")]
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Copy)]
|
||||
struct ProcessCwdParameters32 {
|
||||
reserved: [u32; 9],
|
||||
current_directory: STRING32,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Copy)]
|
||||
struct CurDir {
|
||||
@@ -4022,40 +4113,108 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn windows_process_cwd_reads_normalized_child_launch_directory() {
|
||||
use std::path::PathBuf;
|
||||
|
||||
let name = format!("Herdr-Cwd-Case-{}", std::process::id());
|
||||
let cwd = std::env::temp_dir().join(&name);
|
||||
fs::create_dir_all(&cwd).expect("create cwd fixture");
|
||||
let cwd = super::normalize_cwd_for_launch_platform(&cwd);
|
||||
let launch_cwd = cwd.with_file_name(name.to_ascii_lowercase());
|
||||
|
||||
let shell =
|
||||
std::env::var_os("ComSpec").unwrap_or_else(|| r"C:\Windows\System32\cmd.exe".into());
|
||||
let mut child = Command::new(shell)
|
||||
.args(["/D", "/Q", "/C", "ping -n 11 127.0.0.1 > NUL"])
|
||||
.current_dir(super::normalize_cwd_for_launch_platform(&launch_cwd))
|
||||
.stdin(Stdio::null())
|
||||
.stdout(Stdio::null())
|
||||
.stderr(Stdio::null())
|
||||
.spawn()
|
||||
.expect("spawn cmd");
|
||||
|
||||
let deadline = Instant::now() + Duration::from_secs(5);
|
||||
let mut observed = None;
|
||||
while Instant::now() < deadline {
|
||||
observed = super::process_cwd(child.id());
|
||||
if observed.as_ref().and_then(|path| path.file_name()) == Some(name.as_ref()) {
|
||||
break;
|
||||
}
|
||||
thread::sleep(Duration::from_millis(100));
|
||||
let changed = cwd.join("Changed");
|
||||
fs::create_dir(&changed).expect("create changed cwd");
|
||||
let windows = PathBuf::from(std::env::var_os("SystemRoot").expect("Windows SystemRoot"));
|
||||
let shells = [
|
||||
windows.join("System32").join("cmd.exe"),
|
||||
#[cfg(target_pointer_width = "64")]
|
||||
windows.join("SysWOW64").join("cmd.exe"),
|
||||
];
|
||||
let mut observations = Vec::new();
|
||||
for shell in shells {
|
||||
let mut child = Command::new(&shell)
|
||||
.args(["/D", "/Q", "/K"])
|
||||
.current_dir(super::normalize_cwd_for_launch_platform(&launch_cwd))
|
||||
.stdin(Stdio::piped())
|
||||
.stdout(Stdio::null())
|
||||
.stderr(Stdio::null())
|
||||
.spawn()
|
||||
.expect("spawn cmd");
|
||||
let observe = |expected: &PathBuf| {
|
||||
let deadline = Instant::now() + Duration::from_secs(5);
|
||||
loop {
|
||||
let observed = super::process_cwd(child.id());
|
||||
if observed.as_ref() == Some(expected) || Instant::now() >= deadline {
|
||||
break observed;
|
||||
}
|
||||
thread::sleep(Duration::from_millis(20));
|
||||
}
|
||||
};
|
||||
let initial = observe(&cwd);
|
||||
use std::io::Write;
|
||||
writeln!(
|
||||
child.stdin.as_ref().unwrap(),
|
||||
"cd /d \"{}\"\r",
|
||||
changed.display()
|
||||
)
|
||||
.expect("change cmd cwd");
|
||||
let after_cd = observe(&changed);
|
||||
let pane_cwd = super::pane_process_cwd(child.id());
|
||||
let _ = child.kill();
|
||||
let _ = child.wait();
|
||||
observations.push((shell, initial, after_cd, pane_cwd));
|
||||
}
|
||||
|
||||
let _ = child.kill();
|
||||
let _ = child.wait();
|
||||
let _ = fs::remove_dir_all(&cwd);
|
||||
for (shell, initial, after_cd, pane_cwd) in observations {
|
||||
assert_eq!(initial, Some(cwd.clone()), "{} launch cwd", shell.display());
|
||||
assert_eq!(
|
||||
after_cd,
|
||||
Some(changed.clone()),
|
||||
"{} live cd",
|
||||
shell.display()
|
||||
);
|
||||
assert_eq!(pane_cwd, Some(changed.clone()), "ordinary shell owns cwd");
|
||||
}
|
||||
}
|
||||
|
||||
assert_eq!(
|
||||
observed.as_ref().and_then(|path| path.file_name()),
|
||||
Some(name.as_ref())
|
||||
);
|
||||
#[test]
|
||||
fn windows_shim_cwd_uses_only_an_unambiguous_direct_shell() {
|
||||
let cases = [
|
||||
("cmdx.exe", vec![(11, 10, "cmd.exe")], Some(11)),
|
||||
("pwsh.exe", vec![(11, 10, "pwsh.exe")], Some(11)),
|
||||
(
|
||||
"cmdx.exe",
|
||||
vec![
|
||||
(11, 10, "cmd.exe"),
|
||||
(12, 11, "pwsh.exe"),
|
||||
(13, 11, "node.exe"),
|
||||
],
|
||||
Some(11),
|
||||
),
|
||||
(
|
||||
"cmdx.exe",
|
||||
vec![(11, 10, "node.exe"), (12, 11, "cmd.exe")],
|
||||
None,
|
||||
),
|
||||
("cmdx.exe", vec![], None),
|
||||
(
|
||||
"cmdx.exe",
|
||||
vec![(11, 10, "cmd.exe"), (12, 10, "pwsh.exe")],
|
||||
None,
|
||||
),
|
||||
];
|
||||
for (root_name, children, expected) in cases {
|
||||
let mut entries = vec![test_entry(10, 1, root_name, &[root_name])];
|
||||
entries.extend(
|
||||
children
|
||||
.iter()
|
||||
.map(|&(pid, parent, name)| test_entry(pid, parent, name, &[name])),
|
||||
);
|
||||
let snapshot = super::ProcessSnapshot::new(entries);
|
||||
assert_eq!(
|
||||
super::shim_shell_pid(10, &snapshot),
|
||||
expected,
|
||||
"{root_name}: {children:?}"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
Reference in New Issue
Block a user