add+use FileError class (towards structured errors)

This commit adds a new error class, FileError, as a subclass of
SysError, which allows preserving the paths involved in the error
through unwinds. This is not used yet, but will be.

This commit also switches the file operations in libutil/util.hh which
involve file paths to use this new class.

Change-Id: Iff226ebeea337454ea93ffd33467334a76f33608
This commit is contained in:
Qyriad 2024-04-28 04:10:45 -06:00
parent 76b45b4861
commit 3b43fabe97
2 changed files with 97 additions and 53 deletions

View file

@ -96,8 +96,6 @@ std::ostream & showErrorInfo(std::ostream & out, const ErrorInfo & einfo, bool s
class BaseError : public std::exception
{
protected:
mutable ErrorInfo err;
/**
* Cached formatted contents of `err.msg`.
*/
@ -108,6 +106,8 @@ protected:
const std::string & calcWhat() const;
public:
mutable ErrorInfo err;
BaseError(const BaseError &) = default;
template<typename... Args>
@ -200,4 +200,25 @@ public:
}
};
/// Version of SysError that remembers what paths were involved in the error.
struct FileError : public SysError
{
/// File paths involved in the error.
/// Allowed to be empty, but then you might as well just use SysError.
Paths referencedPaths;
/// Construct a FileError from the specified paths, format string,
/// and format args, using `errno` as the error code.
template<typename... Args>
FileError(Paths && referencedPaths, std::string const & fs, Args... args)
: SysError(errno, fs, args...)
, referencedPaths(referencedPaths)
{ }
template<typename... Args>
FileError(int errNo, Paths && referencedPaths, std::string const & fs, Args... args)
: SysError(errNo, fs, args...)
{ }
};
}

View file

