@@ -23,8 +23,10 @@
|
||||
|
||||
#include "utils/encrypting_reader.hpp"
|
||||
|
||||
#include "utils/base64.hpp"
|
||||
#include "utils/collection.hpp"
|
||||
#include "utils/common.hpp"
|
||||
#include "utils/config.hpp"
|
||||
#include "utils/encryption.hpp"
|
||||
#include "utils/error.hpp"
|
||||
#include "utils/file.hpp"
|
||||
@@ -119,7 +121,7 @@ protected:
|
||||
reader_.set_read_position(reinterpret_cast<std::uintptr_t>(gptr()));
|
||||
|
||||
char c{};
|
||||
const auto res = encrypting_reader::reader_function(&c, 1U, 1U, &reader_);
|
||||
auto res = encrypting_reader::reader_function(&c, 1U, 1U, &reader_);
|
||||
if (res != 1) {
|
||||
return traits_type::eof();
|
||||
}
|
||||
@@ -180,171 +182,218 @@ encrypting_reader::encrypting_reader(
|
||||
std::string_view file_name, std::string_view source_path,
|
||||
stop_type_callback stop_requested_cb, std::string_view token,
|
||||
std::optional<std::string> relative_parent_path, std::size_t error_return)
|
||||
: key_(utils::encryption::generate_key<utils::encryption::hash_256_t>(
|
||||
token)),
|
||||
: keys_(utils::encryption::generate_key<utils::hash::hash_256_t>(token),
|
||||
utils::encryption::generate_key<utils::hash::hash_256_t>(token)),
|
||||
stop_requested_cb_(std::move(stop_requested_cb)),
|
||||
error_return_(error_return),
|
||||
source_file_(utils::file::file::open_or_create_file(source_path, true)) {
|
||||
REPERTORY_USES_FUNCTION_NAME();
|
||||
|
||||
if (not*source_file_) {
|
||||
throw utils::error::create_exception(function_name, {
|
||||
"file open failed",
|
||||
source_path,
|
||||
});
|
||||
}
|
||||
|
||||
data_buffer result;
|
||||
utils::encryption::encrypt_data(
|
||||
key_, reinterpret_cast<const unsigned char *>(file_name.data()),
|
||||
file_name.size(), result);
|
||||
encrypted_file_name_ = utils::collection::to_hex_string(result);
|
||||
|
||||
if (relative_parent_path.has_value()) {
|
||||
for (auto &&part :
|
||||
utils::string::split(relative_parent_path.value(),
|
||||
utils::path::directory_seperator, false)) {
|
||||
utils::encryption::encrypt_data(
|
||||
key_, reinterpret_cast<const unsigned char *>(part.c_str()),
|
||||
strnlen(part.c_str(), part.size()), result);
|
||||
encrypted_file_path_ += '/' + utils::collection::to_hex_string(result);
|
||||
}
|
||||
encrypted_file_path_ += '/' + encrypted_file_name_;
|
||||
}
|
||||
|
||||
auto opt_size = source_file_->size();
|
||||
if (not opt_size.has_value()) {
|
||||
throw utils::error::create_exception(function_name,
|
||||
{
|
||||
"failed to get file size",
|
||||
source_file_->get_path(),
|
||||
});
|
||||
}
|
||||
auto file_size = opt_size.value();
|
||||
|
||||
const auto total_chunks = utils::divide_with_ceiling(
|
||||
file_size, static_cast<std::uint64_t>(data_chunk_size_));
|
||||
total_size_ = file_size + (total_chunks * encryption_header_size);
|
||||
last_data_chunk_ = total_chunks - 1U;
|
||||
last_data_chunk_size_ = (file_size <= data_chunk_size_) ? file_size
|
||||
: (file_size % data_chunk_size_) == 0U
|
||||
? data_chunk_size_
|
||||
: file_size % data_chunk_size_;
|
||||
iv_list_.resize(total_chunks);
|
||||
for (auto &iv : iv_list_) {
|
||||
randombytes_buf(iv.data(), iv.size());
|
||||
}
|
||||
common_initialize(true);
|
||||
create_encrypted_paths(file_name, relative_parent_path);
|
||||
}
|
||||
|
||||
encrypting_reader::encrypting_reader(std::string_view encrypted_file_path,
|
||||
encrypting_reader::encrypting_reader(stop_type_callback stop_requested_cb,
|
||||
std::string_view encrypted_file_path,
|
||||
std::string_view source_path,
|
||||
stop_type_callback stop_requested_cb,
|
||||
std::string_view token,
|
||||
std::size_t error_return)
|
||||
: key_(utils::encryption::generate_key<utils::encryption::hash_256_t>(
|
||||
token)),
|
||||
: keys_(utils::encryption::generate_key<utils::hash::hash_256_t>(token),
|
||||
utils::encryption::generate_key<utils::hash::hash_256_t>(token)),
|
||||
stop_requested_cb_(std::move(stop_requested_cb)),
|
||||
error_return_(error_return),
|
||||
source_file_(utils::file::file::open_or_create_file(source_path, true)) {
|
||||
REPERTORY_USES_FUNCTION_NAME();
|
||||
|
||||
if (not*source_file_) {
|
||||
throw utils::error::create_exception(function_name, {
|
||||
"file open failed",
|
||||
source_path,
|
||||
});
|
||||
}
|
||||
|
||||
encrypted_file_path_ = encrypted_file_path;
|
||||
encrypted_file_name_ = utils::path::strip_to_file_name(encrypted_file_path_);
|
||||
|
||||
auto opt_size = source_file_->size();
|
||||
if (not opt_size.has_value()) {
|
||||
throw utils::error::create_exception(function_name,
|
||||
{
|
||||
"failed to get file size",
|
||||
source_file_->get_path(),
|
||||
});
|
||||
}
|
||||
auto file_size = opt_size.value();
|
||||
|
||||
const auto total_chunks = utils::divide_with_ceiling(
|
||||
file_size, static_cast<std::uint64_t>(data_chunk_size_));
|
||||
total_size_ = file_size + (total_chunks * encryption_header_size);
|
||||
last_data_chunk_ = total_chunks - 1U;
|
||||
last_data_chunk_size_ = (file_size <= data_chunk_size_) ? file_size
|
||||
: (file_size % data_chunk_size_) == 0U
|
||||
? data_chunk_size_
|
||||
: file_size % data_chunk_size_;
|
||||
iv_list_.resize(total_chunks);
|
||||
for (auto &iv : iv_list_) {
|
||||
randombytes_buf(iv.data(), iv.size());
|
||||
}
|
||||
source_file_(utils::file::file::open_or_create_file(source_path, true)),
|
||||
encrypted_file_name_(
|
||||
utils::path::strip_to_file_name(std::string{encrypted_file_path})),
|
||||
encrypted_file_path_(encrypted_file_path) {
|
||||
common_initialize(true);
|
||||
}
|
||||
|
||||
encrypting_reader::encrypting_reader(
|
||||
std::string_view encrypted_file_path, std::string_view source_path,
|
||||
stop_type_callback stop_requested_cb, std::string_view token,
|
||||
stop_type_callback stop_requested_cb, std::string_view encrypted_file_path,
|
||||
std::string_view source_path, std::string_view token,
|
||||
std::vector<
|
||||
std::array<unsigned char, crypto_aead_xchacha20poly1305_IETF_NPUBBYTES>>
|
||||
iv_list,
|
||||
std::size_t error_return)
|
||||
: key_(utils::encryption::generate_key<utils::encryption::hash_256_t>(
|
||||
token)),
|
||||
: keys_(utils::encryption::generate_key<utils::hash::hash_256_t>(token),
|
||||
utils::encryption::generate_key<utils::hash::hash_256_t>(token)),
|
||||
stop_requested_cb_(std::move(stop_requested_cb)),
|
||||
error_return_(error_return),
|
||||
source_file_(utils::file::file::open_or_create_file(source_path, true)),
|
||||
encrypted_file_name_(
|
||||
utils::path::strip_to_file_name(std::string{encrypted_file_path})),
|
||||
encrypted_file_path_(encrypted_file_path),
|
||||
iv_list_(std::move(iv_list)) {
|
||||
common_initialize(false);
|
||||
}
|
||||
|
||||
encrypting_reader::encrypting_reader(
|
||||
std::string_view file_name, std::string_view source_path,
|
||||
stop_type_callback stop_requested_cb, std::string_view token,
|
||||
kdf_config cfg, std::optional<std::string> relative_parent_path,
|
||||
std::size_t error_return)
|
||||
: stop_requested_cb_(std::move(stop_requested_cb)),
|
||||
error_return_(error_return),
|
||||
source_file_(utils::file::file::open_or_create_file(source_path, true)) {
|
||||
REPERTORY_USES_FUNCTION_NAME();
|
||||
common_initialize_kdf_keys(token, cfg);
|
||||
common_initialize(true);
|
||||
create_encrypted_paths(file_name, relative_parent_path);
|
||||
}
|
||||
|
||||
if (not*source_file_) {
|
||||
throw utils::error::create_exception(function_name, {
|
||||
"file open failed",
|
||||
source_path,
|
||||
});
|
||||
}
|
||||
encrypting_reader::encrypting_reader(stop_type_callback stop_requested_cb,
|
||||
std::string_view encrypted_file_path,
|
||||
std::string_view source_path,
|
||||
std::string_view token, kdf_config cfg,
|
||||
std::size_t error_return)
|
||||
: stop_requested_cb_(std::move(stop_requested_cb)),
|
||||
error_return_(error_return),
|
||||
source_file_(utils::file::file::open_or_create_file(source_path, true)),
|
||||
encrypted_file_name_(
|
||||
utils::path::strip_to_file_name(std::string{encrypted_file_path})),
|
||||
encrypted_file_path_(encrypted_file_path) {
|
||||
common_initialize_kdf_keys(token, cfg);
|
||||
common_initialize(true);
|
||||
}
|
||||
|
||||
encrypted_file_path_ = encrypted_file_path;
|
||||
encrypted_file_name_ = utils::path::strip_to_file_name(encrypted_file_path_);
|
||||
encrypting_reader::encrypting_reader(
|
||||
stop_type_callback stop_requested_cb, std::string_view encrypted_file_path,
|
||||
std::string_view source_path, std::string_view token, kdf_config cfg,
|
||||
std::vector<
|
||||
std::array<unsigned char, crypto_aead_xchacha20poly1305_IETF_NPUBBYTES>>
|
||||
iv_list,
|
||||
std::size_t error_return)
|
||||
: stop_requested_cb_(std::move(stop_requested_cb)),
|
||||
error_return_(error_return),
|
||||
source_file_(utils::file::file::open_or_create_file(source_path, true)),
|
||||
encrypted_file_name_(
|
||||
utils::path::strip_to_file_name(std::string{encrypted_file_path})),
|
||||
encrypted_file_path_(encrypted_file_path),
|
||||
iv_list_(std::move(iv_list)) {
|
||||
common_initialize_kdf_keys(token, cfg);
|
||||
common_initialize(false);
|
||||
}
|
||||
|
||||
auto opt_size = source_file_->size();
|
||||
if (not opt_size.has_value()) {
|
||||
throw utils::error::create_exception(
|
||||
function_name, {
|
||||
"get file size failed",
|
||||
std::to_string(utils::get_last_error_code()),
|
||||
source_file_->get_path(),
|
||||
});
|
||||
}
|
||||
auto file_size{opt_size.value()};
|
||||
encrypting_reader::encrypting_reader(
|
||||
std::string_view file_name, std::string_view source_path,
|
||||
stop_type_callback stop_requested_cb,
|
||||
const utils::hash::hash_256_t &master_key, const kdf_config &cfg,
|
||||
std::optional<std::string> relative_parent_path, std::size_t error_return)
|
||||
: stop_requested_cb_(std::move(stop_requested_cb)),
|
||||
error_return_(error_return),
|
||||
source_file_(utils::file::file::open_or_create_file(source_path, true)) {
|
||||
common_initialize_kdf_data(cfg, master_key);
|
||||
auto [path_key, path_cfg] = cfg.create_subkey(
|
||||
kdf_context::path, utils::generate_secure_random<std::uint64_t>(),
|
||||
master_key);
|
||||
keys_.second = std::move(path_key);
|
||||
kdf_headers_->second = path_cfg.to_header();
|
||||
common_initialize(true);
|
||||
create_encrypted_paths(file_name, relative_parent_path);
|
||||
}
|
||||
|
||||
const auto total_chunks = utils::divide_with_ceiling(
|
||||
file_size, static_cast<std::uint64_t>(data_chunk_size_));
|
||||
total_size_ = file_size + (total_chunks * encryption_header_size);
|
||||
last_data_chunk_ = total_chunks - 1U;
|
||||
last_data_chunk_size_ = (file_size <= data_chunk_size_) ? file_size
|
||||
: (file_size % data_chunk_size_) == 0U
|
||||
? data_chunk_size_
|
||||
: file_size % data_chunk_size_;
|
||||
iv_list_ = std::move(iv_list);
|
||||
encrypting_reader::encrypting_reader(
|
||||
std::string_view file_name, std::string_view source_path,
|
||||
stop_type_callback stop_requested_cb,
|
||||
const utils::hash::hash_256_t &master_key,
|
||||
const std::pair<kdf_config, kdf_config> &configs,
|
||||
std::optional<std::string> relative_parent_path, std::size_t error_return)
|
||||
: stop_requested_cb_(std::move(stop_requested_cb)),
|
||||
error_return_(error_return),
|
||||
source_file_(utils::file::file::open_or_create_file(source_path, true)) {
|
||||
keys_ = {
|
||||
configs.first.recreate_subkey(utils::encryption::kdf_context::data,
|
||||
master_key),
|
||||
configs.second.recreate_subkey(utils::encryption::kdf_context::path,
|
||||
master_key),
|
||||
};
|
||||
kdf_headers_ = {
|
||||
configs.first.to_header(),
|
||||
configs.second.to_header(),
|
||||
};
|
||||
common_initialize(true);
|
||||
create_encrypted_paths(file_name, relative_parent_path);
|
||||
}
|
||||
|
||||
encrypting_reader::encrypting_reader(stop_type_callback stop_requested_cb,
|
||||
std::string_view encrypted_file_path,
|
||||
std::string_view source_path,
|
||||
const utils::hash::hash_256_t &master_key,
|
||||
const kdf_config &cfg,
|
||||
std::size_t error_return)
|
||||
: stop_requested_cb_(std::move(stop_requested_cb)),
|
||||
error_return_(error_return),
|
||||
source_file_(utils::file::file::open_or_create_file(source_path, true)),
|
||||
encrypted_file_name_(
|
||||
utils::path::strip_to_file_name(std::string{encrypted_file_path})),
|
||||
encrypted_file_path_(encrypted_file_path) {
|
||||
common_initialize_kdf_data(cfg, master_key);
|
||||
common_initialize_kdf_path(master_key);
|
||||
common_initialize(true);
|
||||
}
|
||||
|
||||
encrypting_reader::encrypting_reader(
|
||||
stop_type_callback stop_requested_cb, std::string_view encrypted_file_path,
|
||||
std::string_view source_path, const utils::hash::hash_256_t &master_key,
|
||||
const kdf_config &cfg,
|
||||
std::vector<
|
||||
std::array<unsigned char, crypto_aead_xchacha20poly1305_IETF_NPUBBYTES>>
|
||||
iv_list,
|
||||
std::size_t error_return)
|
||||
: stop_requested_cb_(std::move(stop_requested_cb)),
|
||||
error_return_(error_return),
|
||||
source_file_(utils::file::file::open_or_create_file(source_path, true)),
|
||||
encrypted_file_name_(
|
||||
utils::path::strip_to_file_name(std::string{encrypted_file_path})),
|
||||
encrypted_file_path_(encrypted_file_path),
|
||||
iv_list_(std::move(iv_list)) {
|
||||
common_initialize_kdf_data(cfg, master_key);
|
||||
common_initialize_kdf_path(master_key);
|
||||
common_initialize(false);
|
||||
}
|
||||
|
||||
encrypting_reader::encrypting_reader(
|
||||
stop_type_callback stop_requested_cb, std::string_view encrypted_file_path,
|
||||
std::string_view source_path, const utils::hash::hash_256_t &master_key,
|
||||
const std::pair<kdf_config, kdf_config> &configs,
|
||||
std::vector<
|
||||
std::array<unsigned char, crypto_aead_xchacha20poly1305_IETF_NPUBBYTES>>
|
||||
iv_list,
|
||||
std::size_t error_return)
|
||||
: stop_requested_cb_(std::move(stop_requested_cb)),
|
||||
error_return_(error_return),
|
||||
source_file_(utils::file::file::open_or_create_file(source_path, true)),
|
||||
encrypted_file_name_(
|
||||
utils::path::strip_to_file_name(std::string{encrypted_file_path})),
|
||||
encrypted_file_path_(encrypted_file_path),
|
||||
iv_list_(std::move(iv_list)) {
|
||||
keys_.first = configs.first.recreate_subkey(
|
||||
utils::encryption::kdf_context::data, master_key);
|
||||
keys_.second = configs.second.recreate_subkey(
|
||||
utils::encryption::kdf_context::path, master_key);
|
||||
kdf_headers_ = {
|
||||
configs.first.to_header(),
|
||||
configs.second.to_header(),
|
||||
};
|
||||
common_initialize(false);
|
||||
}
|
||||
|
||||
encrypting_reader::encrypting_reader(const encrypting_reader &reader)
|
||||
: key_(reader.key_),
|
||||
: keys_(reader.keys_),
|
||||
stop_requested_cb_(reader.stop_requested_cb_),
|
||||
error_return_(reader.error_return_),
|
||||
source_file_(
|
||||
utils::file::file::open_file(reader.source_file_->get_path(), true)),
|
||||
chunk_buffers_(reader.chunk_buffers_),
|
||||
encrypted_file_name_(reader.encrypted_file_name_),
|
||||
encrypted_file_path_(reader.encrypted_file_path_),
|
||||
iv_list_(reader.iv_list_),
|
||||
chunk_buffers_(reader.chunk_buffers_),
|
||||
kdf_headers_(reader.kdf_headers_),
|
||||
last_data_chunk_(reader.last_data_chunk_),
|
||||
last_data_chunk_size_(reader.last_data_chunk_size_),
|
||||
read_offset_(reader.read_offset_),
|
||||
total_size_(reader.total_size_) {
|
||||
REPERTORY_USES_FUNCTION_NAME();
|
||||
|
||||
if (not*source_file_) {
|
||||
if (not *source_file_) {
|
||||
throw utils::error::create_exception(
|
||||
function_name, {
|
||||
"file open failed",
|
||||
@@ -354,15 +403,21 @@ encrypting_reader::encrypting_reader(const encrypting_reader &reader)
|
||||
}
|
||||
}
|
||||
|
||||
auto encrypting_reader::calculate_decrypted_size(std::uint64_t total_size)
|
||||
auto encrypting_reader::calculate_decrypted_size(std::uint64_t total_size,
|
||||
bool uses_kdf)
|
||||
-> std::uint64_t {
|
||||
if (uses_kdf) {
|
||||
total_size -= kdf_config::size();
|
||||
}
|
||||
|
||||
return total_size - (utils::divide_with_ceiling(
|
||||
total_size, static_cast<std::uint64_t>(
|
||||
get_encrypted_chunk_size())) *
|
||||
encryption_header_size);
|
||||
}
|
||||
|
||||
auto encrypting_reader::calculate_encrypted_size(std::string_view source_path)
|
||||
auto encrypting_reader::calculate_encrypted_size(std::string_view source_path,
|
||||
bool uses_kdf)
|
||||
-> std::uint64_t {
|
||||
REPERTORY_USES_FUNCTION_NAME();
|
||||
|
||||
@@ -375,11 +430,135 @@ auto encrypting_reader::calculate_encrypted_size(std::string_view source_path)
|
||||
source_path,
|
||||
});
|
||||
}
|
||||
auto file_size{opt_size.value()};
|
||||
|
||||
const auto total_chunks = utils::divide_with_ceiling(
|
||||
return calculate_encrypted_size(opt_size.value(), uses_kdf);
|
||||
}
|
||||
|
||||
auto encrypting_reader::calculate_encrypted_size(std::uint64_t size,
|
||||
bool uses_kdf)
|
||||
-> std::uint64_t {
|
||||
auto total_chunks = utils::divide_with_ceiling(
|
||||
size, static_cast<std::uint64_t>(data_chunk_size_));
|
||||
return size + (total_chunks * encryption_header_size) +
|
||||
(uses_kdf ? kdf_config::size() : 0U);
|
||||
}
|
||||
|
||||
void encrypting_reader::common_initialize(bool procces_iv_list) {
|
||||
REPERTORY_USES_FUNCTION_NAME();
|
||||
|
||||
if (not *source_file_) {
|
||||
throw utils::error::create_exception(function_name,
|
||||
{
|
||||
"file open failed",
|
||||
source_file_->get_path(),
|
||||
});
|
||||
}
|
||||
|
||||
auto opt_size = source_file_->size();
|
||||
if (not opt_size.has_value()) {
|
||||
throw utils::error::create_exception(function_name,
|
||||
{
|
||||
"failed to get file size",
|
||||
source_file_->get_path(),
|
||||
});
|
||||
}
|
||||
auto file_size = opt_size.value();
|
||||
|
||||
auto total_chunks = utils::divide_with_ceiling(
|
||||
file_size, static_cast<std::uint64_t>(data_chunk_size_));
|
||||
return file_size + (total_chunks * encryption_header_size);
|
||||
total_size_ = file_size + (total_chunks * encryption_header_size) +
|
||||
(kdf_headers_.has_value() ? kdf_headers_->first.size() : 0U);
|
||||
last_data_chunk_ = total_chunks - 1U;
|
||||
last_data_chunk_size_ = (file_size <= data_chunk_size_) ? file_size
|
||||
: (file_size % data_chunk_size_) == 0U
|
||||
? data_chunk_size_
|
||||
: file_size % data_chunk_size_;
|
||||
if (not procces_iv_list) {
|
||||
return;
|
||||
}
|
||||
|
||||
iv_list_.resize(total_chunks);
|
||||
for (auto &data : iv_list_) {
|
||||
randombytes_buf(data.data(), data.size());
|
||||
}
|
||||
}
|
||||
|
||||
void encrypting_reader::common_initialize_kdf_data(
|
||||
const kdf_config &cfg, const utils::hash::hash_256_t &master_key) {
|
||||
auto [data_key, data_cfg] = cfg.create_subkey(
|
||||
kdf_context::data, utils::generate_secure_random<std::uint64_t>(),
|
||||
master_key);
|
||||
keys_.first = std::move(data_key);
|
||||
kdf_headers_ = {data_cfg.to_header(), {}};
|
||||
}
|
||||
|
||||
void encrypting_reader::common_initialize_kdf_keys(std::string_view token,
|
||||
kdf_config &cfg) {
|
||||
auto key =
|
||||
utils::encryption::generate_key<utils::hash::hash_256_t>(token, cfg);
|
||||
keys_ = {key, key};
|
||||
kdf_headers_ = {cfg.to_header(), cfg.to_header()};
|
||||
}
|
||||
|
||||
void encrypting_reader::common_initialize_kdf_path(
|
||||
const utils::hash::hash_256_t &master_key) {
|
||||
REPERTORY_USES_FUNCTION_NAME();
|
||||
|
||||
auto buffer = macaron::Base64::Decode(encrypted_file_path_);
|
||||
|
||||
kdf_config path_cfg;
|
||||
if (not kdf_config::from_header(buffer, path_cfg)) {
|
||||
throw utils::error::create_exception(
|
||||
function_name, {"failed to create path kdf config from header"});
|
||||
}
|
||||
|
||||
utils::hash::hash_256_t path_key;
|
||||
std::tie(path_key, std::ignore) =
|
||||
path_cfg.create_subkey(kdf_context::path, path_cfg.unique_id, master_key);
|
||||
|
||||
kdf_headers_->second = path_cfg.to_header();
|
||||
}
|
||||
|
||||
void encrypting_reader::create_encrypted_paths(
|
||||
std::string_view file_name,
|
||||
std::optional<std::string> relative_parent_path) {
|
||||
data_buffer result;
|
||||
utils::encryption::encrypt_data(
|
||||
keys_.second, reinterpret_cast<const unsigned char *>(file_name.data()),
|
||||
file_name.size(), result);
|
||||
if (kdf_headers_.has_value()) {
|
||||
result.insert(result.begin(), kdf_headers_->second.begin(),
|
||||
kdf_headers_->second.end());
|
||||
}
|
||||
|
||||
encrypted_file_name_ =
|
||||
kdf_headers_.has_value()
|
||||
? macaron::Base64::EncodeUrlSafe(result.data(), result.size())
|
||||
: utils::collection::to_hex_string(result);
|
||||
|
||||
if (not relative_parent_path.has_value()) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const auto &part :
|
||||
utils::string::split(relative_parent_path.value(),
|
||||
utils::path::directory_seperator, false)) {
|
||||
utils::encryption::encrypt_data(
|
||||
keys_.second, reinterpret_cast<const unsigned char *>(part.c_str()),
|
||||
strnlen(part.c_str(), part.size()), result);
|
||||
if (kdf_headers_.has_value()) {
|
||||
result.insert(result.begin(), kdf_headers_->second.begin(),
|
||||
kdf_headers_->second.end());
|
||||
}
|
||||
|
||||
encrypted_file_path_ +=
|
||||
'/' +
|
||||
(kdf_headers_.has_value()
|
||||
? macaron::Base64::EncodeUrlSafe(result.data(), result.size())
|
||||
: utils::collection::to_hex_string(result));
|
||||
}
|
||||
|
||||
encrypted_file_path_ += '/' + encrypted_file_name_;
|
||||
}
|
||||
|
||||
auto encrypting_reader::create_iostream() const
|
||||
@@ -388,24 +567,93 @@ auto encrypting_reader::create_iostream() const
|
||||
std::make_unique<encrypting_streambuf>(*this));
|
||||
}
|
||||
|
||||
auto encrypting_reader::get_kdf_config_for_data() const
|
||||
-> std::optional<kdf_config> {
|
||||
REPERTORY_USES_FUNCTION_NAME();
|
||||
|
||||
if (not kdf_headers_.has_value()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
kdf_config cfg;
|
||||
if (not kdf_config::from_header(kdf_headers_->first, cfg)) {
|
||||
throw utils::error::create_exception(function_name,
|
||||
{
|
||||
"invalid kdf header",
|
||||
});
|
||||
}
|
||||
|
||||
return cfg;
|
||||
}
|
||||
|
||||
auto encrypting_reader::get_kdf_config_for_path() const
|
||||
-> std::optional<kdf_config> {
|
||||
REPERTORY_USES_FUNCTION_NAME();
|
||||
|
||||
if (not kdf_headers_.has_value()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
kdf_config cfg;
|
||||
if (not kdf_config::from_header(kdf_headers_->second, cfg)) {
|
||||
throw utils::error::create_exception(function_name,
|
||||
{
|
||||
"invalid kdf header",
|
||||
});
|
||||
}
|
||||
|
||||
return cfg;
|
||||
}
|
||||
|
||||
auto encrypting_reader::reader_function(char *buffer, size_t size,
|
||||
size_t nitems) -> size_t {
|
||||
REPERTORY_USES_FUNCTION_NAME();
|
||||
|
||||
const auto read_size = static_cast<std::size_t>(std::min(
|
||||
static_cast<std::uint64_t>(size * nitems), total_size_ - read_offset_));
|
||||
auto read_size =
|
||||
static_cast<std::uint64_t>(size) * static_cast<std::uint64_t>(nitems);
|
||||
if (read_size == 0U) {
|
||||
return 0U;
|
||||
}
|
||||
|
||||
auto chunk = read_offset_ / encrypted_chunk_size_;
|
||||
auto chunk_offset = read_offset_ % encrypted_chunk_size_;
|
||||
std::span<char> dest(buffer, read_size);
|
||||
auto read_offset{read_offset_};
|
||||
std::size_t total_read{};
|
||||
auto total_size{total_size_};
|
||||
|
||||
auto ret = false;
|
||||
if (read_offset_ < total_size_) {
|
||||
if (kdf_headers_.has_value()) {
|
||||
auto &hdr = kdf_headers_->first;
|
||||
total_size -= hdr.size();
|
||||
|
||||
if (read_offset < hdr.size()) {
|
||||
auto to_read{
|
||||
utils::calculate_read_size(hdr.size(), read_size, read_offset),
|
||||
};
|
||||
read_offset_ += to_read;
|
||||
|
||||
std::memcpy(&dest[total_read], &hdr.at(read_offset), to_read);
|
||||
if (read_size - to_read == 0) {
|
||||
return to_read;
|
||||
}
|
||||
|
||||
read_offset = 0U;
|
||||
read_size -= to_read;
|
||||
total_read += to_read;
|
||||
} else {
|
||||
read_offset -= hdr.size();
|
||||
}
|
||||
}
|
||||
|
||||
auto chunk = static_cast<std::size_t>(read_offset / encrypted_chunk_size_);
|
||||
auto chunk_offset =
|
||||
static_cast<std::size_t>(read_offset % encrypted_chunk_size_);
|
||||
auto remain = utils::calculate_read_size(total_size, read_size, read_offset);
|
||||
|
||||
auto ret{false};
|
||||
if (read_offset < total_size) {
|
||||
try {
|
||||
ret = true;
|
||||
auto remain = read_size;
|
||||
while (not get_stop_requested() && ret && (remain != 0U)) {
|
||||
if (chunk_buffers_.find(chunk) == chunk_buffers_.end()) {
|
||||
if (not chunk_buffers_.contains(chunk)) {
|
||||
auto &chunk_buffer = chunk_buffers_[chunk];
|
||||
data_buffer file_data(chunk == last_data_chunk_
|
||||
? last_data_chunk_size_
|
||||
@@ -413,24 +661,26 @@ auto encrypting_reader::reader_function(char *buffer, size_t size,
|
||||
chunk_buffer.resize(file_data.size() + encryption_header_size);
|
||||
|
||||
std::size_t bytes_read{};
|
||||
if ((ret = source_file_->read(file_data, chunk * data_chunk_size_,
|
||||
&bytes_read))) {
|
||||
utils::encryption::encrypt_data(iv_list_.at(chunk), key_, file_data,
|
||||
chunk_buffer);
|
||||
ret = source_file_->read(
|
||||
file_data,
|
||||
static_cast<std::uint64_t>(chunk) *
|
||||
static_cast<std::uint64_t>(data_chunk_size_),
|
||||
&bytes_read);
|
||||
if (ret) {
|
||||
utils::encryption::encrypt_data(iv_list_.at(chunk), keys_.first,
|
||||
file_data, chunk_buffer);
|
||||
}
|
||||
} else if (chunk) {
|
||||
chunk_buffers_.erase(chunk - 1u);
|
||||
} else if (chunk != 0U) {
|
||||
chunk_buffers_.erase(chunk - 1U);
|
||||
}
|
||||
|
||||
auto &chunk_buffer = chunk_buffers_[chunk];
|
||||
const auto to_read = std::min(
|
||||
static_cast<std::size_t>(chunk_buffer.size() - chunk_offset),
|
||||
remain);
|
||||
std::memcpy(buffer + total_read, &chunk_buffer[chunk_offset], to_read);
|
||||
auto to_read = std::min(chunk_buffer.size() - chunk_offset, remain);
|
||||
std::memcpy(&dest[total_read], &chunk_buffer[chunk_offset], to_read);
|
||||
total_read += to_read;
|
||||
remain -= to_read;
|
||||
chunk_offset = 0u;
|
||||
chunk++;
|
||||
chunk_offset = 0U;
|
||||
++chunk;
|
||||
read_offset_ += to_read;
|
||||
}
|
||||
} catch (const std::exception &e) {
|
||||
|
||||
@@ -23,11 +23,130 @@
|
||||
|
||||
#include "utils/encryption.hpp"
|
||||
|
||||
#include "utils/base64.hpp"
|
||||
#include "utils/collection.hpp"
|
||||
#include "utils/config.hpp"
|
||||
#include "utils/encrypting_reader.hpp"
|
||||
#include "utils/hash.hpp"
|
||||
#include "utils/path.hpp"
|
||||
|
||||
namespace {
|
||||
constexpr auto resize_by(repertory::data_span &data, std::size_t /* size */)
|
||||
-> repertory::data_span & {
|
||||
return data;
|
||||
}
|
||||
|
||||
auto resize_by(repertory::data_buffer &data, std::size_t size)
|
||||
-> repertory::data_buffer & {
|
||||
data.resize(data.size() + size);
|
||||
return data;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
namespace repertory::utils::encryption {
|
||||
auto kdf_config::to_header() const -> data_buffer {
|
||||
kdf_config tmp{*this};
|
||||
tmp.checksum = boost::endian::native_to_big(tmp.checksum);
|
||||
tmp.unique_id = boost::endian::native_to_big(tmp.unique_id);
|
||||
|
||||
data_buffer ret(size());
|
||||
std::memcpy(ret.data(), &tmp, ret.size());
|
||||
return ret;
|
||||
}
|
||||
|
||||
auto kdf_config::generate_checksum() const -> std::uint64_t {
|
||||
REPERTORY_USES_FUNCTION_NAME();
|
||||
|
||||
kdf_config tmp = *this;
|
||||
tmp.checksum = 0;
|
||||
|
||||
auto hash = utils::hash::create_hash_blake2b_64(tmp.to_header());
|
||||
std::uint64_t ret{};
|
||||
std::memcpy(&ret, hash.data(), hash.size());
|
||||
return ret;
|
||||
}
|
||||
|
||||
auto kdf_config::from_header(data_cspan data, kdf_config &cfg,
|
||||
bool ignore_checksum) -> bool {
|
||||
if (data.size() < kdf_config::size()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::memcpy(&cfg, data.data(), kdf_config::size());
|
||||
|
||||
cfg.checksum = boost::endian::big_to_native(cfg.checksum);
|
||||
cfg.unique_id = boost::endian::big_to_native(cfg.unique_id);
|
||||
return cfg.version == kdf_version::v1 && cfg.kdf == kdf_type::argon2id &&
|
||||
cfg.memlimit >= memlimit_level::level1 &&
|
||||
cfg.memlimit <= memlimit_level::level4 &&
|
||||
cfg.opslimit >= opslimit_level::level1 &&
|
||||
cfg.opslimit <= opslimit_level::level3 &&
|
||||
(ignore_checksum || cfg.checksum == cfg.generate_checksum());
|
||||
}
|
||||
|
||||
void kdf_config::seal() {
|
||||
randombytes_buf(salt.data(), salt.size());
|
||||
checksum = generate_checksum();
|
||||
}
|
||||
|
||||
auto decrypt_file_name(std::string_view encryption_token,
|
||||
std::string &file_name) -> bool {
|
||||
data_buffer buffer;
|
||||
if (not utils::collection::from_hex_string(file_name, buffer)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
file_name.clear();
|
||||
return utils::encryption::decrypt_data(encryption_token, buffer, file_name);
|
||||
}
|
||||
|
||||
auto decrypt_file_name(std::string_view encryption_token, const kdf_config &cfg,
|
||||
std::string &file_name) -> bool {
|
||||
REPERTORY_USES_FUNCTION_NAME();
|
||||
|
||||
try {
|
||||
auto buffer = macaron::Base64::Decode(file_name);
|
||||
|
||||
file_name.clear();
|
||||
return utils::encryption::decrypt_data(encryption_token, cfg, buffer,
|
||||
file_name);
|
||||
} catch (const std::exception &e) {
|
||||
utils::error::handle_exception(function_name, e);
|
||||
} catch (...) {
|
||||
utils::error::handle_exception(function_name);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
auto decrypt_file_name(const utils::hash::hash_256_t &master_key,
|
||||
std::string &file_name) -> bool {
|
||||
REPERTORY_USES_FUNCTION_NAME();
|
||||
|
||||
try {
|
||||
auto buffer = macaron::Base64::Decode(file_name);
|
||||
|
||||
utils::encryption::kdf_config path_cfg;
|
||||
if (not utils::encryption::kdf_config::from_header(buffer, path_cfg)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto path_key = path_cfg.recreate_subkey(
|
||||
utils::encryption::kdf_context::path, master_key);
|
||||
|
||||
file_name.clear();
|
||||
return utils::encryption::decrypt_data(
|
||||
path_key, &buffer[utils::encryption::kdf_config::size()],
|
||||
buffer.size() - utils::encryption::kdf_config::size(), file_name);
|
||||
} catch (const std::exception &e) {
|
||||
utils::error::handle_exception(function_name, e);
|
||||
} catch (...) {
|
||||
utils::error::handle_exception(function_name);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
auto decrypt_file_path(std::string_view encryption_token,
|
||||
std::string &file_path) -> bool {
|
||||
std::vector<std::string> decrypted_parts;
|
||||
@@ -49,21 +168,83 @@ auto decrypt_file_path(std::string_view encryption_token,
|
||||
return true;
|
||||
}
|
||||
|
||||
auto decrypt_file_name(std::string_view encryption_token,
|
||||
std::string &file_name) -> bool {
|
||||
data_buffer buffer;
|
||||
if (not utils::collection::from_hex_string(file_name, buffer)) {
|
||||
return false;
|
||||
auto decrypt_file_path(std::string_view encryption_token, const kdf_config &cfg,
|
||||
std::string &file_path) -> bool {
|
||||
std::vector<std::string> decrypted_parts;
|
||||
for (const auto &part : std::filesystem::path(file_path)) {
|
||||
auto file_name = part.string();
|
||||
if (file_name == "/") {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (not decrypt_file_name(encryption_token, cfg, file_name)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
decrypted_parts.push_back(file_name);
|
||||
}
|
||||
|
||||
file_name.clear();
|
||||
return utils::encryption::decrypt_data(encryption_token, buffer, file_name);
|
||||
file_path =
|
||||
utils::path::create_api_path(utils::string::join(decrypted_parts, '/'));
|
||||
return true;
|
||||
}
|
||||
|
||||
auto read_encrypted_range(const http_range &range,
|
||||
const utils::encryption::hash_256_t &key,
|
||||
reader_func_t reader_func, std::uint64_t total_size,
|
||||
data_buffer &data) -> bool {
|
||||
auto decrypt_file_path(const utils::hash::hash_256_t &master_key,
|
||||
std::string &file_path) -> bool {
|
||||
std::vector<std::string> decrypted_parts;
|
||||
for (const auto &part : std::filesystem::path(file_path)) {
|
||||
auto file_name = part.string();
|
||||
if (file_name == "/") {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (not decrypt_file_name(master_key, file_name)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
decrypted_parts.push_back(file_name);
|
||||
}
|
||||
|
||||
file_path =
|
||||
utils::path::create_api_path(utils::string::join(decrypted_parts, '/'));
|
||||
return true;
|
||||
}
|
||||
|
||||
template <typename data_t>
|
||||
[[nodiscard]] auto
|
||||
read_encrypted_range(http_range range, const utils::hash::hash_256_t &key,
|
||||
reader_func_t reader_func, std::uint64_t total_size,
|
||||
data_t &data, std::uint8_t file_header_size,
|
||||
std::size_t &bytes_read) -> bool {
|
||||
bytes_read = 0U;
|
||||
|
||||
{
|
||||
if (total_size == 0U) {
|
||||
return true;
|
||||
}
|
||||
|
||||
std::uint64_t begin = range.begin;
|
||||
std::uint64_t end = range.end;
|
||||
|
||||
if (begin >= total_size) {
|
||||
return true;
|
||||
}
|
||||
|
||||
std::uint64_t last = total_size - 1U;
|
||||
if (end > last) {
|
||||
end = last;
|
||||
}
|
||||
|
||||
if (end < begin) {
|
||||
return true;
|
||||
}
|
||||
|
||||
range = http_range{
|
||||
.begin = begin,
|
||||
.end = end,
|
||||
};
|
||||
}
|
||||
|
||||
auto encrypted_chunk_size =
|
||||
utils::encryption::encrypting_reader::get_encrypted_chunk_size();
|
||||
auto data_chunk_size =
|
||||
@@ -75,22 +256,22 @@ auto read_encrypted_range(const http_range &range,
|
||||
auto source_offset = static_cast<std::size_t>(range.begin % data_chunk_size);
|
||||
|
||||
for (std::size_t chunk = start_chunk; chunk <= end_chunk; chunk++) {
|
||||
data_buffer cypher;
|
||||
auto start_offset = chunk * encrypted_chunk_size;
|
||||
data_buffer cipher;
|
||||
auto start_offset = (chunk * encrypted_chunk_size) + file_header_size;
|
||||
auto end_offset = std::min(
|
||||
start_offset + (total_size - (chunk * data_chunk_size)) +
|
||||
encryption_header_size - 1U,
|
||||
static_cast<std::uint64_t>(start_offset + encrypted_chunk_size - 1U));
|
||||
|
||||
if (not reader_func(cypher, start_offset, end_offset)) {
|
||||
if (not reader_func(cipher, start_offset, end_offset)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
data_buffer source_buffer;
|
||||
if (not utils::encryption::decrypt_data(key, cypher, source_buffer)) {
|
||||
if (not utils::encryption::decrypt_data(key, cipher, source_buffer)) {
|
||||
return false;
|
||||
}
|
||||
cypher.clear();
|
||||
cipher.clear();
|
||||
|
||||
auto data_size = static_cast<std::size_t>(std::min(
|
||||
remain, static_cast<std::uint64_t>(data_chunk_size - source_offset)));
|
||||
@@ -98,58 +279,8 @@ auto read_encrypted_range(const http_range &range,
|
||||
static_cast<std::int64_t>(source_offset)),
|
||||
std::next(source_buffer.begin(),
|
||||
static_cast<std::int64_t>(source_offset + data_size)),
|
||||
std::back_inserter(data));
|
||||
remain -= data_size;
|
||||
source_offset = 0U;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
auto read_encrypted_range(const http_range &range,
|
||||
const utils::encryption::hash_256_t &key,
|
||||
reader_func_t reader_func, std::uint64_t total_size,
|
||||
unsigned char *data, std::size_t size,
|
||||
std::size_t &bytes_read) -> bool {
|
||||
bytes_read = 0U;
|
||||
|
||||
auto encrypted_chunk_size =
|
||||
utils::encryption::encrypting_reader::get_encrypted_chunk_size();
|
||||
auto data_chunk_size =
|
||||
utils::encryption::encrypting_reader::get_data_chunk_size();
|
||||
|
||||
auto start_chunk = static_cast<std::size_t>(range.begin / data_chunk_size);
|
||||
auto end_chunk = static_cast<std::size_t>(range.end / data_chunk_size);
|
||||
auto remain = range.end - range.begin + 1U;
|
||||
auto source_offset = static_cast<std::size_t>(range.begin % data_chunk_size);
|
||||
|
||||
std::span dest_buffer(data, size);
|
||||
for (std::size_t chunk = start_chunk; chunk <= end_chunk; chunk++) {
|
||||
data_buffer cypher;
|
||||
auto start_offset = chunk * encrypted_chunk_size;
|
||||
auto end_offset = std::min(
|
||||
start_offset + (total_size - (chunk * data_chunk_size)) +
|
||||
encryption_header_size - 1U,
|
||||
static_cast<std::uint64_t>(start_offset + encrypted_chunk_size - 1U));
|
||||
|
||||
if (not reader_func(cypher, start_offset, end_offset)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
data_buffer source_buffer;
|
||||
if (not utils::encryption::decrypt_data(key, cypher, source_buffer)) {
|
||||
return false;
|
||||
}
|
||||
cypher.clear();
|
||||
|
||||
auto data_size = static_cast<std::size_t>(std::min(
|
||||
remain, static_cast<std::uint64_t>(data_chunk_size - source_offset)));
|
||||
std::copy(
|
||||
std::next(source_buffer.begin(),
|
||||
static_cast<std::int64_t>(source_offset)),
|
||||
std::next(source_buffer.begin(),
|
||||
static_cast<std::int64_t>(source_offset + data_size)),
|
||||
std::next(dest_buffer.begin(), static_cast<std::int64_t>(bytes_read)));
|
||||
std::next(resize_by(data, data_size).begin(),
|
||||
static_cast<std::int64_t>(bytes_read)));
|
||||
remain -= data_size;
|
||||
bytes_read += data_size;
|
||||
source_offset = 0U;
|
||||
@@ -157,6 +288,26 @@ auto read_encrypted_range(const http_range &range,
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
auto read_encrypted_range(const http_range &range,
|
||||
const utils::hash::hash_256_t &key, bool uses_kdf,
|
||||
reader_func_t reader_func, std::uint64_t total_size,
|
||||
data_buffer &data) -> bool {
|
||||
std::size_t bytes_read{};
|
||||
return read_encrypted_range<data_buffer>(
|
||||
range, key, reader_func, total_size, data,
|
||||
uses_kdf ? kdf_config::size() : 0U, bytes_read);
|
||||
}
|
||||
|
||||
[[nodiscard]] auto read_encrypted_range(
|
||||
const http_range &range, const utils::hash::hash_256_t &key, bool uses_kdf,
|
||||
reader_func_t reader_func, std::uint64_t total_size, unsigned char *data,
|
||||
std::size_t size, std::size_t &bytes_read) -> bool {
|
||||
data_span dest_buffer(data, size);
|
||||
return read_encrypted_range<data_span>(
|
||||
range, key, reader_func, total_size, dest_buffer,
|
||||
uses_kdf ? kdf_config::size() : 0U, bytes_read);
|
||||
}
|
||||
} // namespace repertory::utils::encryption
|
||||
|
||||
#endif // defined(PROJECT_ENABLE_LIBSODIUM) && defined (PROJECT_ENABLE_BOOST)
|
||||
|
||||
@@ -53,9 +53,11 @@ auto change_to_process_directory() -> bool {
|
||||
}
|
||||
#else // !defined(_WIN32)
|
||||
std::string path;
|
||||
path.resize(PATH_MAX + 1);
|
||||
path.resize(repertory::max_path_length + 1);
|
||||
#if defined(__APPLE__)
|
||||
proc_pidpath(getpid(), path.c_str(), path.size());
|
||||
auto res = proc_pidpath(getpid(), reinterpret_cast<void *>(path.data()),
|
||||
static_cast<uint32_t>(path.size()));
|
||||
path = path.c_str();
|
||||
#else // !defined(__APPLE__)
|
||||
auto res = readlink("/proc/self/exe", path.data(), path.size());
|
||||
if (res == -1) {
|
||||
@@ -133,8 +135,8 @@ auto get_free_drive_space(std::string_view path)
|
||||
#endif // defined(_WIN32)
|
||||
|
||||
#if defined(__linux__)
|
||||
struct statfs64 st{};
|
||||
if (statfs64(std::string{path}.c_str(), &st) != 0) {
|
||||
struct statfs64 u_stat{};
|
||||
if (statfs64(std::string{path}.c_str(), &u_stat) != 0) {
|
||||
throw utils::error::create_exception(
|
||||
function_name, {
|
||||
"failed to get free disk space",
|
||||
@@ -143,12 +145,12 @@ auto get_free_drive_space(std::string_view path)
|
||||
});
|
||||
}
|
||||
|
||||
return st.f_bfree * static_cast<std::uint64_t>(st.f_bsize);
|
||||
return u_stat.f_bfree * static_cast<std::uint64_t>(u_stat.f_bsize);
|
||||
#endif // defined(__linux__)
|
||||
|
||||
#if defined(__APPLE__)
|
||||
struct statvfs st{};
|
||||
if (statvfs(path.c_str(), &st) != 0) {
|
||||
struct statvfs u_stat{};
|
||||
if (statvfs(std::string{path}.c_str(), &u_stat) != 0) {
|
||||
throw utils::error::create_exception(
|
||||
function_name, {
|
||||
"failed to get free disk space",
|
||||
@@ -157,7 +159,7 @@ auto get_free_drive_space(std::string_view path)
|
||||
});
|
||||
}
|
||||
|
||||
return st.f_bfree * static_cast<std::uint64_t>(st.f_frsize);
|
||||
return u_stat.f_bfree * static_cast<std::uint64_t>(u_stat.f_frsize);
|
||||
#endif // defined(__APPLE__)
|
||||
} catch (const std::exception &e) {
|
||||
utils::error::handle_exception(function_name, e);
|
||||
@@ -207,6 +209,7 @@ auto get_times(std::string_view path) -> std::optional<file_times> {
|
||||
if (res) {
|
||||
ret.accessed =
|
||||
utils::time::windows_file_time_to_unix_time(times.at(1U));
|
||||
ret.changed = utils::time::windows_file_time_to_unix_time(times.at(2U));
|
||||
ret.created = utils::time::windows_file_time_to_unix_time(times.at(0U));
|
||||
ret.modified =
|
||||
utils::time::windows_file_time_to_unix_time(times.at(2U));
|
||||
@@ -215,8 +218,8 @@ auto get_times(std::string_view path) -> std::optional<file_times> {
|
||||
}
|
||||
}
|
||||
|
||||
struct _stat64 st{};
|
||||
if (_stat64(std::string{path}.c_str(), &st) != 0) {
|
||||
struct _stat64 u_stat{};
|
||||
if (_stat64(std::string{path}.c_str(), &u_stat) != 0) {
|
||||
throw utils::error::create_exception(
|
||||
function_name, {
|
||||
"failed to get file times",
|
||||
@@ -225,13 +228,14 @@ auto get_times(std::string_view path) -> std::optional<file_times> {
|
||||
});
|
||||
}
|
||||
|
||||
ret.accessed = utils::time::windows_time_t_to_unix_time(st.st_atime);
|
||||
ret.created = utils::time::windows_time_t_to_unix_time(st.st_ctime);
|
||||
ret.modified = utils::time::windows_time_t_to_unix_time(st.st_mtime);
|
||||
ret.written = utils::time::windows_time_t_to_unix_time(st.st_mtime);
|
||||
ret.accessed = utils::time::windows_time_t_to_unix_time(u_stat.st_atime);
|
||||
ret.changed = utils::time::windows_time_t_to_unix_time(u_stat.st_ctime);
|
||||
ret.created = utils::time::windows_time_t_to_unix_time(u_stat.st_ctime);
|
||||
ret.modified = utils::time::windows_time_t_to_unix_time(u_stat.st_mtime);
|
||||
ret.written = utils::time::windows_time_t_to_unix_time(u_stat.st_mtime);
|
||||
#else // !defined(_WIN32)
|
||||
struct stat64 st{};
|
||||
if (stat64(std::string{path}.c_str(), &st) != 0) {
|
||||
struct stat64 u_stat{};
|
||||
if (stat64(std::string{path}.c_str(), &u_stat) != 0) {
|
||||
throw utils::error::create_exception(
|
||||
function_name, {
|
||||
"failed to get file times",
|
||||
@@ -240,19 +244,39 @@ auto get_times(std::string_view path) -> std::optional<file_times> {
|
||||
});
|
||||
}
|
||||
|
||||
ret.accessed = static_cast<std::uint64_t>(st.st_atim.tv_nsec) +
|
||||
static_cast<std::uint64_t>(st.st_atim.tv_sec) *
|
||||
#if defined(__APPLE__)
|
||||
ret.accessed = static_cast<std::uint64_t>(u_stat.st_atimespec.tv_nsec) +
|
||||
static_cast<std::uint64_t>(u_stat.st_atimespec.tv_sec) *
|
||||
utils::time::NANOS_PER_SECOND;
|
||||
ret.created = static_cast<std::uint64_t>(st.st_ctim.tv_nsec) +
|
||||
static_cast<std::uint64_t>(st.st_ctim.tv_sec) *
|
||||
ret.created = static_cast<std::uint64_t>(u_stat.st_birthtimespec.tv_nsec) +
|
||||
static_cast<std::uint64_t>(u_stat.st_birthtimespec.tv_sec) *
|
||||
utils::time::NANOS_PER_SECOND;
|
||||
ret.modified = static_cast<std::uint64_t>(st.st_mtim.tv_nsec) +
|
||||
static_cast<std::uint64_t>(st.st_mtim.tv_sec) *
|
||||
ret.changed = static_cast<std::uint64_t>(u_stat.st_ctimespec.tv_nsec) +
|
||||
static_cast<std::uint64_t>(u_stat.st_ctimespec.tv_sec) *
|
||||
utils::time::NANOS_PER_SECOND;
|
||||
ret.modified = static_cast<std::uint64_t>(u_stat.st_mtimespec.tv_nsec) +
|
||||
static_cast<std::uint64_t>(u_stat.st_mtimespec.tv_sec) *
|
||||
utils::time::NANOS_PER_SECOND;
|
||||
ret.written = static_cast<std::uint64_t>(st.st_mtim.tv_nsec) +
|
||||
static_cast<std::uint64_t>(st.st_mtim.tv_sec) *
|
||||
ret.written = static_cast<std::uint64_t>(u_stat.st_mtimespec.tv_nsec) +
|
||||
static_cast<std::uint64_t>(u_stat.st_mtimespec.tv_sec) *
|
||||
utils::time::NANOS_PER_SECOND;
|
||||
|
||||
#else // !defined(__APPLE__)
|
||||
ret.accessed = static_cast<std::uint64_t>(u_stat.st_atim.tv_nsec) +
|
||||
static_cast<std::uint64_t>(u_stat.st_atim.tv_sec) *
|
||||
utils::time::NANOS_PER_SECOND;
|
||||
ret.changed = static_cast<std::uint64_t>(u_stat.st_ctim.tv_nsec) +
|
||||
static_cast<std::uint64_t>(u_stat.st_ctim.tv_sec) *
|
||||
utils::time::NANOS_PER_SECOND;
|
||||
ret.created = static_cast<std::uint64_t>(u_stat.st_ctim.tv_nsec) +
|
||||
static_cast<std::uint64_t>(u_stat.st_ctim.tv_sec) *
|
||||
utils::time::NANOS_PER_SECOND;
|
||||
ret.modified = static_cast<std::uint64_t>(u_stat.st_mtim.tv_nsec) +
|
||||
static_cast<std::uint64_t>(u_stat.st_mtim.tv_sec) *
|
||||
utils::time::NANOS_PER_SECOND;
|
||||
ret.written = static_cast<std::uint64_t>(u_stat.st_mtim.tv_nsec) +
|
||||
static_cast<std::uint64_t>(u_stat.st_mtim.tv_sec) *
|
||||
utils::time::NANOS_PER_SECOND;
|
||||
#endif // defined(__APPLE__)
|
||||
#endif // defined(_WIN32)
|
||||
|
||||
return ret;
|
||||
@@ -290,8 +314,8 @@ auto get_total_drive_space(std::string_view path)
|
||||
#endif // defined(_WIN32)
|
||||
|
||||
#if defined(__linux__)
|
||||
struct statfs64 st{};
|
||||
if (statfs64(std::string{path}.c_str(), &st) != 0) {
|
||||
struct statfs64 u_stat{};
|
||||
if (statfs64(std::string{path}.c_str(), &u_stat) != 0) {
|
||||
throw utils::error::create_exception(
|
||||
function_name, {
|
||||
"failed to get total disk space",
|
||||
@@ -300,12 +324,12 @@ auto get_total_drive_space(std::string_view path)
|
||||
});
|
||||
}
|
||||
|
||||
return st.f_blocks * static_cast<std::uint64_t>(st.f_bsize);
|
||||
return u_stat.f_blocks * static_cast<std::uint64_t>(u_stat.f_bsize);
|
||||
#endif // defined(__linux__)
|
||||
|
||||
#if defined(__APPLE__)
|
||||
struct statvfs st{};
|
||||
if (statvfs(path.c_str(), &st) != 0) {
|
||||
struct statvfs u_stat{};
|
||||
if (statvfs(std::string{path}.c_str(), &u_stat) != 0) {
|
||||
throw utils::error::create_exception(
|
||||
function_name, {
|
||||
"failed to get total disk space",
|
||||
@@ -314,7 +338,7 @@ auto get_total_drive_space(std::string_view path)
|
||||
});
|
||||
}
|
||||
|
||||
return st.f_blocks * static_cast<std::uint64_t>(st.f_frsize);
|
||||
return u_stat.f_blocks * static_cast<std::uint64_t>(u_stat.f_frsize);
|
||||
#endif // defined(__APPLE__)
|
||||
} catch (const std::exception &e) {
|
||||
utils::error::handle_exception(function_name, e);
|
||||
@@ -498,8 +522,7 @@ auto write_json_file(std::wstring_view path, const nlohmann::json &data)
|
||||
#endif // defined(PROJECT_ENABLE_JSON)
|
||||
|
||||
#if defined(PROJECT_ENABLE_LIBDSM)
|
||||
static constexpr auto validate_smb_path =
|
||||
[](std::string_view path) -> bool {
|
||||
static constexpr auto validate_smb_path = [](std::string_view path) -> bool {
|
||||
return (not utils::string::begins_with(path, "///") &&
|
||||
utils::string::begins_with(path, "//") &&
|
||||
// not utils::string::contains(path, " ") &&
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
*/
|
||||
#include "utils/file_directory.hpp"
|
||||
|
||||
#include "utils/com_init_wrapper.hpp"
|
||||
#include "utils/common.hpp"
|
||||
#include "utils/error.hpp"
|
||||
#include "utils/string.hpp"
|
||||
@@ -83,19 +84,19 @@ auto traverse_directory(
|
||||
});
|
||||
}
|
||||
|
||||
struct dirent *de{nullptr};
|
||||
while (res && (de = readdir(root)) && !is_stop_requested()) {
|
||||
if (de->d_type == DT_DIR) {
|
||||
if ((std::string_view(de->d_name) == ".") ||
|
||||
(std::string_view(de->d_name) == "..")) {
|
||||
struct dirent *entry{nullptr};
|
||||
while (res && (entry = ::readdir(root)) && !is_stop_requested()) {
|
||||
if (entry->d_type == DT_DIR) {
|
||||
if ((std::string_view(entry->d_name) == ".") ||
|
||||
(std::string_view(entry->d_name) == "..")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
res = directory_action(repertory::utils::file::directory(
|
||||
repertory::utils::path::combine(path, {de->d_name})));
|
||||
repertory::utils::path::combine(path, {entry->d_name})));
|
||||
} else {
|
||||
res = file_action(repertory::utils::file::file(
|
||||
repertory::utils::path::combine(path, {de->d_name})));
|
||||
repertory::utils::path::combine(path, {entry->d_name})));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -112,16 +113,67 @@ auto directory::copy_to(std::string_view new_path, bool overwrite) const
|
||||
REPERTORY_USES_FUNCTION_NAME();
|
||||
|
||||
try {
|
||||
throw utils::error::create_exception(
|
||||
function_name, {
|
||||
"failed to copy directory",
|
||||
"not implemented",
|
||||
utils::string::from_bool(overwrite),
|
||||
new_path,
|
||||
path_,
|
||||
});
|
||||
} catch (const std::exception &e) {
|
||||
utils::error::handle_exception(function_name, e);
|
||||
if (not exists()) {
|
||||
throw utils::error::create_exception(function_name,
|
||||
{
|
||||
"failed to copy directory",
|
||||
"source does not exist",
|
||||
path_,
|
||||
std::string{new_path},
|
||||
});
|
||||
}
|
||||
|
||||
auto src_root = utils::path::finalize(path_);
|
||||
auto dst_root = utils::path::finalize(new_path);
|
||||
|
||||
if (directory{dst_root}.exists()) {
|
||||
auto src_base = utils::path::strip_to_file_name(src_root);
|
||||
dst_root = utils::path::combine(dst_root, {src_base});
|
||||
} else {
|
||||
auto dst_parent = utils::path::get_parent_path(dst_root);
|
||||
if (not dst_parent.empty() && not directory{dst_parent}.exists()) {
|
||||
auto parent_parent = utils::path::get_parent_path(dst_parent);
|
||||
auto last_piece = utils::path::strip_to_file_name(dst_parent);
|
||||
[[maybe_unused]] auto sub_dir =
|
||||
directory{parent_parent}.create_directory(last_piece);
|
||||
}
|
||||
if (not directory{dst_root}.exists()) {
|
||||
auto root_parent = utils::path::get_parent_path(dst_root);
|
||||
auto root_name = utils::path::strip_to_file_name(dst_root);
|
||||
[[maybe_unused]] auto sub_dir =
|
||||
directory{root_parent}.create_directory(root_name);
|
||||
}
|
||||
}
|
||||
|
||||
auto success = traverse_directory(
|
||||
src_root,
|
||||
[this, &dst_root, &src_root](auto &&dir_item) -> bool {
|
||||
auto child_src = dir_item.get_path();
|
||||
auto rel_path = utils::path::get_relative_path(child_src, src_root);
|
||||
auto child_dst = utils::path::combine(dst_root, {rel_path});
|
||||
|
||||
auto child_parent = utils::path::get_parent_path(child_dst);
|
||||
auto child_name = utils::path::strip_to_file_name(child_dst);
|
||||
[[maybe_unused]] auto sub_dir =
|
||||
directory{child_parent}.create_directory(child_name);
|
||||
return not is_stop_requested();
|
||||
},
|
||||
[this, &dst_root, overwrite, &src_root](auto &&file_item) -> bool {
|
||||
auto child_src = file_item.get_path();
|
||||
auto rel_path = utils::path::get_relative_path(child_src, src_root);
|
||||
auto child_dst = utils::path::combine(dst_root, {rel_path});
|
||||
|
||||
if (not file{child_src}.copy_to(child_dst, overwrite)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return not is_stop_requested();
|
||||
},
|
||||
stop_requested_);
|
||||
|
||||
return success && not is_stop_requested();
|
||||
} catch (const std::exception &ex) {
|
||||
utils::error::handle_exception(function_name, ex);
|
||||
} catch (...) {
|
||||
utils::error::handle_exception(function_name);
|
||||
}
|
||||
@@ -137,7 +189,7 @@ auto directory::count(bool recursive) const -> std::uint64_t {
|
||||
|
||||
traverse_directory(
|
||||
path_,
|
||||
[&ret, &recursive](auto dir_item) -> bool {
|
||||
[&ret, &recursive](auto &&dir_item) -> bool {
|
||||
if (recursive) {
|
||||
ret += dir_item.count(true);
|
||||
}
|
||||
@@ -145,7 +197,7 @@ auto directory::count(bool recursive) const -> std::uint64_t {
|
||||
++ret;
|
||||
return true;
|
||||
},
|
||||
[&ret](auto /* file_item */) -> bool {
|
||||
[&ret](auto && /* file_item */) -> bool {
|
||||
++ret;
|
||||
return true;
|
||||
},
|
||||
@@ -172,6 +224,8 @@ auto directory::create_directory(std::string_view path) const
|
||||
}
|
||||
|
||||
#if defined(_WIN32)
|
||||
[[maybe_unused]] thread_local const utils::com_init_wrapper wrapper;
|
||||
|
||||
auto res = ::SHCreateDirectory(nullptr,
|
||||
utils::string::from_utf8(abs_path).c_str());
|
||||
if (res != ERROR_SUCCESS) {
|
||||
@@ -215,8 +269,8 @@ auto directory::exists() const -> bool {
|
||||
#if defined(_WIN32)
|
||||
return ::PathIsDirectoryA(path_.c_str()) != 0;
|
||||
#else // !defined(_WIN32)
|
||||
struct stat64 st{};
|
||||
return (stat64(path_.c_str(), &st) == 0 && S_ISDIR(st.st_mode));
|
||||
struct stat64 u_stat{};
|
||||
return (stat64(path_.c_str(), &u_stat) == 0 && S_ISDIR(u_stat.st_mode));
|
||||
#endif // defined(_WIN32)
|
||||
|
||||
return false;
|
||||
@@ -249,14 +303,14 @@ auto directory::get_directories() const -> std::vector<fs_directory_t> {
|
||||
|
||||
traverse_directory(
|
||||
path_,
|
||||
[this, &ret](auto dir_item) -> bool {
|
||||
[this, &ret](auto &&dir_item) -> bool {
|
||||
ret.emplace_back(fs_directory_t{
|
||||
new directory(dir_item.get_path(), stop_requested_),
|
||||
});
|
||||
|
||||
return true;
|
||||
},
|
||||
[](auto /* file_item */) -> bool { return true; }, stop_requested_);
|
||||
[](auto && /* file_item */) -> bool { return true; }, stop_requested_);
|
||||
|
||||
return ret;
|
||||
} catch (const std::exception &e) {
|
||||
@@ -308,8 +362,8 @@ auto directory::get_files() const -> std::vector<fs_file_t> {
|
||||
std::vector<fs_file_t> ret{};
|
||||
|
||||
traverse_directory(
|
||||
path_, [](auto /* dir_item */) -> bool { return true; },
|
||||
[&ret](auto file_item) -> bool {
|
||||
path_, [](auto && /* dir_item */) -> bool { return true; },
|
||||
[&ret](auto &&file_item) -> bool {
|
||||
ret.emplace_back(fs_file_t{
|
||||
new file(file_item.get_path()),
|
||||
});
|
||||
@@ -335,13 +389,13 @@ auto directory::get_items() const -> std::vector<fs_item_t> {
|
||||
|
||||
traverse_directory(
|
||||
path_,
|
||||
[this, &ret](auto dir_item) -> bool {
|
||||
[this, &ret](auto &&dir_item) -> bool {
|
||||
ret.emplace_back(fs_item_t{
|
||||
new directory(dir_item.get_path(), stop_requested_),
|
||||
});
|
||||
return true;
|
||||
},
|
||||
[&ret](auto file_item) -> bool {
|
||||
[&ret](auto &&file_item) -> bool {
|
||||
ret.emplace_back(fs_item_t{
|
||||
new file(file_item.get_path()),
|
||||
});
|
||||
@@ -381,15 +435,68 @@ auto directory::move_to(std::string_view new_path) -> bool {
|
||||
REPERTORY_USES_FUNCTION_NAME();
|
||||
|
||||
try {
|
||||
throw utils::error::create_exception(function_name,
|
||||
{
|
||||
"failed to move directory",
|
||||
"not implemented",
|
||||
new_path,
|
||||
path_,
|
||||
});
|
||||
} catch (const std::exception &e) {
|
||||
utils::error::handle_exception(function_name, e);
|
||||
if (not exists()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
auto src_root = utils::path::finalize(path_);
|
||||
auto dst_root = utils::path::finalize(new_path);
|
||||
if (src_root == dst_root) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (directory{dst_root}.exists()) {
|
||||
throw utils::error::create_exception(function_name,
|
||||
{
|
||||
"failed to move directory",
|
||||
"destination exists",
|
||||
src_root,
|
||||
dst_root,
|
||||
});
|
||||
}
|
||||
|
||||
auto dst_parent = utils::path::get_parent_path(dst_root);
|
||||
if (not dst_parent.empty() && not directory{dst_parent}.exists()) {
|
||||
auto parent_parent = utils::path::get_parent_path(dst_parent);
|
||||
auto last_piece = utils::path::strip_to_file_name(dst_parent);
|
||||
[[maybe_unused]] auto sub_dir =
|
||||
directory{parent_parent}.create_directory(last_piece);
|
||||
}
|
||||
|
||||
if (not directory{dst_root}.exists()) {
|
||||
auto root_parent = utils::path::get_parent_path(dst_root);
|
||||
auto root_name = utils::path::strip_to_file_name(dst_root);
|
||||
[[maybe_unused]] auto sub_dir =
|
||||
directory{root_parent}.create_directory(root_name);
|
||||
}
|
||||
|
||||
if (not copy_to(dst_root, true)) {
|
||||
throw utils::error::create_exception(function_name,
|
||||
{
|
||||
"failed to move directory",
|
||||
"copy failed",
|
||||
src_root,
|
||||
dst_root,
|
||||
});
|
||||
}
|
||||
|
||||
if (is_stop_requested()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (not remove_recursively()) {
|
||||
throw utils::error::create_exception(function_name,
|
||||
{
|
||||
"failed to move directory",
|
||||
"remove source failed",
|
||||
src_root,
|
||||
});
|
||||
}
|
||||
|
||||
path_ = dst_root;
|
||||
return true;
|
||||
} catch (const std::exception &ex) {
|
||||
utils::error::handle_exception(function_name, ex);
|
||||
} catch (...) {
|
||||
utils::error::handle_exception(function_name);
|
||||
}
|
||||
@@ -436,8 +543,10 @@ auto directory::remove_recursively() -> bool {
|
||||
|
||||
if (not traverse_directory(
|
||||
path_,
|
||||
[](auto dir_item) -> bool { return dir_item.remove_recursively(); },
|
||||
[](auto file_item) -> bool { return file_item.remove(); },
|
||||
[](auto &&dir_item) -> bool {
|
||||
return dir_item.remove_recursively();
|
||||
},
|
||||
[](auto &&file_item) -> bool { return file_item.remove(); },
|
||||
stop_requested_)) {
|
||||
return false;
|
||||
}
|
||||
@@ -460,14 +569,14 @@ auto directory::size(bool recursive) const -> std::uint64_t {
|
||||
|
||||
traverse_directory(
|
||||
path_,
|
||||
[&ret, &recursive](auto dir_item) -> bool {
|
||||
[&ret, &recursive](auto &&dir_item) -> bool {
|
||||
if (recursive) {
|
||||
ret += dir_item.size(true);
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
[&ret](auto file_item) -> bool {
|
||||
[&ret](auto &&file_item) -> bool {
|
||||
auto cur_size = file_item.size();
|
||||
if (cur_size.has_value()) {
|
||||
ret += cur_size.value();
|
||||
|
||||
@@ -43,7 +43,7 @@ auto enc_file::copy_to(std::string_view new_path, bool overwrite) const
|
||||
return file_->copy_to(new_path, overwrite);
|
||||
}
|
||||
|
||||
void enc_file::flush() const { return file_->flush(); }
|
||||
void enc_file::flush() const { file_->flush(); }
|
||||
|
||||
auto enc_file::move_to(std::string_view path) -> bool {
|
||||
return file_->move_to(path);
|
||||
@@ -68,8 +68,8 @@ auto enc_file::read(unsigned char *data, std::size_t to_read,
|
||||
std::size_t bytes_read{};
|
||||
auto ret{
|
||||
utils::encryption::read_encrypted_range(
|
||||
{offset, offset + to_read - 1U},
|
||||
utils::encryption::generate_key<utils::encryption::hash_256_t>(
|
||||
{.begin = offset, .end = offset + to_read - 1U},
|
||||
utils::encryption::generate_key<utils::hash::hash_256_t>(
|
||||
encryption_token_),
|
||||
[&](auto &&ct_buffer, auto &&start_offset,
|
||||
auto &&end_offset) -> bool {
|
||||
@@ -145,7 +145,7 @@ auto enc_file::truncate(std::size_t size) -> bool {
|
||||
return i_file::write(data, offset);
|
||||
}
|
||||
|
||||
auto begin_chunk{
|
||||
/* auto begin_chunk{
|
||||
file_size.value() /
|
||||
utils::encryption::encrypting_reader::get_data_chunk_size(),
|
||||
};
|
||||
@@ -153,13 +153,14 @@ auto enc_file::truncate(std::size_t size) -> bool {
|
||||
utils::divide_with_ceiling(
|
||||
file_size.value(),
|
||||
utils::encryption::encrypting_reader::get_data_chunk_size()),
|
||||
};
|
||||
}; */
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
auto enc_file::write(const unsigned char *data, std::size_t to_write,
|
||||
std::size_t offset, std::size_t *total_written) -> bool {
|
||||
auto enc_file::write(const unsigned char * /* data */, std::size_t to_write,
|
||||
std::size_t offset, std::size_t * /* total_written */)
|
||||
-> bool {
|
||||
auto file_size{size()};
|
||||
if (not file_size.has_value()) {
|
||||
return false;
|
||||
@@ -181,7 +182,7 @@ auto enc_file::size() const -> std::optional<std::uint64_t> {
|
||||
}
|
||||
|
||||
return utils::encryption::encrypting_reader::calculate_decrypted_size(
|
||||
file_size.value());
|
||||
file_size.value(), false);
|
||||
}
|
||||
} // namespace repertory::utils::file
|
||||
|
||||
|
||||
@@ -33,13 +33,13 @@ namespace {
|
||||
file_size = 0U;
|
||||
|
||||
#if defined(_WIN32)
|
||||
struct _stat64 st{};
|
||||
auto res = _stat64(std::string{path}.c_str(), &st);
|
||||
struct _stat64 u_stat{};
|
||||
auto res = _stat64(std::string{path}.c_str(), &u_stat);
|
||||
if (res != 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
file_size = static_cast<std::uint64_t>(st.st_size);
|
||||
file_size = static_cast<std::uint64_t>(u_stat.st_size);
|
||||
return true;
|
||||
#else // !defined(_WIN32)
|
||||
std::error_code ec{};
|
||||
@@ -55,8 +55,9 @@ namespace {
|
||||
return ((::PathFileExistsA(abs_path.c_str()) != 0) &&
|
||||
(::PathIsDirectoryA(abs_path.c_str()) == 0));
|
||||
#else // !defined(_WIN32)
|
||||
struct stat64 st{};
|
||||
return (stat64(abs_path.c_str(), &st) == 0 && not S_ISDIR(st.st_mode));
|
||||
struct stat64 u_stat{};
|
||||
return (stat64(abs_path.c_str(), &u_stat) == 0 &&
|
||||
not S_ISDIR(u_stat.st_mode));
|
||||
#endif // defined(_WIN32)
|
||||
}
|
||||
} // namespace
|
||||
@@ -265,7 +266,7 @@ auto file::move_to(std::string_view path) -> bool {
|
||||
#if defined(_WIN32)
|
||||
success = ::MoveFileExA(path_.c_str(), abs_path.c_str(),
|
||||
MOVEFILE_REPLACE_EXISTING) != 0;
|
||||
#else // !// defined(_WIN32)
|
||||
#else // !defined(_WIN32)
|
||||
std::error_code ec{};
|
||||
std::filesystem::rename(path_, abs_path, ec);
|
||||
success = ec.value() == 0;
|
||||
|
||||
@@ -25,7 +25,58 @@
|
||||
|
||||
#include "utils/error.hpp"
|
||||
|
||||
namespace repertory::utils::encryption {
|
||||
namespace repertory::utils::hash {
|
||||
auto create_hash_blake2b_32(std::string_view data) -> hash_32_t {
|
||||
return create_hash_blake2b_t<hash_32_t>(
|
||||
reinterpret_cast<const unsigned char *>(data.data()), data.size());
|
||||
}
|
||||
|
||||
auto create_hash_blake2b_32(std::wstring_view data) -> hash_32_t {
|
||||
return create_hash_blake2b_t<hash_32_t>(
|
||||
reinterpret_cast<const unsigned char *>(data.data()),
|
||||
data.size() * sizeof(wchar_t));
|
||||
}
|
||||
|
||||
auto create_hash_blake2b_32(const data_buffer &data) -> hash_32_t {
|
||||
return create_hash_blake2b_t<hash_32_t>(
|
||||
reinterpret_cast<const unsigned char *>(data.data()),
|
||||
data.size() * sizeof(data_buffer::value_type));
|
||||
}
|
||||
|
||||
auto create_hash_blake2b_64(std::string_view data) -> hash_64_t {
|
||||
return create_hash_blake2b_t<hash_64_t>(
|
||||
reinterpret_cast<const unsigned char *>(data.data()), data.size());
|
||||
}
|
||||
|
||||
auto create_hash_blake2b_64(std::wstring_view data) -> hash_64_t {
|
||||
return create_hash_blake2b_t<hash_64_t>(
|
||||
reinterpret_cast<const unsigned char *>(data.data()),
|
||||
data.size() * sizeof(wchar_t));
|
||||
}
|
||||
|
||||
auto create_hash_blake2b_64(const data_buffer &data) -> hash_64_t {
|
||||
return create_hash_blake2b_t<hash_64_t>(
|
||||
reinterpret_cast<const unsigned char *>(data.data()),
|
||||
data.size() * sizeof(data_buffer::value_type));
|
||||
}
|
||||
|
||||
auto create_hash_blake2b_128(std::string_view data) -> hash_128_t {
|
||||
return create_hash_blake2b_t<hash_128_t>(
|
||||
reinterpret_cast<const unsigned char *>(data.data()), data.size());
|
||||
}
|
||||
|
||||
auto create_hash_blake2b_128(std::wstring_view data) -> hash_128_t {
|
||||
return create_hash_blake2b_t<hash_128_t>(
|
||||
reinterpret_cast<const unsigned char *>(data.data()),
|
||||
data.size() * sizeof(wchar_t));
|
||||
}
|
||||
|
||||
auto create_hash_blake2b_128(const data_buffer &data) -> hash_128_t {
|
||||
return create_hash_blake2b_t<hash_128_t>(
|
||||
reinterpret_cast<const unsigned char *>(data.data()),
|
||||
data.size() * sizeof(data_buffer::value_type));
|
||||
}
|
||||
|
||||
auto create_hash_blake2b_256(std::string_view data) -> hash_256_t {
|
||||
return create_hash_blake2b_t<hash_256_t>(
|
||||
reinterpret_cast<const unsigned char *>(data.data()), data.size());
|
||||
@@ -111,8 +162,8 @@ auto create_hash_sha512(const data_buffer &data) -> hash_512_t {
|
||||
data.size() * sizeof(data_buffer::value_type));
|
||||
}
|
||||
|
||||
auto create_hash_sha512(const unsigned char *data,
|
||||
std::size_t data_size) -> hash_512_t {
|
||||
auto create_hash_sha512(const unsigned char *data, std::size_t data_size)
|
||||
-> hash_512_t {
|
||||
REPERTORY_USES_FUNCTION_NAME();
|
||||
|
||||
hash_512_t hash{};
|
||||
@@ -148,8 +199,8 @@ auto create_hash_sha512(const unsigned char *data,
|
||||
return hash;
|
||||
}
|
||||
|
||||
auto create_hash_sha256(const unsigned char *data,
|
||||
std::size_t data_size) -> hash_256_t {
|
||||
auto create_hash_sha256(const unsigned char *data, std::size_t data_size)
|
||||
-> hash_256_t {
|
||||
REPERTORY_USES_FUNCTION_NAME();
|
||||
|
||||
hash_256_t hash{};
|
||||
@@ -184,6 +235,6 @@ auto create_hash_sha256(const unsigned char *data,
|
||||
|
||||
return hash;
|
||||
}
|
||||
} // namespace repertory::utils::encryption
|
||||
} // namespace repertory::utils::hash
|
||||
|
||||
#endif // defined(PROJECT_ENABLE_LIBSODIUM)
|
||||
|
||||
@@ -36,10 +36,49 @@ auto from_dynamic_bitset(const boost::dynamic_bitset<> &bitset) -> std::string {
|
||||
#endif // defined(PROJECT_ENABLE_BOOST)
|
||||
|
||||
auto from_utf8(std::string_view str) -> std::wstring {
|
||||
return str.empty()
|
||||
? L""
|
||||
: std::wstring_convert<std::codecvt_utf8<wchar_t>, wchar_t>()
|
||||
.from_bytes(std::string{str});
|
||||
if (str.empty()) {
|
||||
return L"";
|
||||
}
|
||||
|
||||
std::wstring out;
|
||||
|
||||
const auto *str_ptr = reinterpret_cast<const std::uint8_t *>(str.data());
|
||||
std::int32_t idx{};
|
||||
auto len{static_cast<std::int32_t>(str.size())};
|
||||
|
||||
#if WCHAR_MAX <= 0xFFFF
|
||||
out.reserve((str.size() + 1U) / 2U);
|
||||
while (idx < len) {
|
||||
UChar32 uni_ch{};
|
||||
U8_NEXT(str_ptr, idx, len, uni_ch);
|
||||
if (uni_ch < 0 || not U_IS_UNICODE_CHAR(uni_ch)) {
|
||||
throw std::runtime_error("from_utf8: invalid UTF-8 sequence");
|
||||
}
|
||||
std::array<UChar, 2U> units{};
|
||||
std::int32_t off{};
|
||||
auto err{false};
|
||||
U16_APPEND(units.data(), off, 2, uni_ch, err);
|
||||
if (err || off <= 0) {
|
||||
throw std::runtime_error("from_utf8: U16_APPEND failed");
|
||||
}
|
||||
out.push_back(static_cast<wchar_t>(units[0U]));
|
||||
if (off == 2) {
|
||||
out.push_back(static_cast<wchar_t>(units[1U]));
|
||||
}
|
||||
}
|
||||
#else // WCHAR_MAX > 0xFFFF
|
||||
out.reserve(str.size());
|
||||
while (idx < len) {
|
||||
UChar32 uni_ch{};
|
||||
U8_NEXT(str_ptr, idx, len, uni_ch);
|
||||
if (uni_ch < 0 || not U_IS_UNICODE_CHAR(uni_ch)) {
|
||||
throw std::runtime_error("from_utf8: invalid UTF-8 sequence");
|
||||
}
|
||||
out.push_back(static_cast<wchar_t>(uni_ch));
|
||||
}
|
||||
#endif // WCHAR_MAX <= 0xFFFF
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
#if defined(PROJECT_ENABLE_SFML)
|
||||
@@ -55,8 +94,8 @@ auto replace_sf(sf::String &src, const sf::String &find, const sf::String &with,
|
||||
return src;
|
||||
}
|
||||
|
||||
auto split_sf(sf::String str, wchar_t delim,
|
||||
bool should_trim) -> std::vector<sf::String> {
|
||||
auto split_sf(sf::String str, wchar_t delim, bool should_trim)
|
||||
-> std::vector<sf::String> {
|
||||
auto result = std::views::split(str.toWideString(), delim);
|
||||
|
||||
std::vector<sf::String> ret{};
|
||||
@@ -130,9 +169,51 @@ auto to_uint64(const std::string &val) -> std::uint64_t {
|
||||
auto to_utf8(std::string_view str) -> std::string { return std::string{str}; }
|
||||
|
||||
auto to_utf8(std::wstring_view str) -> std::string {
|
||||
return str.empty()
|
||||
? ""
|
||||
: std::wstring_convert<std::codecvt_utf8<wchar_t>, wchar_t>()
|
||||
.to_bytes(std::wstring{str});
|
||||
if (str.empty()) {
|
||||
return "";
|
||||
}
|
||||
|
||||
std::string out;
|
||||
out.reserve(static_cast<size_t>(str.size()) * 4);
|
||||
|
||||
#if WCHAR_MAX <= 0xFFFF
|
||||
const auto *u16 = reinterpret_cast<const UChar *>(str.data());
|
||||
std::int32_t idx{};
|
||||
auto len{static_cast<int32_t>(str.size())};
|
||||
while (idx < len) {
|
||||
UChar32 uni_ch{};
|
||||
U16_NEXT(u16, idx, len, uni_ch);
|
||||
if (uni_ch < 0 || not U_IS_UNICODE_CHAR(uni_ch)) {
|
||||
throw std::runtime_error("to_utf8: invalid UTF-16 sequence");
|
||||
}
|
||||
std::array<std::uint8_t, U8_MAX_LENGTH> buf{};
|
||||
std::int32_t off{0};
|
||||
auto err{false};
|
||||
U8_APPEND(buf, off, U8_MAX_LENGTH, uni_ch, err);
|
||||
if (err || off <= 0) {
|
||||
throw std::runtime_error("to_utf8: U8_APPEND failed");
|
||||
}
|
||||
out.append(reinterpret_cast<const char *>(buf.data()),
|
||||
static_cast<std::size_t>(off));
|
||||
}
|
||||
#else // WCHAR_MAX > 0xFFFF
|
||||
for (const auto &cur_ch : str) {
|
||||
auto uni_char{static_cast<UChar32>(cur_ch)};
|
||||
if (not U_IS_UNICODE_CHAR(uni_char)) {
|
||||
throw std::runtime_error("to_utf8: invalid Unicode scalar value");
|
||||
}
|
||||
std::array<std::uint8_t, U8_MAX_LENGTH> buf{};
|
||||
std::int32_t off{0};
|
||||
auto err{false};
|
||||
U8_APPEND(buf, off, U8_MAX_LENGTH, uni_char, err);
|
||||
if (err || off <= 0) {
|
||||
throw std::runtime_error("to_utf8: U8_APPEND failed");
|
||||
}
|
||||
out.append(reinterpret_cast<const char *>(buf.data()),
|
||||
static_cast<std::size_t>(off));
|
||||
}
|
||||
#endif // WCHAR_MAX <= 0xFFFF
|
||||
|
||||
return out;
|
||||
}
|
||||
} // namespace repertory::utils::string
|
||||
|
||||
82
support/src/utils/timeout.cpp
Normal file
82
support/src/utils/timeout.cpp
Normal file
@@ -0,0 +1,82 @@
|
||||
/*
|
||||
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.
|
||||
*/
|
||||
#include "utils/timeout.hpp"
|
||||
|
||||
namespace repertory::utils {
|
||||
timeout::timeout(callback_t timeout_callback,
|
||||
std::chrono::system_clock::duration duration)
|
||||
: duration_(duration),
|
||||
timeout_callback_(std::move(timeout_callback)),
|
||||
timeout_killed_(duration <= std::chrono::system_clock::duration::zero()) {
|
||||
if (timeout_killed_) {
|
||||
return;
|
||||
}
|
||||
|
||||
timeout_thread_ = std::make_unique<std::thread>([this]() {
|
||||
std::unique_lock<std::mutex> loc_lock(timeout_mutex_);
|
||||
|
||||
while (not timeout_killed_) {
|
||||
auto res = timeout_notify_.wait_for(loc_lock, duration_);
|
||||
if (res != std::cv_status::timeout) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (timeout_killed_) {
|
||||
return;
|
||||
}
|
||||
|
||||
timeout_killed_ = true;
|
||||
loc_lock.unlock();
|
||||
|
||||
try {
|
||||
timeout_callback_();
|
||||
} catch (...) {
|
||||
}
|
||||
return;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
timeout::~timeout() { disable(); }
|
||||
|
||||
void timeout::disable() {
|
||||
unique_mutex_lock lock(timeout_mutex_);
|
||||
std::unique_ptr<std::thread> timeout_thread{nullptr};
|
||||
std::swap(timeout_thread, timeout_thread_);
|
||||
|
||||
if (not timeout_thread) {
|
||||
timeout_notify_.notify_all();
|
||||
return;
|
||||
}
|
||||
|
||||
timeout_killed_ = true;
|
||||
timeout_notify_.notify_all();
|
||||
lock.unlock();
|
||||
|
||||
timeout_thread->join();
|
||||
}
|
||||
|
||||
void timeout::reset() {
|
||||
mutex_lock lock(timeout_mutex_);
|
||||
timeout_notify_.notify_all();
|
||||
}
|
||||
} // namespace repertory::utils
|
||||
@@ -22,7 +22,155 @@
|
||||
#if !defined(_WIN32)
|
||||
|
||||
#include "utils/unix.hpp"
|
||||
|
||||
#include "utils/collection.hpp"
|
||||
#include "utils/config.hpp"
|
||||
#include "utils/error.hpp"
|
||||
#include "utils/file.hpp"
|
||||
#include "utils/path.hpp"
|
||||
|
||||
namespace {
|
||||
[[nodiscard]] auto get_group_list(auto *pass) -> std::vector<gid_t> {
|
||||
REPERTORY_USES_FUNCTION_NAME();
|
||||
|
||||
std::vector<gid_t> groups{};
|
||||
#if defined(__APPLE__)
|
||||
constexpr int buffer_count{8};
|
||||
constexpr int max_group_count{1024};
|
||||
groups.resize(buffer_count);
|
||||
|
||||
std::size_t orig_count{0U};
|
||||
while (true) {
|
||||
auto group_count{static_cast<int>(groups.size())};
|
||||
if (group_count > max_group_count) {
|
||||
repertory::utils::error::handle_error(function_name,
|
||||
"group list has too many groups");
|
||||
break;
|
||||
}
|
||||
|
||||
auto res{
|
||||
getgrouplist(pass->pw_name, static_cast<int>(pass->pw_gid),
|
||||
reinterpret_cast<int *>(groups.data()), &group_count),
|
||||
};
|
||||
if (res < 0) {
|
||||
if (orig_count == 0U) {
|
||||
repertory::utils::error::handle_error(
|
||||
function_name, std::string{"failed to get group list|error|"} +
|
||||
std::to_string(errno));
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
groups.resize(static_cast<std::size_t>(group_count));
|
||||
if (groups.size() == orig_count) {
|
||||
break;
|
||||
}
|
||||
|
||||
orig_count = groups.size();
|
||||
}
|
||||
#else // !defined(__APPLE__)
|
||||
int group_count{};
|
||||
auto res = getgrouplist(pass->pw_name, pass->pw_gid, nullptr, &group_count);
|
||||
if (res >= 0) {
|
||||
repertory::utils::error::handle_error(
|
||||
function_name, std::string{"failed to get group list count|error|"} +
|
||||
std::to_string(errno));
|
||||
}
|
||||
|
||||
groups.resize(static_cast<std::size_t>(group_count));
|
||||
res = getgrouplist(pass->pw_name, pass->pw_gid, groups.data(), &group_count);
|
||||
if (res >= 0) {
|
||||
repertory::utils::error::handle_error(
|
||||
function_name,
|
||||
std::string{"failed to get group list|error|"} + std::to_string(errno));
|
||||
}
|
||||
#endif // !defined(__APPLE__)
|
||||
|
||||
return groups;
|
||||
}
|
||||
|
||||
#if defined(__linux__)
|
||||
[[nodiscard]] auto sanitize_basename(std::string_view app_name) -> std::string {
|
||||
std::string out;
|
||||
out.reserve(app_name.size());
|
||||
for (const auto &cur_ch : app_name) {
|
||||
if ((cur_ch >= 'a' && cur_ch <= 'z') || (cur_ch >= '0' && cur_ch <= '9') ||
|
||||
(cur_ch == '-' || cur_ch == '_')) {
|
||||
out.push_back(cur_ch);
|
||||
} else if (cur_ch >= 'A' && cur_ch <= 'Z') {
|
||||
out.push_back(static_cast<char>(cur_ch - 'A' + 'a'));
|
||||
} else {
|
||||
out.push_back('-'); // replace spaces/symbols
|
||||
}
|
||||
}
|
||||
|
||||
std::string collapsed;
|
||||
collapsed.reserve(out.size());
|
||||
bool prev_dash = false;
|
||||
for (const auto &cur_ch : out) {
|
||||
if (cur_ch == '-') {
|
||||
if (not prev_dash) {
|
||||
collapsed.push_back(cur_ch);
|
||||
}
|
||||
prev_dash = true;
|
||||
} else {
|
||||
collapsed.push_back(cur_ch);
|
||||
prev_dash = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (collapsed.empty()) {
|
||||
collapsed = "app";
|
||||
}
|
||||
return collapsed;
|
||||
}
|
||||
|
||||
[[nodiscard]] auto get_autostart_dir() -> std::string {
|
||||
auto config = repertory::utils::get_environment_variable("XDG_CONFIG_HOME");
|
||||
if (config.empty()) {
|
||||
config = repertory::utils::path::combine(
|
||||
repertory::utils::get_environment_variable("HOME"), {".config"});
|
||||
}
|
||||
|
||||
return repertory::utils::path::combine(config, {"autostart"});
|
||||
}
|
||||
|
||||
[[nodiscard]] auto desktop_file_path_for(std::string_view app_name)
|
||||
-> std::string {
|
||||
return repertory::utils::path::combine(
|
||||
get_autostart_dir(), {
|
||||
sanitize_basename(app_name) + ".desktop",
|
||||
});
|
||||
}
|
||||
|
||||
[[nodiscard]] auto join_args_for_exec(const std::vector<std::string> &args)
|
||||
-> std::string {
|
||||
std::string str;
|
||||
for (const auto &arg : args) {
|
||||
if (not str.empty()) {
|
||||
str += ' ';
|
||||
}
|
||||
|
||||
auto needs_quotes = arg.find_first_of(" \t\"'\\$`") != std::string::npos;
|
||||
if (needs_quotes) {
|
||||
str += '"';
|
||||
for (const auto &cur_ch : arg) {
|
||||
if (cur_ch == '"' || cur_ch == '\\') {
|
||||
str += '\\';
|
||||
}
|
||||
str += cur_ch;
|
||||
}
|
||||
str += '"';
|
||||
} else {
|
||||
str += arg;
|
||||
}
|
||||
}
|
||||
|
||||
return str;
|
||||
}
|
||||
#endif // defined(__linux__)
|
||||
} // namespace
|
||||
|
||||
namespace repertory::utils {
|
||||
#if !defined(__APPLE__)
|
||||
@@ -31,6 +179,80 @@ auto convert_to_uint64(const pthread_t &thread) -> std::uint64_t {
|
||||
}
|
||||
#endif // !defined(__APPLE__)
|
||||
|
||||
#if defined(__linux__)
|
||||
auto create_autostart_entry(const autostart_cfg &cfg, bool overwrite_existing)
|
||||
-> bool {
|
||||
REPERTORY_USES_FUNCTION_NAME();
|
||||
|
||||
auto file = desktop_file_path_for(cfg.app_name);
|
||||
if (utils::file::file{file}.exists() && not overwrite_existing) {
|
||||
return true;
|
||||
}
|
||||
|
||||
auto dir = get_autostart_dir();
|
||||
if (dir.empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (not utils::file::directory{dir}.create_directory()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto exec_line = cfg.exec_path;
|
||||
if (not cfg.exec_args.empty()) {
|
||||
exec_line += ' ';
|
||||
exec_line += join_args_for_exec(cfg.exec_args);
|
||||
}
|
||||
|
||||
std::ofstream out(file, std::ios::binary | std::ios::trunc);
|
||||
if (not out) {
|
||||
return false;
|
||||
}
|
||||
|
||||
out << "[Desktop Entry]\n";
|
||||
out << "Type=Application\n";
|
||||
out << "Version=1.0\n";
|
||||
out << "Name=" << cfg.app_name << "\n";
|
||||
out << "Exec=" << exec_line << "\n";
|
||||
out << "Terminal=" << (cfg.terminal ? "true" : "false") << "\n";
|
||||
|
||||
if (cfg.comment && not cfg.comment->empty()) {
|
||||
out << "Comment=" << *cfg.comment << "\n";
|
||||
}
|
||||
|
||||
if (cfg.icon_path && not cfg.icon_path->empty()) {
|
||||
out << "Icon=" << *cfg.icon_path << "\n";
|
||||
}
|
||||
|
||||
if (not cfg.only_show_in.empty()) {
|
||||
out << "OnlyShowIn=";
|
||||
for (std::size_t idx = 0U; idx < cfg.only_show_in.size(); ++idx) {
|
||||
if (idx != 0U) {
|
||||
out << ';';
|
||||
}
|
||||
out << cfg.only_show_in[idx];
|
||||
}
|
||||
out << ";\n";
|
||||
}
|
||||
|
||||
if (not cfg.enabled) {
|
||||
out << "X-GNOME-Autostart-enabled=false\n";
|
||||
out << "Hidden=true\n";
|
||||
}
|
||||
|
||||
out.flush();
|
||||
if (not out) {
|
||||
return false;
|
||||
}
|
||||
|
||||
#if defined(__linux__) || defined(__APPLE__)
|
||||
chmod(file.c_str(), 0644);
|
||||
#endif // defined(__linux__) || defined(__APPLE__)
|
||||
|
||||
return true;
|
||||
}
|
||||
#endif // defined(__linux__)
|
||||
|
||||
auto get_last_error_code() -> int { return errno; }
|
||||
|
||||
auto get_thread_id() -> std::uint64_t {
|
||||
@@ -38,20 +260,11 @@ auto get_thread_id() -> std::uint64_t {
|
||||
}
|
||||
|
||||
auto is_uid_member_of_group(uid_t uid, gid_t gid) -> bool {
|
||||
std::vector<gid_t> groups{};
|
||||
auto res = use_getpwuid(uid, [&groups](struct passwd *pass) {
|
||||
int group_count{};
|
||||
if (getgrouplist(pass->pw_name, pass->pw_gid, nullptr, &group_count) < 0) {
|
||||
groups.resize(static_cast<std::size_t>(group_count));
|
||||
#if defined(__APPLE__)
|
||||
getgrouplist(pass->pw_name, pass->pw_gid,
|
||||
reinterpret_cast<int *>(groups.data()), &group_count);
|
||||
#else // !defined(__APPLE__)
|
||||
getgrouplist(pass->pw_name, pass->pw_gid, groups.data(), &group_count);
|
||||
#endif // defined(__APPLE__)
|
||||
}
|
||||
});
|
||||
REPERTORY_USES_FUNCTION_NAME();
|
||||
|
||||
std::vector<gid_t> groups{};
|
||||
auto res = use_getpwuid(
|
||||
uid, [&groups](struct passwd *pass) { groups = get_group_list(pass); });
|
||||
if (not res) {
|
||||
throw utils::error::create_exception(res.function_name,
|
||||
{"use_getpwuid failed", res.reason});
|
||||
@@ -60,6 +273,19 @@ auto is_uid_member_of_group(uid_t uid, gid_t gid) -> bool {
|
||||
return collection::includes(groups, gid);
|
||||
}
|
||||
|
||||
#if defined(__linux__)
|
||||
auto remove_autostart_entry(std::string_view name) -> bool {
|
||||
REPERTORY_USES_FUNCTION_NAME();
|
||||
|
||||
auto file = desktop_file_path_for(name);
|
||||
if (not utils::file::file{file}.exists()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return utils::file::file{file}.remove();
|
||||
}
|
||||
#endif // defined(__linux__)
|
||||
|
||||
auto use_getpwuid(uid_t uid, passwd_callback_t callback) -> result {
|
||||
REPERTORY_USES_FUNCTION_NAME();
|
||||
|
||||
@@ -69,15 +295,130 @@ auto use_getpwuid(uid_t uid, passwd_callback_t callback) -> result {
|
||||
auto *temp_pw = getpwuid(uid);
|
||||
if (temp_pw == nullptr) {
|
||||
return {
|
||||
std::string{function_name},
|
||||
false,
|
||||
"'getpwuid' returned nullptr",
|
||||
.function_name = std::string{function_name},
|
||||
.ok = false,
|
||||
.reason = "'getpwuid' returned nullptr",
|
||||
};
|
||||
}
|
||||
|
||||
callback(temp_pw);
|
||||
return {std::string{function_name}};
|
||||
return {
|
||||
.function_name = std::string{function_name},
|
||||
};
|
||||
}
|
||||
|
||||
#if defined(__APPLE__)
|
||||
#if defined(PROJECT_ENABLE_PUGIXML)
|
||||
auto generate_launchd_plist(const plist_cfg &cfg, bool overwrite_existing)
|
||||
-> bool {
|
||||
auto file = utils::path::combine(cfg.plist_path, {cfg.label + ".plist"});
|
||||
if (utils::file::file{file}.exists() && not overwrite_existing) {
|
||||
return true;
|
||||
}
|
||||
|
||||
pugi::xml_document doc;
|
||||
auto decl = doc.append_child(pugi::node_declaration);
|
||||
decl.append_attribute("version") = "1.0";
|
||||
decl.append_attribute("encoding") = "UTF-8";
|
||||
|
||||
auto plist = doc.append_child("plist");
|
||||
plist.append_attribute("version") = "1.0";
|
||||
|
||||
auto dict = plist.append_child("dict");
|
||||
|
||||
dict.append_child("key").text().set("Label");
|
||||
dict.append_child("string").text().set(cfg.label.c_str());
|
||||
|
||||
dict.append_child("key").text().set("ProgramArguments");
|
||||
auto array = dict.append_child("array");
|
||||
for (const auto &arg : cfg.args) {
|
||||
array.append_child("string").text().set(arg.c_str());
|
||||
}
|
||||
|
||||
dict.append_child("key").text().set("EnvironmentVariables");
|
||||
pugi::xml_node env_dict = dict.append_child("dict");
|
||||
if (not utils::get_environment_variable("PROJECT_TEST_CONFIG_DIR").empty()) {
|
||||
env_dict.append_child("key").text().set("PROJECT_TEST_CONFIG_DIR");
|
||||
env_dict.append_child("string").text().set(
|
||||
utils::get_environment_variable("PROJECT_TEST_CONFIG_DIR"));
|
||||
}
|
||||
if (not utils::get_environment_variable("PROJECT_TEST_INPUT_DIR").empty()) {
|
||||
env_dict.append_child("key").text().set("PROJECT_TEST_INPUT_DIR");
|
||||
env_dict.append_child("string").text().set(
|
||||
utils::get_environment_variable("PROJECT_TEST_INPUT_DIR"));
|
||||
}
|
||||
|
||||
dict.append_child("key").text().set("WorkingDirectory");
|
||||
dict.append_child("string").text().set(cfg.working_dir.c_str());
|
||||
|
||||
dict.append_child("key").text().set("KeepAlive");
|
||||
dict.append_child(cfg.keep_alive ? "true" : "false");
|
||||
|
||||
dict.append_child("key").text().set("RunAtLoad");
|
||||
dict.append_child(cfg.run_at_load ? "true" : "false");
|
||||
|
||||
dict.append_child("key").text().set("StandardOutPath");
|
||||
dict.append_child("string").text().set(cfg.stdout_log.c_str());
|
||||
|
||||
dict.append_child("key").text().set("StandardErrorPath");
|
||||
dict.append_child("string").text().set(cfg.stderr_log.c_str());
|
||||
|
||||
return doc.save_file(file.c_str(), " ",
|
||||
pugi::format_indent | pugi::format_write_bom);
|
||||
}
|
||||
#endif // defined(PROJECT_ENABLE_PUGIXML)
|
||||
|
||||
#if defined(PROJECT_ENABLE_SPDLOG) || defined(PROJECT_ENABLE_FMT)
|
||||
auto launchctl_command(std::string_view label, launchctl_type type) -> int {
|
||||
switch (type) {
|
||||
case launchctl_type::bootout:
|
||||
return system(
|
||||
fmt::format("launchctl bootout gui/{} '{}' 1>/dev/null 2>&1", getuid(),
|
||||
utils::path::combine("~",
|
||||
{
|
||||
"/Library/LaunchAgents",
|
||||
fmt::format("{}.plist", label),
|
||||
}))
|
||||
.c_str());
|
||||
|
||||
case launchctl_type::bootstrap:
|
||||
return system(
|
||||
fmt::format("launchctl bootstrap gui/{} '{}' 1>/dev/null 2>&1",
|
||||
getuid(),
|
||||
utils::path::combine("~",
|
||||
{
|
||||
"/Library/LaunchAgents",
|
||||
fmt::format("{}.plist", label),
|
||||
}))
|
||||
.c_str());
|
||||
|
||||
case launchctl_type::kickstart:
|
||||
return system(
|
||||
fmt::format("launchctl kickstart gui/{}/{}", getuid(), label).c_str());
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
auto remove_launchd_plist(std::string_view plist_path, std::string_view label,
|
||||
bool should_bootout) -> bool {
|
||||
auto file = utils::file::file{
|
||||
utils::path::combine(plist_path, {std::string{label} + ".plist"}),
|
||||
};
|
||||
if (not file.exists()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
auto res =
|
||||
should_bootout ? launchctl_command(label, launchctl_type::bootout) : 0;
|
||||
if (not file.remove()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return res == 0;
|
||||
}
|
||||
#endif // defined(PROJECT_ENABLE_SPDLOG) || defined(PROJECT_ENABLE_FMT)
|
||||
#endif // defined(__APPLE__)
|
||||
} // namespace repertory::utils
|
||||
|
||||
#endif // !defined(_WIN32)
|
||||
|
||||
@@ -25,8 +25,18 @@
|
||||
|
||||
#include "utils/com_init_wrapper.hpp"
|
||||
#include "utils/error.hpp"
|
||||
#include "utils/file.hpp"
|
||||
#include "utils/path.hpp"
|
||||
#include "utils/string.hpp"
|
||||
|
||||
namespace {
|
||||
constexpr std::array<std::string_view, 26U> drive_letters{
|
||||
"a:", "b:", "c:", "d:", "e:", "f:", "g:", "h:", "i:",
|
||||
"j:", "k:", "l:", "m:", "n:", "o:", "p:", "q:", "r:",
|
||||
"s:", "t:", "u:", "v:", "w:", "x:", "y:", "z:",
|
||||
};
|
||||
}
|
||||
|
||||
namespace repertory::utils {
|
||||
void create_console() {
|
||||
if (::AllocConsole() == 0) {
|
||||
@@ -59,13 +69,55 @@ void create_console() {
|
||||
|
||||
void free_console() { ::FreeConsole(); }
|
||||
|
||||
auto get_available_drive_letter(char first) -> std::optional<std::string_view> {
|
||||
const auto *begin = std::ranges::find_if(
|
||||
drive_letters, [first](auto &&val) { return val.at(0U) == first; });
|
||||
if (begin == drive_letters.end()) {
|
||||
begin = drive_letters.begin();
|
||||
}
|
||||
|
||||
auto available =
|
||||
std::ranges::find_if(begin, drive_letters.end(), [](auto &&val) -> bool {
|
||||
return not utils::file::directory{utils::path::combine(val, {"\\"})}
|
||||
.exists();
|
||||
});
|
||||
if (available == drive_letters.end()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
return *available;
|
||||
}
|
||||
|
||||
auto get_available_drive_letters(char first) -> std::vector<std::string_view> {
|
||||
const auto *begin =
|
||||
std::ranges::find_if(drive_letters, [first](auto &&val) -> bool {
|
||||
return val.at(0U) == first;
|
||||
});
|
||||
if (begin == drive_letters.end()) {
|
||||
begin = drive_letters.begin();
|
||||
}
|
||||
|
||||
return std::accumulate(
|
||||
begin, drive_letters.end(), std::vector<std::string_view>(),
|
||||
[](auto &&vec, auto &&letter) -> auto {
|
||||
if (utils::file::directory{utils::path::combine(letter, {"\\"})}
|
||||
.exists()) {
|
||||
return vec;
|
||||
}
|
||||
|
||||
vec.emplace_back(letter);
|
||||
return vec;
|
||||
});
|
||||
}
|
||||
|
||||
auto get_last_error_code() -> DWORD { return ::GetLastError(); }
|
||||
|
||||
auto get_local_app_data_directory() -> const std::string & {
|
||||
REPERTORY_USES_FUNCTION_NAME();
|
||||
|
||||
[[maybe_unused]] thread_local const utils::com_init_wrapper wrapper;
|
||||
|
||||
static std::string app_data = ([]() -> std::string {
|
||||
com_init_wrapper cw;
|
||||
PWSTR local_app_data{};
|
||||
if (SUCCEEDED(::SHGetKnownFolderPath(FOLDERID_LocalAppData, 0, nullptr,
|
||||
&local_app_data))) {
|
||||
@@ -91,10 +143,11 @@ auto is_process_elevated() -> bool {
|
||||
auto ret{false};
|
||||
HANDLE token{INVALID_HANDLE_VALUE};
|
||||
if (::OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &token)) {
|
||||
TOKEN_ELEVATION te{};
|
||||
DWORD sz = sizeof(te);
|
||||
if (::GetTokenInformation(token, TokenElevation, &te, sizeof(te), &sz)) {
|
||||
ret = (te.TokenIsElevated != 0);
|
||||
TOKEN_ELEVATION token_elevation{};
|
||||
DWORD size = sizeof(token_elevation);
|
||||
if (::GetTokenInformation(token, TokenElevation, &token_elevation,
|
||||
sizeof(token_elevation), &size)) {
|
||||
ret = (token_elevation.TokenIsElevated != 0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -139,6 +192,170 @@ auto run_process_elevated(std::vector<const char *> args) -> int {
|
||||
}
|
||||
|
||||
void set_last_error_code(DWORD error_code) { ::SetLastError(error_code); }
|
||||
|
||||
auto get_startup_folder() -> std::wstring {
|
||||
[[maybe_unused]] thread_local const utils::com_init_wrapper wrapper;
|
||||
|
||||
PWSTR raw{nullptr};
|
||||
auto result = ::SHGetKnownFolderPath(FOLDERID_Startup, 0, nullptr, &raw);
|
||||
if (FAILED(result)) {
|
||||
if (raw != nullptr) {
|
||||
::CoTaskMemFree(raw);
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
std::wstring str{raw};
|
||||
::CoTaskMemFree(raw);
|
||||
return str;
|
||||
}
|
||||
|
||||
auto create_shortcut(const shortcut_cfg &cfg, bool overwrite_existing) -> bool {
|
||||
REPERTORY_USES_FUNCTION_NAME();
|
||||
|
||||
[[maybe_unused]] thread_local const utils::com_init_wrapper wrapper;
|
||||
|
||||
const auto hr_hex = [](HRESULT result) -> std::string {
|
||||
std::ostringstream oss;
|
||||
oss << "0x" << std::uppercase << std::hex << std::setw(8)
|
||||
<< std::setfill('0') << static_cast<std::uint32_t>(result);
|
||||
return oss.str();
|
||||
};
|
||||
|
||||
if (cfg.location.empty()) {
|
||||
utils::error::handle_error(function_name, "shortcut location was empty");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (not utils::file::directory{cfg.location}.create_directory()) {
|
||||
utils::error::handle_error(function_name,
|
||||
"failed to create shortcut directory|path|" +
|
||||
utils::string::to_utf8(cfg.location));
|
||||
return false;
|
||||
}
|
||||
|
||||
auto final_name = cfg.shortcut_name.empty()
|
||||
? utils::path::strip_to_file_name(cfg.exe_path)
|
||||
: cfg.shortcut_name;
|
||||
if (not final_name.ends_with(L".lnk")) {
|
||||
final_name += L".lnk";
|
||||
}
|
||||
|
||||
auto lnk_path = utils::path::combine(cfg.location, {final_name});
|
||||
if (utils::file::file{lnk_path}.exists() && not overwrite_existing) {
|
||||
return true;
|
||||
}
|
||||
|
||||
IShellLinkW *psl{nullptr};
|
||||
auto result = ::CoCreateInstance(CLSID_ShellLink, nullptr,
|
||||
CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&psl));
|
||||
if (FAILED(result)) {
|
||||
utils::error::handle_error(
|
||||
function_name,
|
||||
std::string("CoCreateInstance(CLSID_ShellLink) failed: ") +
|
||||
hr_hex(result));
|
||||
return false;
|
||||
}
|
||||
|
||||
result = psl->SetPath(cfg.exe_path.c_str());
|
||||
if (FAILED(result)) {
|
||||
utils::error::handle_error(function_name,
|
||||
std::string("IShellLink::SetPath failed: ") +
|
||||
hr_hex(result));
|
||||
psl->Release();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (not cfg.arguments.empty()) {
|
||||
result = psl->SetArguments(cfg.arguments.c_str());
|
||||
if (FAILED(result)) {
|
||||
utils::error::handle_error(
|
||||
function_name,
|
||||
std::string("IShellLink::SetArguments failed: ") + hr_hex(result));
|
||||
psl->Release();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (not cfg.working_directory.empty()) {
|
||||
result = psl->SetWorkingDirectory(cfg.working_directory.c_str());
|
||||
if (FAILED(result)) {
|
||||
utils::error::handle_error(
|
||||
function_name,
|
||||
std::string("IShellLink::SetWorkingDirectory failed: ") +
|
||||
hr_hex(result));
|
||||
psl->Release();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
result = psl->SetShowCmd(SW_SHOWNORMAL);
|
||||
if (FAILED(result)) {
|
||||
utils::error::handle_error(function_name,
|
||||
std::string("IShellLink::SetShowCmd failed: ") +
|
||||
hr_hex(result));
|
||||
psl->Release();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (not cfg.icon_path.empty()) {
|
||||
result = psl->SetIconLocation(cfg.icon_path.c_str(), 0);
|
||||
if (FAILED(result)) {
|
||||
utils::error::handle_error(
|
||||
function_name,
|
||||
std::string("IShellLink::SetIconLocation failed: ") + hr_hex(result));
|
||||
psl->Release();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (not utils::file::file{lnk_path}.remove()) {
|
||||
utils::error::handle_error(function_name,
|
||||
"failed to remove existing shortcut|path|" +
|
||||
utils::string::to_utf8(lnk_path));
|
||||
return false;
|
||||
}
|
||||
|
||||
IPersistFile *ppf{nullptr};
|
||||
result = psl->QueryInterface(IID_PPV_ARGS(&ppf));
|
||||
if (FAILED(result)) {
|
||||
utils::error::handle_error(
|
||||
function_name,
|
||||
std::string("QueryInterface(IPersistFile) failed: ") + hr_hex(result));
|
||||
psl->Release();
|
||||
return false;
|
||||
}
|
||||
|
||||
result = ppf->Save(lnk_path.c_str(), TRUE);
|
||||
ppf->SaveCompleted(lnk_path.c_str());
|
||||
|
||||
ppf->Release();
|
||||
psl->Release();
|
||||
|
||||
if (FAILED(result)) {
|
||||
utils::error::handle_error(function_name,
|
||||
std::string("IPersistFile::Save failed: ") +
|
||||
hr_hex(result));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
auto remove_shortcut(std::wstring shortcut_name, const std::wstring &location)
|
||||
-> bool {
|
||||
if (not shortcut_name.ends_with(L".lnk")) {
|
||||
shortcut_name += L".lnk";
|
||||
}
|
||||
|
||||
auto file = utils::path::combine(location, {shortcut_name});
|
||||
if (not utils::file::file{file}.exists()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return utils::file::file{file}.remove();
|
||||
}
|
||||
} // namespace repertory::utils
|
||||
|
||||
#endif // defined(_WIN32)
|
||||
|
||||
Reference in New Issue
Block a user