Summary: Close the file descriptor in File destructor. This will prevent accidental crashes during unexpected destructor calls. For example, if an exception is thrown before the file is closed. File now takes ownership of the descriptor. These changes now honor RAII idiom, which should handle most of the peculiarities of C++. Use optional value for TryOpenFile function, instead of returning a File without a descriptor. It makes the failure state more semantically clear to the API user. Merge utils/filesystem with utils/file The files aren't that big, and the naming is a bit confusing because functions aren't really grouped for file and filesystem distinction. Reviewers: mferencevic, mtomic Reviewed By: mferencevic Subscribers: pullbot Differential Revision: https://phabricator.memgraph.io/D1111
182 lines
5.0 KiB
C++
182 lines
5.0 KiB
C++
#include "utils/file.hpp"
|
|
|
|
#include <fcntl.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/types.h>
|
|
#include <unistd.h>
|
|
|
|
#include "fmt/format.h"
|
|
#include "fmt/ostream.h"
|
|
#include "glog/logging.h"
|
|
|
|
namespace utils {
|
|
|
|
std::vector<fs::path> LoadFilePaths(const fs::path &directory,
|
|
const std::string &extension) {
|
|
// result container
|
|
std::vector<fs::path> file_paths;
|
|
|
|
for (auto &directory_entry : fs::recursive_directory_iterator(directory)) {
|
|
auto path = directory_entry.path().string();
|
|
|
|
// skip directories
|
|
if (!fs::is_regular_file(directory_entry)) continue;
|
|
|
|
// if extension isn't defined then put all file paths from the directory
|
|
// to the result set
|
|
if (!extension.empty()) {
|
|
// skip paths that don't have appropriate extension
|
|
auto file_extension = path.substr(path.find_last_of(".") + 1);
|
|
if (file_extension != extension) continue;
|
|
}
|
|
|
|
file_paths.emplace_back(path);
|
|
|
|
// skip paths that don't have appropriate extension
|
|
auto file_extension = path.substr(path.find_last_of(".") + 1);
|
|
if (file_extension != extension) continue;
|
|
|
|
// path has the right extension and can be placed in the result
|
|
// container
|
|
file_paths.emplace_back(path);
|
|
}
|
|
|
|
return file_paths;
|
|
}
|
|
|
|
std::vector<std::string> ReadLines(const fs::path &path) {
|
|
std::vector<std::string> lines;
|
|
|
|
std::ifstream stream(path.c_str());
|
|
std::string line;
|
|
while (std::getline(stream, line)) {
|
|
lines.emplace_back(line);
|
|
}
|
|
|
|
return lines;
|
|
}
|
|
|
|
void Write(const std::string &text, const fs::path &path) {
|
|
std::ofstream stream;
|
|
stream.open(path.c_str());
|
|
stream << text;
|
|
stream.close();
|
|
}
|
|
|
|
File::File() : fd_(-1), path_() {}
|
|
|
|
File::File(int fd, fs::path path) : fd_(fd), path_(std::move(path)) {}
|
|
|
|
File::~File() { Close(); }
|
|
|
|
File::File(File &&rhs) : fd_(rhs.fd_), path_(rhs.path_) { rhs.Release(); }
|
|
|
|
File &File::operator=(File &&rhs) {
|
|
if (this != &rhs) {
|
|
fd_ = rhs.fd_;
|
|
path_ = rhs.path_;
|
|
rhs.Release();
|
|
}
|
|
return *this;
|
|
}
|
|
|
|
fs::path File::Path() const { return path_; }
|
|
int File::Handle() const { return fd_; }
|
|
bool File::Empty() const { return fd_ == -1; }
|
|
|
|
void File::Release() {
|
|
fd_ = -1;
|
|
path_ = fs::path();
|
|
}
|
|
|
|
void File::Close() {
|
|
if (fd_ == -1) {
|
|
return;
|
|
}
|
|
if (::close(fd_) == -1) {
|
|
throw std::system_error(errno, std::generic_category(),
|
|
fmt::format("cannot close {}", path_));
|
|
}
|
|
Release();
|
|
}
|
|
|
|
File OpenFile(const fs::path &path, int flags, ::mode_t mode) {
|
|
int fd = ::open(path.c_str(), flags, mode);
|
|
if (fd == -1) {
|
|
throw std::system_error(errno, std::generic_category(),
|
|
fmt::format("cannot open {}", path));
|
|
}
|
|
|
|
return File(fd, path);
|
|
}
|
|
|
|
std::experimental::optional<File> TryOpenFile(const fs::path &path, int flags,
|
|
::mode_t mode) {
|
|
int fd = ::open(path.c_str(), flags, mode);
|
|
return fd == -1 ? std::experimental::nullopt
|
|
: std::experimental::make_optional(File(fd, path));
|
|
}
|
|
|
|
File OpenFile(const File &dir, const fs::path &path, int flags, ::mode_t mode) {
|
|
int fd = ::openat(dir.Handle(), path.c_str(), flags, mode);
|
|
if (fd == -1) {
|
|
throw std::system_error(errno, std::generic_category(),
|
|
fmt::format("cannot open {}", dir.Path() / path));
|
|
}
|
|
return File(fd, dir.Path() / path);
|
|
}
|
|
|
|
std::experimental::optional<File> TryOpenFile(const File &dir,
|
|
const fs::path &path, int flags,
|
|
::mode_t mode) {
|
|
int fd = ::openat(dir.Handle(), path.c_str(), flags, mode);
|
|
return fd == -1
|
|
? std::experimental::nullopt
|
|
: std::experimental::make_optional(File(fd, dir.Path() / path));
|
|
}
|
|
|
|
void Fsync(const File &file) {
|
|
if (::fsync(file.Handle()) == -1) {
|
|
throw std::system_error(errno, std::generic_category(),
|
|
fmt::format("cannot fsync {}", file.Path()));
|
|
}
|
|
}
|
|
|
|
void Rename(const File &dir1, const fs::path &path1, const File &dir2,
|
|
const fs::path &path2) {
|
|
if (::renameat(dir1.Handle(), path1.c_str(), dir2.Handle(), path2.c_str()) !=
|
|
0) {
|
|
throw std::system_error(
|
|
errno, std::generic_category(),
|
|
fmt::format("cannot move {} to {}", dir1.Path() / path1,
|
|
dir2.Path() / path2));
|
|
}
|
|
}
|
|
|
|
void SyncDir(const fs::path &path) {
|
|
File dir = OpenFile(path, O_DIRECTORY | O_RDONLY);
|
|
Fsync(dir);
|
|
}
|
|
|
|
File OpenDir(const fs::path &path) {
|
|
int res = ::mkdir(path.c_str(), 0777);
|
|
if (res == 0) {
|
|
if (path.has_parent_path()) {
|
|
SyncDir(path.parent_path());
|
|
}
|
|
} else if (errno != EEXIST) {
|
|
throw std::system_error(errno, std::generic_category(),
|
|
fmt::format("cannot create directory {}", path));
|
|
}
|
|
|
|
int fd = ::open(path.c_str(), O_RDONLY | O_DIRECTORY);
|
|
if (fd == -1) {
|
|
throw std::system_error(errno, std::generic_category(),
|
|
fmt::format("cannot open directory {}", path));
|
|
}
|
|
|
|
return File(fd, path);
|
|
}
|
|
|
|
} // namespace utils
|