mirror of
https://github.com/fish-shell/fish-shell.git
synced 2025-01-21 05:26:15 +08:00
cd first, ask questions later (#7586)
cd: Just try to cd without checking first Some filesystems are broken and error out on `stat(3)` of existing and cd-able directories. So we just try to `fchdir` and report errors later. Fixes #7577.
This commit is contained in:
parent
50398ea9f5
commit
df53d1415d
|
@ -47,55 +47,72 @@ maybe_t<int> builtin_cd(parser_t &parser, io_streams_t &streams, wchar_t **argv)
|
|||
}
|
||||
|
||||
wcstring pwd = parser.vars().get_pwd_slash();
|
||||
maybe_t<wcstring> mdir = path_get_cdpath(dir_in, pwd, parser.vars());
|
||||
if (!mdir) {
|
||||
if (errno == ENOTDIR) {
|
||||
streams.err.append_format(_(L"%ls: '%ls' is not a directory\n"), cmd, dir_in.c_str());
|
||||
} else if (errno == ENOENT) {
|
||||
streams.err.append_format(_(L"%ls: The directory '%ls' does not exist\n"), cmd,
|
||||
dir_in.c_str());
|
||||
} else if (errno == EROTTEN) {
|
||||
streams.err.append_format(_(L"%ls: '%ls' is a rotten symlink\n"), cmd, dir_in.c_str());
|
||||
} else {
|
||||
streams.err.append_format(_(L"%ls: Unknown error trying to locate directory '%ls'\n"),
|
||||
cmd, dir_in.c_str());
|
||||
}
|
||||
auto dirs = path_apply_cdpath(dir_in, pwd, parser.vars());
|
||||
if (dirs.empty()) {
|
||||
streams.err.append_format(_(L"%ls: The directory '%ls' does not exist\n"), cmd,
|
||||
dir_in.c_str());
|
||||
|
||||
if (!parser.is_interactive()) streams.err.append(parser.current_line());
|
||||
|
||||
return STATUS_CMD_ERROR;
|
||||
}
|
||||
const wcstring &dir = *mdir;
|
||||
|
||||
wcstring norm_dir = normalize_path(dir);
|
||||
errno = 0;
|
||||
auto best_errno = errno;
|
||||
|
||||
// We need to keep around the fd for this directory, in the parser.
|
||||
autoclose_fd_t dir_fd(wopen_cloexec(norm_dir, O_RDONLY));
|
||||
bool success = dir_fd.valid() && fchdir(dir_fd.fd()) == 0;
|
||||
for (const auto &dir: dirs) {
|
||||
wcstring norm_dir = normalize_path(dir);
|
||||
|
||||
if (!success) {
|
||||
struct stat buffer;
|
||||
int status;
|
||||
// We need to keep around the fd for this directory, in the parser.
|
||||
errno = 0;
|
||||
autoclose_fd_t dir_fd(wopen_cloexec(norm_dir, O_RDONLY));
|
||||
bool success = dir_fd.valid() && fchdir(dir_fd.fd()) == 0;
|
||||
|
||||
status = wstat(dir, &buffer);
|
||||
if (!status && S_ISDIR(buffer.st_mode)) {
|
||||
streams.err.append_format(_(L"%ls: Permission denied: '%ls'\n"), cmd, dir_in.c_str());
|
||||
} else {
|
||||
streams.err.append_format(_(L"%ls: '%ls' is not a directory\n"), cmd, dir_in.c_str());
|
||||
if (!success) {
|
||||
// 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 (errno == ENOENT) {
|
||||
if (!best_errno) best_errno = errno;
|
||||
continue;
|
||||
} else if (errno == ENOTDIR) {
|
||||
best_errno = errno;
|
||||
continue;
|
||||
}
|
||||
|
||||
best_errno = errno;
|
||||
break;
|
||||
}
|
||||
|
||||
if (!parser.is_interactive()) {
|
||||
streams.err.append(parser.current_line());
|
||||
parser.libdata().cwd_fd = std::make_shared<const autoclose_fd_t>(std::move(dir_fd));
|
||||
std::vector<event_t> evts;
|
||||
parser.vars().set_one(L"PWD", ENV_EXPORT | ENV_GLOBAL, std::move(norm_dir), &evts);
|
||||
for (const auto &evt : evts) {
|
||||
event_fire(parser, evt);
|
||||
}
|
||||
|
||||
return STATUS_CMD_ERROR;
|
||||
return STATUS_CMD_OK;
|
||||
}
|
||||
|
||||
parser.libdata().cwd_fd = std::make_shared<const autoclose_fd_t>(std::move(dir_fd));
|
||||
std::vector<event_t> evts;
|
||||
parser.vars().set_one(L"PWD", ENV_EXPORT | ENV_GLOBAL, std::move(norm_dir), &evts);
|
||||
for (const auto &evt : evts) {
|
||||
event_fire(parser, evt);
|
||||
if (best_errno == ENOTDIR) {
|
||||
streams.err.append_format(_(L"%ls: '%ls' is not a directory\n"), cmd, dir_in.c_str());
|
||||
} else if (best_errno == ENOENT) {
|
||||
streams.err.append_format(_(L"%ls: The directory '%ls' does not exist\n"), cmd,
|
||||
dir_in.c_str());
|
||||
} else if (best_errno == EROTTEN) {
|
||||
streams.err.append_format(_(L"%ls: '%ls' is a rotten symlink\n"), cmd, dir_in.c_str());
|
||||
} else if (best_errno == EACCES) {
|
||||
streams.err.append_format(_(L"%ls: Permission denied: '%ls'\n"), cmd, dir_in.c_str());
|
||||
} else {
|
||||
errno = best_errno;
|
||||
wperror(L"cd");
|
||||
streams.err.append_format(_(L"%ls: Unknown error trying to locate directory '%ls'\n"),
|
||||
cmd, dir_in.c_str());
|
||||
}
|
||||
return STATUS_CMD_OK;
|
||||
|
||||
if (!parser.is_interactive()) {
|
||||
streams.err.append(parser.current_line());
|
||||
}
|
||||
|
||||
return STATUS_CMD_ERROR;
|
||||
}
|
||||
|
|
15
src/path.cpp
15
src/path.cpp
|
@ -126,11 +126,8 @@ wcstring_list_t path_get_paths(const wcstring &cmd, const environment_t &vars) {
|
|||
return paths;
|
||||
}
|
||||
|
||||
maybe_t<wcstring> path_get_cdpath(const wcstring &dir, const wcstring &wd,
|
||||
wcstring_list_t path_apply_cdpath(const wcstring &dir, const wcstring &wd,
|
||||
const environment_t &env_vars) {
|
||||
int err = ENOENT;
|
||||
if (dir.empty()) return none();
|
||||
assert(!wd.empty() && wd.back() == L'/');
|
||||
wcstring_list_t paths;
|
||||
if (dir.at(0) == L'/') {
|
||||
// Absolute path.
|
||||
|
@ -170,6 +167,16 @@ maybe_t<wcstring> path_get_cdpath(const wcstring &dir, const wcstring &wd,
|
|||
}
|
||||
}
|
||||
|
||||
return paths;
|
||||
}
|
||||
|
||||
maybe_t<wcstring> path_get_cdpath(const wcstring &dir, const wcstring &wd,
|
||||
const environment_t &env_vars) {
|
||||
int err = ENOENT;
|
||||
if (dir.empty()) return none();
|
||||
assert(!wd.empty() && wd.back() == L'/');
|
||||
auto paths = path_apply_cdpath(dir, wd, env_vars);
|
||||
|
||||
for (const wcstring &dir : paths) {
|
||||
struct stat buf;
|
||||
if (wstat(dir, &buf) == 0) {
|
||||
|
|
|
@ -64,6 +64,10 @@ wcstring_list_t path_get_paths(const wcstring &cmd, const environment_t &vars);
|
|||
maybe_t<wcstring> path_get_cdpath(const wcstring &dir, const wcstring &wd,
|
||||
const environment_t &vars);
|
||||
|
||||
/// Returns the given directory with all CDPATH components applied.
|
||||
wcstring_list_t path_apply_cdpath(const wcstring &dir, const wcstring &wd,
|
||||
const environment_t &env_vars);
|
||||
|
||||
/// Returns the path resolved as an implicit cd command, or none() if none. This requires it to
|
||||
/// start with one of the allowed prefixes (., .., ~) and resolve to a directory.
|
||||
maybe_t<wcstring> path_as_implicit_cd(const wcstring &path, const wcstring &wd,
|
||||
|
|
Loading…
Reference in New Issue
Block a user