mirror of
https://github.com/nestriness/cdc-file-transfer.git
synced 2026-01-30 10:25:37 +02:00
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.
287 lines
7.7 KiB
C++
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
|