@ -242,8 +242,9 @@ bool isDirOrInDir(std::string_view path, std::string_view dir)
struct stat stat(const Path & path)
{
struct stat st;
if (stat(path.c_str(), &st))
throw SysError("getting status of '%1%'", path);
if (stat(path.c_str(), &st)) {
throw FileError({path}, "getting status of '%1%'", path);
}
return st;
}
@ -251,8 +252,9 @@ struct stat stat(const Path & path)
struct stat lstat(const Path & path)
{
struct stat st;
if (lstat(path.c_str(), &st))
throw SysError("getting status of '%1%'", path);
if (lstat(path.c_str(), &st)) {
throw FileError({path}, "getting status of '%1%'", path);
}
return st;
}
@ -261,10 +263,11 @@ std::optional<struct stat> maybeLstat(const Path & path)
std::optional<struct stat> st{std::in_place};
if (lstat(path.c_str(), &*st))
{
if (errno == ENOENT || errno == ENOTDIR)
if (errno == ENOENT || errno == ENOTDIR) {
st.reset();
else
throw SysError("getting status of '%s'", path);
} else {
throw FileError({path}, "getting status of '%s'", path);
}
}
return st;
}
@ -293,13 +296,16 @@ Path readLink(const Path & path)
for (ssize_t bufSize = PATH_MAX/4; true; bufSize += bufSize/2) {
buf.resize(bufSize);
ssize_t rlSize = readlink(path.c_str(), buf.data(), bufSize);
if (rlSize == -1)
if (errno == EINVAL)
if (rlSize == -1) {
if (errno == EINVAL) {
throw Error("'%1%' is not a symlink", path);
else
throw SysError("reading symbolic link '%1%'", path);
else if (rlSize < bufSize)
} else {
throw FileError({path}, "reading symbolic link '%1%'", path);
}
}
else if (rlSize < bufSize) {
return std::string(buf.data(), rlSize);
}
}
}
@ -329,7 +335,7 @@ DirEntries readDirectory(DIR *dir, const Path & path)
#endif
);
}
if (errno) throw SysError("reading directory '%1%'", path);
if (errno) throw FileError({path}, "reading directory '%1%'", path);
return entries;
}
@ -337,7 +343,7 @@ DirEntries readDirectory(DIR *dir, const Path & path)
DirEntries readDirectory(const Path & path)
{
AutoCloseDir dir(opendir(path.c_str()));
if (!dir) throw SysError("opening directory '%1%'", path);
if (!dir) throw FileError({path}, "opening directory '%1%'", path);
return readDirectory(dir.get(), path);
}
@ -366,8 +372,9 @@ std::string readFile(int fd)
std::string readFile(const Path & path)
{
AutoCloseFD fd{open(path.c_str(), O_RDONLY | O_CLOEXEC)};
if (!fd)
throw SysError("opening file '%1%'", path);
if (!fd) {
throw FileError({path}, "opening file '%1%'", path);
}
return readFile(fd.get());
}
@ -375,8 +382,9 @@ std::string readFile(const Path & path)
void readFile(const Path & path, Sink & sink)
{
AutoCloseFD fd{open(path.c_str(), O_RDONLY | O_CLOEXEC)};
if (!fd)
throw SysError("opening file '%s'", path);
if (!fd) {
throw FileError({path}, "opening file '%s'", path);
}
drainFD(fd.get(), sink);
}
@ -384,8 +392,9 @@ void readFile(const Path & path, Sink & sink)
void writeFile(const Path & path, std::string_view s, mode_t mode, bool sync)
{
AutoCloseFD fd{open(path.c_str(), O_WRONLY | O_TRUNC | O_CREAT | O_CLOEXEC, mode)};
if (!fd)
throw SysError("opening file '%1%'", path);
if (!fd) {
throw FileError({path}, "opening file '%1%'", path);
}
try {
writeFull(fd.get(), s);
} catch (Error & e) {
@ -404,8 +413,9 @@ void writeFile(const Path & path, std::string_view s, mode_t mode, bool sync)
void writeFile(const Path & path, Source & source, mode_t mode, bool sync)
{
AutoCloseFD fd{open(path.c_str(), O_WRONLY | O_TRUNC | O_CREAT | O_CLOEXEC, mode)};
if (!fd)
throw SysError("opening file '%1%'", path);
if (!fd) {
throw FileError({path}, "opening file '%1%'", path);
}
std::vector<char> buf(64 * 1024);
@ -431,8 +441,9 @@ void writeFile(const Path & path, Source & source, mode_t mode, bool sync)
void syncParent(const Path & path)
{
AutoCloseFD fd{open(dirOf(path).c_str(), O_RDONLY, 0)};
if (!fd)
throw SysError("opening file '%1%'", path);
if (!fd) {
throw FileError({path}, "opening file '%1%'", path);
}
fd.fsync();
}
@ -445,8 +456,9 @@ std::string readLine(int fd)
// FIXME: inefficient
ssize_t rd = read(fd, &ch, 1);
if (rd == -1) {
if (errno != EINTR)
if (errno != EINTR) {
throw SysError("reading a line");
}
} else if (rd == 0)
throw EndOfFile("unexpected EOF reading a line");
else {
@ -473,7 +485,7 @@ static void _deletePath(int parentfd, const Path & path, uint64_t & bytesFreed)
struct stat st;
if (fstatat(parentfd, name.c_str(), &st, AT_SYMLINK_NOFOLLOW) == -1) {
if (errno == ENOENT) return;
throw SysError("getting status of '%1%'", path);
throw FileError({path}, "getting status of '%1%'", path);
}
if (!S_ISDIR(st.st_mode)) {
@ -504,24 +516,28 @@ static void _deletePath(int parentfd, const Path & path, uint64_t & bytesFreed)
/* Make the directory accessible. */
const auto PERM_MASK = S_IRUSR | S_IWUSR | S_IXUSR;
if ((st.st_mode & PERM_MASK) != PERM_MASK) {
if (fchmodat(parentfd, name.c_str(), st.st_mode | PERM_MASK, 0) == -1)
throw SysError("chmod '%1%'", path);
if (fchmodat(parentfd, name.c_str(), st.st_mode | PERM_MASK, 0) == -1) {
throw FileError({path}, "chmod '%1%'", path);
}
}
int fd = openat(parentfd, path.c_str(), O_RDONLY);
if (fd == -1)
throw SysError("opening directory '%1%'", path);
if (fd == -1) {
throw FileError({path}, "opening directory '%1%'", path);
}
AutoCloseDir dir(fdopendir(fd));
if (!dir)
throw SysError("opening directory '%1%'", path);
for (auto & i : readDirectory(dir.get(), path))
if (!dir) {
throw FileError({path}, "opening directory '%1%'", path);
}
for (auto & i : readDirectory(dir.get(), path)) {
_deletePath(dirfd(dir.get()), path + "/" + i.name, bytesFreed);
}
}
int flags = S_ISDIR(st.st_mode) ? AT_REMOVEDIR : 0;
if (unlinkat(parentfd, name.c_str(), flags) == -1) {
if (errno == ENOENT) return;
throw SysError("cannot unlink '%1%'", path);
throw FileError({path}, "cannot unlink '%1%'", path);
}
}
@ -534,7 +550,7 @@ static void _deletePath(const Path & path, uint64_t & bytesFreed)
AutoCloseFD dirfd{open(dir.c_str(), O_RDONLY)};
if (!dirfd) {
if (errno == ENOENT) return;
throw SysError("opening directory '%1%'", path);
throw FileError({path}, "opening directory '%1%'", path);
}
_deletePath(dirfd.get(), path, bytesFreed);
@ -679,14 +695,16 @@ Paths createDirs(const Path & path)
struct stat st;
if (lstat(path.c_str(), &st) == -1) {
created = createDirs(dirOf(path));
if (mkdir(path.c_str(), 0777) == -1 && errno != EEXIST)
throw SysError("creating directory '%1%'", path);
if (mkdir(path.c_str(), 0777) == -1 && errno != EEXIST) {
throw FileError({path}, "creating directory '%1%'", path);
}
st = lstat(path);
created.push_back(path);
}
if (S_ISLNK(st.st_mode) && stat(path.c_str(), &st) == -1)
throw SysError("statting symlink '%1%'", path);
if (S_ISLNK(st.st_mode) && stat(path.c_str(), &st) == -1) {
throw FileError({path}, "statting symlink '%1%'", path);
}
if (!S_ISDIR(st.st_mode)) throw Error("'%1%' is not a directory", path);
@ -813,11 +831,13 @@ AutoDelete::~AutoDelete()
{
try {
if (del) {
if (recursive)
if (recursive) {
deletePath(path);
}
else {
if (remove(path.c_str()) == -1)
throw SysError("cannot unlink '%1%'", path);
if (remove(path.c_str()) == -1) {
throw FileError({path}, "cannot unlink '%1%'", path);
}
}
}
} catch (...) {
@ -1781,11 +1801,12 @@ AutoCloseFD createUnixDomainSocket(const Path & path, mode_t mode)
bind(fdSocket.get(), path);
if (chmod(path.c_str(), mode) == -1)
throw SysError("changing permissions on '%1%'", path);
if (chmod(path.c_str(), mode) == -1) {
throw FileError({path}, "changing permissions on '%1%'", path);
}
if (listen(fdSocket.get(), 100) == -1)
throw SysError("cannot listen on socket '%1%'", path);
throw FileError({path}, "cannot listen on socket '%1%'", path);
return fdSocket;
}
@ -1812,14 +1833,15 @@ static void bindConnectProcHelper(
try {
pipe.readSide.close();
Path dir = dirOf(path);
if (chdir(dir.c_str()) == -1)
throw SysError("chdir to '%s' failed", dir);
if (chdir(dir.c_str()) == -1) {
throw FileError({path}, "chdir to '%s' failed", dir);
}
std::string base(baseNameOf(path));
if (base.size() + 1 >= sizeof(addr.sun_path))
throw Error("socket path '%s' is too long", base);
memcpy(addr.sun_path, base.c_str(), base.size() + 1);
if (operation(fd, psaddr, sizeof(addr)) == -1)
throw SysError("cannot %s to socket at '%s'", operationName, path);
throw FileError({path}, "cannot %s to socket at '%s'", operationName, path);
writeFull(pipe.writeSide.get(), "0\n");
} catch (SysError & e) {
writeFull(pipe.writeSide.get(), fmt("%d\n", e.errNo));
@ -1833,12 +1855,13 @@ static void bindConnectProcHelper(
throw Error("cannot %s to socket at '%s'", operationName, path);
else if (*errNo > 0) {
errno = *errNo;
throw SysError("cannot %s to socket at '%s'", operationName, path);
throw FileError({path}, "cannot %s to socket at '%s'", operationName, path);
}
} else {
memcpy(addr.sun_path, path.c_str(), path.size() + 1);
if (operation(fd, psaddr, sizeof(addr)) == -1)
throw SysError("cannot %s to socket at '%s'", operationName, path);
if (operation(fd, psaddr, sizeof(addr)) == -1) {
throw FileError({path}, "cannot %s to socket at '%s'", operationName, path);
}
}
}