libutil: catch std::filesystem errors
catch and rewrap std::fs errors in some meaningful way, otherwise lix can crash with foreign exception aborts if filesystem failures happen that aren't already being caught. renames in particular can fail with incomprehensible errors in even pretty normal circumstances elsewise. fixes #1219, fixes #995 Change-Id: I0b1e5696d300aac3422e43cffc17a2426803d64e
This commit is contained in:
+46
-25
@@ -9,6 +9,7 @@
|
||||
|
||||
#include "lix/libutil/logging.hh"
|
||||
|
||||
#include <filesystem>
|
||||
#include <glob.h>
|
||||
|
||||
namespace nix {
|
||||
@@ -395,8 +396,19 @@ static bool isAcceptableLixSubcommandExe(const std::filesystem::path & exe_path)
|
||||
{
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
return fs::is_regular_file(exe_path) &&
|
||||
(fs::status(exe_path).permissions() & (fs::perms::owner_exec | fs::perms::group_exec | fs::perms::others_exec)) != fs::perms::none;
|
||||
try {
|
||||
return fs::is_regular_file(exe_path)
|
||||
&& (fs::status(exe_path).permissions()
|
||||
& (fs::perms::owner_exec | fs::perms::group_exec | fs::perms::others_exec))
|
||||
!= fs::perms::none;
|
||||
} catch (fs::filesystem_error & fs_exc) { // NOLINT(lix-foreign-exceptions)
|
||||
if (fs_exc.code() != std::errc::no_such_file_or_directory
|
||||
&& fs_exc.code() != std::errc::not_a_directory && fs_exc.code() != std::errc::permission_denied)
|
||||
{
|
||||
throw Error("%s", Uncolored(fs_exc.what()));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<ref<Command>> searchForCustomSubcommand(AsyncIoRoot & aio, const std::string_view & command, const std::string_view & prefix, const Strings & searchPaths)
|
||||
@@ -412,10 +424,10 @@ std::optional<ref<Command>> searchForCustomSubcommand(AsyncIoRoot & aio, const s
|
||||
debug("Found requested external subcommand '%s' in '%s'", command, path);
|
||||
return make_ref<ExternalCommand>(aio, path);
|
||||
}
|
||||
} catch (fs::filesystem_error & fs_exc) { // NOLINT(lix-foreign-exceptions)
|
||||
if (fs_exc.code() != std::errc::no_such_file_or_directory && fs_exc.code() != std::errc::not_a_directory && fs_exc.code() != std::errc::permission_denied) {
|
||||
throw SysError("while searching for the subcommand '%1%' in search path '%2%': '%3%'", command, searchPath, fs_exc.what());
|
||||
}
|
||||
} catch (Error & exc) {
|
||||
exc.addTrace(
|
||||
{}, "while searching for the subcommand '%1%' in search path '%2%'", command, searchPath
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -430,33 +442,42 @@ Strings searchForAllAvailableCustomSubcommands(const std::string_view & prefix,
|
||||
for (auto searchPath : searchPaths) {
|
||||
if (searchPath.empty()) continue;
|
||||
|
||||
if (!fs::exists(searchPath) || !fs::is_directory(searchPath)) {
|
||||
// TODO(Raito): this will break all the time our functional tests
|
||||
// for people with garbage in their $PATH which is my personal case.
|
||||
try {
|
||||
if (!fs::exists(searchPath) || !fs::is_directory(searchPath)) {
|
||||
// TODO(Raito): this will break all the time our functional tests
|
||||
// for people with garbage in their $PATH which is my personal case.
|
||||
|
||||
// warn("The search path '%s' for custom subcommands does not exist or is not a directory, ignoring...", searchPath);
|
||||
continue;
|
||||
}
|
||||
// warn("The search path '%s' for custom subcommands does not exist or is not a directory,
|
||||
// ignoring...", searchPath);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Browse per prefix.
|
||||
for (const auto& entry : fs::directory_iterator(searchPath)) {
|
||||
try {
|
||||
if (isAcceptableLixSubcommandExe(entry.path())) {
|
||||
auto filename = entry.path().filename().string();
|
||||
// Browse per prefix.
|
||||
for (const auto & entry : fs::directory_iterator(searchPath)) {
|
||||
try {
|
||||
if (isAcceptableLixSubcommandExe(entry.path())) {
|
||||
auto filename = entry.path().filename().string();
|
||||
|
||||
if (filename.starts_with(prefix)) {
|
||||
auto suffix = filename.substr(prefix.size());
|
||||
if (filename.starts_with(prefix)) {
|
||||
auto suffix = filename.substr(prefix.size());
|
||||
|
||||
debug("Found custom subcommand ('%s') '%s'", filename, suffix);
|
||||
debug("Found custom subcommand ('%s') '%s'", filename, suffix);
|
||||
|
||||
commandNames.push_back(suffix);
|
||||
commandNames.push_back(suffix);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (fs::filesystem_error & fs_exc) { // NOLINT(lix-foreign-exceptions)
|
||||
if (fs_exc.code() != std::errc::no_such_file_or_directory && fs_exc.code() != std::errc::not_a_directory && fs_exc.code() != std::errc::permission_denied) {
|
||||
throw SysError("while searching for all available commands in search path '%1%', while analyzing '%2%': %3%'", searchPath, entry.path(), fs_exc.what());
|
||||
} catch (Error & exc) {
|
||||
exc.addTrace(
|
||||
{},
|
||||
"while searching for all available commands in search path '%1%', while analyzing "
|
||||
"'%2%'",
|
||||
searchPath,
|
||||
entry.path()
|
||||
);
|
||||
}
|
||||
}
|
||||
} catch (fs::filesystem_error & exc) { // NOLINT(lix-foreign-exceptions)
|
||||
throw Error("failed to look up available subcommands in %s: %s", searchPath, exc.what());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+21
-13
@@ -767,7 +767,7 @@ void setWriteTime(const fs::path & p, const struct stat & st)
|
||||
}
|
||||
|
||||
void copy(const fs::directory_entry & from, const fs::path & to, CopyFileFlags flags)
|
||||
{
|
||||
try {
|
||||
// TODO: Rewrite the `is_*` to use `symlink_status()`
|
||||
auto statOfFrom = lstat(from.path());
|
||||
auto fromStatus = from.symlink_status();
|
||||
@@ -801,9 +801,10 @@ void copy(const fs::directory_entry & from, const fs::path & to, CopyFileFlags f
|
||||
fs::permissions(from.path(), fs::perms::owner_write, fs::perm_options::add | fs::perm_options::nofollow);
|
||||
fs::remove(from.path());
|
||||
}
|
||||
} catch (fs::filesystem_error & e) { // NOLINT(lix-foreign-exceptions)
|
||||
throw Error("failed to copy %s to %s: %s", from.path(), to, e.what());
|
||||
}
|
||||
|
||||
|
||||
void copyFile(const Path & oldPath, const Path & newPath, CopyFileFlags flags)
|
||||
{
|
||||
return copy(fs::directory_entry(fs::path(oldPath)), fs::path(newPath), flags);
|
||||
@@ -811,28 +812,35 @@ void copyFile(const Path & oldPath, const Path & newPath, CopyFileFlags flags)
|
||||
|
||||
void renameFile(const Path & oldName, const Path & newName)
|
||||
{
|
||||
fs::rename(oldName, newName);
|
||||
try {
|
||||
fs::rename(oldName, newName);
|
||||
} catch (fs::filesystem_error & e) { // NOLINT(lix-foreign-exceptions)
|
||||
throw Error("failed to rename %s to %s: %s", oldName, newName, e.what());
|
||||
}
|
||||
}
|
||||
|
||||
void moveFile(const Path & oldName, const Path & newName)
|
||||
{
|
||||
try {
|
||||
renameFile(oldName, newName);
|
||||
fs::rename(oldName, newName);
|
||||
} catch (fs::filesystem_error & e) { // NOLINT(lix-foreign-exceptions)
|
||||
auto oldPath = fs::path(oldName);
|
||||
auto newPath = fs::path(newName);
|
||||
// For the move to be as atomic as possible, copy to a temporary
|
||||
// directory
|
||||
fs::path temp = createTempSubdir(newPath.parent_path(), "rename-tmp");
|
||||
Finally removeTemp = [&]() { fs::remove(temp); };
|
||||
auto tempCopyTarget = temp / "copy-target";
|
||||
if (e.code().value() == EXDEV) {
|
||||
fs::remove(newPath);
|
||||
printTaggedWarning("Can’t rename %s as %s, copying instead", oldName, newName);
|
||||
copy(fs::directory_entry(oldPath), tempCopyTarget, { .deleteAfter = true });
|
||||
renameFile(tempCopyTarget, newPath);
|
||||
try {
|
||||
fs::path temp = createTempSubdir(newPath.parent_path(), "rename-tmp");
|
||||
Finally removeTemp = [&]() { fs::remove(temp); };
|
||||
auto tempCopyTarget = temp / "copy-target";
|
||||
if (e.code().value() == EXDEV) {
|
||||
fs::remove(newPath);
|
||||
printTaggedWarning("Can’t rename %s as %s, copying instead", oldName, newName);
|
||||
copy(fs::directory_entry(oldPath), tempCopyTarget, {.deleteAfter = true});
|
||||
fs::rename(tempCopyTarget, newPath);
|
||||
}
|
||||
} catch (fs::filesystem_error & e) { // NOLINT(lix-foreign-exceptions)
|
||||
throw Error("failed to move %s to %s: %s", oldName, newName, e.what());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user