env: fix stale $PWD after mv $PWD elsewhere

Fixes #12700
This commit is contained in:
Vishrut Sachan
2026-05-06 06:46:50 +05:30
committed by Peter Ammon
parent de0e519b22
commit 411a43254b
2 changed files with 56 additions and 18 deletions

View File

@@ -12,7 +12,6 @@
use errno::Errno;
use libc::{EACCES, ELOOP, ENOENT, ENOTDIR, EPERM};
use nix::unistd::fchdir;
use std::sync::Arc;
// cd is highlighted specially in src/highlight/highlight.rs - new options also need to be added
// there
@@ -23,6 +22,31 @@
wopt(L!("dereference"), ArgType::NoArgument, 'P'),
];
fn try_chdir(dirs: Vec<WString>, parser: &mut Parser, deref_symlink: bool) -> bool {
for dir in dirs {
let norm_dir = normalize_path(&dir, true);
if let Ok(fd) = wopen_dir(&norm_dir, BEST_O_SEARCH) {
if fchdir(&fd).is_ok() {
parser.libdata_mut().cwd_fd = Some(std::sync::Arc::new(fd));
let mut new_pwd = norm_dir;
if deref_symlink {
if let Some(real_dir) = wrealpath(&new_pwd) {
new_pwd = real_dir;
}
}
parser.set_var_and_fire(
L!("PWD"),
ParserEnvSetMode::new(EnvMode::EXPORT | EnvMode::GLOBAL),
vec![new_pwd],
);
return true;
}
}
}
false
}
// The cd builtin. Changes the current directory to the one specified or to $HOME if none is
// specified. The directory can be relative to any directory in the CDPATH variable.
pub fn cd(parser: &mut Parser, streams: &mut IoStreams, args: &mut [&wstr]) -> BuiltinResult {
@@ -107,6 +131,18 @@ pub fn cd(parser: &mut Parser, streams: &mut IoStreams, args: &mut [&wstr]) -> B
}
}
let is_relative_to_cwd = [L!("."), L!("..")].contains(&dir_in)
|| dir_in.starts_with(L!("./"))
|| dir_in.starts_with(L!("../"));
if is_relative_to_cwd && wopen_dir(&normalize_path(&pwd, true), BEST_O_SEARCH).is_err() {
if let Some(mut real_pwd) = wrealpath(L!(".")) {
if !real_pwd.ends_with('/') {
real_pwd.push('/');
}
pwd = real_pwd;
}
}
let dirs = path_apply_cdpath(dir_in, &pwd, vars);
assert!(
!dirs.is_empty(),
@@ -133,7 +169,7 @@ pub fn cd(parser: &mut Parser, streams: &mut IoStreams, args: &mut [&wstr]) -> B
.map(|()| fd)
});
let fd = match res {
let _fd = match res {
Ok(fd) => fd,
Err(err) => {
// Some errors we skip and only report if nothing worked.
@@ -158,23 +194,9 @@ pub fn cd(parser: &mut Parser, streams: &mut IoStreams, args: &mut [&wstr]) -> B
}
};
// Stash the fd for the cwd in the parser so it stays open.
// Eventually fish will support distinct CWDs for different parsers in different threads.
parser.libdata_mut().cwd_fd = Some(Arc::new(fd));
let mut new_pwd = norm_dir;
if deref_symlink {
if let Some(real_dir) = wrealpath(&new_pwd) {
new_pwd = real_dir;
}
if try_chdir(vec![dir], parser, deref_symlink) {
return Ok(SUCCESS);
}
parser.set_var_and_fire(
L!("PWD"),
ParserEnvSetMode::new(EnvMode::EXPORT | EnvMode::GLOBAL),
vec![new_pwd],
);
return Ok(SUCCESS);
}
let mut err = if best_errno == ENOTDIR {

View File

@@ -430,3 +430,19 @@ cd (string repeat 4096 a)
# named "bin")
cd /
cd bin
# Test that cd works after the current directory has been moved (issue #12700)
if __fish_is_cygwin
# Not supported on Cygwin/MSYS. Satisfy the CHECK below.
echo "cd after move succeeded"
else
set -l tmp (mktemp -d)
set -l tmp_moved {$tmp}_moved
cd $tmp
mv $tmp $tmp_moved
cd .
test $PWD = $tmp_moved
and echo "cd after move succeeded"
cd $tmp_moved
end
# CHECK: cd after move succeeded