Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

change cd to handle not readable directories: chdir+open instead of open+fchdir #10436

Closed
wants to merge 6 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,7 @@ Other improvements
- ``fish_indent`` will now collapse multiple successive empty lines into one (:issue:`10325`).
- The HTML-based configuration UI (``fish_config``) now uses Alpine.js instead of AngularJS (:issue:`9554`).
- ``fish_config`` now also works in a Windows MSYS environment (:issue:`10111`).
- `cd` into a directory that is not readable but accessile (permissions `--x`) is now possible (:issue:`10432`).

.. _rust-packaging:

Expand Down
28 changes: 8 additions & 20 deletions src/builtins/cd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,14 @@

use super::prelude::*;
use crate::{
common::wcs2zstring,
env::{EnvMode, Environment},
fds::wopen_dir,
path::path_apply_cdpath,
wutil::{normalize_path, wperror, wreadlink},
};
use errno::Errno;
use libc::{fchdir, EACCES, ELOOP, ENOENT, ENOTDIR, EPERM};
use nix::sys::stat::Mode;
use std::{os::fd::AsRawFd, sync::Arc};
use libc::{EACCES, ELOOP, ENOENT, ENOTDIR, EPERM};
use nix::unistd::chdir;

// 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.
Expand Down Expand Up @@ -87,18 +86,10 @@ pub fn cd(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr]) -> Optio

errno::set_errno(Errno(0));

let res = wopen_dir(&norm_dir, Mode::empty()).map_err(|err| err as i32);
let res = chdir(wcs2zstring(&norm_dir).as_c_str()).map_err(|e| e as i32);

let res = res.and_then(|fd| {
if unsafe { fchdir(fd.as_raw_fd()) } == 0 {
Ok(fd)
} else {
Err(errno::errno().0)
}
});

let fd = match res {
Ok(fd) => fd,
match res {
Ok(_) => {}
Err(err) => {
// Some errors we skip and only report if nothing worked.
// ENOENT in particular is very low priority
Expand All @@ -122,13 +113,10 @@ pub fn cd(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr]) -> Optio
best_errno = err;
break;
}
};
}

// We need to keep around the fd for this directory, in the parser.
let dir_fd = Arc::new(fd);

// Stash the fd for the cwd in the parser.
parser.libdata_mut().cwd_fd = Some(dir_fd);
parser.libdata_mut().update_cwd_fd();

parser.set_var_and_fire(L!("PWD"), EnvMode::EXPORT | EnvMode::GLOBAL, vec![norm_dir]);
return STATUS_CMD_OK;
Expand Down
21 changes: 13 additions & 8 deletions src/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,18 @@ impl LibraryData {
..Default::default()
}
}

pub fn update_cwd_fd(&mut self) {
match open_dir(CStr::from_bytes_with_nul(b".\0").unwrap(), Mode::empty()) {
Ok(fd) => {
self.cwd_fd = Some(Arc::new(fd));
}
Err(_) => {
perror("Unable to open the current working directory");
self.cwd_fd = None;
}
}
}
}

impl Default for LoopStatus {
Expand Down Expand Up @@ -353,14 +365,7 @@ impl Parser {
global_event_blocks: AtomicU64::new(0),
});

match open_dir(CStr::from_bytes_with_nul(b".\0").unwrap(), Mode::empty()) {
Ok(fd) => {
result.libdata_mut().cwd_fd = Some(Arc::new(fd));
}
Err(_) => {
perror("Unable to open the current working directory");
}
}
result.libdata_mut().update_cwd_fd();

result
}
Expand Down
24 changes: 24 additions & 0 deletions tests/checks/cd.fish
Original file line number Diff line number Diff line change
Expand Up @@ -271,3 +271,27 @@ end
complete -C'cd .'
# CHECK: ../
# CHECK: ./

# Check that cd works with minimal permissions (issue #10432)
begin
set -l oldpwd (pwd)
set -l tmp (mktemp -d)
cd $tmp
mkdir -p a/b/c
chmod -r a

cd a
# CHECKERR: Unable to open the current working directory: Permission denied
pwd
# CHECK: {{.*}}/a

cd b
pwd
ls
# CHECK: {{.*}}/a/b
# CHECK: c

cd $oldpwd
chmod -R +rx $tmp # we must be able to list the directory to delete its children
rm -rf $tmp
end