diff --git a/src/builtin_cd.cpp b/src/builtin_cd.cpp index a09c05edb..ccdd01958 100644 --- a/src/builtin_cd.cpp +++ b/src/builtin_cd.cpp @@ -47,55 +47,72 @@ maybe_t builtin_cd(parser_t &parser, io_streams_t &streams, wchar_t **argv) } wcstring pwd = parser.vars().get_pwd_slash(); - maybe_t 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(std::move(dir_fd)); + std::vector 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(std::move(dir_fd)); - std::vector 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; } diff --git a/src/path.cpp b/src/path.cpp index a387bac8d..3c3ccc36b 100644 --- a/src/path.cpp +++ b/src/path.cpp @@ -126,11 +126,8 @@ wcstring_list_t path_get_paths(const wcstring &cmd, const environment_t &vars) { return paths; } -maybe_t 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 path_get_cdpath(const wcstring &dir, const wcstring &wd, } } + return paths; +} + +maybe_t 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) { diff --git a/src/path.h b/src/path.h index 475be6100..b411050e2 100644 --- a/src/path.h +++ b/src/path.h @@ -64,6 +64,10 @@ wcstring_list_t path_get_paths(const wcstring &cmd, const environment_t &vars); maybe_t 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 path_as_implicit_cd(const wcstring &path, const wcstring &wd,