Merge branch 'v2.1.0-rc-develop' of https://git.fifthgrid.com/blockstorage/repertory into v2.1.0-rc-develop
This commit is contained in:
@@ -26,6 +26,7 @@
|
||||
### Changes from v2.0.7-release
|
||||
|
||||
* Added check version support to remote mounts
|
||||
* Fixed directory item count bug on S3 provider
|
||||
* Fixed handling of `FALLOC_FL_KEEP_SIZE` on Linux
|
||||
* Fixed intermittent client hang on remote mount server disconnect
|
||||
* Implemented POSIX-compliant `unlink()` with FUSE `hard_remove`
|
||||
|
@@ -25,6 +25,7 @@
|
||||
#include "utils/common.hpp"
|
||||
|
||||
namespace repertory::comm {
|
||||
inline static constexpr std::uint32_t max_packet_bytes{32U * 1024U * 1024U};
|
||||
inline constexpr const std::uint8_t max_read_attempts{5U};
|
||||
inline constexpr const std::uint16_t packet_nonce_size{256U};
|
||||
inline constexpr const std::uint16_t server_handshake_timeout_ms{3000U};
|
||||
@@ -45,6 +46,8 @@ private:
|
||||
boost::asio::ip::tcp::socket &sock;
|
||||
};
|
||||
|
||||
void apply_common_socket_properties(boost::asio::ip::tcp::socket &sock);
|
||||
|
||||
[[nodiscard]] auto is_socket_still_alive(boost::asio::ip::tcp::socket &sock)
|
||||
-> bool;
|
||||
|
||||
|
@@ -1,17 +1,13 @@
|
||||
/*
|
||||
Copyright <2018-2025> <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
|
||||
copies of the Software, and to permit persons 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
|
||||
@@ -24,6 +20,7 @@
|
||||
|
||||
#include "comm/packet/packet.hpp"
|
||||
#include "types/remote.hpp"
|
||||
#include "utils/atomic.hpp"
|
||||
|
||||
using boost::asio::ip::tcp;
|
||||
|
||||
@@ -47,24 +44,25 @@ public:
|
||||
auto operator=(packet_client &&) -> packet_client & = delete;
|
||||
|
||||
private:
|
||||
mutable boost::asio::io_context io_context_;
|
||||
remote::remote_config cfg_;
|
||||
std::string unique_id_;
|
||||
mutable boost::asio::io_context io_context_;
|
||||
utils::atomic<std::string> unique_id_;
|
||||
|
||||
private:
|
||||
bool allow_connections_{true};
|
||||
boost::asio::ip::basic_resolver<boost::asio::ip::tcp>::results_type
|
||||
std::atomic<bool> allow_connections_{true};
|
||||
utils::atomic<
|
||||
boost::asio::ip::basic_resolver<boost::asio::ip::tcp>::results_type>
|
||||
resolve_results_;
|
||||
std::mutex clients_mutex_;
|
||||
std::vector<std::shared_ptr<client>> clients_;
|
||||
std::vector<std::thread> service_threads_;
|
||||
|
||||
private:
|
||||
static void close(client &cli);
|
||||
static void close(client &cli) noexcept;
|
||||
|
||||
void close_all();
|
||||
|
||||
void connect(client &cli);
|
||||
[[nodiscard]] auto connect(client &cli) -> bool;
|
||||
|
||||
[[nodiscard]] auto get_client() -> std::shared_ptr<client>;
|
||||
|
||||
|
@@ -176,6 +176,8 @@ public:
|
||||
const open_file_data &ofd, std::uint64_t &handle,
|
||||
std::shared_ptr<i_open_file> &file) -> api_error;
|
||||
|
||||
[[nodiscard]] auto remove_directory(const std::string &api_path) -> api_error;
|
||||
|
||||
[[nodiscard]] auto remove_file(const std::string &api_path) -> api_error;
|
||||
|
||||
[[nodiscard]] auto rename_directory(const std::string &from_api_path,
|
||||
|
@@ -34,6 +34,11 @@ struct i_http_comm;
|
||||
struct head_object_result;
|
||||
|
||||
class s3_provider final : public base_provider {
|
||||
private:
|
||||
using interate_callback_t = std::function<api_error(
|
||||
const std::string &prefix, const pugi::xml_node &node,
|
||||
const std::string &api_prefix)>;
|
||||
|
||||
public:
|
||||
static const constexpr auto type{provider_type::s3};
|
||||
|
||||
@@ -170,6 +175,11 @@ public:
|
||||
return false;
|
||||
};
|
||||
|
||||
[[nodiscard]] auto iterate_prefix(const std::string &prefix,
|
||||
interate_callback_t prefix_action,
|
||||
interate_callback_t key_action) const
|
||||
-> api_error;
|
||||
|
||||
[[nodiscard]] auto read_file_bytes(const std::string &api_path,
|
||||
std::size_t size, std::uint64_t offset,
|
||||
data_buffer &data,
|
||||
|
@@ -77,6 +77,12 @@ auto is_socket_still_alive(boost::asio::ip::tcp::socket &sock) -> bool {
|
||||
return false;
|
||||
}
|
||||
|
||||
void apply_common_socket_properties(boost::asio::ip::tcp::socket &sock) {
|
||||
sock.set_option(boost::asio::ip::tcp::no_delay(true));
|
||||
sock.set_option(boost::asio::socket_base::linger(false, 0));
|
||||
sock.set_option(boost::asio::socket_base::keep_alive(true));
|
||||
}
|
||||
|
||||
template <class op_t>
|
||||
void run_with_deadline(boost::asio::io_context &io_ctx,
|
||||
boost::asio::ip::tcp::socket &sock, op_t &&operation,
|
||||
|
@@ -1,17 +1,13 @@
|
||||
/*
|
||||
Copyright <2018-2025> <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
|
||||
copies of the Software, and to permit persons 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
|
||||
@@ -26,10 +22,10 @@
|
||||
#include "types/repertory.hpp"
|
||||
#include "utils/collection.hpp"
|
||||
#include "utils/common.hpp"
|
||||
#include "utils/config.hpp"
|
||||
#include "utils/error_utils.hpp"
|
||||
#include "utils/utils.hpp"
|
||||
#include "version.hpp"
|
||||
#include <utils/config.hpp>
|
||||
|
||||
using namespace repertory::comm;
|
||||
|
||||
@@ -42,29 +38,30 @@ packet_client::packet_client(remote::remote_config cfg)
|
||||
}
|
||||
|
||||
packet_client::~packet_client() {
|
||||
allow_connections_ = false;
|
||||
close_all();
|
||||
REPERTORY_USES_FUNCTION_NAME();
|
||||
|
||||
for (std::size_t idx = 0U; idx < service_threads_.size(); ++idx) {
|
||||
io_context_.stop();
|
||||
allow_connections_ = false;
|
||||
|
||||
try {
|
||||
close_all();
|
||||
} catch (...) {
|
||||
}
|
||||
|
||||
io_context_.stop();
|
||||
|
||||
for (auto &thread : service_threads_) {
|
||||
thread.join();
|
||||
if (thread.joinable()) {
|
||||
thread.join();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void packet_client::close(client &cli) {
|
||||
REPERTORY_USES_FUNCTION_NAME();
|
||||
void packet_client::close(client &cli) noexcept {
|
||||
boost::system::error_code err1;
|
||||
auto res = cli.socket.shutdown(boost::asio::socket_base::shutdown_both, err1);
|
||||
|
||||
try {
|
||||
cli.socket.shutdown(boost::asio::socket_base::shutdown_both);
|
||||
|
||||
boost::system::error_code err;
|
||||
[[maybe_unused]] auto res = cli.socket.close(err);
|
||||
} catch (const std::exception &e) {
|
||||
utils::error::raise_error(function_name, e, "connection handshake failed");
|
||||
}
|
||||
boost::system::error_code err2;
|
||||
res = cli.socket.close(err2);
|
||||
}
|
||||
|
||||
void packet_client::close_all() {
|
||||
@@ -72,10 +69,8 @@ void packet_client::close_all() {
|
||||
for (auto &cli : clients_) {
|
||||
close(*cli);
|
||||
}
|
||||
|
||||
clients_.clear();
|
||||
io_context_.restart();
|
||||
resolve_results_ = {};
|
||||
resolve_results_.store({});
|
||||
unique_id_ = utils::create_uuid_string();
|
||||
}
|
||||
|
||||
@@ -94,19 +89,12 @@ auto packet_client::check_version(std::uint32_t client_version,
|
||||
connect_with_deadline(ctx, cli.socket, resolve_results,
|
||||
std::chrono::milliseconds(cfg_.conn_timeout_ms));
|
||||
|
||||
cli.socket.set_option(boost::asio::ip::tcp::no_delay(true));
|
||||
cli.socket.set_option(boost::asio::socket_base::linger(false, 0));
|
||||
cli.socket.set_option(boost::asio::socket_base::keep_alive(true));
|
||||
comm::apply_common_socket_properties(cli.socket);
|
||||
|
||||
if (not handshake(cli, ctx, min_version)) {
|
||||
return api_error::comm_error;
|
||||
}
|
||||
|
||||
if ((min_version & 0xFFU) != 0U) {
|
||||
min_version = 0U;
|
||||
return api_error::incompatible_version;
|
||||
}
|
||||
|
||||
if (client_version < min_version) {
|
||||
return api_error::incompatible_version;
|
||||
}
|
||||
@@ -119,21 +107,20 @@ auto packet_client::check_version(std::uint32_t client_version,
|
||||
return api_error::error;
|
||||
}
|
||||
|
||||
void packet_client::connect(client &cli) {
|
||||
auto packet_client::connect(client &cli) -> bool {
|
||||
try {
|
||||
resolve();
|
||||
|
||||
connect_with_deadline(io_context_, cli.socket, resolve_results_,
|
||||
auto cached = resolve_results_.load();
|
||||
connect_with_deadline(io_context_, cli.socket, cached,
|
||||
std::chrono::milliseconds(cfg_.conn_timeout_ms));
|
||||
|
||||
cli.socket.set_option(boost::asio::ip::tcp::no_delay(true));
|
||||
cli.socket.set_option(boost::asio::socket_base::linger(false, 0));
|
||||
cli.socket.set_option(boost::asio::socket_base::keep_alive(true));
|
||||
comm::apply_common_socket_properties(cli.socket);
|
||||
|
||||
std::uint32_t min_version{};
|
||||
if (not handshake(cli, io_context_, min_version)) {
|
||||
close(cli);
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
packet response;
|
||||
@@ -141,9 +128,10 @@ void packet_client::connect(client &cli) {
|
||||
if (res != 0) {
|
||||
throw std::runtime_error(fmt::format("read packet failed|err|{}", res));
|
||||
}
|
||||
return true;
|
||||
} catch (...) {
|
||||
close(cli);
|
||||
resolve_results_ = {};
|
||||
resolve_results_.store({});
|
||||
throw;
|
||||
}
|
||||
}
|
||||
@@ -151,17 +139,19 @@ void packet_client::connect(client &cli) {
|
||||
auto packet_client::get_client() -> std::shared_ptr<packet_client::client> {
|
||||
REPERTORY_USES_FUNCTION_NAME();
|
||||
|
||||
unique_mutex_lock clients_lock(clients_mutex_);
|
||||
if (not allow_connections_) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
try {
|
||||
unique_mutex_lock clients_lock(clients_mutex_);
|
||||
if (clients_.empty()) {
|
||||
clients_lock.unlock();
|
||||
|
||||
auto cli = std::make_shared<client>(io_context_);
|
||||
connect(*cli);
|
||||
if (not connect(*cli)) {
|
||||
return nullptr;
|
||||
}
|
||||
return cli;
|
||||
}
|
||||
|
||||
@@ -169,7 +159,7 @@ auto packet_client::get_client() -> std::shared_ptr<packet_client::client> {
|
||||
utils::collection::remove_element(clients_, cli);
|
||||
return cli;
|
||||
} catch (const std::exception &e) {
|
||||
utils::error::raise_error(function_name, e, "connection handshake failed");
|
||||
utils::error::raise_error(function_name, e, "get_client failed");
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
@@ -190,7 +180,8 @@ auto packet_client::handshake(client &cli, boost::asio::io_context &ctx,
|
||||
data_buffer buffer;
|
||||
{
|
||||
packet tmp;
|
||||
tmp.encode(utils::get_version_number(REPERTORY_MIN_REMOTE_VERSION));
|
||||
tmp.encode(utils::get_version_number(project_get_version()));
|
||||
tmp.encode(~utils::get_version_number(project_get_version()));
|
||||
tmp.encode(utils::generate_random_string(packet_nonce_size));
|
||||
tmp.to_buffer(buffer);
|
||||
}
|
||||
@@ -198,11 +189,22 @@ auto packet_client::handshake(client &cli, boost::asio::io_context &ctx,
|
||||
read_exact_with_deadline(ctx, cli.socket, boost::asio::buffer(buffer),
|
||||
std::chrono::milliseconds(cfg_.recv_timeout_ms));
|
||||
packet response(buffer);
|
||||
|
||||
auto res = response.decode(min_version);
|
||||
if (res != 0) {
|
||||
throw std::runtime_error("failed to decode server version");
|
||||
}
|
||||
|
||||
std::uint32_t min_version_check{};
|
||||
res = response.decode(min_version_check);
|
||||
if (res != 0) {
|
||||
throw std::runtime_error("failed to decode server version");
|
||||
}
|
||||
|
||||
if (min_version_check != ~min_version) {
|
||||
throw std::runtime_error("failed to decode server version");
|
||||
}
|
||||
|
||||
response.encrypt(cfg_.encryption_token, false);
|
||||
response.to_buffer(buffer);
|
||||
|
||||
@@ -210,7 +212,7 @@ auto packet_client::handshake(client &cli, boost::asio::io_context &ctx,
|
||||
std::chrono::milliseconds(cfg_.send_timeout_ms));
|
||||
return true;
|
||||
} catch (const std::exception &e) {
|
||||
utils::error::raise_error(function_name, e, "handlshake failed");
|
||||
utils::error::raise_error(function_name, e, "handshake failed");
|
||||
}
|
||||
|
||||
return false;
|
||||
@@ -241,6 +243,11 @@ auto packet_client::read_packet(client &cli, boost::asio::io_context &ctx,
|
||||
std::uint32_t to_read{};
|
||||
std::memcpy(&to_read, buffer.data(), sizeof(to_read));
|
||||
boost::endian::big_to_native_inplace(to_read);
|
||||
|
||||
if (to_read == 0U || to_read > comm::max_packet_bytes) {
|
||||
return utils::from_api_error(api_error::comm_error);
|
||||
}
|
||||
|
||||
buffer.resize(to_read);
|
||||
|
||||
read_exact_with_deadline(ctx, cli.socket, boost::asio::buffer(buffer),
|
||||
@@ -255,7 +262,9 @@ auto packet_client::read_packet(client &cli, boost::asio::io_context &ctx,
|
||||
}
|
||||
|
||||
void packet_client::resolve() {
|
||||
if (not resolve_results_.empty()) {
|
||||
boost::asio::ip::basic_resolver<boost::asio::ip::tcp>::results_type cached =
|
||||
resolve_results_;
|
||||
if (not cached.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -282,12 +291,14 @@ auto packet_client::send(std::string_view method, packet &request,
|
||||
REPERTORY_USES_FUNCTION_NAME();
|
||||
|
||||
auto success = false;
|
||||
packet::error_type ret = utils::from_api_error(api_error::error);
|
||||
request.encode_top(method);
|
||||
request.encode_top(utils::get_thread_id());
|
||||
request.encode_top(unique_id_);
|
||||
request.encode_top(PACKET_SERVICE_FLAGS);
|
||||
request.encode_top(std::string{project_get_version()});
|
||||
auto ret = utils::from_api_error(api_error::error);
|
||||
|
||||
auto base_request = request;
|
||||
base_request.encode_top(method);
|
||||
base_request.encode_top(utils::get_thread_id());
|
||||
base_request.encode_top(unique_id_.load());
|
||||
base_request.encode_top(PACKET_SERVICE_FLAGS);
|
||||
base_request.encode_top(std::string{project_get_version()});
|
||||
|
||||
for (std::uint8_t retry = 1U;
|
||||
allow_connections_ && not success && (retry <= max_read_attempts);
|
||||
@@ -295,12 +306,16 @@ auto packet_client::send(std::string_view method, packet &request,
|
||||
auto current_client = get_client();
|
||||
if (current_client) {
|
||||
try {
|
||||
request.encode_top(current_client->nonce);
|
||||
request.encrypt(cfg_.encryption_token);
|
||||
auto current_request = base_request;
|
||||
current_request.encode_top(current_client->nonce);
|
||||
request = current_request;
|
||||
|
||||
current_request.encrypt(cfg_.encryption_token);
|
||||
|
||||
write_all_with_deadline(
|
||||
io_context_, current_client->socket,
|
||||
boost::asio::buffer(&request[0], request.get_size()),
|
||||
boost::asio::buffer(¤t_request[0],
|
||||
current_request.get_size()),
|
||||
std::chrono::milliseconds(cfg_.send_timeout_ms));
|
||||
|
||||
ret = read_packet(*current_client, response);
|
||||
@@ -318,7 +333,6 @@ auto packet_client::send(std::string_view method, packet &request,
|
||||
}
|
||||
} catch (const std::exception &e) {
|
||||
utils::error::raise_error(function_name, e, "send failed");
|
||||
|
||||
close(*current_client);
|
||||
if (allow_connections_ && (retry < max_read_attempts)) {
|
||||
std::this_thread::sleep_for(1s);
|
||||
|
@@ -92,6 +92,7 @@ auto packet_server::handshake(std::shared_ptr<connection> conn) const -> bool {
|
||||
data_buffer buffer;
|
||||
packet request;
|
||||
request.encode(utils::get_version_number(REPERTORY_MIN_REMOTE_VERSION));
|
||||
request.encode(~utils::get_version_number(REPERTORY_MIN_REMOTE_VERSION));
|
||||
request.encode(conn->nonce);
|
||||
request.to_buffer(buffer);
|
||||
auto to_read{buffer.size() + utils::encryption::encryption_header_size};
|
||||
@@ -145,17 +146,26 @@ auto packet_server::handshake(std::shared_ptr<connection> conn) const -> bool {
|
||||
if (response.decrypt(encryption_token_) == 0) {
|
||||
std::uint32_t client_version{};
|
||||
if (response.decode(client_version) == 0) {
|
||||
std::string nonce;
|
||||
if (response.decode(nonce) == 0) {
|
||||
if (nonce == conn->nonce) {
|
||||
conn->generate_nonce();
|
||||
return true;
|
||||
std::uint32_t client_version_check{};
|
||||
if (response.decode(client_version_check) == 0) {
|
||||
if (~client_version != client_version_check) {
|
||||
throw std::runtime_error("client version check failed");
|
||||
}
|
||||
|
||||
throw std::runtime_error("nonce mismatch");
|
||||
std::string nonce;
|
||||
if (response.decode(nonce) == 0) {
|
||||
if (nonce == conn->nonce) {
|
||||
conn->generate_nonce();
|
||||
return true;
|
||||
}
|
||||
|
||||
throw std::runtime_error("nonce mismatch");
|
||||
}
|
||||
|
||||
throw std::runtime_error("invalid nonce");
|
||||
}
|
||||
|
||||
throw std::runtime_error("invalid nonce");
|
||||
throw std::runtime_error("invalid client version");
|
||||
}
|
||||
|
||||
throw std::runtime_error("invalid client version");
|
||||
@@ -222,9 +232,7 @@ void packet_server::on_accept(std::shared_ptr<connection> conn,
|
||||
return;
|
||||
}
|
||||
|
||||
conn->socket.set_option(boost::asio::ip::tcp::no_delay(true));
|
||||
conn->socket.set_option(boost::asio::socket_base::linger(false, 0));
|
||||
conn->socket.set_option(boost::asio::socket_base::keep_alive(true));
|
||||
comm::apply_common_socket_properties(conn->socket);
|
||||
|
||||
boost::asio::dispatch(conn->socket.get_executor(), [this, conn]() {
|
||||
if (not handshake(conn)) {
|
||||
@@ -276,6 +284,11 @@ void packet_server::read_packet(std::shared_ptr<connection> conn,
|
||||
}
|
||||
};
|
||||
|
||||
if (data_size > comm::max_packet_bytes) {
|
||||
throw std::runtime_error(
|
||||
fmt::format("packet too large|size|{}", data_size));
|
||||
}
|
||||
|
||||
auto should_send_response = true;
|
||||
auto response = std::make_shared<packet>();
|
||||
conn->buffer.resize(data_size);
|
||||
|
@@ -926,17 +926,20 @@ auto fuse_drive::rename_impl(std::string from_api_path, std::string to_api_path)
|
||||
}
|
||||
|
||||
auto fuse_drive::rmdir_impl(std::string api_path) -> api_error {
|
||||
REPERTORY_USES_FUNCTION_NAME();
|
||||
utils::error::handle_info(function_name, "called");
|
||||
|
||||
auto res = check_parent_access(api_path, W_OK | X_OK);
|
||||
if (res != api_error::success) {
|
||||
return res;
|
||||
}
|
||||
|
||||
res = provider_.remove_directory(api_path);
|
||||
res = fm_->remove_directory(api_path);
|
||||
if (res != api_error::success) {
|
||||
return res;
|
||||
}
|
||||
|
||||
auto iter = directory_cache_->remove_directory(api_path);
|
||||
directory_cache_->remove_directory(api_path);
|
||||
return api_error::success;
|
||||
}
|
||||
|
||||
|
@@ -1721,7 +1721,6 @@ auto remote_server::update_to_windows_format(const std::string &root_api_path,
|
||||
}
|
||||
|
||||
auto update_meta{false};
|
||||
auto default_value = std::to_string(utils::time::get_time_now());
|
||||
const auto ensure_set = [&item, &update_meta](
|
||||
const std::string &name,
|
||||
const std::string &default_value, bool as_time) {
|
||||
@@ -1734,9 +1733,10 @@ auto remote_server::update_to_windows_format(const std::string &root_api_path,
|
||||
}
|
||||
};
|
||||
|
||||
ensure_set(META_ACCESSED, default_value, true);
|
||||
ensure_set(META_CREATION, default_value, true);
|
||||
ensure_set(META_MODIFIED, default_value, true);
|
||||
auto default_time = std::to_string(utils::time::get_time_now());
|
||||
ensure_set(META_ACCESSED, default_time, true);
|
||||
ensure_set(META_CREATION, default_time, true);
|
||||
ensure_set(META_MODIFIED, default_time, true);
|
||||
ensure_set(META_CHANGED, item[JSON_META][META_MODIFIED], true);
|
||||
ensure_set(META_WRITTEN, item[JSON_META][META_MODIFIED], true);
|
||||
ensure_set(META_ATTRIBUTES, "0", false);
|
||||
|
@@ -216,15 +216,11 @@ VOID winfsp_drive::Cleanup(PVOID file_node, PVOID file_desc,
|
||||
FspFileSystemDeleteDirectoryBuffer(&directory_buffer);
|
||||
}
|
||||
|
||||
if (not directory) {
|
||||
return handle_error(fm_->remove_file(api_path));
|
||||
if (directory) {
|
||||
return handle_error(fm_->remove_directory(api_path));
|
||||
}
|
||||
|
||||
if (provider_.get_directory_item_count(api_path) == 0) {
|
||||
return handle_error(provider_.remove_directory(api_path));
|
||||
}
|
||||
|
||||
return handle_error(api_error::directory_not_empty);
|
||||
return handle_error(fm_->remove_file(api_path));
|
||||
}
|
||||
|
||||
if (((flags & FspCleanupSetArchiveBit) != 0U) && not directory) {
|
||||
|
@@ -101,6 +101,10 @@ void file_manager::close(std::uint64_t handle) {
|
||||
|
||||
closeable_file->close();
|
||||
|
||||
if (closeable_file->is_directory()) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto file = utils::file::file{closeable_file->get_source_path()};
|
||||
if (file.remove()) {
|
||||
return;
|
||||
@@ -689,9 +693,45 @@ void file_manager::queue_upload(const std::string &api_path,
|
||||
}
|
||||
}
|
||||
|
||||
auto file_manager::remove_directory(const std::string &api_path) -> api_error {
|
||||
REPERTORY_USES_FUNCTION_NAME();
|
||||
|
||||
if (api_path == "/") {
|
||||
return api_error::permission_denied;
|
||||
}
|
||||
|
||||
unique_recur_mutex_lock open_lock(open_file_mtx_);
|
||||
if (provider_.get_directory_item_count(api_path) != 0) {
|
||||
return api_error::directory_not_empty;
|
||||
}
|
||||
|
||||
auto res = provider_.remove_directory(api_path);
|
||||
if (res != api_error::success) {
|
||||
return res;
|
||||
}
|
||||
|
||||
auto file_iter = open_file_lookup_.find(api_path);
|
||||
if (file_iter == open_file_lookup_.end()) {
|
||||
return api_error::success;
|
||||
}
|
||||
|
||||
auto closed_file = file_iter->second;
|
||||
open_file_lookup_.erase(api_path);
|
||||
|
||||
closed_file->set_unlinked(true);
|
||||
for (const auto &[handle, ofd] : closed_file->get_open_data()) {
|
||||
unlinked_file_lookup_[handle] = closed_file;
|
||||
}
|
||||
open_lock.unlock();
|
||||
|
||||
return api_error::success;
|
||||
}
|
||||
|
||||
auto file_manager::remove_file(const std::string &api_path) -> api_error {
|
||||
REPERTORY_USES_FUNCTION_NAME();
|
||||
|
||||
unique_recur_mutex_lock open_lock(open_file_mtx_);
|
||||
|
||||
filesystem_item fsi{};
|
||||
auto res = provider_.get_filesystem_item(api_path, false, fsi);
|
||||
if (res != api_error::success) {
|
||||
@@ -704,8 +744,6 @@ auto file_manager::remove_file(const std::string &api_path) -> api_error {
|
||||
return res;
|
||||
}
|
||||
|
||||
unique_recur_mutex_lock open_lock(open_file_mtx_);
|
||||
|
||||
unique_mutex_lock upload_lock(upload_mtx_);
|
||||
remove_upload(api_path, true);
|
||||
remove_resume(api_path, fsi.source_path, true);
|
||||
|
@@ -275,69 +275,34 @@ auto s3_provider::get_directory_item_count(const std::string &api_path) const
|
||||
|
||||
try {
|
||||
const auto &cfg{get_s3_config()};
|
||||
auto is_encrypted{not cfg.encryption_token.empty()};
|
||||
const auto is_encrypted{not cfg.encryption_token.empty()};
|
||||
|
||||
std::string key;
|
||||
if (is_encrypted) {
|
||||
auto res{get_item_meta(api_path, META_KEY, key)};
|
||||
if (res != api_error::success) {
|
||||
if (get_item_meta(api_path, META_KEY, key) != api_error::success) {
|
||||
return 0U;
|
||||
}
|
||||
}
|
||||
|
||||
auto object_name{
|
||||
api_path == "/"
|
||||
? ""
|
||||
: utils::path::create_api_path(is_encrypted ? key : api_path),
|
||||
};
|
||||
auto object_name =
|
||||
(api_path == "/")
|
||||
? std::string{}
|
||||
: utils::path::create_api_path(is_encrypted ? key : api_path);
|
||||
|
||||
std::string response_data{};
|
||||
long response_code{};
|
||||
auto prefix{object_name.empty() ? object_name : object_name + "/"};
|
||||
|
||||
auto grab_more{true};
|
||||
std::string token{};
|
||||
std::uint64_t total_count{};
|
||||
while (grab_more) {
|
||||
if (not get_object_list(response_data, response_code, "/", "", token)) {
|
||||
return total_count;
|
||||
}
|
||||
|
||||
if (response_code == http_error_codes::not_found) {
|
||||
return total_count;
|
||||
}
|
||||
|
||||
if (response_code != http_error_codes::ok) {
|
||||
return total_count;
|
||||
}
|
||||
|
||||
pugi::xml_document doc;
|
||||
auto res{doc.load_string(response_data.c_str())};
|
||||
if (res.status != pugi::xml_parse_status::status_ok) {
|
||||
return total_count;
|
||||
}
|
||||
|
||||
grab_more = doc.select_node("/ListBucketResult/IsTruncated")
|
||||
.node()
|
||||
.text()
|
||||
.as_bool();
|
||||
if (grab_more) {
|
||||
token = doc.select_node("/ListBucketResult/NextContinuationToken")
|
||||
.node()
|
||||
.text()
|
||||
.as_string();
|
||||
}
|
||||
|
||||
auto node_list{
|
||||
doc.select_nodes("/ListBucketResult/CommonPrefixes/Prefix"),
|
||||
};
|
||||
total_count += node_list.size();
|
||||
|
||||
node_list = doc.select_nodes("/ListBucketResult/Contents");
|
||||
total_count += node_list.size();
|
||||
if (not prefix.empty()) {
|
||||
--total_count;
|
||||
}
|
||||
auto prefix = object_name.empty() ? std::string{} : object_name + "/";
|
||||
std::uint64_t total_count{0};
|
||||
auto res = iterate_prefix(
|
||||
prefix,
|
||||
[&total_count](auto &&, auto &&, auto &&) {
|
||||
++total_count;
|
||||
return api_error::success;
|
||||
},
|
||||
[&total_count](auto &&, auto &&, auto &&) {
|
||||
++total_count;
|
||||
return api_error::success;
|
||||
});
|
||||
if (res != api_error::success) {
|
||||
return 0U;
|
||||
}
|
||||
|
||||
return total_count;
|
||||
@@ -356,7 +321,7 @@ auto s3_provider::get_directory_items_impl(const std::string &api_path,
|
||||
const auto &cfg{get_s3_config()};
|
||||
auto is_encrypted{not cfg.encryption_token.empty()};
|
||||
|
||||
const auto add_diretory_item =
|
||||
const auto add_directory_item =
|
||||
[this, &is_encrypted, &list](const std::string &child_object_name,
|
||||
bool directory, auto node) -> api_error {
|
||||
auto res{api_error::success};
|
||||
@@ -440,74 +405,25 @@ auto s3_provider::get_directory_items_impl(const std::string &api_path,
|
||||
object_name.empty() ? object_name : object_name + "/",
|
||||
};
|
||||
|
||||
auto grab_more{true};
|
||||
std::string token{};
|
||||
while (grab_more) {
|
||||
std::string response_data{};
|
||||
long response_code{};
|
||||
if (not get_object_list(response_data, response_code, "/", prefix, token)) {
|
||||
return api_error::comm_error;
|
||||
}
|
||||
return iterate_prefix(
|
||||
prefix,
|
||||
[&](auto && /* prefix */, auto &&node, auto &&) -> auto {
|
||||
return add_directory_item(
|
||||
utils::path::create_api_path(
|
||||
utils::path::combine("/", {node.text().as_string()})),
|
||||
true, node);
|
||||
},
|
||||
[&](auto && /* key */, auto &&node, auto &&api_prefix) -> auto {
|
||||
auto child_api_path{
|
||||
utils::path::create_api_path(
|
||||
node.select_node("Key").node().text().as_string()),
|
||||
};
|
||||
if (child_api_path == api_prefix) {
|
||||
return api_error::success;
|
||||
}
|
||||
|
||||
if (response_code == http_error_codes::not_found) {
|
||||
return api_error::directory_not_found;
|
||||
}
|
||||
|
||||
if (response_code != http_error_codes::ok) {
|
||||
utils::error::raise_api_path_error(function_name, api_path, response_code,
|
||||
"failed to get directory items");
|
||||
return api_error::comm_error;
|
||||
}
|
||||
|
||||
pugi::xml_document doc;
|
||||
auto parse_res{doc.load_string(response_data.c_str())};
|
||||
if (parse_res.status != pugi::xml_parse_status::status_ok) {
|
||||
return api_error::error;
|
||||
}
|
||||
|
||||
grab_more = doc.select_node("/ListBucketResult/IsTruncated")
|
||||
.node()
|
||||
.text()
|
||||
.as_bool();
|
||||
if (grab_more) {
|
||||
token = doc.select_node("/ListBucketResult/NextContinuationToken")
|
||||
.node()
|
||||
.text()
|
||||
.as_string();
|
||||
}
|
||||
|
||||
auto node_list{
|
||||
doc.select_nodes("/ListBucketResult/CommonPrefixes/Prefix"),
|
||||
};
|
||||
for (const auto &node : node_list) {
|
||||
auto child_object_name{
|
||||
utils::path::create_api_path(
|
||||
utils::path::combine("/", {node.node().text().as_string()})),
|
||||
};
|
||||
auto res{add_diretory_item(child_object_name, true, node.node())};
|
||||
if (res != api_error::success) {
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
||||
node_list = doc.select_nodes("/ListBucketResult/Contents");
|
||||
for (const auto &node : node_list) {
|
||||
auto child_object_name{
|
||||
utils::path::create_api_path(
|
||||
node.node().select_node("Key").node().text().as_string()),
|
||||
};
|
||||
if (child_object_name == utils::path::create_api_path(prefix)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
auto res{add_diretory_item(child_object_name, false, node.node())};
|
||||
if (res != api_error::success) {
|
||||
return res;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return api_error::success;
|
||||
return add_directory_item(child_api_path, false, node);
|
||||
});
|
||||
}
|
||||
|
||||
auto s3_provider::get_file(const std::string &api_path, api_file &file) const
|
||||
@@ -918,6 +834,114 @@ auto s3_provider::is_online() const -> bool {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto s3_provider::iterate_prefix(const std::string &prefix,
|
||||
interate_callback_t prefix_action,
|
||||
interate_callback_t key_action) const
|
||||
-> api_error {
|
||||
auto api_prefix = utils::path::create_api_path(prefix);
|
||||
auto api_prefix_parent = api_prefix;
|
||||
if (api_prefix != "/") {
|
||||
api_prefix_parent += '/';
|
||||
}
|
||||
|
||||
std::unordered_set<std::string> seen_prefixes;
|
||||
std::unordered_set<std::string> seen_keys;
|
||||
|
||||
bool grab_more{true};
|
||||
std::string token{};
|
||||
|
||||
while (grab_more) {
|
||||
long response_code{};
|
||||
std::string response_data{};
|
||||
if (not get_object_list(response_data, response_code, "/", prefix, token)) {
|
||||
return api_error::comm_error;
|
||||
}
|
||||
|
||||
if (response_code == http_error_codes::not_found) {
|
||||
return api_error::item_not_found;
|
||||
}
|
||||
|
||||
if (response_code != http_error_codes::ok) {
|
||||
return api_error::comm_error;
|
||||
}
|
||||
|
||||
pugi::xml_document doc;
|
||||
auto parsed = doc.load_string(response_data.c_str());
|
||||
if (parsed.status != pugi::xml_parse_status::status_ok) {
|
||||
return api_error::error;
|
||||
}
|
||||
|
||||
grab_more = doc.select_node("/ListBucketResult/IsTruncated")
|
||||
.node()
|
||||
.text()
|
||||
.as_bool();
|
||||
if (grab_more) {
|
||||
token = doc.select_node("/ListBucketResult/NextContinuationToken")
|
||||
.node()
|
||||
.text()
|
||||
.as_string();
|
||||
}
|
||||
|
||||
for (auto const &node :
|
||||
doc.select_nodes("/ListBucketResult/CommonPrefixes/Prefix")) {
|
||||
std::string cur_prefix = node.node().text().as_string();
|
||||
auto cur_api_path = utils::path::create_api_path(cur_prefix);
|
||||
if (cur_prefix.empty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (not prefix.empty() &&
|
||||
(cur_api_path == api_prefix ||
|
||||
not cur_api_path.starts_with(api_prefix_parent))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (not seen_prefixes.contains(cur_prefix)) {
|
||||
seen_prefixes.insert(cur_prefix);
|
||||
auto res = prefix_action(cur_prefix, node.node(), api_prefix);
|
||||
if (res != api_error::success) {
|
||||
return res;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (auto const &node : doc.select_nodes("/ListBucketResult/Contents")) {
|
||||
std::string cur_key = node.node().child("Key").text().as_string();
|
||||
auto cur_api_path = utils::path::create_api_path(cur_key);
|
||||
|
||||
if (cur_key.empty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (not prefix.empty() &&
|
||||
(cur_api_path == api_prefix ||
|
||||
not cur_api_path.starts_with(api_prefix_parent))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (cur_key.back() == '/') {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (seen_prefixes.contains(cur_key + "/")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (seen_keys.contains(cur_key)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
seen_keys.insert(cur_key);
|
||||
auto res = key_action(cur_key, node.node(), api_prefix);
|
||||
if (res != api_error::success) {
|
||||
return res;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return api_error::success;
|
||||
}
|
||||
|
||||
auto s3_provider::remove_directory_impl(const std::string &api_path)
|
||||
-> api_error {
|
||||
REPERTORY_USES_FUNCTION_NAME();
|
||||
|
@@ -113,6 +113,31 @@ TYPED_TEST(fuse_test, directory_can_opendir_after_closedir) {
|
||||
|
||||
this->rmdir_and_test(dir);
|
||||
}
|
||||
|
||||
TYPED_TEST(fuse_test, directory_rmdir_on_non_empty_directory_should_fail) {
|
||||
if (this->current_provider == provider_type::encrypt) {
|
||||
GTEST_SKIP();
|
||||
return;
|
||||
}
|
||||
|
||||
std::string dir_name{"non_empty"};
|
||||
auto dir = this->create_directory_and_test(dir_name);
|
||||
|
||||
std::string dir_name2{"non_empty_2"};
|
||||
auto dir2 = this->create_directory_and_test(dir_name2);
|
||||
|
||||
std::string name{dir_name + "/child"};
|
||||
auto file = this->create_file_and_test(name, 0644);
|
||||
this->overwrite_text(file, "X");
|
||||
|
||||
errno = 0;
|
||||
EXPECT_EQ(-1, ::rmdir(dir.c_str()));
|
||||
EXPECT_EQ(ENOTEMPTY, errno);
|
||||
|
||||
this->unlink_file_and_test(file);
|
||||
this->rmdir_and_test(dir);
|
||||
this->rmdir_and_test(dir2);
|
||||
}
|
||||
} // namespace repertory
|
||||
|
||||
#endif // !defined(_WIN32)
|
||||
|
123
repertory/repertory_test/src/fuse_drive_fsync_test.cpp
Normal file
123
repertory/repertory_test/src/fuse_drive_fsync_test.cpp
Normal file
@@ -0,0 +1,123 @@
|
||||
/*
|
||||
Copyright <2018-2025> <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.
|
||||
*/
|
||||
#if !defined(_WIN32)
|
||||
|
||||
#include "fixtures/drive_fixture.hpp"
|
||||
|
||||
namespace repertory {
|
||||
TYPED_TEST_SUITE(fuse_test, platform_provider_types);
|
||||
|
||||
TYPED_TEST(fuse_test, fsync_basic_succeeds_on_dirty_desc) {
|
||||
std::string name{"fsync_dirty"};
|
||||
auto path = this->create_file_and_test(name, 0644);
|
||||
|
||||
auto desc = ::open(path.c_str(), O_RDWR);
|
||||
ASSERT_NE(desc, -1);
|
||||
|
||||
this->write_all(desc, "ABC");
|
||||
errno = 0;
|
||||
ASSERT_EQ(0, ::fsync(desc));
|
||||
::close(desc);
|
||||
|
||||
EXPECT_EQ("ABC", this->slurp(path));
|
||||
this->unlink_file_and_test(path);
|
||||
}
|
||||
|
||||
TYPED_TEST(fuse_test, fsync_noop_on_clean_desc) {
|
||||
std::string name{"fsync_clean"};
|
||||
auto path = this->create_file_and_test(name, 0644);
|
||||
|
||||
auto desc = ::open(path.c_str(), O_RDONLY);
|
||||
ASSERT_NE(desc, -1);
|
||||
|
||||
errno = 0;
|
||||
EXPECT_EQ(0, ::fsync(desc));
|
||||
::close(desc);
|
||||
|
||||
this->unlink_file_and_test(path);
|
||||
}
|
||||
|
||||
TYPED_TEST(fuse_test, fsync_on_unlinked_file) {
|
||||
std::string name{"fsync_unlinked"};
|
||||
auto path = this->create_file_and_test(name, 0644);
|
||||
|
||||
auto desc = ::open(path.c_str(), O_RDWR);
|
||||
ASSERT_NE(desc, -1);
|
||||
|
||||
ASSERT_EQ(0, ::unlink(path.c_str()));
|
||||
|
||||
this->write_all(desc, "XYZ");
|
||||
errno = 0;
|
||||
EXPECT_EQ(0, ::fsync(desc));
|
||||
::close(desc);
|
||||
}
|
||||
|
||||
TYPED_TEST(fuse_test, fsync_after_rename) {
|
||||
if (this->current_provider != provider_type::sia) {
|
||||
// TODO finish test
|
||||
GTEST_SKIP();
|
||||
return;
|
||||
}
|
||||
|
||||
std::string src_name{"fsync_ren_src"};
|
||||
auto src = this->create_file_and_test(src_name, 0644);
|
||||
|
||||
auto desc = ::open(src.c_str(), O_RDWR);
|
||||
ASSERT_NE(desc, -1);
|
||||
|
||||
this->write_all(desc, "AAA");
|
||||
|
||||
std::string dst_name{"fsync_ren_dst"};
|
||||
auto dst =
|
||||
utils::path::combine(utils::path::get_parent_path(src), {dst_name});
|
||||
errno = 0;
|
||||
ASSERT_EQ(0, ::rename(src.c_str(), dst.c_str()));
|
||||
|
||||
this->write_all(desc, "_BBB");
|
||||
errno = 0;
|
||||
ASSERT_EQ(0, ::fsync(desc));
|
||||
::close(desc);
|
||||
|
||||
EXPECT_EQ("AAA_BBB", this->slurp(dst));
|
||||
this->unlink_file_and_test(dst);
|
||||
}
|
||||
|
||||
#if defined(__linux__)
|
||||
TYPED_TEST(fuse_test, fsync_fdatasync_behaves_like_fsync_on_linux) {
|
||||
std::string name{"descatasync_linux"};
|
||||
auto path = this->create_file_and_test(name, 0644);
|
||||
|
||||
auto desc = ::open(path.c_str(), O_RDWR);
|
||||
ASSERT_NE(desc, -1);
|
||||
|
||||
this->write_all(desc, "DATA");
|
||||
errno = 0;
|
||||
ASSERT_EQ(0, ::fdatasync(desc));
|
||||
::close(desc);
|
||||
|
||||
EXPECT_EQ("DATA", this->slurp(path));
|
||||
this->unlink_file_and_test(path);
|
||||
}
|
||||
#endif // defined(__linux__)
|
||||
} // namespace repertory
|
||||
|
||||
#endif // !defined(_WIN32)
|
@@ -205,6 +205,38 @@ TYPED_TEST(fuse_test, rdrw_can_append_to_file) {
|
||||
|
||||
this->unlink_file_and_test(file_path);
|
||||
}
|
||||
|
||||
TYPED_TEST(fuse_test, rdrw_open_with_o_trunc_resets_size) {
|
||||
std::string name{"trunc_test"};
|
||||
auto path = this->create_file_and_test(name, 0644);
|
||||
|
||||
this->overwrite_text(path, "ABCDEFG");
|
||||
EXPECT_GT(this->stat_size(path), 0);
|
||||
|
||||
auto desc = ::open(path.c_str(), O_WRONLY | O_TRUNC);
|
||||
ASSERT_NE(desc, -1);
|
||||
::close(desc);
|
||||
|
||||
EXPECT_EQ(0, this->stat_size(path));
|
||||
this->unlink_file_and_test(path);
|
||||
}
|
||||
|
||||
TYPED_TEST(fuse_test, rdrw_o_append_writes_at_eof) {
|
||||
std::string name{"append_test"};
|
||||
auto path = this->create_file_and_test(name, 0644);
|
||||
|
||||
this->overwrite_text(path, "HEAD");
|
||||
|
||||
auto desc = ::open(path.c_str(), O_WRONLY | O_APPEND);
|
||||
ASSERT_NE(desc, -1);
|
||||
|
||||
ASSERT_NE(-1, ::lseek(desc, 0, SEEK_SET));
|
||||
this->write_all(desc, "TAIL");
|
||||
::close(desc);
|
||||
|
||||
EXPECT_EQ("HEADTAIL", this->slurp(path));
|
||||
this->unlink_file_and_test(path);
|
||||
}
|
||||
} // namespace repertory
|
||||
|
||||
#endif // !defined(_WIN32)
|
||||
|
@@ -54,7 +54,6 @@ TYPED_TEST(fuse_test, unlink_open_file_leaves_handle_intact) {
|
||||
ASSERT_EQ(0, ::unlink(path.c_str()));
|
||||
|
||||
auto res = ::lseek(desc, 0, SEEK_END);
|
||||
fmt::println("lseek|{}|{}", res, errno);
|
||||
|
||||
ASSERT_NE(-1, res);
|
||||
this->write_all(desc, " WORLD");
|
||||
|
122
repertory/repertory_test/src/packet_client_test.cpp
Normal file
122
repertory/repertory_test/src/packet_client_test.cpp
Normal file
@@ -0,0 +1,122 @@
|
||||
/*
|
||||
Copyright <2018-2025> <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 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.
|
||||
*/
|
||||
#include "test_common.hpp"
|
||||
|
||||
#include "comm/packet/common.hpp"
|
||||
#include "comm/packet/packet.hpp"
|
||||
#include "comm/packet/packet_client.hpp"
|
||||
#include "comm/packet/packet_server.hpp"
|
||||
#include "types/remote.hpp"
|
||||
#include "utils/common.hpp"
|
||||
#include "utils/utils.hpp"
|
||||
#include "version.hpp"
|
||||
|
||||
using namespace repertory;
|
||||
using namespace repertory::comm;
|
||||
|
||||
namespace {
|
||||
class test_packet_server final {
|
||||
public:
|
||||
test_packet_server(std::uint16_t port, std::string token,
|
||||
std::uint8_t pool_size = 2)
|
||||
: server_(std::make_unique<packet_server>(
|
||||
port, std::move(token), pool_size,
|
||||
[](const std::string & /*client_id*/) {},
|
||||
[](std::uint32_t /*service_flags_in*/,
|
||||
const std::string & /*client_id*/, std::uint64_t /*thread_id*/,
|
||||
const std::string &method, packet * /*request*/,
|
||||
packet & /*response*/,
|
||||
packet_server::message_complete_callback done) {
|
||||
if (method == "ping") {
|
||||
done(packet::error_type{0});
|
||||
} else {
|
||||
done(packet::error_type{-1});
|
||||
}
|
||||
})) {}
|
||||
|
||||
private:
|
||||
std::unique_ptr<packet_server> server_;
|
||||
};
|
||||
|
||||
inline auto make_cfg(std::uint16_t port, const std::string &token)
|
||||
-> remote::remote_config {
|
||||
remote::remote_config cfg{};
|
||||
cfg.host_name_or_ip = "127.0.0.1";
|
||||
cfg.api_port = port;
|
||||
cfg.max_connections = 2U;
|
||||
cfg.conn_timeout_ms = 1500U;
|
||||
cfg.recv_timeout_ms = 1500U;
|
||||
cfg.send_timeout_ms = 1500U;
|
||||
cfg.encryption_token = token;
|
||||
return cfg;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
TEST(packet_client_test, can_check_version) {
|
||||
std::string token = "cow_moose_doge_chicken";
|
||||
|
||||
std::uint16_t port{};
|
||||
ASSERT_TRUE(utils::get_next_available_port(50000U, port));
|
||||
|
||||
test_packet_server server(port, token);
|
||||
|
||||
packet_client client(make_cfg(port, token));
|
||||
|
||||
std::uint32_t min_version{};
|
||||
auto api = client.check_version(
|
||||
utils::get_version_number(project_get_version()), min_version);
|
||||
|
||||
EXPECT_EQ(api, api_error::success);
|
||||
EXPECT_NE(min_version, 0U);
|
||||
}
|
||||
|
||||
TEST(packet_client_test, can_detect_incompatible_version) {
|
||||
std::string token = "cow_moose_doge_chicken";
|
||||
|
||||
std::uint16_t port{};
|
||||
ASSERT_TRUE(utils::get_next_available_port(50000U, port));
|
||||
|
||||
test_packet_server server(port, token);
|
||||
|
||||
packet_client client(make_cfg(port, token));
|
||||
|
||||
std::uint32_t min_version{};
|
||||
auto api =
|
||||
client.check_version(utils::get_version_number("1.0.0-rc"), min_version);
|
||||
|
||||
EXPECT_EQ(api, api_error::incompatible_version);
|
||||
EXPECT_NE(min_version, 0U);
|
||||
}
|
||||
|
||||
TEST(packet_client_test, can_send_request_and_receive_response) {
|
||||
std::string token = "cow_moose_doge_chicken";
|
||||
|
||||
std::uint16_t port{};
|
||||
ASSERT_TRUE(utils::get_next_available_port(50000U, port));
|
||||
|
||||
test_packet_server server(port, token);
|
||||
|
||||
packet_client client(make_cfg(port, token));
|
||||
|
||||
std::uint32_t service_flags{};
|
||||
packet request;
|
||||
packet response;
|
||||
auto ret = client.send("ping", request, response, service_flags);
|
||||
|
||||
EXPECT_EQ(ret, 0);
|
||||
}
|
@@ -1,27 +1,24 @@
|
||||
/*
|
||||
Copyright <2018-2025> <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.
|
||||
/* Copyright <2018-2025>
|
||||
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.
|
||||
*/
|
||||
#include "test_common.hpp"
|
||||
|
||||
#include "comm/packet/packet.hpp"
|
||||
#include "types/remote.hpp"
|
||||
|
||||
namespace repertory {
|
||||
TEST(packet_test, encrypt_and_decrypt) {
|
||||
@@ -35,7 +32,435 @@ TEST(packet_test, encrypt_and_decrypt) {
|
||||
|
||||
std::string data;
|
||||
EXPECT_EQ(0, test_packet.decode(data));
|
||||
|
||||
EXPECT_STREQ("test", data.c_str());
|
||||
}
|
||||
|
||||
TEST(packet_test, encode_decode_primitives_and_strings) {
|
||||
packet pkt;
|
||||
|
||||
const std::int8_t i8{-12};
|
||||
const std::uint8_t u8{250};
|
||||
const std::int16_t i16{-12345};
|
||||
const std::uint16_t u16{54321};
|
||||
const std::int32_t i32{-123456789};
|
||||
const std::uint32_t u32{3141592653U};
|
||||
const std::int64_t i64{-1234567890123456789LL};
|
||||
const std::uint64_t u64{12345678901234567890ULL};
|
||||
const std::string s{"hello world"};
|
||||
const std::wstring ws{L"wide 🌟"};
|
||||
|
||||
pkt.encode(i8);
|
||||
pkt.encode(u8);
|
||||
pkt.encode(i16);
|
||||
pkt.encode(u16);
|
||||
pkt.encode(i32);
|
||||
pkt.encode(u32);
|
||||
pkt.encode(i64);
|
||||
pkt.encode(u64);
|
||||
pkt.encode(s);
|
||||
pkt.encode(ws);
|
||||
|
||||
std::int8_t i8_r{};
|
||||
std::uint8_t u8_r{};
|
||||
std::int16_t i16_r{};
|
||||
std::uint16_t u16_r{};
|
||||
std::int32_t i32_r{};
|
||||
std::uint32_t u32_r{};
|
||||
std::int64_t i64_r{};
|
||||
std::uint64_t u64_r{};
|
||||
std::string s_r;
|
||||
std::wstring ws_r;
|
||||
|
||||
EXPECT_EQ(0, pkt.decode(i8_r));
|
||||
EXPECT_EQ(0, pkt.decode(u8_r));
|
||||
EXPECT_EQ(0, pkt.decode(i16_r));
|
||||
EXPECT_EQ(0, pkt.decode(u16_r));
|
||||
EXPECT_EQ(0, pkt.decode(i32_r));
|
||||
EXPECT_EQ(0, pkt.decode(u32_r));
|
||||
EXPECT_EQ(0, pkt.decode(i64_r));
|
||||
EXPECT_EQ(0, pkt.decode(u64_r));
|
||||
EXPECT_EQ(0, pkt.decode(s_r));
|
||||
EXPECT_EQ(0, pkt.decode(ws_r));
|
||||
|
||||
EXPECT_EQ(i8, i8_r);
|
||||
EXPECT_EQ(u8, u8_r);
|
||||
EXPECT_EQ(i16, i16_r);
|
||||
EXPECT_EQ(u16, u16_r);
|
||||
EXPECT_EQ(i32, i32_r);
|
||||
EXPECT_EQ(u32, u32_r);
|
||||
EXPECT_EQ(i64, i64_r);
|
||||
EXPECT_EQ(u64, u64_r);
|
||||
EXPECT_EQ(s, s_r);
|
||||
EXPECT_EQ(ws, ws_r);
|
||||
}
|
||||
|
||||
TEST(packet_test, encode_decode_null_c_string_is_empty) {
|
||||
packet pkt;
|
||||
const char *p_null{nullptr};
|
||||
pkt.encode(p_null);
|
||||
|
||||
std::string out;
|
||||
EXPECT_EQ(0, pkt.decode(out));
|
||||
EXPECT_TRUE(out.empty());
|
||||
}
|
||||
|
||||
TEST(packet_test, encode_decode_raw_buffer) {
|
||||
packet pkt;
|
||||
|
||||
std::array<unsigned char, 32> src{};
|
||||
for (std::size_t i = 0; i < src.size(); ++i) {
|
||||
src[i] = static_cast<unsigned char>(i);
|
||||
}
|
||||
|
||||
pkt.encode(src.data(), src.size());
|
||||
|
||||
std::array<unsigned char, 32> dst{};
|
||||
EXPECT_EQ(0, pkt.decode(dst.data(), dst.size()));
|
||||
EXPECT_EQ(0, std::memcmp(src.data(), dst.data(), dst.size()));
|
||||
}
|
||||
|
||||
TEST(packet_test, encode_decode_pointer_round_trip) {
|
||||
packet pkt;
|
||||
|
||||
int val{42};
|
||||
void *in_ptr = &val;
|
||||
|
||||
pkt.encode(in_ptr);
|
||||
|
||||
void *out_ptr = nullptr;
|
||||
EXPECT_EQ(0, pkt.decode(out_ptr));
|
||||
EXPECT_EQ(in_ptr, out_ptr);
|
||||
}
|
||||
|
||||
TEST(packet_test, encode_top_affects_decode_order) {
|
||||
packet pkt;
|
||||
|
||||
pkt.encode(static_cast<std::uint32_t>(1));
|
||||
pkt.encode(static_cast<std::uint32_t>(2));
|
||||
pkt.encode_top(static_cast<std::uint32_t>(99));
|
||||
|
||||
std::uint32_t a{}, b{}, c{};
|
||||
EXPECT_EQ(0, pkt.decode(a));
|
||||
EXPECT_EQ(0, pkt.decode(b));
|
||||
EXPECT_EQ(0, pkt.decode(c));
|
||||
|
||||
EXPECT_EQ(99U, a);
|
||||
EXPECT_EQ(1U, b);
|
||||
EXPECT_EQ(2U, c);
|
||||
}
|
||||
|
||||
TEST(packet_test, copy_ctor_preserves_decode_offset) {
|
||||
packet pkt;
|
||||
pkt.encode(static_cast<std::uint32_t>(10));
|
||||
pkt.encode(static_cast<std::uint32_t>(20));
|
||||
pkt.encode(static_cast<std::uint32_t>(30));
|
||||
|
||||
std::uint32_t first{};
|
||||
ASSERT_EQ(0, pkt.decode(first));
|
||||
ASSERT_EQ(10U, first);
|
||||
|
||||
packet pkt_copy{pkt};
|
||||
|
||||
std::uint32_t from_copy{};
|
||||
EXPECT_EQ(0, pkt_copy.decode(from_copy));
|
||||
EXPECT_EQ(20U, from_copy);
|
||||
|
||||
std::uint32_t from_src{};
|
||||
|
||||
EXPECT_EQ(0, pkt.decode(from_src));
|
||||
EXPECT_EQ(20U, from_src);
|
||||
}
|
||||
|
||||
TEST(packet_test, copy_assign_preserves_decode_offset) {
|
||||
packet src;
|
||||
src.encode(static_cast<std::uint32_t>(1));
|
||||
src.encode(static_cast<std::uint32_t>(2));
|
||||
src.encode(static_cast<std::uint32_t>(3));
|
||||
|
||||
std::uint32_t first{};
|
||||
ASSERT_EQ(0, src.decode(first));
|
||||
ASSERT_EQ(1U, first);
|
||||
|
||||
packet dst;
|
||||
dst = src;
|
||||
|
||||
std::uint32_t from_dst{};
|
||||
EXPECT_EQ(0, dst.decode(from_dst));
|
||||
EXPECT_EQ(2U, from_dst);
|
||||
|
||||
std::uint32_t from_src{};
|
||||
EXPECT_EQ(0, src.decode(from_src));
|
||||
EXPECT_EQ(2U, from_src);
|
||||
}
|
||||
|
||||
TEST(packet_test, move_ctor_preserves_decode_offset) {
|
||||
packet pkt;
|
||||
pkt.encode(static_cast<std::uint32_t>(100));
|
||||
pkt.encode(static_cast<std::uint32_t>(200));
|
||||
|
||||
std::uint32_t x{};
|
||||
ASSERT_EQ(0, pkt.decode(x));
|
||||
ASSERT_EQ(100U, x);
|
||||
|
||||
packet moved{std::move(pkt)};
|
||||
|
||||
std::uint32_t y{};
|
||||
EXPECT_EQ(0, moved.decode(y));
|
||||
EXPECT_EQ(200U, y);
|
||||
}
|
||||
|
||||
TEST(packet_test, move_assign_preserves_decode_offset) {
|
||||
packet src;
|
||||
src.encode(static_cast<std::uint32_t>(7));
|
||||
src.encode(static_cast<std::uint32_t>(8));
|
||||
src.encode(static_cast<std::uint32_t>(9));
|
||||
|
||||
std::uint32_t first{};
|
||||
ASSERT_EQ(0, src.decode(first));
|
||||
ASSERT_EQ(7U, first);
|
||||
|
||||
packet dst;
|
||||
dst.encode(
|
||||
static_cast<std::uint32_t>(999)); // give dst some state to overwrite
|
||||
dst = std::move(src);
|
||||
|
||||
std::uint32_t next{};
|
||||
EXPECT_EQ(0, dst.decode(next));
|
||||
EXPECT_EQ(8U, next);
|
||||
}
|
||||
|
||||
TEST(packet_test, operator_index_read_write_byte) {
|
||||
packet pkt;
|
||||
pkt.encode(static_cast<std::uint8_t>(7));
|
||||
|
||||
EXPECT_EQ(pkt[0], static_cast<unsigned char>(7));
|
||||
|
||||
pkt[0] = static_cast<unsigned char>(8);
|
||||
|
||||
std::uint8_t out{};
|
||||
EXPECT_EQ(0, pkt.decode(out));
|
||||
EXPECT_EQ(8U, out);
|
||||
}
|
||||
|
||||
TEST(packet_test, current_pointer_tracks_decode_offset) {
|
||||
packet pkt;
|
||||
pkt.encode(static_cast<std::uint8_t>(1));
|
||||
pkt.encode(static_cast<std::uint8_t>(2));
|
||||
|
||||
std::uint8_t first{};
|
||||
EXPECT_EQ(0, pkt.decode(first));
|
||||
EXPECT_EQ(1U, first);
|
||||
|
||||
auto *ptr = pkt.current_pointer();
|
||||
ASSERT_NE(ptr, nullptr);
|
||||
EXPECT_EQ(*ptr, static_cast<unsigned char>(2));
|
||||
|
||||
const auto &const_pkt = pkt;
|
||||
const auto *cptr = const_pkt.current_pointer();
|
||||
ASSERT_NE(cptr, nullptr);
|
||||
EXPECT_EQ(*cptr, static_cast<unsigned char>(2));
|
||||
}
|
||||
|
||||
TEST(packet_test, open_flags_round_trip) {
|
||||
packet pkt;
|
||||
|
||||
remote::open_flags flags = remote::open_flags::read_only |
|
||||
remote::open_flags::create |
|
||||
remote::open_flags::truncate;
|
||||
|
||||
pkt.encode(flags);
|
||||
|
||||
remote::open_flags decoded{};
|
||||
EXPECT_EQ(0, pkt.decode(decoded));
|
||||
|
||||
EXPECT_EQ(static_cast<std::uint32_t>(flags),
|
||||
static_cast<std::uint32_t>(decoded));
|
||||
}
|
||||
|
||||
TEST(packet_test, encrypt_and_decrypt_without_size_prefix) {
|
||||
packet pkt;
|
||||
pkt.encode("opaque");
|
||||
pkt.encrypt("moose", false);
|
||||
|
||||
EXPECT_EQ(0, pkt.decrypt("moose"));
|
||||
|
||||
std::string out;
|
||||
EXPECT_EQ(0, pkt.decode(out));
|
||||
EXPECT_EQ("opaque", out);
|
||||
}
|
||||
|
||||
TEST(packet_test, decode_fails_when_empty) {
|
||||
packet pkt;
|
||||
std::uint32_t val{};
|
||||
EXPECT_NE(0, pkt.decode(val));
|
||||
}
|
||||
|
||||
TEST(packet_test, decode_json_round_trip) {
|
||||
packet pkt;
|
||||
|
||||
const json src = {{"x", 1}, {"y", "z"}, {"ok", true}};
|
||||
pkt.encode(src.dump());
|
||||
|
||||
json got{};
|
||||
EXPECT_EQ(0, packet::decode_json(pkt, got));
|
||||
EXPECT_EQ(src, got);
|
||||
}
|
||||
|
||||
TEST(packet_test, remote_setattr_x_round_trip) {
|
||||
packet pkt;
|
||||
|
||||
remote::setattr_x sa{};
|
||||
sa.valid = 0x7;
|
||||
sa.mode = static_cast<remote::file_mode>(0644);
|
||||
sa.uid = 1001U;
|
||||
sa.gid = 1002U;
|
||||
sa.size = 1234567ULL;
|
||||
sa.acctime = 111ULL;
|
||||
sa.modtime = 222ULL;
|
||||
sa.crtime = 333ULL;
|
||||
sa.chgtime = 444ULL;
|
||||
sa.bkuptime = 555ULL;
|
||||
sa.flags = 0xA5A5A5A5U;
|
||||
|
||||
pkt.encode(sa);
|
||||
|
||||
remote::setattr_x out{};
|
||||
EXPECT_EQ(0, pkt.decode(out));
|
||||
|
||||
EXPECT_EQ(sa.valid, out.valid);
|
||||
EXPECT_EQ(sa.mode, out.mode);
|
||||
EXPECT_EQ(sa.uid, out.uid);
|
||||
EXPECT_EQ(sa.gid, out.gid);
|
||||
EXPECT_EQ(sa.size, out.size);
|
||||
EXPECT_EQ(sa.acctime, out.acctime);
|
||||
EXPECT_EQ(sa.modtime, out.modtime);
|
||||
EXPECT_EQ(sa.crtime, out.crtime);
|
||||
EXPECT_EQ(sa.chgtime, out.chgtime);
|
||||
EXPECT_EQ(sa.bkuptime, out.bkuptime);
|
||||
EXPECT_EQ(sa.flags, out.flags);
|
||||
}
|
||||
|
||||
TEST(packet_test, remote_stat_round_trip) {
|
||||
packet pkt;
|
||||
|
||||
remote::stat st{};
|
||||
st.st_mode = static_cast<remote::file_mode>(0755);
|
||||
st.st_nlink = static_cast<remote::file_nlink>(2);
|
||||
st.st_uid = 2001U;
|
||||
st.st_gid = 2002U;
|
||||
st.st_atimespec = 101ULL;
|
||||
st.st_mtimespec = 202ULL;
|
||||
st.st_ctimespec = 303ULL;
|
||||
st.st_birthtimespec = 404ULL;
|
||||
st.st_size = 987654321ULL;
|
||||
st.st_blocks = 4096ULL;
|
||||
st.st_blksize = 8192U;
|
||||
st.st_flags = 0xDEADBEEFU;
|
||||
|
||||
pkt.encode(st);
|
||||
|
||||
remote::stat out{};
|
||||
EXPECT_EQ(0, pkt.decode(out));
|
||||
|
||||
EXPECT_EQ(st.st_mode, out.st_mode);
|
||||
EXPECT_EQ(st.st_nlink, out.st_nlink);
|
||||
EXPECT_EQ(st.st_uid, out.st_uid);
|
||||
EXPECT_EQ(st.st_gid, out.st_gid);
|
||||
EXPECT_EQ(st.st_atimespec, out.st_atimespec);
|
||||
|
||||
EXPECT_EQ(st.st_mtimespec, out.st_mtimespec);
|
||||
EXPECT_EQ(st.st_ctimespec, out.st_ctimespec);
|
||||
EXPECT_EQ(st.st_birthtimespec, out.st_birthtimespec);
|
||||
EXPECT_EQ(st.st_size, out.st_size);
|
||||
EXPECT_EQ(st.st_blocks, out.st_blocks);
|
||||
EXPECT_EQ(st.st_blksize, out.st_blksize);
|
||||
EXPECT_EQ(st.st_flags, out.st_flags);
|
||||
}
|
||||
|
||||
TEST(packet_test, remote_statfs_round_trip) {
|
||||
packet pkt;
|
||||
|
||||
remote::statfs sfs{};
|
||||
sfs.f_bavail = 1'000'000ULL;
|
||||
sfs.f_bfree = 2'000'000ULL;
|
||||
sfs.f_blocks = 3'000'000ULL;
|
||||
sfs.f_favail = 4'000'000ULL;
|
||||
sfs.f_ffree = 5'000'000ULL;
|
||||
sfs.f_files = 6'000'000ULL;
|
||||
|
||||
pkt.encode(sfs);
|
||||
|
||||
remote::statfs out{};
|
||||
EXPECT_EQ(0, pkt.decode(out));
|
||||
|
||||
EXPECT_EQ(sfs.f_bavail, out.f_bavail);
|
||||
EXPECT_EQ(sfs.f_bfree, out.f_bfree);
|
||||
EXPECT_EQ(sfs.f_blocks, out.f_blocks);
|
||||
EXPECT_EQ(sfs.f_favail, out.f_favail);
|
||||
EXPECT_EQ(sfs.f_ffree, out.f_ffree);
|
||||
EXPECT_EQ(sfs.f_files, out.f_files);
|
||||
}
|
||||
|
||||
TEST(packet_test, remote_statfs_x_round_trip) {
|
||||
packet pkt;
|
||||
|
||||
remote::statfs_x sfsx{};
|
||||
sfsx.f_bavail = 7'000'000ULL;
|
||||
sfsx.f_bfree = 8'000'000ULL;
|
||||
sfsx.f_blocks = 9'000'000ULL;
|
||||
sfsx.f_favail = 10'000'000ULL;
|
||||
sfsx.f_ffree = 11'000'000ULL;
|
||||
sfsx.f_files = 12'000'000ULL;
|
||||
|
||||
const char *mnt = "test_mnt";
|
||||
std::memcpy(sfsx.f_mntfromname.data(), mnt, std::strlen(mnt));
|
||||
|
||||
pkt.encode(sfsx);
|
||||
|
||||
remote::statfs_x out{};
|
||||
EXPECT_EQ(0, pkt.decode(out));
|
||||
|
||||
EXPECT_EQ(sfsx.f_bavail, out.f_bavail);
|
||||
EXPECT_EQ(sfsx.f_bfree, out.f_bfree);
|
||||
EXPECT_EQ(sfsx.f_blocks, out.f_blocks);
|
||||
EXPECT_EQ(sfsx.f_favail, out.f_favail);
|
||||
EXPECT_EQ(sfsx.f_ffree, out.f_ffree);
|
||||
EXPECT_EQ(sfsx.f_files, out.f_files);
|
||||
EXPECT_EQ(0, std::memcmp(sfsx.f_mntfromname.data(), out.f_mntfromname.data(),
|
||||
sfsx.f_mntfromname.size()));
|
||||
}
|
||||
|
||||
TEST(packet_test, remote_file_info_round_trip) {
|
||||
packet pkt;
|
||||
|
||||
remote::file_info fi{};
|
||||
fi.FileAttributes = 0x1234U;
|
||||
fi.ReparseTag = 0x5678U;
|
||||
fi.AllocationSize = 1111ULL;
|
||||
fi.FileSize = 2222ULL;
|
||||
fi.CreationTime = 3333ULL;
|
||||
fi.LastAccessTime = 4444ULL;
|
||||
fi.LastWriteTime = 5555ULL;
|
||||
fi.ChangeTime = 6666ULL;
|
||||
fi.IndexNumber = 7777ULL;
|
||||
fi.HardLinks = 3U;
|
||||
fi.EaSize = 0U;
|
||||
|
||||
pkt.encode(fi);
|
||||
|
||||
remote::file_info out{};
|
||||
EXPECT_EQ(0, pkt.decode(out));
|
||||
|
||||
EXPECT_EQ(fi.FileAttributes, fi.FileAttributes);
|
||||
EXPECT_EQ(fi.ReparseTag, out.ReparseTag);
|
||||
EXPECT_EQ(fi.AllocationSize, out.AllocationSize);
|
||||
EXPECT_EQ(fi.FileSize, out.FileSize);
|
||||
EXPECT_EQ(fi.CreationTime, out.CreationTime);
|
||||
EXPECT_EQ(fi.LastAccessTime, out.LastAccessTime);
|
||||
EXPECT_EQ(fi.LastWriteTime, out.LastWriteTime);
|
||||
EXPECT_EQ(fi.ChangeTime, out.ChangeTime);
|
||||
EXPECT_EQ(fi.IndexNumber, out.IndexNumber);
|
||||
EXPECT_EQ(fi.HardLinks, out.HardLinks);
|
||||
EXPECT_EQ(fi.EaSize, out.EaSize);
|
||||
}
|
||||
} // namespace repertory
|
||||
|
@@ -544,6 +544,7 @@ TYPED_TEST(providers_test, get_directory_items_fails_if_item_is_file) {
|
||||
|
||||
EXPECT_EQ(api_error::success, this->provider->remove_file("/pt01.txt"));
|
||||
}
|
||||
|
||||
TYPED_TEST(providers_test, get_directory_item_count) {
|
||||
if (this->provider->get_provider_type() == provider_type::encrypt) {
|
||||
EXPECT_EQ(std::size_t(2U), this->provider->get_directory_item_count("/"));
|
||||
|
Reference in New Issue
Block a user