From 3b43fabe971b92723e656b444badc5a8a23c565b Mon Sep 17 00:00:00 2001 From: Qyriad Date: Sun, 28 Apr 2024 04:10:45 -0600 Subject: [PATCH] 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 --- src/libutil/error.hh | 25 ++++++++- src/libutil/util.cc | 125 +++++++++++++++++++++++++------------------ 2 files changed, 97 insertions(+), 53 deletions(-) diff --git a/src/libutil/error.hh b/src/libutil/error.hh index 924366580..5072adbdd 100644 --- a/src/libutil/error.hh +++ b/src/libutil/error.hh @@ -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 @@ -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 + FileError(Paths && referencedPaths, std::string const & fs, Args... args) + : SysError(errno, fs, args...) + , referencedPaths(referencedPaths) + { } + + template + FileError(int errNo, Paths && referencedPaths, std::string const & fs, Args... args) + : SysError(errNo, fs, args...) + { } +}; + } diff --git a/src/libutil/util.cc b/src/libutil/util.cc index dc724db3e..5894e32f5 100644 --- a/src/libutil/util.cc +++ b/src/libutil/util.cc @@ -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 maybeLstat(const Path & path) std::optional 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 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); + } } }