diff --git a/src/builtins/cd.rs b/src/builtins/cd.rs index 739909611..d5468eca2 100644 --- a/src/builtins/cd.rs +++ b/src/builtins/cd.rs @@ -12,6 +12,7 @@ use errno::Errno; use libc::{EACCES, ELOOP, ENOENT, ENOTDIR, EPERM}; use nix::unistd::fchdir; +use std::sync::Arc; // 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. @@ -82,35 +83,42 @@ pub fn cd(parser: &mut Parser, streams: &mut IoStreams, args: &mut [&wstr]) -> B let res = wopen_dir(&norm_dir, BEST_O_SEARCH).map_err(|err| err as i32); let res = res.and_then(|fd| { - fchdir(&fd).map_err(|_| + fchdir(&fd) + .map_err(|_| // nix::Result::Err contains nix::errno::Errno, which does not offer an API for // converting to a raw int. errno::errno().0) + .map(|()| fd) }); - if let Err(err) = res { - // Some errors we skip and only report if nothing worked. - // ENOENT in particular is very low priority - // - if in another directory there was a *file* by the correct name - // we prefer *that* error because it's more specific - if err == ENOENT { - let tmp = wreadlink(&norm_dir); - // clippy doesn't like this is_some/unwrap pair, but using if let is harder to read IMO - // TODO: if-let-chains - if let Some(tmp) = tmp.filter(|_| broken_symlink.is_empty()) { - broken_symlink = norm_dir; - broken_symlink_target = tmp; - } else if best_errno == 0 { - best_errno = errno::errno().0; + let fd = match res { + Ok(fd) => fd, + Err(err) => { + // Some errors we skip and only report if nothing worked. + // ENOENT in particular is very low priority + // - if in another directory there was a *file* by the correct name + // we prefer *that* error because it's more specific + if err == ENOENT { + let tmp = wreadlink(&norm_dir); + if let Some(tmp) = tmp.filter(|_| broken_symlink.is_empty()) { + broken_symlink = norm_dir; + broken_symlink_target = tmp; + } else if best_errno == 0 { + best_errno = errno::errno().0; + } + continue; + } else if err == ENOTDIR { + best_errno = err; + continue; } - continue; - } else if err == ENOTDIR { best_errno = err; - continue; + break; } - best_errno = err; - break; - } + }; + + // 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)); parser.set_var_and_fire( L!("PWD"), diff --git a/src/parser.rs b/src/parser.rs index 08acdfe38..c82444a82 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -11,6 +11,7 @@ }, event::{self, Event}, expand::{ExpandFlags, ExpandResultCode, expand_string, replace_home_directory_with_tilde}, + fds::{BEST_O_SEARCH, open_dir}, flog, flogf, function, io::IoChain, job_group::MaybeJobId, @@ -25,6 +26,7 @@ proc::{InternalJobId, JobGroupRef, JobList, JobRef, Pid, ProcStatus, job_reap}, signal::{RawSignal, signal_check_cancel, signal_clear_cancel}, wait_handle::WaitHandleStore, + wutil::perror_nix, }; use assert_matches::assert_matches; use fish_common::{ @@ -38,6 +40,7 @@ use std::io::Write as _; use std::num::NonZeroU32; use std::ops::DerefMut; +use std::os::fd::OwnedFd; use std::rc::Rc; use std::sync::Arc; use std::time::Duration; @@ -271,6 +274,10 @@ pub struct LibraryData { /// the command line. pub transient_commandline: ScopedRefCell>, + /// A file descriptor holding the current working directory, for use in openat(). + /// This is never null and never invalid. + pub cwd_fd: Option>, + /// Variables supporting the "status" builtin. pub status_vars: StatusVars, @@ -443,7 +450,7 @@ pub fn user(mode: EnvMode) -> Self { impl Parser { /// Create a parser. pub fn new(variables: EnvStack, cancel_behavior: CancelBehavior) -> Parser { - let result = Self { + let mut result = Self { interactive_initialized: false, current_node: ScopedRefCell::new(None), current_filename: ScopedRefCell::new(None), @@ -462,6 +469,15 @@ pub fn new(variables: EnvStack, cancel_behavior: CancelBehavior) -> Parser { test_only_suppress_stderr: false, }; + match open_dir(c".", BEST_O_SEARCH) { + Ok(fd) => { + result.libdata_mut().cwd_fd = Some(Arc::new(fd)); + } + Err(err) => { + perror_nix("Unable to open the current working directory", err); + } + } + result }