Files
repertory/src/drives/fuse/fuse_drive.cpp
Scott E. Graves 1b8de3b097
All checks were successful
BlockStorage/repertory_osx_builds/pipeline/head This commit looks good
BlockStorage/repertory_linux_builds/pipeline/head This commit looks good
v2.0.1-rc (#13)
Reviewed-on: #13
2023-12-10 18:11:20 +00:00

1354 lines
43 KiB
C++

/*
Copyright <2018-2023> <scott.e.graves@protonmail.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
#ifndef _WIN32
#include "drives/fuse/fuse_drive.hpp"
#include "app_config.hpp"
#include "drives/directory_cache.hpp"
#include "drives/directory_iterator.hpp"
#include "drives/eviction.hpp"
#include "drives/fuse/events.hpp"
#include "drives/fuse/remotefuse/remote_server.hpp"
#include "events/consumers/console_consumer.hpp"
#include "events/consumers/logging_consumer.hpp"
#include "events/event_system.hpp"
#include "events/events.hpp"
#include "platform/platform.hpp"
#include "providers/i_provider.hpp"
#include "rpc/server/full_server.hpp"
#include "types/repertory.hpp"
#include "types/startup_exception.hpp"
#include "utils/Base64.hpp"
#include "utils/error_utils.hpp"
#include "utils/file_utils.hpp"
#include "utils/polling.hpp"
#include "utils/utils.hpp"
namespace repertory {
fuse_drive::fuse_drive(app_config &config, lock_data &lock_data,
i_provider &provider)
: fuse_drive_base(config), lock_data_(lock_data), provider_(provider) {}
#ifdef __APPLE__
api_error fuse_drive::chflags_impl(std::string api_path, uint32_t flags) {
return check_and_perform(api_path, X_OK, [&](api_meta_map &) -> api_error {
return provider_.set_item_meta(api_path, META_OSXFLAGS,
std::to_string(flags));
});
}
#endif // __APPLE__
#if FUSE_USE_VERSION >= 30
auto fuse_drive::chmod_impl(std::string api_path, mode_t mode,
struct fuse_file_info * /*file_info*/)
-> api_error {
#else
auto fuse_drive::chmod_impl(std::string api_path, mode_t mode) -> api_error {
#endif
return check_and_perform(api_path, X_OK, [&](api_meta_map &) -> api_error {
return provider_.set_item_meta(api_path, META_MODE, std::to_string(mode));
});
}
#if FUSE_USE_VERSION >= 30
auto fuse_drive::chown_impl(std::string api_path, uid_t uid, gid_t gid,
struct fuse_file_info * /*file_info*/)
-> api_error {
#else
auto fuse_drive::chown_impl(std::string api_path, uid_t uid, gid_t gid)
-> api_error {
#endif
return check_and_perform(api_path, X_OK,
[&](api_meta_map &meta) -> api_error {
meta.clear();
if (uid != static_cast<uid_t>(-1)) {
meta[META_UID] = std::to_string(uid);
}
if (gid != static_cast<gid_t>(-1)) {
meta[META_GID] = std::to_string(gid);
}
if (not meta.empty()) {
return provider_.set_item_meta(api_path, meta);
}
return api_error::success;
});
}
auto fuse_drive::create_impl(std::string api_path, mode_t mode,
struct fuse_file_info *file_info) -> api_error {
file_info->fh = 0U;
const auto is_directory_op =
((file_info->flags & O_DIRECTORY) == O_DIRECTORY);
const auto is_create_op = ((file_info->flags & O_CREAT) == O_CREAT);
const auto is_truncate_op = (((file_info->flags & O_TRUNC) != 0) &&
(((file_info->flags & O_WRONLY) != 0) ||
((file_info->flags & O_RDWR) != 0)));
if (((file_info->flags & O_WRONLY) != 0) ||
((file_info->flags & O_RDWR) != 0)) {
const auto res = provider_.is_file_writeable(api_path)
? api_error::success
: api_error::permission_denied;
if (res != api_error::success) {
return res;
}
}
auto res = check_parent_access(api_path, X_OK);
if (res != api_error::success) {
return res;
}
if (is_create_op) {
if ((res = check_access(api_path, W_OK)) == api_error::item_not_found) {
res = check_parent_access(api_path, W_OK);
}
} else if ((res = check_access(api_path, R_OK)) ==
api_error::item_not_found) {
res = check_parent_access(api_path, R_OK);
}
if (res != api_error::success) {
return res;
}
if (is_create_op && is_directory_op) {
return api_error::invalid_operation;
}
if (not is_create_op) {
bool dir_exists{};
res = provider_.is_directory(api_path, dir_exists);
if (res != api_error::success) {
return res;
}
bool file_exists{};
res = provider_.is_file(api_path, file_exists);
if (res != api_error::success) {
return res;
}
if (not(is_directory_op ? dir_exists : file_exists)) {
return (is_directory_op ? api_error::directory_not_found
: api_error::item_not_found);
}
}
std::uint64_t handle{};
{
std::shared_ptr<i_open_file> open_file;
if (is_create_op) {
const auto now = utils::get_file_time_now();
#ifdef __APPLE__
const auto osx_flags = static_cast<std::uint32_t>(file_info->flags);
#else
const auto osx_flags = 0U;
#endif
auto meta = create_meta_attributes(
now, FILE_ATTRIBUTE_NORMAL | FILE_ATTRIBUTE_ARCHIVE, now, now,
is_directory_op, get_effective_gid(), "", mode, now, 0U, osx_flags,
0U,
utils::path::combine(config_.get_cache_directory(),
{utils::create_uuid_string()}),
get_effective_uid(), now);
res = fm_->create(api_path, meta, file_info->flags, handle, open_file);
if ((res != api_error::item_exists) && (res != api_error::success)) {
return res;
}
} else if (((res = fm_->open(api_path, is_directory_op, file_info->flags,
handle, open_file)) != api_error::success)) {
return res;
}
}
file_info->fh = handle;
if (is_truncate_op) {
#if FUSE_USE_VERSION >= 30
if ((res = truncate_impl(api_path, 0, file_info)) != api_error::success) {
#else
if ((res = ftruncate_impl(api_path, 0, file_info)) != api_error::success) {
#endif
fm_->close(handle);
file_info->fh = 0U;
errno = std::abs(utils::from_api_error(res));
return res;
}
}
return api_error::success;
}
void fuse_drive::destroy_impl(void *ptr) {
event_system::instance().raise<drive_unmount_pending>(get_mount_location());
remote_server_.reset();
if (server_) {
server_->stop();
}
polling::instance().stop();
if (eviction_) {
eviction_->stop();
}
if (fm_) {
fm_->stop();
}
provider_.stop();
if (directory_cache_) {
directory_cache_->stop();
}
directory_cache_.reset();
eviction_.reset();
server_.reset();
fm_.reset();
event_system::instance().raise<drive_unmounted>(get_mount_location());
config_.save();
if (not lock_data_.set_mount_state(false, "", -1)) {
utils::error::raise_error(__FUNCTION__, "failed to set mount state");
}
fuse_base::destroy_impl(ptr);
}
auto fuse_drive::fallocate_impl(std::string /*api_path*/, int mode,
off_t offset, off_t length,
struct fuse_file_info *file_info) -> api_error {
std::shared_ptr<i_open_file> open_file;
if (not fm_->get_open_file(file_info->fh, true, open_file)) {
return api_error::invalid_handle;
}
auto res = check_writeable(open_file->get_open_data(file_info->fh),
api_error::invalid_handle);
if (res != api_error::success) {
return res;
}
if ((res = check_open_flags(
open_file->get_open_data(file_info->fh), O_WRONLY | O_APPEND,
api_error::invalid_handle)) != api_error::success) {
return res;
}
i_open_file::native_operation_callback allocator;
#ifdef __APPLE__
fstore_t fstore = {0};
if (not(mode & PREALLOCATE)) {
if (mode & ALLOCATECONTIG) {
fstore.fst_flags |= F_ALLOCATECONTIG;
}
if (mode & ALLOCATEALL) {
fstore.fst_flags |= F_ALLOCATEALL;
}
if (mode & ALLOCATEFROMPEOF) {
fstore.fst_posmode = F_PEOFPOSMODE;
} else if (mode & ALLOCATEFROMVOL) {
fstore.fst_posmode = F_VOLPOSMODE;
}
fstore.fst_offset = offset;
fstore.fst_length = length;
allocator = [&](native_handle handle) -> api_error {
return fcntl(handle, F_PREALLOCATE, &fstore) >= 0 ? api_error::success
: api_error::os_error;
};
} else {
return api_error::not_supported;
}
#else // __APPLE__
allocator = [&](native_handle handle) -> api_error {
return (fallocate(handle, mode, offset, length) == -1) ? api_error::os_error
: api_error::success;
};
#endif // __APPLE__
return open_file->native_operation(
static_cast<std::uint64_t>(offset + length), allocator);
}
auto fuse_drive::fgetattr_impl(std::string api_path, struct stat *st,
struct fuse_file_info *file_info) -> api_error {
std::shared_ptr<i_open_file> open_file;
if (not fm_->get_open_file(file_info->fh, false, open_file)) {
return api_error::invalid_handle;
}
api_meta_map meta{};
auto res = provider_.get_item_meta(api_path, meta);
if (res != api_error::success) {
return res;
}
bool directory{};
res = provider_.is_directory(api_path, directory);
if (res != api_error::success) {
return res;
}
fuse_drive_base::populate_stat(api_path, open_file->get_file_size(), meta,
directory, provider_, st);
return api_error::success;
}
#ifdef __APPLE__
auto fuse_drive::fsetattr_x_impl(std::string api_path, struct setattr_x *attr,
struct fuse_file_info *file_info)
-> api_error {
std::shared_ptr<i_open_file> f;
if (not fm_->get_open_file(file_info->fh, false, f)) {
return api_error::invalid_handle;
}
return setattr_x_impl(api_path, attr);
}
#endif // __APPLE__
auto fuse_drive::fsync_impl(std::string /*api_path*/, int datasync,
struct fuse_file_info *file_info) -> api_error {
std::shared_ptr<i_open_file> open_file;
if (not fm_->get_open_file(file_info->fh, false, open_file)) {
return api_error::invalid_handle;
}
return open_file->native_operation([&datasync](int handle) -> api_error {
if (handle != REPERTORY_INVALID_HANDLE) {
#ifdef __APPLE__
if ((datasync == 0 ? fsync(handle) : fcntl(handle, F_FULLFSYNC)) == -1) {
#else // __APPLE__
if ((datasync == 0 ? fsync(handle) : fdatasync(handle)) == -1) {
#endif // __APPLE__
return api_error::os_error;
}
}
return api_error::success;
});
}
#if FUSE_USE_VERSION < 30
api_error fuse_drive::ftruncate_impl(std::string /*api_path*/, off_t size,
struct fuse_file_info *file_info) {
std::shared_ptr<i_open_file> f;
if (not fm_->get_open_file(file_info->fh, true, f)) {
return api_error::invalid_handle;
}
const auto res = check_writeable(f->get_open_data(file_info->fh),
api_error::invalid_handle);
if (res != api_error::success) {
return res;
}
return f->resize(size);
}
#endif
auto fuse_drive::get_directory_item_count(const std::string &api_path) const
-> std::uint64_t {
return provider_.get_directory_item_count(api_path);
}
auto fuse_drive::get_directory_items(const std::string &api_path) const
-> directory_item_list {
directory_item_list list{};
auto res = provider_.get_directory_items(api_path, list);
if (res != api_error::success) {
utils::error::raise_api_path_error(__FUNCTION__, api_path, res,
"failed to get directory items");
}
return list;
}
auto fuse_drive::get_file_size(const std::string &api_path) const
-> std::uint64_t {
std::uint64_t file_size{};
auto res = provider_.get_file_size(api_path, file_size);
if (res == api_error::success) {
utils::error::raise_api_path_error(__FUNCTION__, api_path, res,
"failed to get file size from provider");
}
return file_size;
}
auto fuse_drive::get_item_meta(const std::string &api_path,
api_meta_map &meta) const -> api_error {
return provider_.get_item_meta(api_path, meta);
}
auto fuse_drive::get_item_meta(const std::string &api_path,
const std::string &name,
std::string &value) const -> api_error {
api_meta_map meta{};
const auto ret = get_item_meta(api_path, meta);
if (ret == api_error::success) {
value = meta[name];
}
return ret;
}
#if FUSE_USE_VERSION >= 30
auto fuse_drive::getattr_impl(std::string api_path, struct stat *st,
struct fuse_file_info * /*file_info*/)
-> api_error {
#else
auto fuse_drive::getattr_impl(std::string api_path, struct stat *st)
-> api_error {
#endif
const auto parent = utils::path::get_parent_api_path(api_path);
auto res = check_parent_access(api_path, X_OK);
if (res != api_error::success) {
return res;
}
auto found = false;
directory_cache_->execute_action(parent, [&](directory_iterator &iter) {
directory_item dir_item{};
if ((found = (iter.get_directory_item(api_path, dir_item) ==
api_error::success))) {
fuse_drive_base::populate_stat(api_path, dir_item.size, dir_item.meta,
dir_item.directory, provider_, st);
}
});
if (not found) {
api_meta_map meta{};
if ((res = provider_.get_item_meta(api_path, meta)) != api_error::success) {
return res;
}
bool directory{};
res = provider_.is_directory(api_path, directory);
if (res != api_error::success) {
return res;
}
fuse_drive_base::populate_stat(api_path,
utils::string::to_uint64(meta[META_SIZE]),
meta, directory, provider_, st);
}
return api_error::success;
}
auto fuse_drive::get_total_drive_space() const -> std::uint64_t {
return provider_.get_total_drive_space();
}
auto fuse_drive::get_total_item_count() const -> std::uint64_t {
return provider_.get_total_item_count();
}
auto fuse_drive::get_used_drive_space() const -> std::uint64_t {
return provider_.get_used_drive_space();
}
void fuse_drive::get_volume_info(UINT64 &total_size, UINT64 &free_size,
std::string &volume_label) const {
total_size = provider_.get_total_drive_space();
free_size = total_size - provider_.get_used_drive_space();
volume_label = utils::create_volume_label(config_.get_provider_type());
}
#ifdef __APPLE__
auto fuse_drive::getxtimes_impl(std::string api_path, struct timespec *bkuptime,
struct timespec *crtime) -> api_error {
if (not(bkuptime && crtime)) {
return api_error::bad_address;
}
auto res = check_parent_access(api_path, X_OK);
if (res != api_error::success) {
return res;
}
api_meta_map meta{};
if ((res = provider_.get_item_meta(api_path, meta)) != api_error::success) {
return res;
}
get_timespec_from_meta(meta, META_CREATION, *crtime);
get_timespec_from_meta(meta, META_BACKUP, *bkuptime);
return api_error::success;
}
#endif // __APPLE__
#if FUSE_USE_VERSION >= 30
auto fuse_drive::init_impl(struct fuse_conn_info *conn, struct fuse_config *cfg)
-> void * {
#else
void *fuse_drive::init_impl(struct fuse_conn_info *conn) {
#endif
#if FUSE_USE_VERSION >= 30
auto *ret = fuse_drive_base::init_impl(conn, cfg);
#else
auto *ret = fuse_drive_base::init_impl(conn);
#endif
if (console_enabled_) {
console_consumer_ = std::make_unique<console_consumer>();
}
logging_consumer_ = std::make_unique<logging_consumer>(
config_.get_log_directory(), config_.get_event_level());
event_system::instance().start();
was_mounted_ = true;
polling::instance().start(&config_);
fm_ = std::make_unique<file_manager>(config_, provider_);
server_ = std::make_unique<full_server>(config_, provider_, *fm_);
if (not provider_.is_direct_only()) {
eviction_ = std::make_unique<eviction>(provider_, config_, *fm_);
}
directory_cache_ = std::make_unique<directory_cache>();
try {
directory_cache_->start();
server_->start();
if (not provider_.start(
[this](bool directory, api_file &file) -> api_error {
return provider_meta_handler(provider_, directory, file);
},
fm_.get())) {
throw startup_exception("provider is offline");
}
fm_->start();
if (eviction_) {
eviction_->start();
}
if (config_.get_enable_remote_mount()) {
remote_server_ = std::make_unique<remote_fuse::remote_server>(
config_, *this, get_mount_location());
}
if (not lock_data_.set_mount_state(true, get_mount_location(), getpid())) {
utils::error::raise_error(__FUNCTION__, "failed to set mount state");
}
event_system::instance().raise<drive_mounted>(get_mount_location());
} catch (const std::exception &e) {
utils::error::raise_error(__FUNCTION__, e, "exception during fuse init");
destroy_impl(this);
fuse_exit(fuse_get_context()->fuse);
}
return ret;
}
auto fuse_drive::is_processing(const std::string &api_path) const -> bool {
return fm_->is_processing(api_path);
}
auto fuse_drive::mkdir_impl(std::string api_path, mode_t mode) -> api_error {
auto res = check_parent_access(api_path, W_OK | X_OK);
if (res != api_error::success) {
return res;
}
const auto now = utils::get_file_time_now();
auto meta = create_meta_attributes(now, FILE_ATTRIBUTE_DIRECTORY, now, now,
true, get_effective_gid(), "", mode, now,
0U, 0U, 0U, "", get_effective_uid(), now);
if ((res = provider_.create_directory(api_path, meta)) !=
api_error::success) {
return res;
}
if (api_path != "/") {
if ((res = provider_.set_item_meta(
utils::path::get_parent_api_path(api_path), META_MODIFIED,
std::to_string(now))) != api_error::success) {
utils::error::raise_api_path_error(
__FUNCTION__, api_path, res, "failed to set directory modified time");
}
}
return api_error::success;
}
void fuse_drive::notify_fuse_main_exit(int &ret) {
if (was_mounted_) {
event_system::instance().raise<drive_mount_result>(std::to_string(ret));
event_system::instance().stop();
logging_consumer_.reset();
console_consumer_.reset();
}
}
auto fuse_drive::open_impl(std::string api_path,
struct fuse_file_info *file_info) -> api_error {
file_info->flags &= (~O_CREAT);
return create_impl(api_path, 0, file_info);
}
auto fuse_drive::opendir_impl(std::string api_path,
struct fuse_file_info *file_info) -> api_error {
const auto mask =
(O_RDONLY != (file_info->flags & O_ACCMODE) ? W_OK : R_OK) | X_OK;
auto res = check_access(api_path, mask);
if (res != api_error::success) {
return res;
}
if ((res = check_parent_access(api_path, mask)) != api_error::success) {
return res;
}
bool exists{};
res = provider_.is_directory(api_path, exists);
if (res != api_error::success) {
return res;
}
if (not exists) {
return api_error::directory_not_found;
}
directory_item_list list{};
if ((res = provider_.get_directory_items(api_path, list)) !=
api_error::success) {
return res;
}
auto *iter = new directory_iterator(std::move(list));
file_info->fh = reinterpret_cast<std::uint64_t>(iter);
directory_cache_->set_directory(api_path, iter);
return api_error::success;
}
auto fuse_drive::read_impl(std::string api_path, char *buffer, size_t read_size,
off_t read_offset, struct fuse_file_info *file_info,
std::size_t &bytes_read) -> api_error {
std::shared_ptr<i_open_file> open_file;
if (not fm_->get_open_file(file_info->fh, false, open_file)) {
return api_error::item_not_found;
}
auto res = check_readable(open_file->get_open_data(file_info->fh),
api_error::invalid_handle);
if (res != api_error::success) {
return res;
}
data_buffer data;
res =
open_file->read(read_size, static_cast<std::uint64_t>(read_offset), data);
if ((bytes_read = data.size()) != 0U) {
std::memcpy(buffer, data.data(), data.size());
data.clear();
update_accessed_time(api_path);
}
return res;
}
#if FUSE_USE_VERSION >= 30
auto fuse_drive::readdir_impl(std::string api_path, void *buf,
fuse_fill_dir_t fuse_fill_dir, off_t offset,
struct fuse_file_info *file_info,
fuse_readdir_flags /*flags*/) -> api_error {
#else
auto fuse_drive::readdir_impl(std::string api_path, void *buf,
fuse_fill_dir_t fuse_fill_dir, off_t offset,
struct fuse_file_info *file_info) -> api_error {
#endif
auto res = check_access(api_path, X_OK);
if (res != api_error::success) {
return res;
}
auto *iter = reinterpret_cast<directory_iterator *>(file_info->fh);
if (iter == nullptr) {
return api_error::invalid_handle;
}
while (res == api_error::success) {
res = (iter->fill_buffer(
static_cast<remote::file_offset>(offset++), fuse_fill_dir, buf,
[this](const std::string &cur_api_path,
std::uint64_t cur_file_size, const api_meta_map &meta,
bool directory, struct stat *st) {
fuse_drive_base::populate_stat(cur_api_path, cur_file_size,
meta, directory, provider_, st);
}) == 0)
? api_error::success
: api_error::os_error;
}
if ((res == api_error::os_error) && ((errno == 120) || (errno == ENOMEM))) {
errno = 0;
return api_error::success;
}
return res;
}
auto fuse_drive::release_impl(std::string /*api_path*/,
struct fuse_file_info *file_info) -> api_error {
fm_->close(file_info->fh);
return api_error::success;
}
auto fuse_drive::releasedir_impl(std::string /*api_path*/,
struct fuse_file_info *file_info)
-> api_error {
auto *iter = reinterpret_cast<directory_iterator *>(file_info->fh);
if (iter == nullptr) {
return api_error::invalid_handle;
}
directory_cache_->remove_directory(iter);
delete iter;
return api_error::success;
}
auto fuse_drive::rename_directory(const std::string &from_api_path,
const std::string &to_api_path) -> int {
const auto res = fm_->rename_directory(from_api_path, to_api_path);
errno = std::abs(utils::from_api_error(res));
return (res == api_error::success) ? 0 : -1;
}
auto fuse_drive::rename_file(const std::string &from_api_path,
const std::string &to_api_path, bool overwrite)
-> int {
const auto res = fm_->rename_file(from_api_path, to_api_path, overwrite);
errno = std::abs(utils::from_api_error(res));
return (res == api_error::success) ? 0 : -1;
}
#if FUSE_USE_VERSION >= 30
auto fuse_drive::rename_impl(std::string from_api_path, std::string to_api_path,
unsigned int /*flags*/) -> api_error {
#else
auto fuse_drive::rename_impl(std::string from_api_path, std::string to_api_path)
-> api_error {
#endif
auto res = check_parent_access(to_api_path, W_OK | X_OK);
if (res != api_error::success) {
return res;
}
if ((res = check_parent_access(from_api_path, W_OK | X_OK)) !=
api_error::success) {
return res;
}
bool file{};
res = provider_.is_file(from_api_path, file);
if (res != api_error::success) {
return res;
}
bool directory{};
res = provider_.is_directory(from_api_path, directory);
if (res != api_error::success) {
return res;
}
return file ? fm_->rename_file(from_api_path, to_api_path, true)
: directory ? fm_->rename_directory(from_api_path, to_api_path)
: api_error::item_not_found;
}
auto fuse_drive::rmdir_impl(std::string api_path) -> api_error {
auto res = check_parent_access(api_path, W_OK | X_OK);
if (res != api_error::success) {
return res;
}
if ((res = provider_.remove_directory(api_path)) != api_error::success) {
return res;
}
auto *iter = directory_cache_->remove_directory(api_path);
delete iter;
return api_error::success;
}
#ifdef HAS_SETXATTR
auto fuse_drive::getxattr_common(std::string api_path, const char *name,
char *value, size_t size, int &attribute_size,
uint32_t *position) -> api_error {
std::string attribute_name;
#ifdef __APPLE__
auto res = parse_xattr_parameters(name, value, size, *position,
attribute_name, api_path);
#else // __APPLE__
auto res =
parse_xattr_parameters(name, value, size, attribute_name, api_path);
#endif // __APPLE__
if (res != api_error::success) {
return res;
}
if ((res = check_parent_access(api_path, X_OK)) != api_error::success) {
return res;
}
api_meta_map meta;
auto found = false;
directory_cache_->execute_action(
utils::path::get_parent_api_path(api_path),
[&](directory_iterator &iterator) {
directory_item dir_item{};
if ((found = (iterator.get_directory_item(api_path, dir_item) ==
api_error::success))) {
meta = dir_item.meta;
}
});
if (found ||
((res = provider_.get_item_meta(api_path, meta)) == api_error::success)) {
res = api_error::xattr_not_found;
if (meta.find(attribute_name) != meta.end()) {
const auto data = macaron::Base64::Decode(meta[attribute_name]);
if ((position == nullptr) || (*position < data.size())) {
res = api_error::success;
attribute_size = static_cast<int>(data.size());
if (size != 0U) {
res = api_error::xattr_buffer_small;
if (size >= data.size()) {
memcpy(value, data.data(), data.size());
return api_error::success;
}
}
}
}
}
return res;
}
#ifdef __APPLE__
auto fuse_drive::getxattr_impl(std::string api_path, const char *name,
char *value, size_t size, uint32_t position,
int &attribute_size) -> api_error {
return getxattr_common(api_path, name, value, size, attribute_size,
&position);
}
#else // __APPLE__
auto fuse_drive::getxattr_impl(std::string api_path, const char *name,
char *value, size_t size, int &attribute_size)
-> api_error {
return getxattr_common(api_path, name, value, size, attribute_size, nullptr);
}
#endif // __APPLE__
auto fuse_drive::listxattr_impl(std::string api_path, char *buffer, size_t size,
int &required_size, bool &return_size)
-> api_error {
const auto check_size = (size == 0);
auto res = check_parent_access(api_path, X_OK);
if (res != api_error::success) {
return res;
}
api_meta_map meta;
if ((res = provider_.get_item_meta(api_path, meta)) == api_error::success) {
for (const auto &meta_item : meta) {
if (utils::collection_excludes(META_USED_NAMES, meta_item.first)) {
auto attribute_name = meta_item.first;
#ifdef __APPLE__
if (attribute_name != G_KAUTH_FILESEC_XATTR) {
#endif
const auto attribute_name_size = strlen(attribute_name.c_str()) + 1U;
if (size >= attribute_name_size) {
std::memcpy(&buffer[required_size], attribute_name.data(),
attribute_name_size);
size -= attribute_name_size;
} else {
res = api_error::xattr_buffer_small;
}
required_size += static_cast<int>(attribute_name_size);
#ifdef __APPLE__
}
#endif
}
}
}
return_size = ((res == api_error::success) ||
((res == api_error::xattr_buffer_small) && check_size));
return res;
}
auto fuse_drive::removexattr_impl(std::string api_path, const char *name)
-> api_error {
std::string attribute_name;
#ifdef __APPLE__
auto res = parse_xattr_parameters(name, 0, attribute_name, api_path);
#else
auto res = parse_xattr_parameters(name, attribute_name, api_path);
#endif
if (res != api_error::success) {
return res;
}
return check_and_perform(
api_path, X_OK, [&](api_meta_map &meta) -> api_error {
if ((meta.find(name) != meta.end()) &&
(utils::collection_excludes(META_USED_NAMES, name))) {
return provider_.remove_item_meta(api_path, attribute_name);
}
return api_error::xattr_not_found;
});
}
#ifdef __APPLE__
auto fuse_drive::setxattr_impl(std::string api_path, const char *name,
const char *value, size_t size, int flags,
uint32_t position) -> api_error {
#else // __APPLE__
auto fuse_drive::setxattr_impl(std::string api_path, const char *name,
const char *value, size_t size, int flags)
-> api_error {
#endif
std::string attribute_name;
#ifdef __APPLE__
auto res = parse_xattr_parameters(name, value, size, position, attribute_name,
api_path);
#else // __APPLE__
auto res =
parse_xattr_parameters(name, value, size, attribute_name, api_path);
#endif // __APPLE__
if (res != api_error::success) {
return res;
}
const auto attribute_namespace =
utils::string::contains(attribute_name, ".")
? utils::string::split(attribute_name, '.', false)[0U]
: "";
if ((attribute_name.size() > XATTR_NAME_MAX) || (size > XATTR_SIZE_MAX)) {
return api_error::xattr_too_big;
}
if (utils::string::contains(attribute_name, " .") ||
utils::string::contains(attribute_name, ". ")
#ifndef __APPLE__
|| utils::collection_excludes(utils::attribute_namespaces,
attribute_namespace)
#endif
) {
return api_error::not_supported;
}
api_meta_map meta;
if ((res = provider_.get_item_meta(api_path, meta)) != api_error::success) {
return res;
}
if ((res = check_parent_access(api_path, X_OK)) != api_error::success) {
return res;
}
if ((res = check_owner(meta)) != api_error::success) {
return res;
}
if (flags == XATTR_CREATE) {
if (meta.find(attribute_name) != meta.end()) {
return api_error::xattr_exists;
}
} else if (flags == XATTR_REPLACE) {
if (meta.find(attribute_name) == meta.end()) {
return api_error::xattr_not_found;
}
}
return provider_.set_item_meta(api_path, attribute_name,
macaron::Base64::Encode(value, size));
}
#endif // HAS_SETXATTR
void fuse_drive::set_item_meta(const std::string &api_path,
const std::string &key,
const std::string &value) {
auto res = provider_.set_item_meta(api_path, key, value);
if (res != api_error::success) {
utils::error::raise_api_path_error(__FUNCTION__, api_path, res,
"key|" + key + "|value|" + value);
}
}
#ifdef __APPLE__
auto fuse_drive::setattr_x_impl(std::string api_path, struct setattr_x *attr)
-> api_error {
bool exists{};
auto res = provider_.is_file(api_path, exists);
if (res != api_error::success) {
return res;
}
if (not exists) {
res = provider_.is_directory(api_path, exists);
if (res != api_error::success) {
return res;
}
if (not exists) {
return api_error::item_not_found;
}
}
if (SETATTR_WANTS_MODE(attr)) {
res = chmod_impl(api_path, attr->mode);
if (res != api_error::success) {
return res;
}
}
uid_t uid = -1;
if (SETATTR_WANTS_UID(attr)) {
uid = attr->uid;
}
gid_t gid = -1;
if (SETATTR_WANTS_GID(attr)) {
gid = attr->gid;
}
if ((uid != -1) || (gid != -1)) {
res = chown_impl(api_path, uid, gid);
if (res != api_error::success) {
return res;
}
}
if (SETATTR_WANTS_SIZE(attr)) {
res = truncate_impl(api_path, attr->size);
if (res != api_error::success) {
return res;
}
}
if (SETATTR_WANTS_MODTIME(attr)) {
struct timespec ts[2];
if (SETATTR_WANTS_ACCTIME(attr)) {
ts[0].tv_sec = attr->acctime.tv_sec;
ts[0].tv_nsec = attr->acctime.tv_nsec;
} else {
struct timeval tv {};
gettimeofday(&tv, NULL);
ts[0].tv_sec = tv.tv_sec;
ts[0].tv_nsec = tv.tv_usec * 1000;
}
ts[1].tv_sec = attr->modtime.tv_sec;
ts[1].tv_nsec = attr->modtime.tv_nsec;
res = utimens_impl(api_path, ts);
if (res != api_error::success) {
return res;
}
}
if (SETATTR_WANTS_CRTIME(attr)) {
res = setcrtime_impl(api_path, &attr->crtime);
if (res != api_error::success) {
return res;
}
}
if (SETATTR_WANTS_CHGTIME(attr)) {
res = setchgtime_impl(api_path, &attr->chgtime);
if (res != api_error::success) {
return res;
}
}
if (SETATTR_WANTS_BKUPTIME(attr)) {
res = setbkuptime_impl(api_path, &attr->bkuptime);
if (res != api_error::success) {
return res;
}
}
if (SETATTR_WANTS_FLAGS(attr)) {
res = chflags_impl(api_path, attr->flags);
if (res != api_error::success) {
return res;
}
}
return api_error::success;
}
auto fuse_drive::setbkuptime_impl(std::string api_path,
const struct timespec *bkuptime)
-> api_error {
return check_and_perform(
api_path, X_OK, [&](api_meta_map &meta) -> api_error {
const auto nanos =
bkuptime->tv_nsec + (bkuptime->tv_nsec * NANOS_PER_SECOND);
return provider_.set_item_meta(api_path, META_BACKUP,
std::to_string(nanos));
});
}
auto fuse_drive::setchgtime_impl(std::string api_path,
const struct timespec *chgtime) -> api_error {
return check_and_perform(
api_path, X_OK, [&](api_meta_map &meta) -> api_error {
const auto nanos =
chgtime->tv_nsec + (chgtime->tv_nsec * NANOS_PER_SECOND);
return provider_.set_item_meta(api_path, META_CHANGED,
std::to_string(nanos));
});
}
auto fuse_drive::setcrtime_impl(std::string api_path,
const struct timespec *crtime) -> api_error {
return check_and_perform(
api_path, X_OK, [&](api_meta_map &meta) -> api_error {
const auto nanos =
crtime->tv_nsec + (crtime->tv_nsec * NANOS_PER_SECOND);
return provider_.set_item_meta(api_path, META_CREATION,
std::to_string(nanos));
});
}
auto fuse_drive::setvolname_impl(const char * /*volname*/) -> api_error {
return api_error::success;
}
auto fuse_drive::statfs_x_impl(std::string /*api_path*/, struct statfs *stbuf)
-> api_error {
if (statfs(&config_.get_cache_directory()[0], stbuf) != 0) {
return api_error::os_error;
}
const auto total_bytes = provider_.get_total_drive_space();
const auto total_used = provider_.get_used_drive_space();
const auto used_blocks = utils::divide_with_ceiling(
total_used, static_cast<std::uint64_t>(stbuf->f_bsize));
stbuf->f_blocks = utils::divide_with_ceiling(
total_bytes, static_cast<std::uint64_t>(stbuf->f_bsize));
stbuf->f_bavail = stbuf->f_bfree =
stbuf->f_blocks ? (stbuf->f_blocks - used_blocks) : 0;
stbuf->f_files = 4294967295;
stbuf->f_ffree = stbuf->f_files - provider_.get_total_item_count();
stbuf->f_owner = getuid();
strncpy(&stbuf->f_mntonname[0], get_mount_location().c_str(), MNAMELEN);
strncpy(&stbuf->f_mntfromname[0],
(utils::create_volume_label(config_.get_provider_type())).c_str(),
MNAMELEN);
return api_error::success;
}
#else // __APPLE__
auto fuse_drive::statfs_impl(std::string /*api_path*/, struct statvfs *stbuf)
-> api_error {
if (statvfs(config_.get_cache_directory().data(), stbuf) != 0) {
return api_error::os_error;
}
const auto total_bytes = provider_.get_total_drive_space();
const auto total_used = provider_.get_used_drive_space();
const auto used_blocks =
utils::divide_with_ceiling(total_used, stbuf->f_frsize);
stbuf->f_files = 4294967295;
stbuf->f_blocks = utils::divide_with_ceiling(total_bytes, stbuf->f_frsize);
stbuf->f_bavail = stbuf->f_bfree =
stbuf->f_blocks == 0U ? 0 : (stbuf->f_blocks - used_blocks);
stbuf->f_ffree = stbuf->f_favail =
stbuf->f_files - provider_.get_total_item_count();
return api_error::success;
}
#endif // __APPLE__
#if FUSE_USE_VERSION >= 30
auto fuse_drive::truncate_impl(std::string api_path, off_t size,
struct fuse_file_info * /*file_info*/)
-> api_error {
#else
auto fuse_drive::truncate_impl(std::string api_path, off_t size) -> api_error {
#endif
auto res = provider_.is_file_writeable(api_path)
? api_error::success
: api_error::permission_denied;
if (res != api_error::success) {
return res;
}
if ((res = check_parent_access(api_path, X_OK)) != api_error::success) {
return res;
}
if ((res = check_access(api_path, W_OK)) != api_error::success) {
return res;
}
std::uint64_t handle{};
{
open_file_data ofd{O_RDWR};
std::shared_ptr<i_open_file> open_file;
if ((res = fm_->open(api_path, false, ofd, handle, open_file)) !=
api_error::success) {
return res;
}
res = open_file->resize(static_cast<std::uint64_t>(size));
}
fm_->close(handle);
return res;
}
auto fuse_drive::unlink_impl(std::string api_path) -> api_error {
bool exists{};
auto res = provider_.is_directory(api_path, exists);
if (res != api_error::success) {
return res;
}
if (exists) {
return api_error::directory_exists;
}
res = check_parent_access(api_path, W_OK | X_OK);
if (res != api_error::success) {
return res;
}
return fm_->remove_file(api_path);
}
#if FUSE_USE_VERSION >= 30
auto fuse_drive::utimens_impl(std::string api_path, const struct timespec tv[2],
struct fuse_file_info * /*file_info*/)
-> api_error {
#else
auto fuse_drive::utimens_impl(std::string api_path, const struct timespec tv[2])
-> api_error {
#endif
api_meta_map meta;
auto res = provider_.get_item_meta(api_path, meta);
if (res != api_error::success) {
return res;
}
if ((res = check_owner(meta)) != api_error::success) {
return res;
}
meta.clear();
if ((tv == nullptr) || (tv[0U].tv_nsec == UTIME_NOW)) {
meta[META_ACCESSED] = std::to_string(utils::get_file_time_now());
} else if (tv[0U].tv_nsec != UTIME_OMIT) {
const auto val = tv[0U].tv_nsec + (tv[0U].tv_sec * NANOS_PER_SECOND);
meta[META_ACCESSED] = std::to_string(val);
}
if ((tv == nullptr) || (tv[1U].tv_nsec == UTIME_NOW)) {
meta[META_MODIFIED] = std::to_string(utils::get_file_time_now());
} else if (tv[1U].tv_nsec != UTIME_OMIT) {
const auto val = tv[1U].tv_nsec + (tv[1U].tv_sec * NANOS_PER_SECOND);
meta[META_MODIFIED] = std::to_string(val);
}
if (not meta.empty()) {
return provider_.set_item_meta(api_path, meta);
}
return api_error::success;
}
auto fuse_drive::write_impl(std::string /*api_path*/
,
const char *buffer, size_t write_size,
off_t write_offset,
struct fuse_file_info *file_info,
std::size_t &bytes_written) -> api_error {
std::shared_ptr<i_open_file> open_file;
if (not fm_->get_open_file(file_info->fh, true, open_file)) {
return api_error::item_not_found;
}
auto res = check_writeable(open_file->get_open_data(file_info->fh),
api_error::invalid_handle);
if (res != api_error::success) {
return res;
}
if (write_size > 0) {
if ((open_file->get_open_data(file_info->fh) & O_APPEND) != 0) {
write_offset = static_cast<off_t>(open_file->get_file_size());
}
data_buffer data(write_size);
std::memcpy(data.data(), buffer, write_size);
return open_file->write(static_cast<std::uint64_t>(write_offset),
std::move(data), bytes_written);
}
return api_error::success;
}
void fuse_drive::update_accessed_time(const std::string &api_path) {
if (atime_enabled_) {
auto res = provider_.set_item_meta(
api_path, META_ACCESSED, std::to_string(utils::get_file_time_now()));
if (res != api_error::success) {
utils::error::raise_api_path_error(__FUNCTION__, api_path, res,
"failed to set accessed time");
}
}
}
} // namespace repertory
#endif // _WIN32