new_build_system (#18)
All checks were successful
BlockStorage/repertory/pipeline/head This commit looks good

Reviewed-on: #18
This commit is contained in:
2024-09-06 15:05:48 +00:00
parent 9d3e4b8767
commit a7239558bd
191 changed files with 10683 additions and 10598 deletions

View File

@ -21,7 +21,7 @@
*/
#include "utils/common.hpp"
#include "utils/path.hpp"
#include "utils/error.hpp"
#include "utils/string.hpp"
namespace repertory::utils {
@ -67,7 +67,7 @@ auto compare_version_strings(std::wstring_view version1,
#if defined(PROJECT_ENABLE_STDUUID)
auto create_uuid_string() -> std::string {
std::random_device random_device;
std::random_device random_device{};
auto seed_data = std::array<int, std::mt19937::state_size>{};
std::generate(std::begin(seed_data), std::end(seed_data),
std::ref(random_device));
@ -83,7 +83,6 @@ auto create_uuid_wstring() -> std::wstring {
}
#endif // defined(PROJECT_ENABLE_STDUUID)
#if defined(PROJECT_ENABLE_LIBSODIUM)
auto generate_random_string(std::size_t length) -> std::string {
std::string ret;
if (length == 0U) {
@ -98,9 +97,7 @@ auto generate_random_string(std::size_t length) -> std::string {
generate_random_between(97U, 255U),
};
ch = static_cast<char>(
random_list.at(repertory::utils::generate_random_between(0U, 2U)) %
74U +
48U);
random_list.at(generate_random_between(0U, 2U)) % 74U + 48U);
}
return ret;
@ -109,7 +106,6 @@ auto generate_random_string(std::size_t length) -> std::string {
auto generate_random_wstring(std::size_t length) -> std::wstring {
return utils::string::from_utf8(generate_random_string(length));
}
#endif // defined(PROJECT_ENABLE_LIBSODIUM)
auto get_environment_variable(std::string_view variable) -> std::string {
static std::mutex mtx{};
@ -154,4 +150,27 @@ auto get_next_available_port(std::uint16_t first_port,
return not error_code;
}
#endif // defined(PROJECT_ENABLE_BOOST)
auto retry_action(retryable_action_t action, std::size_t retry_count,
std::chrono::milliseconds retry_wait) -> bool {
static constexpr const std::string_view function_name{
static_cast<const char *>(__FUNCTION__),
};
try {
for (std::size_t idx = 0U; idx < retry_count; ++idx) {
if (action()) {
return true;
}
std::this_thread::sleep_for(retry_wait);
}
} catch (const std::exception &e) {
utils::error::handle_exception(function_name, e);
} catch (...) {
utils::error::handle_exception(function_name);
}
return false;
}
} // namespace repertory::utils

View File

@ -0,0 +1,415 @@
/*
Copyright <2018-2024> <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/encrypting_reader.hpp"
#include "utils/collection.hpp"
#include "utils/common.hpp"
#include "utils/encryption.hpp"
#include "utils/error.hpp"
#include "utils/file.hpp"
#include "utils/unix.hpp"
#include "utils/windows.hpp"
#if defined(PROJECT_ENABLE_LIBSODIUM) && defined(PROJECT_ENABLE_BOOST)
#if !defined(CURL_READFUNC_ABORT)
#define CURL_READFUNC_ABORT (-1)
#endif // !defined(CURL_READFUNC_ABORT)
namespace repertory::utils::encryption {
class encrypting_streambuf final : public encrypting_reader::streambuf {
public:
encrypting_streambuf(const encrypting_streambuf &) = default;
encrypting_streambuf(encrypting_streambuf &&) = delete;
auto
operator=(const encrypting_streambuf &) -> encrypting_streambuf & = delete;
auto operator=(encrypting_streambuf &&) -> encrypting_streambuf & = delete;
explicit encrypting_streambuf(const encrypting_reader &reader)
: reader_(reader) {
setg(reinterpret_cast<char *>(0), reinterpret_cast<char *>(0),
reinterpret_cast<char *>(reader_.get_total_size()));
}
~encrypting_streambuf() override = default;
private:
encrypting_reader reader_;
protected:
auto seekoff(off_type off, std::ios_base::seekdir dir,
std::ios_base::openmode which = std::ios_base::out |
std::ios_base::in)
-> pos_type override {
if ((which & std::ios_base::in) != std::ios_base::in) {
throw std::runtime_error("output is not supported");
}
const auto set_position = [this](char *next) -> pos_type {
if ((next <= egptr()) && (next >= eback())) {
setg(eback(), next, reinterpret_cast<char *>(reader_.get_total_size()));
return static_cast<std::streamoff>(
reinterpret_cast<std::uintptr_t>(gptr()));
}
return {traits_type::eof()};
};
switch (dir) {
case std::ios_base::beg:
return set_position(eback() + off);
case std::ios_base::cur:
return set_position(gptr() + off);
case std::ios_base::end:
return set_position(egptr() + off);
}
return encrypting_reader::streambuf::seekoff(off, dir, which);
}
auto seekpos(pos_type pos, std::ios_base::openmode which =
std::ios_base::out |
std::ios_base::in) -> pos_type override {
return seekoff(pos, std::ios_base::beg, which);
}
auto uflow() -> int_type override {
auto ret = underflow();
if (ret == traits_type::eof()) {
return ret;
}
gbump(1);
return ret;
}
auto underflow() -> int_type override {
if (gptr() == egptr()) {
return traits_type::eof();
}
reader_.set_read_position(reinterpret_cast<std::uintptr_t>(gptr()));
char c{};
const auto res = encrypting_reader::reader_function(&c, 1U, 1U, &reader_);
if (res != 1) {
return traits_type::eof();
}
return c;
}
auto xsgetn(char *ptr, std::streamsize count) -> std::streamsize override {
if (gptr() == egptr()) {
return traits_type::eof();
}
reader_.set_read_position(reinterpret_cast<std::uintptr_t>(gptr()));
auto res = encrypting_reader::reader_function(
ptr, 1U, static_cast<std::size_t>(count), &reader_);
if ((res == reader_.get_error_return()) ||
(reader_.get_stop_requested() &&
(res == static_cast<std::size_t>(CURL_READFUNC_ABORT)))) {
return traits_type::eof();
}
setg(eback(), gptr() + res,
reinterpret_cast<char *>(reader_.get_total_size()));
return static_cast<std::streamsize>(res);
}
};
class encrypting_reader_iostream final : public encrypting_reader::iostream {
public:
encrypting_reader_iostream(const encrypting_reader_iostream &) = delete;
encrypting_reader_iostream(encrypting_reader_iostream &&) = delete;
auto operator=(const encrypting_reader_iostream &)
-> encrypting_reader_iostream & = delete;
auto operator=(encrypting_reader_iostream &&)
-> encrypting_reader_iostream & = delete;
explicit encrypting_reader_iostream(
std::unique_ptr<encrypting_streambuf> buffer)
: encrypting_reader::iostream(buffer.get()), buffer_(std::move(buffer)) {}
~encrypting_reader_iostream() override = default;
private:
std::unique_ptr<encrypting_streambuf> buffer_;
};
const std::size_t encrypting_reader::header_size_ = ([]() {
return crypto_aead_xchacha20poly1305_IETF_NPUBBYTES +
crypto_aead_xchacha20poly1305_IETF_ABYTES;
})();
const std::size_t encrypting_reader::data_chunk_size_ = (8UL * 1024UL * 1024UL);
const std::size_t encrypting_reader::encrypted_chunk_size_ =
data_chunk_size_ + header_size_;
encrypting_reader::encrypting_reader(
std::string_view file_name, std::string_view source_path,
stop_type &stop_requested, 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)),
stop_requested_(stop_requested),
error_return_(error_return),
source_file_(utils::file::file::open_or_create_file(source_path, true)) {
if (not *source_file_) {
throw std::runtime_error("file open failed|src|" +
std::string{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 std::runtime_error("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());
}
}
encrypting_reader::encrypting_reader(std::string_view encrypted_file_path,
std::string_view source_path,
stop_type &stop_requested,
std::string_view token,
std::size_t error_return)
: key_(utils::encryption::generate_key<utils::encryption::hash_256_t>(
token)),
stop_requested_(stop_requested),
error_return_(error_return),
source_file_(utils::file::file::open_or_create_file(source_path, true)) {
if (not *source_file_) {
throw std::runtime_error("file open failed|src|" +
std::string{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 std::runtime_error("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());
}
}
encrypting_reader::encrypting_reader(
std::string_view encrypted_file_path, std::string_view source_path,
stop_type &stop_requested, 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)),
stop_requested_(stop_requested),
error_return_(error_return),
source_file_(utils::file::file::open_or_create_file(source_path, true)) {
if (not *source_file_) {
throw std::runtime_error("file open failed|src|" +
std::string{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 std::runtime_error("get file size failed|src|" +
source_file_->get_path() + '|' +
std::to_string(utils::get_last_error_code()));
}
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_ = std::move(iv_list);
}
encrypting_reader::encrypting_reader(const encrypting_reader &reader)
: key_(reader.key_),
stop_requested_(reader.stop_requested_),
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_),
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_) {
if (not *source_file_) {
throw std::runtime_error("file open failed|src|" +
source_file_->get_path());
}
}
auto encrypting_reader::calculate_decrypted_size(std::uint64_t total_size)
-> std::uint64_t {
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)
-> std::uint64_t {
auto opt_size = utils::file::file{source_path}.size();
if (not opt_size.has_value()) {
throw std::runtime_error("get file size failed|src|" +
std::string{source_path} + '|' +
std::to_string(utils::get_last_error_code()));
}
auto file_size{opt_size.value()};
const 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);
}
auto encrypting_reader::create_iostream() const
-> std::shared_ptr<encrypting_reader::iostream> {
return std::make_shared<encrypting_reader_iostream>(
std::make_unique<encrypting_streambuf>(*this));
}
auto encrypting_reader::reader_function(char *buffer, size_t size,
size_t nitems) -> size_t {
static constexpr const std::string_view function_name{
static_cast<const char *>(__FUNCTION__),
};
const auto read_size = static_cast<std::size_t>(std::min(
static_cast<std::uint64_t>(size * nitems), total_size_ - read_offset_));
auto chunk = read_offset_ / encrypted_chunk_size_;
auto chunk_offset = read_offset_ % encrypted_chunk_size_;
std::size_t total_read{};
auto ret = false;
if (read_offset_ < total_size_) {
try {
ret = true;
auto remain = read_size;
while (not stop_requested_ && ret && (remain != 0U)) {
if (chunk_buffers_.find(chunk) == chunk_buffers_.end()) {
auto &chunk_buffer = chunk_buffers_[chunk];
data_buffer file_data(chunk == last_data_chunk_
? last_data_chunk_size_
: data_chunk_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);
}
} else if (chunk) {
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);
total_read += to_read;
remain -= to_read;
chunk_offset = 0u;
chunk++;
read_offset_ += to_read;
}
} catch (const std::exception &e) {
utils::error::handle_exception(function_name, e);
ret = false;
} catch (...) {
utils::error::handle_exception(function_name);
ret = false;
}
}
return stop_requested_ ? static_cast<std::size_t>(CURL_READFUNC_ABORT)
: ret ? total_read
: error_return_;
}
} // namespace repertory::utils::encryption
#endif // defined(PROJECT_ENABLE_LIBSODIUM) && defined(PROJECT_ENABLE_BOOST)

View File

@ -21,34 +21,97 @@
*/
#include "utils/encryption.hpp"
#if defined(PROJECT_ENABLE_LIBSODIUM)
namespace repertory::utils::encryption {
#if defined(PROJECT_ENABLE_BOOST)
auto decrypt_data(std::string_view password,
std::string_view data) -> data_buffer {
auto key = generate_key<hash_256_t>(password);
#include "utils/collection.hpp"
#include "utils/encrypting_reader.hpp"
data_buffer buf{};
if (not decrypt_data(key,
reinterpret_cast<const unsigned char *>(data.data()),
data.size(), buf)) {
throw std::runtime_error("decryption failed");
#if defined(PROJECT_ENABLE_LIBSODIUM) && defined(PROJECT_ENABLE_BOOST)
namespace repertory::utils::encryption {
auto decrypt_file_path(std::string_view encryption_token,
std::string &file_path) -> bool {
std::string decrypted_file_path{};
for (const auto &part : std::filesystem::path(file_path)) {
auto file_name = part.string();
if (file_name == "/") {
continue;
}
auto res = decrypt_file_name(encryption_token, file_name);
if (not res) {
return res;
}
decrypted_file_path += '/' + file_name;
}
return buf;
file_path = decrypted_file_path;
return true;
}
auto encrypt_data(std::string_view password,
std::string_view data) -> data_buffer {
auto key = generate_key<hash_256_t>(password);
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;
}
data_buffer buf{};
encrypt_data(key, reinterpret_cast<const unsigned char *>(data.data()),
data.size(), buf);
file_name.clear();
if (not utils::encryption::decrypt_data(encryption_token, buffer,
file_name)) {
return false;
}
return buf;
return true;
}
#endif // defined(PROJECT_ENABLE_BOOST)
#if defined(PROJECT_ENABLE_CURL)
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 {
const auto encrypted_chunk_size =
utils::encryption::encrypting_reader::get_encrypted_chunk_size();
const auto data_chunk_size =
utils::encryption::encrypting_reader::get_data_chunk_size();
const auto start_chunk =
static_cast<std::size_t>(range.begin / data_chunk_size);
const 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);
for (std::size_t chunk = start_chunk; chunk <= end_chunk; chunk++) {
data_buffer cypher;
const auto start_offset = chunk * encrypted_chunk_size;
const 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();
const 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::back_inserter(data));
remain -= data_size;
source_offset = 0U;
}
return true;
}
#endif // defined(PROJECT_ENABLE_CURL)
} // namespace repertory::utils::encryption
#endif // defined(PROJECT_ENABLE_LIBSODIUM)
#endif // defined(PROJECT_ENABLE_LIBSODIUM) && defined (PROJECT_ENABLE_BOOST)

View File

@ -21,51 +21,32 @@
*/
#include "utils/error.hpp"
namespace {
struct default_exception_handler final
: repertory::utils::error::i_exception_handler {
void handle_exception(std::string_view function_name) const override {
std::cerr << function_name << "|exception|unknown" << std::endl;
}
void handle_exception(std::string_view function_name,
const std::exception &ex) const override {
std::cerr << function_name << "|exception|"
<< (ex.what() == nullptr ? "unknown" : ex.what()) << std::endl;
}
};
static default_exception_handler default_handler{};
static std::atomic<repertory::utils::error::i_exception_handler *>
exception_handler{
&default_handler,
};
} // namespace
namespace repertory::utils::error {
std::atomic<const i_exception_handler *> exception_handler{
&default_exception_handler};
void handle_exception(std::string_view function_name) {
i_exception_handler *handler{exception_handler};
const i_exception_handler *handler{exception_handler};
if (handler != nullptr) {
handler->handle_exception(function_name);
return;
}
default_handler.handle_exception(function_name);
default_exception_handler.handle_exception(function_name);
}
void handle_exception(std::string_view function_name,
const std::exception &ex) {
i_exception_handler *handler{exception_handler};
const i_exception_handler *handler{exception_handler};
if (handler != nullptr) {
handler->handle_exception(function_name, ex);
return;
}
default_handler.handle_exception(function_name, ex);
default_exception_handler.handle_exception(function_name, ex);
}
void set_exception_handler(i_exception_handler *handler) {
void set_exception_handler(const i_exception_handler *handler) {
exception_handler = handler;
}
} // namespace repertory::utils::error

View File

@ -21,227 +21,309 @@
*/
#include "utils/file.hpp"
#include "utils/common.hpp"
#include "utils/encryption.hpp"
#include "utils/error.hpp"
#include "utils/path.hpp"
#include "utils/string.hpp"
#include "utils/time.hpp"
#include "utils/unix.hpp"
#include "utils/windows.hpp"
namespace repertory::utils::file {
auto file::open_file(std::filesystem::path path) -> file {
path = utils::path::absolute(path.string());
if (not is_file(path.string())) {
throw std::runtime_error("file not found: " + path.string());
}
auto stream = std::fstream{
path,
std::ios_base::binary | std::ios_base::in | std::ios_base::out,
};
return {
std::move(stream),
path,
};
}
auto file::open_or_create_file(std::filesystem::path path) -> file {
path = utils::path::absolute(path.string());
auto stream = std::fstream{
path.string().c_str(),
std::ios_base::binary | std::ios_base::trunc | std::ios_base::in |
std::ios_base::out,
};
return {
std::move(stream),
path,
};
}
void file::close() {
if (stream_.is_open()) {
stream_.close();
}
}
auto file::move_to(std::filesystem::path new_path) -> bool {
new_path = utils::path::absolute(new_path.string());
auto reopen{false};
if (stream_.is_open()) {
reopen = true;
close();
}
std::filesystem::rename(path_, new_path, error_);
if (not error_) {
path_ = new_path;
if (reopen) {
*this = open_file(path_);
}
return true;
}
if (reopen) {
*this = open_file(path_);
}
return false;
}
auto file::read_(unsigned char *data, std::size_t to_read, std::uint64_t offset,
std::size_t *total_read) -> bool {
auto change_to_process_directory() -> bool {
static constexpr const std::string_view function_name{
static_cast<const char *>(__FUNCTION__),
};
if (total_read != nullptr) {
(*total_read) = 0U;
try {
#if defined(_WIN32)
std::string file_name;
file_name.resize(MAX_PATH + 1U);
::GetModuleFileNameA(nullptr, file_name.data(),
static_cast<DWORD>(file_name.size() - 1U));
auto path = utils::path::strip_to_file_name(file_name.c_str());
::SetCurrentDirectoryA(path.c_str());
#else // !defined(_WIN32)
std::string path;
path.resize(PATH_MAX + 1);
#if defined(__APPLE__)
proc_pidpath(getpid(), path.c_str(), path.size());
#else // !defined(__APPLE__)
auto res = readlink("/proc/self/exe", path.data(), path.size());
if (res == -1) {
throw std::runtime_error("failed to readlink|" + path + '|' +
std::to_string(errno));
}
#endif // defined(__APPLE__)
path = utils::path::get_parent_path(path);
res = chdir(path.c_str());
if (res != 0) {
throw std::runtime_error("failed to chdir|" + path + '|' +
std::to_string(errno));
}
#endif // defined(_WIN32)
return true;
} catch (const std::exception &e) {
utils::error::handle_exception(function_name, e);
} catch (...) {
utils::error::handle_exception(function_name);
}
try {
stream_.seekg(static_cast<std::streamoff>(offset));
return false;
}
auto before = stream_.tellg();
if (before == -1) {
throw std::runtime_error("failed to tellg() before read");
auto create_temp_name(std::string_view file_part) -> std::string {
std::array<std::uint8_t, 8U> data{
utils::generate_random_between<std::uint8_t>(0U, 9U),
utils::generate_random_between<std::uint8_t>(0U, 9U),
utils::generate_random_between<std::uint8_t>(0U, 9U),
utils::generate_random_between<std::uint8_t>(0U, 9U),
utils::generate_random_between<std::uint8_t>(0U, 9U),
utils::generate_random_between<std::uint8_t>(0U, 9U),
utils::generate_random_between<std::uint8_t>(0U, 9U),
utils::generate_random_between<std::uint8_t>(0U, 9U),
};
return std::accumulate(data.begin(), data.end(), std::string{file_part} + '_',
[](auto &&name, auto &&val) -> auto {
return name + std::to_string(val);
});
}
auto create_temp_name(std::wstring_view file_part) -> std::wstring {
return utils::string::from_utf8(
create_temp_name(utils::string::to_utf8(file_part)));
}
auto get_free_drive_space(std::string_view path)
-> std::optional<std::uint64_t> {
static constexpr const std::string_view function_name{
static_cast<const char *>(__FUNCTION__),
};
try {
#if defined(_WIN32)
ULARGE_INTEGER li{};
if (not ::GetDiskFreeSpaceEx(std::string{path}.c_str(), &li, nullptr,
nullptr)) {
throw std::runtime_error("failed to get free disk space|" +
std::string{path} + '|' +
std::to_string(utils::get_last_error_code()));
}
stream_.read(reinterpret_cast<char *>(data),
static_cast<std::streamoff>(to_read));
return li.QuadPart;
#endif // defined(_WIN32)
#if defined(__linux__)
struct statfs64 st {};
if (statfs64(std::string{path}.c_str(), &st) != 0) {
throw std::runtime_error("failed to get free disk space|" +
std::string{path} + '|' +
std::to_string(utils::get_last_error_code()));
}
return st.f_bfree * static_cast<std::uint64_t>(st.f_bsize);
#endif // defined(__linux__)
#if defined(__APPLE__)
struct statvfs st {};
if (statvfs(path.c_str(), &st) != 0) {
throw std::runtime_error("failed to get free disk space|" +
std::string{path} + '|' +
std::to_string(utils::get_last_error_code()));
}
return st.f_bfree * static_cast<std::uint64_t>(st.f_frsize);
#endif // defined(__APPLE__)
} catch (const std::exception &e) {
utils::error::handle_exception(function_name, e);
} catch (...) {
utils::error::handle_exception(function_name);
}
return std::nullopt;
}
auto get_free_drive_space(std::wstring_view path)
-> std::optional<std::uint64_t> {
return get_free_drive_space(utils::string::to_utf8(path));
}
auto get_time(std::string_view path,
time_type type) -> std::optional<std::uint64_t> {
auto times = get_times(path);
if (times.has_value()) {
return times->get(type);
}
return std::nullopt;
}
auto get_time(std::wstring_view path,
time_type type) -> std::optional<std::uint64_t> {
return get_time(utils::string::to_utf8(path), type);
}
auto get_times(std::string_view path) -> std::optional<file_times> {
static constexpr const std::string_view function_name{
static_cast<const char *>(__FUNCTION__),
};
try {
file_times ret{};
#if defined(_WIN32)
auto file_handle =
::CreateFileA(std::string{path}.c_str(), GENERIC_READ,
FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE,
nullptr, OPEN_EXISTING, 0U, nullptr);
if (file_handle == INVALID_HANDLE_VALUE) {
throw std::runtime_error("failed to get file times|" + std::string{path} +
'|' +
std::to_string(utils::get_last_error_code()));
}
std::array<FILETIME, 3U> times{};
auto res =
::GetFileTime(file_handle, &times.at(0U), &times.at(1U), &times.at(2U));
::CloseHandle(file_handle);
if (not res) {
throw std::runtime_error("failed to get file times|" + std::string{path} +
'|' +
std::to_string(utils::get_last_error_code()));
}
ret.accessed = utils::time::windows_file_time_to_unix_time(times.at(1U));
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));
ret.written = utils::time::windows_file_time_to_unix_time(times.at(2U));
#else // !defined(_WIN32)
struct stat64 st {};
if (stat64(std::string{path}.c_str(), &st) != 0) {
throw std::runtime_error("failed to get file times|" + std::string{path} +
'|' + std::to_string(errno));
}
ret.accessed = static_cast<std::uint64_t>(st.st_atim.tv_nsec) +
static_cast<std::uint64_t>(st.st_atim.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) *
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) *
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) *
utils::time::NANOS_PER_SECOND;
#endif // defined(_WIN32)
return ret;
} catch (const std::exception &e) {
utils::error::handle_exception(function_name, e);
} catch (...) {
utils::error::handle_exception(function_name);
}
return std::nullopt;
}
auto get_times(std::wstring_view path) -> std::optional<file_times> {
return get_times(utils::string::to_utf8(path));
}
auto get_total_drive_space(std::string_view path)
-> std::optional<std::uint64_t> {
static constexpr const std::string_view function_name{
static_cast<const char *>(__FUNCTION__),
};
try {
#if defined(_WIN32)
ULARGE_INTEGER li{};
if (not ::GetDiskFreeSpaceEx(std::string{path}.c_str(), nullptr, &li,
nullptr)) {
throw std::runtime_error("failed to get total disk space|" +
std::string{path} + '|' +
std::to_string(utils::get_last_error_code()));
}
return li.QuadPart;
#endif // defined(_WIN32)
#if defined(__linux__)
struct statfs64 st {};
if (statfs64(std::string{path}.c_str(), &st) != 0) {
throw std::runtime_error("failed to get total disk space|" +
std::string{path} + '|' +
std::to_string(utils::get_last_error_code()));
}
return st.f_blocks * static_cast<std::uint64_t>(st.f_bsize);
#endif // defined(__linux__)
#if defined(__APPLE__)
struct statvfs st {};
if (statvfs(path.c_str(), &st) != 0) {
throw std::runtime_error("failed to get total disk space|" +
std::string{path} + '|' +
std::to_string(utils::get_last_error_code()));
}
return st.f_blocks * static_cast<std::uint64_t>(st.f_frsize);
#endif // defined(__APPLE__)
} catch (const std::exception &e) {
utils::error::handle_exception(function_name, e);
} catch (...) {
utils::error::handle_exception(function_name);
}
return std::nullopt;
}
auto get_total_drive_space(std::wstring_view path)
-> std::optional<std::uint64_t> {
return get_total_drive_space(utils::string::to_utf8(path));
}
auto i_fs_item::get_time(time_type type) const -> std::optional<std::uint64_t> {
return utils::file::get_time(get_path(), type);
}
auto i_file::read_all(data_buffer &data, std::uint64_t offset,
std::size_t *total_read) -> bool {
data_buffer buffer;
buffer.resize(get_read_buffer_size());
std::size_t current_read{};
while (read(reinterpret_cast<unsigned char *>(buffer.data()),
buffer.size() * sizeof(data_buffer::value_type), offset,
&current_read)) {
if (total_read != nullptr) {
auto after = stream_.tellg();
if (after >= 0) {
(*total_read) = static_cast<std::size_t>(after - before);
} else if (after == -1 && not stream_.eof()) {
throw std::runtime_error("failed to tellg() after read");
}
*total_read += current_read;
}
if (current_read != 0U) {
offset += current_read;
data.insert(
data.end(), buffer.begin(),
std::next(buffer.begin(),
static_cast<std::int64_t>(
current_read / sizeof(data_buffer::value_type))));
continue;
}
return true;
} catch (const std::exception &e) {
utils::error::handle_exception(function_name, e);
} catch (...) {
utils::error::handle_exception(function_name);
}
return false;
}
auto file::remove() -> bool {
close();
return std::filesystem::remove(path_, error_);
}
auto file::truncate(std::size_t size) -> bool {
auto reopen{false};
if (stream_.is_open()) {
reopen = true;
close();
}
std::filesystem::resize_file(path_, size, error_);
if (reopen) {
*this = open_file(path_);
}
return not error_;
}
auto file::write_(const unsigned char *data, std::size_t to_write,
std::size_t offset, std::size_t *total_written) -> bool {
static constexpr const std::string_view function_name{
static_cast<const char *>(__FUNCTION__),
};
if (total_written != nullptr) {
(*total_written) = 0U;
}
try {
stream_.seekp(static_cast<std::streamoff>(offset));
auto before = stream_.tellp();
if (before == -1) {
throw std::runtime_error("failed to tellp() before write");
}
stream_.write(reinterpret_cast<const char *>(data),
static_cast<std::streamoff>(to_write));
auto after = stream_.tellp();
if (after == -1) {
throw std::runtime_error("failed to tellp() after write");
}
if (total_written != nullptr) {
(*total_written) = static_cast<std::size_t>(after - before);
}
return true;
} catch (const std::exception &e) {
utils::error::handle_exception(function_name, e);
} catch (...) {
utils::error::handle_exception(function_name);
}
return false;
}
auto get_file_size(std::string_view path, std::uint64_t &file_size) -> bool {
auto abs_path = utils::path::absolute(path);
file_size = 0U;
#if defined(_WIN32)
struct _stat64 st {};
if (_stat64(abs_path.c_str(), &st) != 0) {
#else // !defined(_WIN32)
struct stat st {};
if (stat(abs_path.c_str(), &st) != 0) {
#endif // defined(_WIN32)
return false;
}
if (st.st_size >= 0) {
file_size = static_cast<std::uint64_t>(st.st_size);
}
return (st.st_size >= 0);
}
auto get_file_size(std::wstring_view path, std::uint64_t &file_size) -> bool {
return get_file_size(utils::string::to_utf8(path), file_size);
}
auto is_directory(std::string_view path) -> bool {
auto abs_path = utils::path::absolute(path);
#if defined(_WIN32)
return ::PathIsDirectoryA(abs_path.c_str()) != 0;
#else // !defined(_WIN32)
struct stat st {};
return (stat(abs_path.c_str(), &st) == 0 && S_ISDIR(st.st_mode));
#endif // defined(_WIN32)
}
auto is_directory(std::wstring_view path) -> bool {
return is_directory(utils::string::to_utf8(path));
}
auto is_file(std::string_view path) -> bool {
auto abs_path = utils::path::absolute(path);
#if defined(_WIN32)
return (::PathFileExistsA(abs_path.c_str()) &&
not ::PathIsDirectoryA(abs_path.c_str()));
#else // !defined(_WIN32)
struct stat st {};
return (stat(abs_path.c_str(), &st) == 0 && not S_ISDIR(st.st_mode));
#endif // defined(_WIN32)
}
auto is_file(std::wstring_view path) -> bool {
return is_file(utils::string::to_utf8(path));
}
#if defined(PROJECT_ENABLE_JSON)
#if defined(PROJECT_ENABLE_LIBSODIUM) && defined(PROJECT_ENABLE_BOOST)
auto read_json_file(std::string_view path, nlohmann::json &data,
@ -255,44 +337,42 @@ auto read_json_file(std::string_view path, nlohmann::json &data) -> bool {
try {
auto abs_path = utils::path::absolute(path);
if (not is_file(abs_path)) {
return true;
auto file = file::open_file(abs_path);
if (not *file) {
return false;
}
std::ifstream file_stream{
abs_path.c_str(),
std::ios_base::binary | std::ios::in,
};
if (not file_stream.is_open()) {
throw std::runtime_error("failed to open file: " + abs_path);
}
auto ret{true};
try {
std::stringstream stream;
stream << file_stream.rdbuf();
data_buffer buffer{};
if (not file->read_all(buffer, 0U)) {
return false;
}
auto json_text = stream.str();
#if defined(PROJECT_ENABLE_LIBSODIUM) && defined(PROJECT_ENABLE_BOOST)
if (password.has_value()) {
auto decrypted_data =
utils::encryption::decrypt_data(*password, json_text);
json_text = {decrypted_data.begin(), decrypted_data.end()};
data_buffer decrypted_data{};
if (not utils::encryption::decrypt_data(*password, buffer,
decrypted_data)) {
return false;
}
buffer = decrypted_data;
}
#endif // defined(PROJECT_ENABLE_LIBSODIUM) && defined(PROJECT_ENABLE_BOOST)
if (not json_text.empty()) {
data = nlohmann::json::parse(json_text.c_str());
std::string json_str(buffer.begin(), buffer.end());
if (not json_str.empty()) {
data = nlohmann::json::parse(json_str);
}
} catch (const std::exception &e) {
utils::error::handle_exception(function_name, e);
ret = false;
return false;
} catch (...) {
utils::error::handle_exception(function_name);
ret = false;
return false;
}
file_stream.close();
return ret;
return true;
} catch (const std::exception &e) {
utils::error::handle_exception(function_name, e);
} catch (...) {
@ -315,19 +395,26 @@ auto write_json_file(std::string_view path,
try {
auto file = file::open_or_create_file(path);
if (not file.truncate()) {
throw std::runtime_error("failed to truncate file: " +
file.get_error_code().message());
if (not file->truncate()) {
throw std::runtime_error("failed to truncate file");
}
#if defined(PROJECT_ENABLE_LIBSODIUM) && defined(PROJECT_ENABLE_BOOST)
if (password.has_value()) {
return file.write(utils::encryption::encrypt_data(*password, data.dump()),
0U);
const auto str_data = data.dump();
data_buffer encrypted_data{};
utils::encryption::encrypt_data(
*password, reinterpret_cast<const unsigned char *>(str_data.c_str()),
str_data.size(), encrypted_data);
return file->write(encrypted_data, 0U);
}
#endif // defined(PROJECT_ENABLE_LIBSODIUM) && defined(PROJECT_ENABLE_BOOST)
return file.write(data, 0U);
auto json_str = data.dump();
return file->write(
reinterpret_cast<const unsigned char *>(json_str.c_str()),
json_str.size(), 0U);
} catch (const std::exception &e) {
utils::error::handle_exception(function_name, e);
} catch (...) {
@ -368,4 +455,170 @@ auto write_json_file(std::wstring_view path,
}
#endif // defined(PROJECT_ENABLE_LIBSODIUM) && defined(PROJECT_ENABLE_BOOST)
#endif // defined(PROJECT_ENABLE_JSON)
#if defined(PROJECT_ENABLE_LIBDSM)
static constexpr const 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, " ") &&
std::count(path.begin(), path.end(), '/') >= 3U);
};
auto smb_create_smb_path(std::string_view smb_path,
std::string_view rel_path) -> std::string {
if (not validate_smb_path(smb_path)) {
throw std::runtime_error("invalid smb path|" + std::string{smb_path});
}
std::string path{rel_path};
utils::path::format_path(path, "/", "\\");
utils::string::left_trim(path, '/');
auto old_parts =
repertory::utils::string::split(smb_path.substr(2U), '/', false);
old_parts.erase(std::next(old_parts.begin(), 2U), old_parts.end());
auto new_parts = repertory::utils::string::split(path, '/', false);
old_parts.insert(old_parts.end(), new_parts.begin(), new_parts.end());
path = utils::string::join(old_parts, '/');
path = "//" + utils::path::format_path(path, "/", "\\");
if (not validate_smb_path(path)) {
throw std::runtime_error("invalid smb path|" + std::string{path});
}
return path;
}
auto smb_create_and_validate_relative_path(
std::string_view smb_path, std::string_view path) -> std::string {
if (not validate_smb_path(smb_path)) {
throw std::runtime_error("invalid smb path|" + std::string{smb_path});
}
std::string dir_path;
if (utils::string::begins_with(path, "//")) {
if (not utils::file::smb_parent_is_same(smb_path, path)) {
throw std::runtime_error("failed to validate path|" +
std::string{smb_path} + '|' + std::string{path} +
"|parent paths are not the same");
}
return utils::file::smb_create_relative_path(path);
}
return utils::file::smb_create_relative_path(std::string{smb_path} + '/' +
std::string{path});
}
auto smb_create_relative_path(std::string_view smb_path) -> std::string {
if (not validate_smb_path(smb_path)) {
throw std::runtime_error("invalid smb path|" + std::string{smb_path});
}
std::string path{smb_path};
utils::path::format_path(path, "\\", "/");
utils::string::left_trim(path, '\\');
auto parts = repertory::utils::string::split(path, '\\', false);
parts.erase(parts.begin(), std::next(parts.begin(), 2U));
return "\\" + utils::string::join(parts, '\\');
}
auto smb_create_search_path(std::string_view smb_path) -> std::string {
if (not validate_smb_path(smb_path)) {
throw std::runtime_error("invalid smb path|" + std::string{smb_path});
}
std::string path{smb_path};
utils::string::left_trim(path, '/');
auto parts = repertory::utils::string::split(path, '/', false);
parts.erase(parts.begin(), std::next(parts.begin(), 2U));
auto search_path = repertory::utils::string::join(parts, '\\');
return search_path.empty() ? "\\*" : "\\" + search_path + "\\*";
}
auto smb_get_parent_path(std::string_view smb_path) -> std::string {
if (not validate_smb_path(smb_path)) {
throw std::runtime_error("invalid smb path|" + std::string{smb_path});
}
auto parts = repertory::utils::string::split(smb_path.substr(2U), '/', false);
if (parts.size() > 2U) {
parts.erase(std::prev(parts.end()), parts.end());
}
auto parent_smb_path = "//" + utils::string::join(parts, '/');
if (not validate_smb_path(parent_smb_path)) {
throw std::runtime_error("invalid smb path|" + parent_smb_path);
}
return parent_smb_path;
}
auto smb_get_root_path(std::string_view smb_path) -> std::string {
if (not validate_smb_path(smb_path)) {
throw std::runtime_error("invalid smb path|" + std::string{smb_path});
}
auto parts = repertory::utils::string::split(smb_path.substr(2U), '/', false);
if (parts.size() > 2U) {
parts.erase(std::next(parts.begin(), 2U), parts.end());
}
return "//" + utils::string::join(parts, '/');
}
auto smb_get_unc_path(std::string_view smb_path) -> std::string {
if (not validate_smb_path(smb_path)) {
throw std::runtime_error("invalid smb path|" + std::string{smb_path});
}
std::string unc_path{smb_path};
utils::path::format_path(unc_path, "\\", "/");
return '\\' + unc_path;
}
auto smb_get_uri_path(std::string_view smb_path) -> std::string {
if (not validate_smb_path(smb_path)) {
throw std::runtime_error("invalid smb path|" + std::string{smb_path});
}
return "smb:" + std::string{smb_path};
}
auto smb_get_uri_path(std::string_view smb_path, std::string_view user,
std::string_view password) -> std::string {
if (not validate_smb_path(smb_path)) {
throw std::runtime_error("invalid smb path|" + std::string{smb_path});
}
return "smb://" + std::string{user} + ':' + std::string{password} + '@' +
std::string{smb_path.substr(2U)};
}
auto smb_parent_is_same(std::string_view smb_path1,
std::string_view smb_path2) -> bool {
if (not(validate_smb_path(smb_path1) && validate_smb_path(smb_path2))) {
return false;
}
auto parts1 = utils::string::split(smb_path1.substr(2U), "/", false);
auto parts2 = utils::string::split(smb_path2.substr(2U), "/", false);
if (parts1.size() < 2U || parts2.size() < 2U) {
return false;
}
if (parts2.at(1U).empty() || parts1.at(1U).empty()) {
return false;
}
return std::equal(parts1.begin(), std::next(parts1.begin(), 2U),
parts2.begin());
}
#endif // defined(PROJECT_ENABLE_LIBDSM)
} // namespace repertory::utils::file

View File

@ -0,0 +1,466 @@
/*
Copyright <2018-2024> <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/file.hpp"
#include "utils/common.hpp"
#include "utils/error.hpp"
#include "utils/unix.hpp"
#include "utils/windows.hpp"
namespace {
auto traverse_directory(
std::string_view path,
std::function<bool(repertory::utils::file::directory)> directory_action,
std::function<bool(repertory::utils::file::file)> file_action) -> bool {
auto res{true};
#if defined(_WIN32)
WIN32_FIND_DATAA fd{};
auto search = repertory::utils::path::combine(path, {"*.*"});
auto find = ::FindFirstFileA(search.c_str(), &fd);
if (find == INVALID_HANDLE_VALUE) {
throw std::runtime_error(
"failed to open directory|" + std::string{path} + '|' +
std::to_string(repertory::utils::get_last_error_code()));
}
do {
if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
if ((std::string_view(fd.cFileName) != ".") &&
(std::string_view(fd.cFileName) != "..")) {
res = directory_action(repertory::utils::file::directory{
repertory::utils::path::combine(path, {fd.cFileName})});
}
} else {
res = file_action(repertory::utils::file::file(
repertory::utils::path::combine(path, {fd.cFileName})));
}
} while (res && (::FindNextFileA(find, &fd) != 0));
::FindClose(find);
#else // !defined(_WIN32)
auto *root = opendir(std::string{path}.c_str());
if (root == nullptr) {
throw std::runtime_error(
"failed to open directory|" + std::string{path} + '|' +
std::to_string(repertory::utils::get_last_error_code()));
}
struct dirent *de{nullptr};
while (res && (de = readdir(root))) {
if (de->d_type == DT_DIR) {
if ((std::string_view(de->d_name) != ".") &&
(std::string_view(de->d_name) != "..")) {
res = directory_action(repertory::utils::file::directory(
repertory::utils::path::combine(path, {de->d_name})));
}
} else {
res = file_action(repertory::utils::file::file(
repertory::utils::path::combine(path, {de->d_name})));
}
}
closedir(root);
#endif // defined(_WIN32)
return res;
}
} // namespace
namespace repertory::utils::file {
auto directory::copy_to(std::string_view new_path,
bool overwrite) const -> bool {
static constexpr const std::string_view function_name{
static_cast<const char *>(__FUNCTION__),
};
try {
throw std::runtime_error("failed to copy directory|" + path_ + '|' +
std::string{new_path} + '+' +
std::to_string(overwrite) + "|not implemented");
} catch (const std::exception &e) {
utils::error::handle_exception(function_name, e);
} catch (...) {
utils::error::handle_exception(function_name);
}
return false;
}
auto directory::count(bool recursive) const -> std::uint64_t {
static constexpr const std::string_view function_name{
static_cast<const char *>(__FUNCTION__),
};
try {
std::uint64_t ret{0U};
traverse_directory(
path_,
[&ret, &recursive](auto dir_item) -> bool {
if (recursive) {
ret += dir_item.count(true);
}
++ret;
return true;
},
[&ret](auto /* file_item */) -> bool {
++ret;
return true;
});
return ret;
} catch (const std::exception &e) {
utils::error::handle_exception(function_name, e);
} catch (...) {
utils::error::handle_exception(function_name);
}
return 0U;
}
auto directory::create_directory(std::string_view path) const
-> fs_directory_t {
static constexpr const std::string_view function_name{
static_cast<const char *>(__FUNCTION__),
};
try {
auto abs_path = utils::path::combine(path_, {path});
if (directory{abs_path}.exists()) {
return std::make_unique<directory>(abs_path);
}
#if defined(_WIN32)
auto res = ::SHCreateDirectory(nullptr,
utils::string::from_utf8(abs_path).c_str());
if (res != ERROR_SUCCESS) {
throw std::runtime_error("failed to create directory|" +
std::string{abs_path} + '|' +
std::to_string(res));
}
#else // !defined(_WIN32)
auto ret{true};
auto paths =
utils::string::split(abs_path, utils::path::directory_seperator, false);
std::string current_path;
for (std::size_t idx = 0U; ret && (idx < paths.size()); ++idx) {
if (paths.at(idx).empty()) {
current_path = utils::path::directory_seperator;
continue;
}
current_path = utils::path::combine(current_path, {paths.at(idx)});
auto status = mkdir(current_path.c_str(), S_IRWXU);
ret = ((status == 0) || (errno == EEXIST));
}
#endif
return std::make_unique<directory>(abs_path);
} catch (const std::exception &e) {
utils::error::handle_exception(function_name, e);
} catch (...) {
utils::error::handle_exception(function_name);
}
return nullptr;
}
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));
#endif // defined(_WIN32)
return false;
}
auto directory::get_directory(std::string_view path) const -> fs_directory_t {
static constexpr const std::string_view function_name{
static_cast<const char *>(__FUNCTION__),
};
try {
auto dir_path = utils::path::combine(path_, {path});
return fs_directory_t{
directory{dir_path}.exists() ? new directory{dir_path} : nullptr,
};
} catch (const std::exception &e) {
utils::error::handle_exception(function_name, e);
} catch (...) {
utils::error::handle_exception(function_name);
}
return nullptr;
}
auto directory::get_directories() const -> std::vector<fs_directory_t> {
static constexpr const std::string_view function_name{
static_cast<const char *>(__FUNCTION__),
};
try {
std::vector<fs_directory_t> ret{};
traverse_directory(
path_,
[&ret](auto dir_item) -> bool {
ret.emplace_back(fs_directory_t{
new directory(dir_item.get_path()),
});
return true;
},
[](auto /* file_item */) -> bool { return true; });
return ret;
} catch (const std::exception &e) {
utils::error::handle_exception(function_name, e);
} catch (...) {
utils::error::handle_exception(function_name);
}
return {};
}
auto directory::create_file(std::string_view file_name,
bool read_only) const -> fs_file_t {
static constexpr const std::string_view function_name{
static_cast<const char *>(__FUNCTION__),
};
try {
auto file_path = utils::path::combine(path_, {file_name});
return file::open_or_create_file(file_path, read_only);
} catch (const std::exception &e) {
utils::error::handle_exception(function_name, e);
} catch (...) {
utils::error::handle_exception(function_name);
}
return nullptr;
}
auto directory::get_file(std::string_view path) const -> fs_file_t {
static constexpr const std::string_view function_name{
static_cast<const char *>(__FUNCTION__),
};
try {
auto file_path = utils::path::combine(path_, {path});
return fs_file_t{
file{file_path}.exists() ? new file(file_path) : nullptr,
};
} catch (const std::exception &e) {
utils::error::handle_exception(function_name, e);
} catch (...) {
utils::error::handle_exception(function_name);
}
return nullptr;
}
auto directory::get_files() const -> std::vector<fs_file_t> {
static constexpr const std::string_view function_name{
static_cast<const char *>(__FUNCTION__),
};
try {
std::vector<fs_file_t> ret{};
traverse_directory(
path_, [](auto /* dir_item */) -> bool { return true; },
[&ret](auto file_item) -> bool {
ret.emplace_back(fs_file_t{
new file(file_item.get_path()),
});
return true;
});
return ret;
} catch (const std::exception &e) {
utils::error::handle_exception(function_name, e);
} catch (...) {
utils::error::handle_exception(function_name);
}
return {};
}
auto directory::get_items() const -> std::vector<fs_item_t> {
static constexpr const std::string_view function_name{
static_cast<const char *>(__FUNCTION__),
};
try {
std::vector<fs_item_t> ret{};
traverse_directory(
path_,
[&ret](auto dir_item) -> bool {
ret.emplace_back(fs_item_t{
new directory(dir_item.get_path()),
});
return true;
},
[&ret](auto file_item) -> bool {
ret.emplace_back(fs_item_t{
new file(file_item.get_path()),
});
return true;
});
return ret;
} catch (const std::exception &e) {
utils::error::handle_exception(function_name, e);
} catch (...) {
utils::error::handle_exception(function_name);
}
return {};
}
auto directory::is_symlink() const -> bool {
static constexpr const std::string_view function_name{
static_cast<const char *>(__FUNCTION__),
};
try {
return std::filesystem::is_symlink(path_);
} catch (const std::exception &e) {
utils::error::handle_exception(function_name, e);
} catch (...) {
utils::error::handle_exception(function_name);
}
return false;
}
auto directory::move_to(std::string_view new_path) -> bool {
static constexpr const std::string_view function_name{
static_cast<const char *>(__FUNCTION__),
};
try {
throw std::runtime_error("failed to move directory|" + path_ + '|' +
std::string{new_path} + "|not implemented");
} catch (const std::exception &e) {
utils::error::handle_exception(function_name, e);
} catch (...) {
utils::error::handle_exception(function_name);
}
return false;
}
auto directory::remove() -> bool {
static constexpr const std::string_view function_name{
static_cast<const char *>(__FUNCTION__),
};
if (not exists()) {
return true;
}
return utils::retry_action([this]() -> bool {
try {
#if defined(_WIN32)
return ::RemoveDirectoryA(path_.c_str());
#else // !defined(_WIN32)
return (rmdir(path_.c_str()) == 0);
#endif // defined(_WIN32)
} catch (const std::exception &e) {
utils::error::handle_exception(function_name, e);
} catch (...) {
utils::error::handle_exception(function_name);
}
return false;
});
}
auto directory::remove_recursively() -> bool {
static constexpr const std::string_view function_name{
static_cast<const char *>(__FUNCTION__),
};
try {
if (not exists()) {
return true;
}
if (not traverse_directory(
path_,
[](auto dir_item) -> bool { return dir_item.remove_recursively(); },
[](auto file_item) -> bool { return file_item.remove(); })) {
return false;
}
return remove();
} catch (const std::exception &e) {
utils::error::handle_exception(function_name, e);
} catch (...) {
utils::error::handle_exception(function_name);
}
return false;
}
auto directory::size(bool recursive) const -> std::uint64_t {
static constexpr const std::string_view function_name{
static_cast<const char *>(__FUNCTION__),
};
try {
std::uint64_t ret{0U};
traverse_directory(
path_,
[&ret, &recursive](auto dir_item) -> bool {
if (recursive) {
ret += dir_item.size(true);
}
return true;
},
[&ret](auto file_item) -> bool {
auto cur_size = file_item.size();
if (cur_size.has_value()) {
ret += cur_size.value();
}
return true;
});
return ret;
} catch (const std::exception &e) {
utils::error::handle_exception(function_name, e);
} catch (...) {
utils::error::handle_exception(function_name);
}
return 0U;
}
} // namespace repertory::utils::file

View File

@ -0,0 +1,53 @@
/*
Copyright <2018-2024> <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/file.hpp"
#if defined(PROJECT_ENABLE_LIBSODIUM) && defined(PROJECT_ENABLE_BOOST)
namespace repertory::utils::file {
auto enc_file::attach_file(fs_file_t file) -> fs_file_t {}
enc_file::enc_file(fs_file_t file) : file_(std::move(file)) {}
void enc_file::close() {}
auto enc_file::copy_to(std::string_view new_path,
bool overwrite) const -> bool {}
void enc_file::flush() const {}
auto enc_file::move_to(std::string_view path) -> bool {}
auto enc_file::read(unsigned char *data, std::size_t to_read,
std::uint64_t offset, std::size_t *total_read) -> bool {}
auto enc_file::remove() -> bool {}
auto enc_file::truncate(std::size_t size) -> 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 enc_file::size() const -> std::optional<std::uint64_t> {}
} // namespace repertory::utils::file
#endif // defined(PROJECT_ENABLE_LIBSODIUM) && defined(PROJECT_ENABLE_BOOST)

View File

@ -0,0 +1,539 @@
/*
Copyright <2018-2024> <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/file.hpp"
#include "utils/collection.hpp"
#include "utils/common.hpp"
#include "utils/error.hpp"
#include "utils/path.hpp"
namespace {
[[nodiscard]] auto get_file_size(std::string_view path,
std::uint64_t &file_size) -> bool {
auto abs_path = repertory::utils::path::absolute(path);
file_size = 0U;
#if defined(_WIN32)
struct _stat64 st {};
auto res = _stat64(std::string{path}.c_str(), &st);
if (res != 0) {
return false;
}
file_size = static_cast<std::uint64_t>(st.st_size);
return true;
#else // !defined(_WIN32)
std::error_code ec{};
file_size = std::filesystem::file_size(abs_path, ec);
return (ec.value() == 0);
#endif // defined(_WIN32)
}
[[nodiscard]] auto is_file(std::string_view path) -> bool {
auto abs_path = repertory::utils::path::absolute(path);
#if defined(_WIN32)
return (::PathFileExistsA(abs_path.c_str()) &&
not ::PathIsDirectoryA(abs_path.c_str()));
#else // !defined(_WIN32)
struct stat64 st {};
return (stat64(abs_path.c_str(), &st) == 0 && not S_ISDIR(st.st_mode));
#endif // defined(_WIN32)
}
} // namespace
namespace repertory::utils::file {
// auto file::attach_file(native_handle handle,
// bool read_only) -> fs_file_t {
// static constexpr const std::string_view function_name{
// static_cast<const char *>(__FUNCTION__),
// };
//
// try {
// std::string path;
//
// #if defined(_WIN32)
// path.resize(repertory::max_path_length + 1);
// ::GetFinalPathNameByHandleA(handle, path.data(),
// static_cast<DWORD>(path.size()),
// FILE_NAME_NORMALIZED | VOLUME_NAME_DOS);
// #else // !defined(_WIN32)
// path.resize(repertory::max_path_length + 1);
//
// #if defined(__APPLE__)
// fcntl(handle, F_GETPATH, source_path.data());
// #else // !defined(__APPLE__)
// readlink(("/proc/self/fd/" + std::to_string(handle)).c_str(),
// path.data(),
// path.size());
// #endif // defined(__APPLE__)
// #endif // defined(_WIN32)
//
// path = path.c_str();
//
// #if defined(_WIN32)
// auto *ptr = _fdopen(
// static_cast<int>(_open_osfhandle(reinterpret_cast<intptr_t>(handle),
// read_only ? _O_RDONLY : _O_RDWR)),
// read_only ? "rb" : "rb+");
// #else // !defined(_WIN32)
// auto *ptr = fdopen(handle, read_only ? "rb" : "rb+");
// #endif // defined(_WIN32)
//
// return fs_file_t(new file{
// file_t{ptr},
// utils::path::absolute(path),
// read_only,
// });
// } catch (const std::exception &e) {
// utils::error::handle_exception(function_name, e);
// } catch (...) {
// utils::error::handle_exception(function_name);
// }
//
// return nullptr;
// }
void file::open() {
if (not is_file(path_)) {
throw std::runtime_error("file not found: " + path_);
}
#if defined(_WIN32)
file_.reset(_fsopen(path_.c_str(), read_only_ ? "rb" : "rb+", _SH_DENYNO));
#else // !defined(_WIN32)
file_.reset(fopen(path_.c_str(), read_only_ ? "rb" : "rb+"));
#endif // defined(_WIN32)
}
auto file::open_file(std::string_view path, bool read_only) -> fs_file_t {
static constexpr const std::string_view function_name{
static_cast<const char *>(__FUNCTION__),
};
auto *ptr = new file{
nullptr,
utils::path::absolute(path),
read_only,
};
auto new_file = fs_file_t(ptr);
try {
ptr->open();
} catch (const std::exception &e) {
utils::error::handle_exception(function_name, e);
} catch (...) {
utils::error::handle_exception(function_name);
}
return new_file;
}
auto file::open_or_create_file(std::string_view path,
bool read_only) -> fs_file_t {
auto abs_path = utils::path::absolute(path);
if (not is_file(abs_path)) {
#if defined(_WIN32)
int old_mode{};
_umask_s(077, &old_mode);
#else // !defined(_WIN32)
auto old_mode = umask(077);
#endif // defined(_WIN32)
#if defined(_WIN32)
auto *ptr = _fsopen(abs_path.c_str(), "ab+", _SH_DENYNO);
#else // !defined(_WIN32)
auto *ptr = fopen(abs_path.c_str(), "ab+");
#endif // defined(_WIN32)
if (ptr != nullptr) {
fclose(ptr);
}
#if defined(_WIN32)
_umask_s(old_mode, nullptr);
#else // !defined(_WIN32)
umask(old_mode);
#endif // defined(_WIN32)
}
return open_file(abs_path, read_only);
}
void file::close() { file_.reset(); }
auto file::copy_to(std::string_view new_path, bool overwrite) const -> bool {
static constexpr const std::string_view function_name{
static_cast<const char *>(__FUNCTION__),
};
try {
auto to_path = utils::path::absolute(new_path);
if (directory(to_path).exists()) {
return false;
}
#if defined(_WIN32)
return ::CopyFileA(path_.c_str(), to_path.c_str(),
overwrite ? TRUE : FALSE);
#else // !defined(_WIN32)
return std::filesystem::copy_file(
path_, to_path,
overwrite ? std::filesystem::copy_options::overwrite_existing
: std::filesystem::copy_options::skip_existing);
#endif // defined(_WIN32)
} catch (const std::exception &e) {
utils::error::handle_exception(function_name, e);
} catch (...) {
utils::error::handle_exception(function_name);
}
return false;
}
auto file::exists() const -> bool { return is_file(path_); }
void file::flush() const {
if (file_) {
fflush(file_.get());
}
}
auto file::get_handle() const -> native_handle {
if (file_) {
#if defined(_WIN32)
return reinterpret_cast<native_handle>(
_get_osfhandle(_fileno(file_.get())));
#else // !defined(_WIN32)
return fileno(file_.get());
#endif // defined(_WIN32)
}
return INVALID_HANDLE_VALUE;
}
auto file::is_symlink() const -> bool {
static constexpr const std::string_view function_name{
static_cast<const char *>(__FUNCTION__),
};
try {
return std::filesystem::is_symlink(path_);
} catch (const std::exception &e) {
utils::error::handle_exception(function_name, e);
} catch (...) {
utils::error::handle_exception(function_name);
}
return false;
}
auto file::move_to(std::string_view path) -> bool {
static constexpr const std::string_view function_name{
static_cast<const char *>(__FUNCTION__),
};
auto abs_path = utils::path::absolute(path);
auto reopen{false};
if (file_) {
reopen = true;
close();
}
auto success{false};
#if defined(_WIN32)
success = !!::MoveFileExA(path_.c_str(), abs_path.c_str(),
MOVEFILE_REPLACE_EXISTING);
#else // !// defined(_WIN32)
std::error_code ec{};
std::filesystem::rename(path_, abs_path, ec);
success = ec.value() == 0;
#endif // defined(_WIN32)
if (success) {
path_ = abs_path;
}
if (reopen) {
try {
open();
return success;
} catch (const std::exception &e) {
utils::error::handle_exception(function_name, e);
} catch (...) {
utils::error::handle_exception(function_name);
}
}
return false;
}
auto file::read(unsigned char *data, std::size_t to_read, std::uint64_t offset,
std::size_t *total_read) -> bool {
static constexpr const std::string_view function_name{
static_cast<const char *>(__FUNCTION__),
};
if (total_read != nullptr) {
(*total_read) = 0U;
}
try {
if (not file_) {
throw std::runtime_error("file is not open for reading");
}
if (fseeko(file_.get(), static_cast<std::int64_t>(offset), SEEK_SET) ==
-1) {
throw std::runtime_error("failed to seek before read");
}
std::size_t bytes_read{0U};
while (bytes_read != to_read) {
auto res =
fread(&data[bytes_read], 1U, to_read - bytes_read, file_.get());
if (not feof(file_.get()) && ferror(file_.get())) {
throw std::runtime_error("failed to read file bytes");
}
if (res == 0) {
break;
}
bytes_read += static_cast<std::size_t>(res);
}
if (total_read != nullptr) {
(*total_read) = bytes_read;
}
return true;
} catch (const std::exception &e) {
utils::error::handle_exception(function_name, e);
} catch (...) {
utils::error::handle_exception(function_name);
}
return false;
}
#if defined(PROJECT_ENABLE_LIBSODIUM)
auto file::sha256() -> std::optional<std::string> {
static constexpr const std::string_view function_name{
static_cast<const char *>(__FUNCTION__),
};
auto should_close{false};
auto read_only{read_only_};
std::optional<std::string> ret;
try {
if (file_ == nullptr) {
should_close = true;
read_only_ = true;
this->open();
}
crypto_hash_sha256_state state{};
auto res = crypto_hash_sha256_init(&state);
if (res != 0) {
throw std::runtime_error("failed to initialize sha256|" +
std::to_string(res));
}
{
data_buffer buffer(get_read_buffer_size());
std::uint64_t read_offset{0U};
std::size_t bytes_read{0U};
while (i_file::read(buffer, read_offset, &bytes_read)) {
if (not bytes_read) {
break;
}
read_offset += bytes_read;
res = crypto_hash_sha256_update(
&state, reinterpret_cast<const unsigned char *>(buffer.data()),
bytes_read);
if (res != 0) {
throw std::runtime_error("failed to update sha256|" +
std::to_string(res));
}
}
}
std::array<unsigned char, crypto_hash_sha256_BYTES> out{};
res = crypto_hash_sha256_final(&state, out.data());
if (res != 0) {
throw std::runtime_error("failed to finalize sha256|" +
std::to_string(res));
}
ret = utils::collection::to_hex_string(out);
} catch (const std::exception &e) {
utils::error::handle_exception(function_name, e);
} catch (...) {
utils::error::handle_exception(function_name);
}
if (should_close) {
read_only_ = read_only;
close();
}
return ret;
}
#endif // defined(PROJECT_ENABLE_LIBSODIUM)
auto file::remove() -> bool {
if (not exists()) {
return true;
}
close();
return utils::retry_action([this]() -> bool {
#if defined(_WIN32)
return ::DeleteFileA(path_.c_str());
#else // !defined(_WIN32)
std::error_code ec{};
return std::filesystem::remove(path_, ec);
#endif // defined(_WIN32)
});
}
auto file::truncate(std::size_t size) -> bool {
static constexpr const std::string_view function_name{
static_cast<const char *>(__FUNCTION__),
};
auto reopen{false};
if (file_) {
reopen = true;
close();
}
std::error_code ec{};
std::filesystem::resize_file(path_, size, ec);
auto success{ec.value() == 0};
if (reopen) {
try {
open();
} catch (const std::exception &e) {
utils::error::handle_exception(function_name, e);
success = false;
} catch (...) {
utils::error::handle_exception(function_name);
success = false;
}
}
return success;
}
auto file::write(const unsigned char *data, std::size_t to_write,
std::size_t offset, std::size_t *total_written) -> bool {
static constexpr const std::string_view function_name{
static_cast<const char *>(__FUNCTION__),
};
if (total_written != nullptr) {
(*total_written) = 0U;
}
try {
if (not file_) {
throw std::runtime_error("file is not open for writing");
}
auto res = fseeko(file_.get(), static_cast<std::int64_t>(offset), SEEK_SET);
if (res == -1) {
throw std::runtime_error("failed to seek before write");
}
std::size_t bytes_written{0U};
while (bytes_written != to_write) {
res = fwrite(reinterpret_cast<const char *>(&data[bytes_written]), 1U,
to_write - bytes_written, file_.get());
if (not feof(file_.get()) && ferror(file_.get())) {
throw std::runtime_error("failed to write file bytes");
}
if (res == 0) {
break;
}
bytes_written += static_cast<std::size_t>(res);
}
flush();
if (total_written != nullptr) {
(*total_written) = bytes_written;
}
return true;
} catch (const std::exception &e) {
utils::error::handle_exception(function_name, e);
} catch (...) {
utils::error::handle_exception(function_name);
}
return false;
}
auto file::size() const -> std::optional<std::uint64_t> {
static constexpr const std::string_view function_name{
static_cast<const char *>(__FUNCTION__),
};
try {
if (file_) {
if (fseeko(file_.get(), 0, SEEK_END) == -1) {
throw std::runtime_error("failed to seek");
}
auto size = ftello(file_.get());
if (size == -1) {
throw std::runtime_error("failed to get position");
}
return static_cast<std::uint64_t>(size);
}
std::uint64_t size{};
if (not get_file_size(path_, size)) {
throw std::runtime_error("failed to get file size");
}
return size;
} catch (const std::exception &e) {
utils::error::handle_exception(function_name, e);
} catch (...) {
utils::error::handle_exception(function_name);
}
return std::nullopt;
}
} // namespace repertory::utils::file

View File

@ -0,0 +1,615 @@
/*
Copyright <2018-2024> <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/file.hpp"
#include "utils/common.hpp"
#include "utils/error.hpp"
#if defined(PROJECT_ENABLE_LIBDSM)
namespace repertory::utils::file {
auto smb_directory::open(std::string_view host, std::string_view user,
std::string_view password,
std::string_view share_name) -> smb_directory_t {
static constexpr const std::string_view function_name{
static_cast<const char *>(__FUNCTION__),
};
try {
smb_session_t session{smb_session_new(), smb_session_deleter};
netbios_ns_t ns{netbios_ns_new()};
sockaddr_in addr{};
auto res = netbios_ns_resolve(
ns.get(), std::string{host}.c_str(), NETBIOS_FILESERVER,
reinterpret_cast<std::uint32_t *>(&addr.sin_addr.s_addr));
if (res != DSM_SUCCESS) {
res = inet_pton(AF_INET, std::string{host}.c_str(), &addr.sin_addr);
if (res != 1) {
throw std::runtime_error("failed to resolve host|" + std::string{host} +
'|' + std::to_string(errno));
}
}
res = smb_session_connect(session.get(), std::string{host}.c_str(),
static_cast<std::uint32_t>(addr.sin_addr.s_addr),
SMB_TRANSPORT_TCP);
if (res != DSM_SUCCESS) {
throw std::runtime_error("failed to connect to host|" +
std::string{host} + '|' + std::to_string(res));
}
smb_session_set_creds(session.get(), std::string{host}.c_str(),
std::string{user}.c_str(),
std::string{password}.c_str());
res = smb_session_login(session.get());
if (res != DSM_SUCCESS) {
throw std::runtime_error("failed to logon to host|" + std::string{host} +
'|' + std::string{user} + '|' +
std::to_string(res));
}
smb_tid tid{};
res =
smb_tree_connect(session.get(), std::string{share_name}.c_str(), &tid);
if (res != DSM_SUCCESS) {
throw std::runtime_error("failed to connect to share|" +
std::string{share_name} + '|' +
std::to_string(res));
}
return smb_directory_t{
new smb_directory{
"//" + std::string{host} + "/" + std::string{share_name},
session,
share_name,
tid,
},
};
} catch (const std::exception &e) {
utils::error::handle_exception(function_name, e);
} catch (...) {
utils::error::handle_exception(function_name);
}
return nullptr;
}
auto smb_directory::open(std::wstring_view host, std::wstring_view user,
std::wstring_view password,
std::wstring_view share_name) -> smb_directory_t {
return open(utils::string::to_utf8(host), utils::string::to_utf8(user),
utils::string::to_utf8(password),
utils::string::to_utf8(share_name));
}
auto smb_directory::copy_to(std::string_view new_path,
bool overwrite) const -> bool {
static constexpr const std::string_view function_name{
static_cast<const char *>(__FUNCTION__),
};
try {
if (not session_) {
throw std::runtime_error("session not found|" + path_);
}
// auto to_path = utils::path::absolute(new_path);
throw std::runtime_error("failed to copy directory|" + path_ + '|' +
std::string{new_path} + "|not implemented");
} catch (const std::exception &e) {
utils::error::handle_exception(function_name, e);
} catch (...) {
utils::error::handle_exception(function_name);
}
return false;
}
auto smb_directory::count(bool recursive) const -> std::uint64_t {
static constexpr const std::string_view function_name{
static_cast<const char *>(__FUNCTION__),
};
try {
if (not session_) {
throw std::runtime_error("session not found|" + path_);
}
smb_stat_list_t list{
smb_find(session_.get(), tid_, smb_create_search_path(path_).c_str())};
auto count = smb_stat_list_count(list.get());
if (not recursive) {
return count;
}
throw std::runtime_error("failed to get directory count recursively|" +
path_ + "|not implemented");
} catch (const std::exception &e) {
utils::error::handle_exception(function_name, e);
} catch (...) {
utils::error::handle_exception(function_name);
}
return 0U;
}
auto smb_directory::create_directory(std::string_view path) const
-> fs_directory_t {
static constexpr const std::string_view function_name{
static_cast<const char *>(__FUNCTION__),
};
try {
if (not session_) {
throw std::runtime_error("session not found|" + path_);
}
auto dir = get_directory(path);
if (dir) {
return dir;
}
auto res = smb_directory_create(
session_.get(), tid_,
smb_create_and_validate_relative_path(path_, path).c_str());
if (res != DSM_SUCCESS) {
throw std::runtime_error("failed to create directory|" + path_ + '/' +
std::string{path} + '|' + std::to_string(res));
}
return get_directory(path);
} catch (const std::exception &e) {
utils::error::handle_exception(function_name, e);
} catch (...) {
utils::error::handle_exception(function_name);
}
return nullptr;
}
auto smb_directory::create_file(std::string_view file_name,
bool read_only) const -> fs_file_t {
static constexpr const std::string_view function_name{
static_cast<const char *>(__FUNCTION__),
};
try {
if (not session_) {
throw std::runtime_error("session not found|" + path_);
}
auto fs_file = get_file(file_name);
if (fs_file) {
if (not dynamic_cast<smb_file *>(fs_file.get())->open(read_only)) {
throw std::runtime_error("failed to open existing file|" +
std::string{file_name});
}
return fs_file;
}
auto rel_path = smb_create_and_validate_relative_path(path_, file_name);
smb_fd fd{};
auto res =
smb_fopen(session_.get(), tid_, rel_path.c_str(), SMB_MOD_RW, &fd);
if (res != DSM_SUCCESS) {
return nullptr;
}
smb_fclose(session_.get(), fd);
res = smb_fopen(session_.get(), tid_, rel_path.c_str(),
read_only ? SMB_MOD_RO : SMB_MOD_RW2, &fd);
if (res != DSM_SUCCESS) {
return nullptr;
}
return std::make_unique<smb_file>(
fd, smb_create_smb_path(path_, std::string{rel_path}), session_,
share_name_, tid_);
} catch (const std::exception &e) {
utils::error::handle_exception(function_name, e);
} catch (...) {
utils::error::handle_exception(function_name);
}
return nullptr;
}
auto smb_directory::exists() const -> bool {
static constexpr const std::string_view function_name{
static_cast<const char *>(__FUNCTION__),
};
try {
if (not session_) {
throw std::runtime_error("session not found|" + path_);
}
smb_stat_t st{smb_fstat(session_.get(), tid_,
smb_create_relative_path(path_).c_str())};
if (not st) {
return false;
}
return smb_stat_get(st.get(), SMB_STAT_ISDIR) != 0U;
} catch (const std::exception &e) {
utils::error::handle_exception(function_name, e);
} catch (...) {
utils::error::handle_exception(function_name);
}
return false;
}
auto smb_directory::get_directory(std::string_view path) const
-> fs_directory_t {
static constexpr const std::string_view function_name{
static_cast<const char *>(__FUNCTION__),
};
try {
if (not session_) {
throw std::runtime_error("session not found|" + path_);
}
auto rel_path = smb_create_and_validate_relative_path(path_, path);
smb_stat_t st{smb_fstat(session_.get(), tid_, rel_path.c_str())};
if (not st) {
throw std::runtime_error("failed to stat directory|" + rel_path);
}
bool is_dir{smb_stat_get(st.get(), SMB_STAT_ISDIR) != 0U};
if (not is_dir) {
throw std::runtime_error("path is not a directory|" + rel_path);
}
return smb_directory_t{
new smb_directory{
smb_create_smb_path(path_, rel_path),
session_,
share_name_,
tid_,
},
};
} catch (const std::exception &e) {
utils::error::handle_exception(function_name, e);
} catch (...) {
utils::error::handle_exception(function_name);
}
return nullptr;
}
auto smb_directory::get_directories() const -> std::vector<fs_directory_t> {
static constexpr const std::string_view function_name{
static_cast<const char *>(__FUNCTION__),
};
try {
if (not session_) {
throw std::runtime_error("session not found|" + path_);
}
smb_stat_list_t list{
smb_find(session_.get(), tid_, smb_create_search_path(path_).c_str())};
if (not list) {
throw std::runtime_error("failed to get directory list|" + path_);
}
std::vector<fs_directory_t> ret{};
auto count = smb_stat_list_count(list.get());
for (std::size_t idx = 0U; idx < count; ++idx) {
auto st = smb_stat_list_at(list.get(), idx);
bool is_dir{smb_stat_get(st, SMB_STAT_ISDIR) != 0U};
if (not is_dir) {
continue;
}
std::string name{smb_stat_name(st)};
if (name == "." || name == "..") {
continue;
}
ret.emplace_back(smb_directory_t{
new smb_directory{
smb_create_smb_path(path_, name),
session_,
share_name_,
tid_,
},
});
}
return ret;
} catch (const std::exception &e) {
utils::error::handle_exception(function_name, e);
} catch (...) {
utils::error::handle_exception(function_name);
}
return {};
}
auto smb_directory::get_file(std::string_view path) const -> fs_file_t {
static constexpr const std::string_view function_name{
static_cast<const char *>(__FUNCTION__),
};
try {
if (not session_) {
throw std::runtime_error("session not found|" + path_);
}
auto rel_path = smb_create_and_validate_relative_path(path_, path);
smb_stat_t st{smb_fstat(session_.get(), tid_, rel_path.c_str())};
if (not st) {
throw std::runtime_error("failed to stat file|" + rel_path);
}
bool is_dir{smb_stat_get(st.get(), SMB_STAT_ISDIR) != 0U};
if (is_dir) {
throw std::runtime_error("path is not a file|" + rel_path);
}
return std::make_unique<smb_file>(
std::nullopt, smb_create_smb_path(path_, std::string{rel_path}),
session_, share_name_, tid_);
} catch (const std::exception &e) {
utils::error::handle_exception(function_name, e);
} catch (...) {
utils::error::handle_exception(function_name);
}
return nullptr;
}
auto smb_directory::get_files() const -> std::vector<fs_file_t> {
static constexpr const std::string_view function_name{
static_cast<const char *>(__FUNCTION__),
};
try {
if (not session_) {
throw std::runtime_error("session not found|" + path_);
}
smb_stat_list_t list{
smb_find(session_.get(), tid_, smb_create_search_path(path_).c_str())};
if (not list) {
throw std::runtime_error("failed to get file list|" + path_);
}
std::vector<fs_file_t> ret{};
auto count = smb_stat_list_count(list.get());
for (std::size_t idx = 0U; idx < count; ++idx) {
auto st = smb_stat_list_at(list.get(), idx);
bool is_dir{smb_stat_get(st, SMB_STAT_ISDIR) != 0U};
if (is_dir) {
continue;
}
std::string name{smb_stat_name(st)};
ret.emplace_back(std::make_unique<smb_file>(
std::nullopt, smb_create_smb_path(path_, name), session_, share_name_,
tid_));
}
return ret;
} catch (const std::exception &e) {
utils::error::handle_exception(function_name, e);
} catch (...) {
utils::error::handle_exception(function_name);
}
return {};
}
auto smb_directory::get_items() const -> std::vector<fs_item_t> {
static constexpr const std::string_view function_name{
static_cast<const char *>(__FUNCTION__),
};
try {
if (not session_) {
throw std::runtime_error("session not found|" + path_);
}
smb_stat_list_t list{
smb_find(session_.get(), tid_, smb_create_search_path(path_).c_str())};
if (not list) {
throw std::runtime_error("failed to get item list|" + path_);
}
std::vector<fs_item_t> ret{};
auto count = smb_stat_list_count(list.get());
for (std::size_t idx = 0U; idx < count; ++idx) {
auto st = smb_stat_list_at(list.get(), idx);
bool is_dir{smb_stat_get(st, SMB_STAT_ISDIR) != 0U};
std::string name{smb_stat_name(st)};
if (is_dir) {
if (name == "." || name == "..") {
continue;
}
ret.emplace_back(smb_directory_t{
new smb_directory{
path_ + '/' + name,
session_,
share_name_,
tid_,
},
});
continue;
}
ret.emplace_back(std::make_unique<smb_file>(
std::nullopt, smb_create_smb_path(path_, name), session_, share_name_,
tid_));
}
return ret;
} catch (const std::exception &e) {
utils::error::handle_exception(function_name, e);
} catch (...) {
utils::error::handle_exception(function_name);
}
return {};
}
auto smb_directory::is_symlink() const -> bool {
static constexpr const std::string_view function_name{
static_cast<const char *>(__FUNCTION__),
};
try {
if (not session_) {
throw std::runtime_error("session not found|" + path_);
}
} catch (const std::exception &e) {
utils::error::handle_exception(function_name, e);
} catch (...) {
utils::error::handle_exception(function_name);
}
return false;
}
auto smb_directory::move_to(std::string_view new_path) -> bool {
static constexpr const std::string_view function_name{
static_cast<const char *>(__FUNCTION__),
};
try {
if (not session_) {
throw std::runtime_error("session not found|" + path_);
}
throw std::runtime_error("failed to move directory|" + path_ + '|' +
std::string{new_path} + "|not implemented");
} catch (const std::exception &e) {
utils::error::handle_exception(function_name, e);
} catch (...) {
utils::error::handle_exception(function_name);
}
return false;
}
auto smb_directory::remove() -> bool {
static constexpr const std::string_view function_name{
static_cast<const char *>(__FUNCTION__),
};
try {
if (not session_) {
throw std::runtime_error("session not found|" + path_);
}
if (not exists()) {
return true;
}
return utils::retry_action([this]() -> bool {
try {
auto res = smb_directory_rm(session_.get(), tid_,
smb_create_relative_path(path_).c_str());
if (res != DSM_SUCCESS) {
throw std::runtime_error("failed to remove directory|" + path_ + '|' +
std::to_string(res));
}
return true;
} catch (const std::exception &e) {
utils::error::handle_exception(function_name, e);
} catch (...) {
utils::error::handle_exception(function_name);
}
return false;
});
} catch (const std::exception &e) {
utils::error::handle_exception(function_name, e);
} catch (...) {
utils::error::handle_exception(function_name);
}
return false;
}
auto smb_directory::remove_recursively() -> bool {
static constexpr const std::string_view function_name{
static_cast<const char *>(__FUNCTION__),
};
try {
if (not session_) {
throw std::runtime_error("session not found|" + path_);
}
if (not exists()) {
return true;
}
throw std::runtime_error("failed to remove directory recursively|" + path_ +
"|not implemented");
} catch (const std::exception &e) {
utils::error::handle_exception(function_name, e);
} catch (...) {
utils::error::handle_exception(function_name);
}
return false;
}
auto smb_directory::size(bool /* recursive */) const -> std::uint64_t {
static constexpr const std::string_view function_name{
static_cast<const char *>(__FUNCTION__),
};
try {
if (not session_) {
throw std::runtime_error("session not found|" + path_);
}
throw std::runtime_error("failed to get directory size|" + path_ +
"|not implemented");
} catch (const std::exception &e) {
utils::error::handle_exception(function_name, e);
} catch (...) {
utils::error::handle_exception(function_name);
}
return false;
}
} // namespace repertory::utils::file
#endif // defined(PROJECT_ENABLE_LIBDSM)

View File

@ -0,0 +1,460 @@
/*
Copyright <2018-2024> <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/file.hpp"
#include "utils/common.hpp"
#include "utils/error.hpp"
#include "utils/string.hpp"
#if defined(PROJECT_ENABLE_LIBDSM)
namespace repertory::utils::file {
void smb_file::close() {
if (fd_.has_value()) {
smb_fclose(session_.get(), *fd_);
fd_.reset();
}
}
auto smb_file::copy_to(std::string_view new_path,
bool overwrite) const -> bool {
static constexpr const std::string_view function_name{
static_cast<const char *>(__FUNCTION__),
};
try {
if (not session_) {
throw std::runtime_error("session not found|" + path_);
}
// auto to_path = utils::path::absolute(new_path);
throw std::runtime_error("failed to copy file|" + path_ + '|' +
std::string{new_path} + '|' +
std::to_string(overwrite) + "|not implemented");
} catch (const std::exception &e) {
utils::error::handle_exception(function_name, e);
} catch (...) {
utils::error::handle_exception(function_name);
}
return false;
}
auto smb_file::exists() const -> bool {
static constexpr const std::string_view function_name{
static_cast<const char *>(__FUNCTION__),
};
try {
if (not session_) {
throw std::runtime_error("session not found|" + path_);
}
smb_stat_t st{smb_fstat(session_.get(), tid_,
smb_create_relative_path(path_).c_str())};
if (not st) {
return false;
}
return smb_stat_get(st.get(), SMB_STAT_ISDIR) == 0U;
} catch (const std::exception &e) {
utils::error::handle_exception(function_name, e);
} catch (...) {
utils::error::handle_exception(function_name);
}
return false;
}
void smb_file::flush() const {
static constexpr const std::string_view function_name{
static_cast<const char *>(__FUNCTION__),
};
try {
throw std::runtime_error("failed to flush file|" + path_ +
"|not implemented");
} catch (const std::exception &e) {
utils::error::handle_exception(function_name, e);
} catch (...) {
utils::error::handle_exception(function_name);
}
}
auto smb_file::get_time(smb_session *session, smb_tid tid, std::string path,
time_type type) -> std::optional<std::uint64_t> {
static constexpr const std::string_view function_name{
static_cast<const char *>(__FUNCTION__),
};
try {
if (session == nullptr) {
throw std::runtime_error("session not found|" + path);
}
auto rel_path = smb_create_relative_path(path);
smb_stat_t st{smb_fstat(session, tid, rel_path.c_str())};
if (not st) {
throw std::runtime_error("failed to stat directory|" + rel_path);
}
switch (type) {
case time_type::accessed:
return smb_stat_get(st.get(), SMB_STAT_ATIME);
case time_type::created:
return smb_stat_get(st.get(), SMB_STAT_CTIME);
case time_type::modified:
return smb_stat_get(st.get(), SMB_STAT_MTIME);
case time_type::written:
return smb_stat_get(st.get(), SMB_STAT_WTIME);
}
} catch (const std::exception &e) {
utils::error::handle_exception(function_name, e);
} catch (...) {
utils::error::handle_exception(function_name);
}
return std::nullopt;
}
auto smb_file::is_symlink() const -> bool {
static constexpr const std::string_view function_name{
static_cast<const char *>(__FUNCTION__),
};
try {
if (not session_) {
throw std::runtime_error("session not found|" + path_);
}
} catch (const std::exception &e) {
utils::error::handle_exception(function_name, e);
} catch (...) {
utils::error::handle_exception(function_name);
}
return false;
}
auto smb_file::move_to(std::string_view new_path) -> bool {
static constexpr const std::string_view function_name{
static_cast<const char *>(__FUNCTION__),
};
try {
if (utils::string::begins_with(new_path, "//")) {
throw std::runtime_error("failed to move file|" + path_ + '|' +
std::string{new_path} +
"|new path must be in same share");
}
auto from_path = smb_create_relative_path(path_);
auto to_path = smb_create_and_validate_relative_path(
utils::string::begins_with(new_path, "/") ? smb_get_root_path(path_)
: smb_get_parent_path(path_),
new_path);
auto was_open{false};
if (fd_.has_value()) {
close();
was_open = true;
}
auto res = smb_tree_connect(session_.get(), share_name_.c_str(), &tid_);
if (res != DSM_SUCCESS) {
throw std::runtime_error("failed to connect to share|" + share_name_ +
'|' + std::to_string(res));
}
res = smb_file_mv(session_.get(), tid_, from_path.c_str(), to_path.c_str());
if (res != DSM_SUCCESS) {
throw std::runtime_error("failed to move file|" + path_ + '|' +
from_path + '|' + to_path + '|' +
std::to_string(res));
}
path_ = smb_create_smb_path(path_, to_path);
if (was_open) {
return open(read_only_);
}
return true;
} catch (const std::exception &e) {
utils::error::handle_exception(function_name, e);
} catch (...) {
utils::error::handle_exception(function_name);
}
return false;
}
auto smb_file::open(bool read_only) -> bool {
static constexpr const std::string_view function_name{
static_cast<const char *>(__FUNCTION__),
};
try {
if (fd_.has_value()) {
if (read_only == read_only_) {
return true;
}
close();
}
auto rel_path = smb_create_relative_path(path_);
auto res = smb_tree_connect(session_.get(), share_name_.c_str(), &tid_);
if (res != DSM_SUCCESS) {
throw std::runtime_error("failed to connect to share|" + share_name_ +
'|' + std::to_string(res));
}
smb_fd fd{};
res = smb_fopen(session_.get(), tid_, rel_path.c_str(),
read_only ? SMB_MOD_RO : SMB_MOD_RW2, &fd);
if (res != DSM_SUCCESS) {
throw std::runtime_error("failed to open file|" + path_ + '|' + rel_path +
'|' + utils::string::from_bool(read_only) + '|' +
std::to_string(res));
}
fd_ = fd;
read_only_ = read_only;
return true;
} catch (const std::exception &e) {
utils::error::handle_exception(function_name, e);
} catch (...) {
utils::error::handle_exception(function_name);
}
return false;
}
auto smb_file::read(unsigned char *data, std::size_t to_read,
std::uint64_t offset, std::size_t *total_read) -> bool {
static constexpr const std::string_view function_name{
static_cast<const char *>(__FUNCTION__),
};
try {
if (total_read != nullptr) {
(*total_read) = 0U;
}
if (not fd_.has_value()) {
throw std::runtime_error("failed to read file|" + path_ +
"|file not open");
}
auto res = smb_fseek(session_.get(), *fd_, static_cast<off_t>(offset),
SMB_SEEK_SET);
if (res == -1) {
throw std::runtime_error("failed to seek file|" + path_ + '|' +
std::to_string(offset) + '|' +
std::to_string(res));
}
std::size_t bytes_read{0U};
while (bytes_read != to_read) {
res = smb_fread(session_.get(), *fd_, &data[bytes_read],
to_read - bytes_read);
if (res == -1) {
throw std::runtime_error("failed to read file|" + path_ + '|' +
std::to_string(to_read) + '|' +
std::to_string(res));
}
if (res == 0) {
break;
}
bytes_read += static_cast<std::size_t>(res);
}
if (total_read != nullptr) {
(*total_read) = bytes_read;
}
return true;
} catch (const std::exception &e) {
utils::error::handle_exception(function_name, e);
} catch (...) {
utils::error::handle_exception(function_name);
}
return false;
}
auto smb_file::remove() -> bool {
static constexpr const std::string_view function_name{
static_cast<const char *>(__FUNCTION__),
};
try {
if (not exists()) {
return true;
}
close();
return utils::retry_action([this]() -> bool {
try {
auto res = smb_tree_connect(session_.get(), share_name_.c_str(), &tid_);
if (res != DSM_SUCCESS) {
throw std::runtime_error("failed to connect to share|" + share_name_ +
'|' + std::to_string(res));
}
auto rel_path = smb_create_relative_path(path_);
res = smb_file_rm(session_.get(), tid_, rel_path.c_str());
if (res != DSM_SUCCESS) {
throw std::runtime_error(
"failed to remove file|" + path_ + '|' + rel_path + '|' +
std::to_string(res) + '|' +
std::to_string(smb_session_get_nt_status(session_.get())));
}
return true;
} catch (const std::exception &e) {
utils::error::handle_exception(function_name, e);
} catch (...) {
utils::error::handle_exception(function_name);
}
return false;
});
} catch (const std::exception &e) {
utils::error::handle_exception(function_name, e);
} catch (...) {
utils::error::handle_exception(function_name);
}
return false;
}
auto smb_file::size() const -> std::optional<std::uint64_t> {
static constexpr const std::string_view function_name{
static_cast<const char *>(__FUNCTION__),
};
try {
if (not session_) {
throw std::runtime_error("session not found|" + path_);
}
auto rel_path = smb_create_relative_path(path_);
smb_stat_t st{smb_fstat(session_.get(), tid_, rel_path.c_str())};
if (not st) {
throw std::runtime_error("failed to stat directory|" + rel_path);
}
return smb_stat_get(st.get(), SMB_STAT_SIZE);
} catch (const std::exception &e) {
utils::error::handle_exception(function_name, e);
} catch (...) {
utils::error::handle_exception(function_name);
}
return std::nullopt;
}
auto smb_file::truncate(std::size_t size) -> bool {
static constexpr const std::string_view function_name{
static_cast<const char *>(__FUNCTION__),
};
try {
throw std::runtime_error("failed to truncate file|" + path_ + '|' +
std::to_string(size) + "|not implemented");
} catch (const std::exception &e) {
utils::error::handle_exception(function_name, e);
} catch (...) {
utils::error::handle_exception(function_name);
}
return false;
}
auto smb_file::write(const unsigned char *data, std::size_t to_write,
std::size_t offset, std::size_t *total_written) -> bool {
static constexpr const std::string_view function_name{
static_cast<const char *>(__FUNCTION__),
};
try {
if (total_written != nullptr) {
(*total_written) = 0U;
}
if (not fd_.has_value()) {
throw std::runtime_error("failed to write file|" + path_ +
"|file not open");
}
auto res = smb_fseek(session_.get(), *fd_, static_cast<off_t>(offset),
SMB_SEEK_SET);
if (res == -1) {
throw std::runtime_error("failed to seek file|" + path_ + '|' +
std::to_string(offset) + '|' +
std::to_string(res));
}
std::size_t bytes_written{0U};
while (bytes_written != to_write) {
res = smb_fwrite(session_.get(), *fd_,
const_cast<unsigned char *>(&data[bytes_written]),
to_write - bytes_written);
if (res == -1) {
throw std::runtime_error("failed to write file|" + path_ + '|' +
std::to_string(to_write) + '|' +
std::to_string(res));
}
if (res == 0) {
break;
}
bytes_written += static_cast<std::size_t>(res);
}
if (total_written != nullptr) {
(*total_written) = bytes_written;
}
return true;
} catch (const std::exception &e) {
utils::error::handle_exception(function_name, e);
} catch (...) {
utils::error::handle_exception(function_name);
}
return false;
}
} // namespace repertory::utils::file
#endif // defined(PROJECT_ENABLE_LIBDSM)

View File

@ -0,0 +1,59 @@
/*
Copyright <2018-2024> <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/file.hpp"
namespace repertory::utils::file {
// auto thread_file::attach_file(native_handle handle,
// bool read_only) -> fs_file_t {}
auto thread_file::attach_file(fs_file_t file) -> fs_file_t {}
auto thread_file::open_file(std::string_view path,
bool read_only) -> fs_file_t {}
auto thread_file::open_or_create_file(std::string_view path,
bool read_only) -> fs_file_t {}
thread_file::thread_file(fs_file_t file) : file_(std::move(file)) {}
void thread_file::close() {}
auto thread_file::copy_to(std::string_view new_path,
bool overwrite) const -> bool {}
void thread_file::flush() const {}
auto thread_file::move_to(std::string_view path) -> bool {}
auto thread_file::read(unsigned char *data, std::size_t to_read,
std::uint64_t offset, std::size_t *total_read) -> bool {}
auto thread_file::remove() -> bool {}
auto thread_file::truncate(std::size_t size) -> bool {}
auto thread_file::write(const unsigned char *data, std::size_t to_write,
std::size_t offset,
std::size_t *total_written) -> bool {}
auto thread_file::size() const -> std::optional<std::uint64_t> {}
} // namespace repertory::utils::file

View File

@ -23,47 +23,140 @@
#if defined(PROJECT_ENABLE_LIBSODIUM)
namespace {} // namespace
namespace repertory::utils::encryption {
auto create_hash_blake2b_256(std::string_view data) -> hash_256_t {
return create_hash_blake2b_t<char, hash_256_t>(data);
return create_hash_blake2b_t<hash_256_t>(
reinterpret_cast<const unsigned char *>(data.data()), data.size());
}
auto create_hash_blake2b_256(std::wstring_view data) -> hash_256_t {
return create_hash_blake2b_t<wchar_t, hash_256_t>(data);
return create_hash_blake2b_t<hash_256_t>(
reinterpret_cast<const unsigned char *>(data.data()),
data.size() * sizeof(wchar_t));
}
auto create_hash_blake2b_256(const data_buffer &data) -> hash_256_t {
return create_hash_blake2b_t<hash_256_t>(
reinterpret_cast<const unsigned char *>(data.data()),
data.size() * sizeof(data_buffer::value_type));
}
auto create_hash_blake2b_384(std::string_view data) -> hash_384_t {
return create_hash_blake2b_t<char, hash_384_t>(data);
return create_hash_blake2b_t<hash_384_t>(
reinterpret_cast<const unsigned char *>(data.data()), data.size());
}
auto create_hash_blake2b_384(std::wstring_view data) -> hash_384_t {
return create_hash_blake2b_t<wchar_t, hash_384_t>(data);
return create_hash_blake2b_t<hash_384_t>(
reinterpret_cast<const unsigned char *>(data.data()),
data.size() * sizeof(wchar_t));
}
auto create_hash_blake2b_384(const data_buffer &data) -> hash_384_t {
return create_hash_blake2b_t<hash_384_t>(
reinterpret_cast<const unsigned char *>(data.data()),
data.size() * sizeof(data_buffer::value_type));
}
auto create_hash_blake2b_512(std::string_view data) -> hash_512_t {
return create_hash_blake2b_t<char, hash_512_t>(data);
return create_hash_blake2b_t<hash_512_t>(
reinterpret_cast<const unsigned char *>(data.data()), data.size());
}
auto create_hash_blake2b_512(std::wstring_view data) -> hash_512_t {
return create_hash_blake2b_t<wchar_t, hash_512_t>(data);
return create_hash_blake2b_t<hash_512_t>(
reinterpret_cast<const unsigned char *>(data.data()),
data.size() * sizeof(wchar_t));
}
auto create_hash_blake2b_512(const data_buffer &data) -> hash_512_t {
return create_hash_blake2b_t<hash_512_t>(
reinterpret_cast<const unsigned char *>(data.data()),
data.size() * sizeof(data_buffer::value_type));
}
auto create_hash_sha256(std::string_view data) -> hash_256_t {
return create_hash_sha256_t<char>(data);
return create_hash_sha256(
reinterpret_cast<const unsigned char *>(data.data()), data.size());
}
auto create_hash_sha256(std::wstring_view data) -> hash_256_t {
return create_hash_sha256_t<wchar_t>(data);
return create_hash_sha256(
reinterpret_cast<const unsigned char *>(data.data()),
data.size() * sizeof(wchar_t));
}
auto create_hash_sha256(const data_buffer &data) -> hash_256_t {
return create_hash_sha256(
reinterpret_cast<const unsigned char *>(data.data()),
data.size() * sizeof(data_buffer::value_type));
}
auto create_hash_sha512(std::string_view data) -> hash_512_t {
return create_hash_sha512_t<char>(data);
return create_hash_sha512(
reinterpret_cast<const unsigned char *>(data.data()), data.size());
}
auto create_hash_sha512(std::wstring_view data) -> hash_512_t {
return create_hash_sha512_t<wchar_t>(data);
return create_hash_sha512(
reinterpret_cast<const unsigned char *>(data.data()),
data.size() * sizeof(wchar_t));
}
auto create_hash_sha512(const data_buffer &data) -> hash_512_t {
return create_hash_sha512(
reinterpret_cast<const unsigned char *>(data.data()),
data.size() * sizeof(data_buffer::value_type));
}
auto create_hash_sha512(const unsigned char *data,
std::size_t data_size) -> hash_512_t {
hash_512_t hash{};
crypto_hash_sha512_state state{};
auto res = crypto_hash_sha512_init(&state);
if (res != 0) {
throw std::runtime_error("failed to initialize sha-512|" +
std::to_string(res));
}
res = crypto_hash_sha512_update(&state, data, data_size);
if (res != 0) {
throw std::runtime_error("failed to update sha-512|" + std::to_string(res));
}
res = crypto_hash_sha512_final(&state, hash.data());
if (res != 0) {
throw std::runtime_error("failed to finalize sha-512|" +
std::to_string(res));
}
return hash;
}
auto create_hash_sha256(const unsigned char *data,
std::size_t data_size) -> hash_256_t {
hash_256_t hash{};
crypto_hash_sha256_state state{};
auto res = crypto_hash_sha256_init(&state);
if (res != 0) {
throw std::runtime_error("failed to initialize sha-256|" +
std::to_string(res));
}
res = crypto_hash_sha256_update(&state, data, data_size);
if (res != 0) {
throw std::runtime_error("failed to update sha-256|" + std::to_string(res));
}
res = crypto_hash_sha256_final(&state, hash.data());
if (res != 0) {
throw std::runtime_error("failed to finalize sha-256|" +
std::to_string(res));
}
return hash;
}
} // namespace repertory::utils::encryption

View File

@ -23,18 +23,11 @@
#include "utils/common.hpp"
#include "utils/error.hpp"
#include "utils/file.hpp"
#include "utils/string.hpp"
#include "utils/unix.hpp"
namespace {
static const std::string directory_seperator_str{
repertory::utils::path::directory_seperator,
};
static const std::wstring directory_seperator_str_w{
repertory::utils::path::directory_seperator_w,
};
[[nodiscard]] auto resolve(std::string path) -> std::string {
#if defined(_WIN32)
if (repertory::utils::string::contains(path, "~") ||
@ -52,12 +45,17 @@ static const std::wstring directory_seperator_str_w{
#else // !defined (_WIN32)
if (repertory::utils::string::contains(path, "~")) {
std::string home{};
repertory::utils::use_getpwuid(getuid(), [&home](struct passwd *pw) {
home = (pw->pw_dir ? pw->pw_dir : "");
if (home.empty() || ((home == "/") && (getuid() != 0))) {
home = repertory::utils::path::combine("/home", {pw->pw_name});
}
});
auto res =
repertory::utils::use_getpwuid(getuid(), [&home](struct passwd *pw) {
home = (pw->pw_dir ? pw->pw_dir : "");
if (home.empty() ||
((home == repertory::utils::path::slash) && (getuid() != 0))) {
home = repertory::utils::path::combine("/home", {pw->pw_name});
}
});
if (res) {
throw std::runtime_error("failed to getpwuid: " + res.reason);
}
return repertory::utils::string::replace(path, "~", home);
}
@ -70,42 +68,59 @@ static const std::wstring directory_seperator_str_w{
namespace repertory::utils::path {
auto absolute(std::string_view path) -> std::string {
std::string abs_path{path};
abs_path = resolve(abs_path);
format_path(abs_path, directory_seperator, not_directory_seperator);
if (abs_path.empty()) {
return abs_path;
}
abs_path = finalize(resolve(abs_path));
#if defined(_WIN32)
if (not abs_path.empty() && ::PathIsRelativeA(abs_path.c_str())) {
std::string temp;
temp.resize(MAX_PATH + 1);
abs_path = _fullpath(temp.data(), abs_path.c_str(), MAX_PATH);
if (not utils::string::contains(abs_path, dot)) {
return abs_path;
}
std::string temp;
temp.resize(repertory::max_path_length + 1);
::GetFullPathNameA(abs_path.c_str(), static_cast<DWORD>(temp.size()),
temp.data(), nullptr);
#else // !defined(_WIN32)
if (not abs_path.empty() && (abs_path.at(0U) != '/')) {
auto found{false};
std::string tmp{abs_path};
do {
auto *res = realpath(tmp.c_str(), nullptr);
if (res != nullptr) {
abs_path = res + std::string{directory_seperator} +
abs_path.substr(tmp.size());
free(res);
found = true;
} else if (tmp == ".") {
found = true;
} else {
tmp = dirname(tmp.data());
}
} while (not found);
if (not utils::string::contains(abs_path, dot) ||
utils::string::begins_with(abs_path, slash)) {
return abs_path;
}
auto found{false};
std::string tmp{abs_path};
do {
auto *res = realpath(tmp.c_str(), nullptr);
if (res != nullptr) {
abs_path =
res + std::string{directory_seperator} + abs_path.substr(tmp.size());
free(res);
found = true;
} else if (tmp == dot) {
found = true;
} else {
tmp = dirname(tmp.data());
}
} while (not found);
#endif // defined(_WIN32)
return format_path(abs_path, directory_seperator, not_directory_seperator);
return finalize(abs_path);
}
auto absolute(std::wstring_view path) -> std::wstring {
return utils::string::from_utf8(absolute(utils::string::to_utf8(path)));
}
auto exists(std::string_view path) -> bool {
return utils::file::file{path}.exists() ||
utils::file::directory{path}.exists();
}
auto exists(std::wstring_view path) -> bool {
return exists(utils::string::to_utf8(path));
}
auto find_program_in_path(const std::string &name_without_extension)
-> std::string {
static std::mutex mtx{};
@ -137,12 +152,12 @@ auto find_program_in_path(const std::string &name_without_extension)
static constexpr const auto split_char = ':';
#endif // defined(_WIN32)
const auto search_path_list = utils::string::split(path, split_char, false);
auto search_path_list = utils::string::split(path, split_char, false);
for (auto &&search_path : search_path_list) {
for (auto &&extension : extension_list) {
auto exec_path = combine(
search_path, {name_without_extension + std::string{extension}});
if (std::filesystem::exists(exec_path)) {
if (utils::file::file(exec_path).exists()) {
found_items[name_without_extension] = exec_path;
return exec_path;
}
@ -158,41 +173,66 @@ find_program_in_path(std::wstring_view name_without_extension) -> std::wstring {
find_program_in_path(utils::string::to_utf8(name_without_extension)));
}
auto get_parent_directory(std::string_view path) -> std::string {
auto ret = std::filesystem::path{path}.parent_path().string();
#if !defined(_WIN32)
if (ret == ".") {
ret = "/";
}
#endif // !defined(_WIN32)
auto get_parent_path(std::string_view path) -> std::string {
auto abs_path = absolute(path);
return absolute(ret);
#if defined(_WIN32)
::PathRemoveFileSpecA(abs_path.data());
abs_path = abs_path.c_str();
#else // !defined(_WIN32)
abs_path = std::filesystem::path{abs_path}.parent_path().string();
#endif // defined(_WIN32)
return finalize(abs_path);
}
auto get_parent_directory(std::wstring_view path) -> std::wstring {
auto get_parent_path(std::wstring_view path) -> std::wstring {
return utils::string::from_utf8(
get_parent_directory(utils::string::to_utf8(path)));
get_parent_path(utils::string::to_utf8(path)));
}
auto is_trash_directory(std::string_view path) -> bool {
auto trash_path = utils::string::to_lower(absolute(path));
return utils::string::begins_with(trash_path,
directory_seperator_str + ".trash-") ||
utils::string::begins_with(trash_path,
directory_seperator_str + ".trashes") ||
utils::string::begins_with(trash_path,
directory_seperator_str + "$recycle.bin");
auto get_relative_path(std::string_view path,
std::string_view root_path) -> std::string {
auto abs_path = absolute(path);
auto abs_root_path =
absolute(root_path) + std::string{get_directory_seperator<char>()};
#if defined(_WIN32)
if (utils::string::to_lower(abs_path).starts_with(
utils::string::to_lower(abs_root_path))) {
#else // !defined(_WIN32)
if (abs_path.starts_with(abs_root_path)) {
#endif // defined(_WIN32)
return abs_path.substr(abs_root_path.size());
}
return abs_path;
}
auto is_trash_directory(std::wstring_view path) -> bool {
return is_trash_directory(utils::string::to_utf8(path));
auto get_relative_path(std::wstring_view path,
std::wstring_view root_path) -> std::wstring {
return utils::string::from_utf8(get_relative_path(
utils::string::to_utf8(path), utils::string::to_utf8(root_path)));
}
auto contains_trash_directory(std::string_view path) -> bool {
auto parts = utils::string::split(utils::string::to_lower(absolute(path)),
get_directory_seperator<char>(), false);
return std::find_if(parts.begin(), parts.end(), [](auto &&part) -> bool {
return utils::string::begins_with(part, ".trash-") ||
part == ".trashes" || part == "$recycle.bin";
}) != parts.end();
}
auto contains_trash_directory(std::wstring_view path) -> bool {
return contains_trash_directory(utils::string::to_utf8(path));
}
auto make_file_uri(std::string_view path) -> std::string {
auto abs_path = absolute(path);
#if defined(_WIN32)
utils::string::replace(abs_path, '\\', '/');
abs_path = '/' + abs_path;
utils::string::replace(abs_path, backslash, slash);
abs_path = std::string{slash} + abs_path;
#endif // defined(_WIN32)
return "file://" + abs_path;
}
@ -201,31 +241,11 @@ auto make_file_uri(std::wstring_view path) -> std::wstring {
return utils::string::from_utf8(make_file_uri(utils::string::to_utf8(path)));
}
auto remove_file_name(std::string_view path) -> std::string {
auto abs_path = absolute(path);
#if defined(_WIN32)
::PathRemoveFileSpecA(abs_path.data());
abs_path = abs_path.c_str();
#else // !defined(_WIN32)
if (abs_path != "/") {
auto idx{abs_path.size() - 1U};
while ((idx != 0U) && (abs_path.at(idx) != '/')) {
idx--;
}
abs_path = (idx > 0U) ? absolute(abs_path.substr(0U, idx)) : "/";
}
#endif // defined(_WIN32)
return abs_path;
}
auto strip_to_file_name(std::string path) -> std::string {
#if defined(_WIN32)
return ::PathFindFileNameA(path.c_str());
#else // !defined(_WIN32)
return utils::string::contains(path, "/") ? basename(path.data()) : path;
return utils::string::contains(path, slash) ? basename(path.data()) : path;
#endif // defined(_WIN32)
}

View File

@ -22,32 +22,6 @@
#include "utils/time.hpp"
namespace repertory::utils::time {
#if defined(_WIN32)
// https://www.frenk.com/2009/12/convert-filetime-to-unix-timestamp/
auto filetime_to_unix_time(const FILETIME &file_time) -> std::uint64_t {
LARGE_INTEGER date{};
date.HighPart = static_cast<LONG>(file_time.dwHighDateTime);
date.LowPart = file_time.dwLowDateTime;
date.QuadPart -= 116444736000000000LL;
return static_cast<std::uint64_t>(date.QuadPart) * 100ULL;
}
#endif // defined(_WIN32)
auto get_file_time_now() -> std::uint64_t {
#if defined(_WIN32)
SYSTEMTIME sys_time{};
::GetSystemTime(&sys_time);
FILETIME file_time{};
::SystemTimeToFileTime(&sys_time, &file_time);
return static_cast<std::uint64_t>(
(reinterpret_cast<LARGE_INTEGER *>(&file_time))->QuadPart);
#else // !defined(_WIN32)
return get_time_now();
#endif // defined(_WIN32)
}
void get_local_time_now(struct tm &local_time) {
std::memset(&local_time, 0, sizeof(local_time));
@ -61,21 +35,10 @@ void get_local_time_now(struct tm &local_time) {
}
auto get_time_now() -> std::uint64_t {
#if defined(_WIN32)
return static_cast<std::uint64_t>(
std::chrono::system_clock::to_time_t(std::chrono::system_clock::now()));
#endif // defined(_WIN32)
#if defined(__APPLE__)
return std::chrono::nanoseconds(
std::chrono::system_clock::now().time_since_epoch())
.count();
#else // !defined(__APPLE__)
return static_cast<std::uint64_t>(
std::chrono::nanoseconds(
std::chrono::high_resolution_clock::now().time_since_epoch())
std::chrono::duration_cast<std::chrono::nanoseconds>(
std::chrono::system_clock::now().time_since_epoch())
.count());
#endif // defined(__APPLE__)
}
#if defined(_WIN32)
@ -91,17 +54,35 @@ auto strptime(const char *s, const char *f, struct tm *tm) -> const char * {
return reinterpret_cast<const char *>(s + input.tellg());
}
auto time64_to_unix_time(const __time64_t &time) -> std::uint64_t {
return static_cast<std::uint64_t>(time * NANOS_PER_SECOND);
}
// https://www.frenk.com/2009/12/convert-filetime-to-unix-timestamp/
auto unix_time_to_filetime(std::uint64_t unix_time) -> FILETIME {
const auto win_time = (unix_time / 100ULL) + 116444736000000000ULL;
auto win_time = unix_time_to_windows_time(unix_time);
FILETIME file_time{};
file_time.dwHighDateTime = static_cast<DWORD>(win_time >> 32U);
file_time.dwLowDateTime = win_time & 0xFFFFFFFF;
return file_time;
}
auto windows_file_time_to_unix_time(FILETIME win_time) -> std::uint64_t {
return windows_time_to_unix_time(
(static_cast<std::uint64_t>(win_time.dwHighDateTime) << 32ULL) |
static_cast<std::uint64_t>(win_time.dwLowDateTime));
}
auto windows_time_t_to_unix_time(__time64_t win_time) -> std::uint64_t {
return static_cast<std::uint64_t>(
std::chrono::duration_cast<std::chrono::nanoseconds>(
std::chrono::system_clock::from_time_t(win_time).time_since_epoch())
.count());
}
#endif // defined(_WIN32)
auto unix_time_to_windows_time(std::uint64_t unix_time) -> std::uint64_t {
return (unix_time / WIN32_TIME_NANOS_PER_TICK) + WIN32_TIME_CONVERSION;
}
auto windows_time_to_unix_time(std::uint64_t win_time) -> std::uint64_t {
return (win_time * WIN32_TIME_NANOS_PER_TICK) - WIN32_TIME_CONVERSION;
}
} // namespace repertory::utils::time

View File

@ -76,7 +76,7 @@ auto run_process_elevated(std::vector<const char *> args) -> int {
}
std::string full_path;
full_path.resize(MAX_PATH + 1);
full_path.resize(repertory::max_path_length + 1);
if (::GetModuleFileNameA(nullptr, full_path.data(), MAX_PATH)) {
SHELLEXECUTEINFOA sei{};