Files
netris-cdc-file-transfer/common/dir_iter.cc
ljusten 23fcd5ef1d Improve cdc_fuse_fs and path (#2)
Improve cdc_fuse_fs and path

Improves the error handling in path so that std:error_codes are not
assumed to be of system category, and also that their messages are
displayed. Also improves debug messages in GameletComponent.
2022-11-15 12:53:02 +01:00

287 lines
7.7 KiB
C++

// Copyright 2022 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "common/dir_iter.h"
#include <errno.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <cassert>
#include <list>
#if defined(_MSC_VER)
#include "dirent.h"
#else
#include <dirent.h>
#endif
#include <algorithm>
#include <memory>
#include "absl/strings/str_cat.h"
#include "absl/strings/str_format.h"
#include "common/errno_mapping.h"
#include "common/path.h"
namespace cdc_ft {
namespace {
const std::string empty_string;
}
// Allows testing if a specific flag is set using the & operator.
inline int operator&(DirectorySearchFlags a, DirectorySearchFlags b) {
using T = std::underlying_type_t<DirectorySearchFlags>;
return static_cast<int>(static_cast<T>(a) & static_cast<T>(b));
}
class DirectoryEntry::Impl {
friend class DirectoryEntry;
public:
Impl(const std::string& rel_path, const dirent* dent)
: rel_path(rel_path),
name(dent ? dent->d_name : ""),
type(dent ? dent->d_type : 0) {}
const std::string rel_path;
const std::string name;
const int type;
};
DirectoryEntry::DirectoryEntry() : impl_(nullptr) {}
DirectoryEntry::~DirectoryEntry() {
if (impl_) delete impl_;
}
bool DirectoryEntry::Valid() const { return impl_ && impl_->type; }
bool DirectoryEntry::IsDir() const { return impl_ && impl_->type & DT_DIR; }
bool DirectoryEntry::IsRegularFile() const {
return impl_ && impl_->type & DT_REG;
}
bool DirectoryEntry::IsSymlink() const { return impl_ && impl_->type & DT_LNK; }
const std::string& DirectoryEntry::Name() const {
return impl_ ? impl_->name : empty_string;
}
const std::string& DirectoryEntry::RelPath() const {
return impl_ ? impl_->rel_path : empty_string;
}
std::string DirectoryEntry::RelPathName() const {
return impl_ ? path::Join(impl_->rel_path, impl_->name) : std::string();
}
void DirectoryEntry::Clear() { SetImpl(nullptr); }
void DirectoryEntry::SetImpl(Impl* impl) {
if (impl_) delete impl_;
impl_ = impl;
}
class DirectoryIterator::Impl {
public:
Impl(const std::string& path, DirectorySearchFlags results, bool recursive)
: path_(path),
flags_(results),
recursive_(recursive),
last_dir_(nullptr) {}
~Impl() {
while (!DirsEmpty()) PopDir();
}
inline const std::string& Path() const { return path_; }
inline bool ReturnDirs() const {
return flags_ & DirectorySearchFlags::kDirectories;
}
inline bool ReturnFiles() const {
return flags_ & DirectorySearchFlags::kFiles;
}
inline bool Recursive() const { return recursive_; }
// Reads the next directory entry from the topmost opened directory. Returns
// nullptr when all directory entries have been read or in case of an error.
// Check errno to distinguish between those two cases.
inline struct dirent* NextDirEntry() {
assert(!dirs_.empty());
// Update relative path if it changed.
DIR* dir = dirs_.back().dir;
if (last_dir_ != dir) {
UpdateRelPath();
last_dir_ = dir;
}
// Reset previous error so that we know if we reached the end the dir.
errno = 0;
return readdir(dir);
}
inline const std::string& RelPath() const { return rel_path_; }
inline std::string DirsPath() { return path::Join(path_, rel_path_); }
inline void PushDir(DIR* dir, const std::string& name) {
assert(dir != nullptr);
dirs_.push_back({dir, name});
}
inline void PopDir() {
assert(!dirs_.empty());
closedir(dirs_.back().dir);
dirs_.pop_back();
}
inline bool DirsEmpty() const { return dirs_.empty(); }
inline absl::Status Status() const { return status_; }
inline void SetStatus(absl::Status status) { status_ = status; }
private:
// On Windows, the struct DIR::ent doesn't seem properly aligned, so the
// DIR::ent::d_name field is unusable. Thus, we store the DIR pointer along
// with its name in this struct.
struct OpenedDir {
DIR* dir;
std::string name;
};
inline void UpdateRelPath() {
rel_path_.clear();
bool first = true;
for (const OpenedDir& dir : dirs_) {
if (first) {
// The first component is already included in path_.
first = false;
continue;
}
path::Append(&rel_path_, dir.name);
}
}
const std::string path_;
const DirectorySearchFlags flags_;
const bool recursive_;
absl::Status status_;
std::list<OpenedDir> dirs_;
const DIR* last_dir_;
std::string rel_path_;
};
DirectoryIterator::DirectoryIterator() : impl_(nullptr) {}
DirectoryIterator::DirectoryIterator(const std::string& path,
DirectorySearchFlags results,
bool recursive)
: impl_(nullptr) {
Open(path, results, recursive);
}
DirectoryIterator::~DirectoryIterator() {
if (impl_) delete impl_;
}
absl::Status DirectoryIterator::Status() const {
return impl_ ? impl_->Status() : absl::OkStatus();
}
bool DirectoryIterator::Valid() const {
return impl_ && impl_->Status().ok() && !impl_->DirsEmpty();
}
const std::string& DirectoryIterator::Path() const {
return impl_ ? impl_->Path() : empty_string;
}
bool DirectoryIterator::Open(const std::string& path,
DirectorySearchFlags results, bool recursive) {
if (impl_) delete impl_;
impl_ = new Impl(path, results, recursive);
DIR* dir = opendir(path.c_str());
if (dir) {
impl_->PushDir(dir, path::BaseName(path));
return true;
}
impl_->SetStatus(
ErrnoToCanonicalStatus(errno, "Failed to open directory '%s'", path));
return false;
}
bool DirectoryIterator::NextEntry(DirectoryEntry* entry) {
if (!impl_ || !impl_->Status().ok()) return false;
// Reset last error to not report a previous one below.
errno = 0;
while (!impl_->DirsEmpty()) {
struct dirent* dent = impl_->NextDirEntry();
if (!dent) {
if (!errno) {
// We have reached the end of this directory.
impl_->PopDir();
continue;
}
break;
}
// Handle directories.
if (dent->d_type & DT_DIR) {
// Skip "." and ".." directory entries.
if (!strcmp(dent->d_name, ".") || !strcmp(dent->d_name, "..")) {
continue;
}
// For recursive traversal, push new directory on top of the stack.
if (impl_->Recursive()) {
std::string dname(dent->d_name);
std::string subdir = path::Join(impl_->DirsPath(), dname);
DIR* dir = opendir(subdir.c_str());
if (dir) {
impl_->PushDir(dir, dname);
} else if (errno == EACCES) {
// Ignore access errors and proceed.
} else {
impl_->SetStatus(ErrnoToCanonicalStatus(
errno, "Failed to open directory '%s'", subdir));
return false;
}
}
if (impl_->ReturnDirs()) {
entry->SetImpl(new DirectoryEntry::Impl(impl_->RelPath(), dent));
return true;
}
} else if (dent->d_type & DT_REG) {
// Handle regular files.
if (impl_->ReturnFiles()) {
entry->SetImpl(new DirectoryEntry::Impl(impl_->RelPath(), dent));
return true;
}
}
}
if (errno) {
impl_->SetStatus(ErrnoToCanonicalStatus(
errno, "Failed to iterate over directory '%s'", impl_->DirsPath()));
}
return false;
}
}; // namespace cdc_ft