v2.0.2-rc (#27)
Some checks reported errors
BlockStorage/repertory/pipeline/head Something is wrong with the build of this commit

## v2.0.2-rc

### BREAKING CHANGES

* Refactored `config.json` - will need to verify configuration settings prior to mounting

### Issues

* \#12 \[Unit Test\] Complete all providers unit tests
* \#14 \[Unit Test\] SQLite mini-ORM unit tests and cleanup
* \#16 Add support for bucket name in Sia provider
* \#17 Update to common c++ build system
  * A single 64-bit Linux Jenkins server is used to build all Linux and Windows versions
  * All dependency sources are now included
  * MSVC is no longer supported
  * MSYS2 is required for building Windows binaries on Windows
  * OS X support is temporarily disabled
* \#19 \[bug\] Rename file is broken for files that are existing
* \#23 \[bug\] Incorrect file size displayed while upload is pending
* \#24 RocksDB implementations should be transactional
* \#25 Writes should block when maximum cache size is reached
* \#26 Complete ring buffer and direct download support

### Changes from v2.0.1-rc

* Ability to choose between RocksDB and SQLite databases
* Added direct reads and implemented download fallback
* Corrected file times on S3 and Sia providers
* Corrected handling of `chown()` and `chmod()`
* Fixed erroneous download of chunks after resize

Reviewed-on: #27
This commit is contained in:
2024-12-28 15:56:40 -06:00
parent 1b8de3b097
commit 8dd46b8ad8
790 changed files with 49979 additions and 417734 deletions

View File

@ -0,0 +1,174 @@
/*
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/common.hpp"
#include "utils/error.hpp"
#include "utils/string.hpp"
namespace repertory::utils {
auto compare_version_strings(std::string version1,
std::string version2) -> std::int32_t {
if (utils::string::contains(version1, "-")) {
version1 = utils::string::split(version1, '-', true)[0U];
}
if (utils::string::contains(version2, "-")) {
version2 = utils::string::split(version2, '-', true)[0U];
}
auto nums1 = utils::string::split(version1, '.', true);
auto nums2 = utils::string::split(version2, '.', true);
while (nums1.size() > nums2.size()) {
nums2.emplace_back("0");
}
while (nums2.size() > nums1.size()) {
nums1.emplace_back("0");
}
for (std::size_t idx = 0U; idx < nums1.size(); idx++) {
auto int1 = utils::string::to_uint32(nums1[idx]);
auto int2 = utils::string::to_uint32(nums2[idx]);
auto res = std::memcmp(&int1, &int2, sizeof(int1));
if (res != 0) {
return res;
}
}
return 0;
}
auto compare_version_strings(std::wstring_view version1,
std::wstring_view version2) -> std::int32_t {
return compare_version_strings(utils::string::to_utf8(version1),
utils::string::to_utf8(version2));
}
#if defined(PROJECT_ENABLE_STDUUID)
auto create_uuid_string() -> std::string {
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));
std::seed_seq seq(std::begin(seed_data), std::end(seed_data));
std::mt19937 generator(seq);
uuids::uuid_random_generator gen{generator};
return uuids::to_string(gen());
}
auto create_uuid_wstring() -> std::wstring {
return utils::string::from_utf8(create_uuid_string());
}
#endif // defined(PROJECT_ENABLE_STDUUID)
auto generate_random_string(std::size_t length) -> std::string {
std::string ret;
if (length == 0U) {
return ret;
}
ret.resize(length);
for (auto &ch : ret) {
std::array<unsigned int, 3U> random_list{
generate_random_between(0U, 57U),
generate_random_between(65U, 90U),
generate_random_between(97U, 255U),
};
ch = static_cast<char>(
random_list.at(generate_random_between(0U, 2U)) % 74U + 48U);
}
return ret;
}
auto generate_random_wstring(std::size_t length) -> std::wstring {
return utils::string::from_utf8(generate_random_string(length));
}
auto get_environment_variable(std::string_view variable) -> std::string {
static std::mutex mtx{};
mutex_lock lock{mtx};
const auto *val = std::getenv(std::string{variable}.c_str());
return std::string{val == nullptr ? "" : val};
}
auto get_environment_variable(std::wstring_view variable) -> std::wstring {
return utils::string::from_utf8(
get_environment_variable(utils::string::to_utf8(variable)));
}
#if defined(PROJECT_ENABLE_BOOST)
auto get_next_available_port(std::uint16_t first_port,
std::uint16_t &available_port) -> bool {
if (first_port == 0U) {
return false;
}
using namespace boost::asio;
using ip::tcp;
boost::system::error_code error_code{};
while (first_port != 0U) {
io_context ctx{};
tcp::acceptor acceptor(ctx);
acceptor.open(tcp::v4(), error_code) ||
acceptor.bind({tcp::v4(), first_port}, error_code);
if (not error_code) {
break;
}
++first_port;
}
if (not error_code) {
available_port = 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 {
REPERTORY_USES_FUNCTION_NAME();
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,250 @@
/*
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.
*/
#if defined(PROJECT_ENABLE_SQLITE)
#include "utils/db/sqlite/db_common.hpp"
#include "utils/common.hpp"
#include "utils/error.hpp"
namespace repertory::utils::db::sqlite {
void sqlite3_deleter::operator()(sqlite3 *db3) const {
REPERTORY_USES_FUNCTION_NAME();
if (db3 == nullptr) {
return;
}
std::string err_msg;
if (not execute_sql(*db3, "VACUUM;", err_msg)) {
utils::error::handle_error(function_name,
utils::error::create_error_message({
"failed to vacuum database",
err_msg,
}));
}
if (not utils::retry_action([&db3]() -> bool {
auto res = sqlite3_close_v2(db3);
if (res == SQLITE_OK) {
return true;
}
auto &&err_str = sqlite3_errstr(res);
utils::error::handle_error(
function_name,
utils::error::create_error_message({
"failed to close database",
(err_str == nullptr ? std::to_string(res) : err_str),
}));
return false;
})) {
repertory::utils::error::handle_error(function_name,
"failed to close database");
}
}
db_result::db_column::db_column(std::int32_t index, std::string name,
db_types_t value) noexcept
: index_(index), name_(std::move(name)), value_(std::move(value)) {}
#if defined(PROJECT_ENABLE_JSON)
auto db_result::db_column::get_value_as_json() const -> nlohmann::json {
return std::visit(
overloaded{
[this](std::int64_t value) -> auto {
return nlohmann::json({{name_, value}});
},
[](auto &&value) -> auto { return nlohmann::json::parse(value); },
},
value_);
}
#endif // defined(PROJECT_ENABLE_JSON)
db_result::db_row::db_row(std::shared_ptr<context> ctx) {
REPERTORY_USES_FUNCTION_NAME();
auto column_count = sqlite3_column_count(ctx->stmt.get());
for (std::int32_t col = 0; col < column_count; col++) {
std::string name{sqlite3_column_name(ctx->stmt.get(), col)};
auto column_type = sqlite3_column_type(ctx->stmt.get(), col);
db_types_t value;
switch (column_type) {
case SQLITE_INTEGER: {
value = sqlite3_column_int64(ctx->stmt.get(), col);
} break;
case SQLITE_TEXT: {
const auto *text = reinterpret_cast<const char *>(
sqlite3_column_text(ctx->stmt.get(), col));
value = std::string(text == nullptr ? "" : text);
} break;
default:
throw utils::error::create_exception(function_name,
{
"column type not implemented",
std::to_string(column_type),
});
}
columns_[name] = db_column{col, name, value};
}
}
auto db_result::db_row::get_columns() const -> std::vector<db_column> {
std::vector<db_column> ret;
for (const auto &item : columns_) {
ret.push_back(item.second);
}
return ret;
}
auto db_result::db_row::get_column(std::int32_t index) const -> db_column {
auto iter = std::find_if(
columns_.begin(), columns_.end(),
[&index](auto &&col) -> bool { return col.second.get_index() == index; });
if (iter == columns_.end()) {
throw std::out_of_range("");
}
return iter->second;
}
auto db_result::db_row::get_column(std::string name) const -> db_column {
return columns_.at(name);
}
db_result::db_result(db3_stmt_t stmt, std::int32_t res)
: ctx_(std::make_shared<context>()), res_(res) {
ctx_->stmt = std::move(stmt);
if (res == SQLITE_OK) {
set_res(sqlite3_step(ctx_->stmt.get()));
}
}
auto db_result::get_error_str() const -> std::string {
auto &&err_msg = sqlite3_errstr(res_);
return err_msg == nullptr ? std::to_string(res_) : err_msg;
}
auto db_result::get_row(std::optional<row> &opt_row) const -> bool {
opt_row.reset();
if (not has_row()) {
return false;
}
opt_row = db_row{ctx_};
set_res(sqlite3_step(ctx_->stmt.get()));
return true;
}
auto db_result::has_row() const -> bool { return res_ == SQLITE_ROW; }
void db_result::next_row() const {
if (not has_row()) {
return;
}
set_res(sqlite3_step(ctx_->stmt.get()));
}
auto db_result::ok() const -> bool {
return res_ == SQLITE_DONE || res_ == SQLITE_ROW;
}
auto create_db(std::string db_path,
const std::map<std::string, std::string> &sql_create_tables)
-> db3_t {
REPERTORY_USES_FUNCTION_NAME();
sqlite3 *db_ptr{nullptr};
auto db_res =
sqlite3_open_v2(db_path.c_str(), &db_ptr,
SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, nullptr);
if (db_res != SQLITE_OK) {
const auto *msg = sqlite3_errstr(db_res);
throw utils::error::create_exception(
function_name, {
"failed to open db",
db_path,
(msg == nullptr ? std::to_string(db_res) : msg),
});
}
auto db3 = db3_t{
db_ptr,
sqlite3_deleter(),
};
for (auto &&create_item : sql_create_tables) {
std::string err_msg;
if (not sqlite::execute_sql(*db3, create_item.second, err_msg)) {
db3.reset();
throw utils::error::create_exception(function_name, {
err_msg,
});
}
}
set_journal_mode(*db3);
return db3;
}
auto execute_sql(sqlite3 &db3, const std::string &sql, std::string &err)
-> bool {
REPERTORY_USES_FUNCTION_NAME();
char *err_msg{nullptr};
auto res = sqlite3_exec(&db3, sql.c_str(), nullptr, nullptr, &err_msg);
if (err_msg != nullptr) {
err = err_msg;
sqlite3_free(err_msg);
err_msg = nullptr;
}
if (res == SQLITE_OK) {
return true;
}
err = utils::error::create_error_message({
function_name,
"failed to execute sql",
err,
sql,
});
return false;
}
void set_journal_mode(sqlite3 &db3) {
sqlite3_exec(&db3,
"PRAGMA journal_mode = WAL;PRAGMA synchronous = NORMAL;PRAGMA "
"auto_vacuum = NONE;",
nullptr, nullptr, nullptr);
}
} // namespace repertory::utils::db::sqlite
#endif // defined(PROJECT_ENABLE_SQLITE)

View File

@ -0,0 +1,115 @@
/*
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.
*/
#if defined(PROJECT_ENABLE_SQLITE)
#include "utils/db/sqlite/db_delete.hpp"
namespace repertory::utils::db::sqlite {
auto db_delete::context::db_delete_op_t::dump() const -> std::string {
return db_delete{ctx}.dump();
}
auto db_delete::context::db_delete_op_t::go() const -> db_result {
return db_delete{ctx}.go();
}
auto db_delete::dump() const -> std::string {
std::stringstream query;
query << "DELETE FROM \"" << ctx_->table_name << "\"";
if (ctx_->where_data) {
std::int32_t idx{};
query << " WHERE " << ctx_->where_data->base.dump(idx);
}
query << ';';
return query.str();
}
auto db_delete::go() const -> db_result {
sqlite3_stmt *stmt_ptr{nullptr};
auto query_str = dump();
auto res =
sqlite3_prepare_v2(ctx_->db3, query_str.c_str(), -1, &stmt_ptr, nullptr);
auto stmt = db3_stmt_t{
stmt_ptr,
sqlite3_statement_deleter(),
};
if (res != SQLITE_OK) {
return {std::move(stmt), res};
}
if (not ctx_->where_data) {
return {std::move(stmt), res};
}
for (std::int32_t idx = 0;
idx < static_cast<std::int32_t>(ctx_->where_data->values.size());
idx++) {
res =
std::visit(overloaded{
[&stmt, &idx](std::int64_t data) -> std::int32_t {
return sqlite3_bind_int64(stmt.get(), idx + 1, data);
},
[&stmt, &idx](const std::string &data) -> std::int32_t {
return sqlite3_bind_text(stmt.get(), idx + 1,
data.c_str(), -1, nullptr);
},
},
ctx_->where_data->values.at(static_cast<std::size_t>(idx)));
if (res != SQLITE_OK) {
return {std::move(stmt), res};
}
}
return {std::move(stmt), res};
}
auto db_delete::group(context::w_t::group_func_t func) -> context::w_t::wn_t {
if (not ctx_->where_data) {
ctx_->where_data = std::make_unique<context::wd_t>(context::wd_t{
context::w_t{0U, ctx_},
{},
{},
});
}
return ctx_->where_data->base.group(std::move(func));
}
auto db_delete::where(std::string column_name) const -> context::w_t::cn_t {
if (not ctx_->where_data) {
ctx_->where_data = std::make_unique<context::wd_t>(context::wd_t{
context::w_t{0U, ctx_},
{},
{},
});
}
return ctx_->where_data->base.where(column_name);
}
} // namespace repertory::utils::db::sqlite
#endif // defined(PROJECT_ENABLE_SQLITE)

View File

@ -0,0 +1,99 @@
/*
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.
*/
#if defined(PROJECT_ENABLE_SQLITE)
#include "utils/db/sqlite/db_insert.hpp"
namespace repertory::utils::db::sqlite {
auto db_insert::column_value(std::string column_name, db_types_t value)
-> db_insert {
ctx_->values[column_name] = value;
return *this;
}
auto db_insert::dump() const -> std::string {
std::stringstream query;
query << "INSERT ";
if (ctx_->or_replace) {
query << "OR REPLACE ";
}
query << "INTO \"" << ctx_->table_name << "\" (";
for (std::int32_t idx = 0;
idx < static_cast<std::int32_t>(ctx_->values.size()); idx++) {
if (idx > 0) {
query << ", ";
}
query << '"' << std::next(ctx_->values.begin(), idx)->first << '"';
}
query << ") VALUES (";
for (std::int32_t idx = 0;
idx < static_cast<std::int32_t>(ctx_->values.size()); idx++) {
if (idx > 0) {
query << ", ";
}
query << "?" << (idx + 1);
}
query << ");";
return query.str();
}
auto db_insert::go() const -> db_result {
sqlite3_stmt *stmt_ptr{nullptr};
auto query_str = dump();
auto res =
sqlite3_prepare_v2(ctx_->db3, query_str.c_str(), -1, &stmt_ptr, nullptr);
auto stmt = db3_stmt_t{
stmt_ptr,
sqlite3_statement_deleter(),
};
if (res != SQLITE_OK) {
return {std::move(stmt), res};
}
for (std::int32_t idx = 0;
idx < static_cast<std::int32_t>(ctx_->values.size()); idx++) {
res =
std::visit(overloaded{
[&idx, &stmt](std::int64_t data) -> std::int32_t {
return sqlite3_bind_int64(stmt.get(), idx + 1, data);
},
[&idx, &stmt](const std::string &data) -> std::int32_t {
return sqlite3_bind_text(stmt.get(), idx + 1,
data.c_str(), -1, nullptr);
},
},
std::next(ctx_->values.begin(), idx)->second);
if (res != SQLITE_OK) {
return {std::move(stmt), res};
}
}
return {std::move(stmt), res};
}
} // namespace repertory::utils::db::sqlite
#endif // defined(PROJECT_ENABLE_SQLITE)

View File

@ -0,0 +1,221 @@
/*
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.
*/
#if defined(PROJECT_ENABLE_SQLITE)
#include "utils/db/sqlite/db_select.hpp"
namespace repertory::utils::db::sqlite {
auto db_select::context::db_select_op_t::dump() const -> std::string {
return db_select{ctx}.dump();
}
auto db_select::context::db_select_op_t::go() const -> db_result {
return db_select{ctx}.go();
}
auto db_select::context::db_select_op_t::group_by(std::string column_name)
-> db_select::context::db_select_op_t {
db_select{ctx}.group_by(column_name);
return *this;
}
auto db_select::context::db_select_op_t::limit(std::int32_t value)
-> db_select::context::db_select_op_t {
db_select{ctx}.limit(value);
return *this;
}
auto db_select::context::db_select_op_t::offset(std::int32_t value)
-> db_select::context::db_select_op_t {
db_select{ctx}.offset(value);
return *this;
}
auto db_select::context::db_select_op_t::order_by(std::string column_name,
bool ascending)
-> db_select::context::db_select_op_t {
db_select{ctx}.order_by(column_name, ascending);
return *this;
}
auto db_select::column(std::string column_name) -> db_select {
ctx_->columns.push_back(column_name);
return *this;
}
auto db_select::count(std::string column_name, std::string as_column_name)
-> db_select {
ctx_->count_columns[column_name] = as_column_name;
return *this;
}
auto db_select::dump() const -> std::string {
std::stringstream query;
query << "SELECT ";
bool has_column{false};
if (ctx_->columns.empty()) {
if (ctx_->count_columns.empty()) {
query << "*";
has_column = true;
}
} else {
has_column = not ctx_->columns.empty();
for (std::size_t idx = 0U; idx < ctx_->columns.size(); idx++) {
if (idx > 0U) {
query << ", ";
}
query << ctx_->columns.at(idx);
}
}
for (std::int32_t idx = 0U;
idx < static_cast<std::int32_t>(ctx_->count_columns.size()); idx++) {
if (has_column || idx > 0) {
query << ", ";
}
query << "COUNT(\"";
auto &count_column = *std::next(ctx_->count_columns.begin(), idx);
query << count_column.first << "\") AS \"" << count_column.second << '"';
}
query << " FROM \"" << ctx_->table_name << "\"";
if (ctx_->where_data) {
std::int32_t idx{};
query << " WHERE " << ctx_->where_data->base.dump(idx);
}
if (not ctx_->group_by.empty()) {
query << " GROUP BY ";
for (std::size_t idx = 0U; idx < ctx_->group_by.size(); idx++) {
if (idx > 0U) {
query << ", ";
}
query << "\"" << ctx_->group_by.at(idx) << "\"";
}
}
if (ctx_->order_by.has_value()) {
query << " ORDER BY \"" << ctx_->order_by.value().first << "\" ";
query << (ctx_->order_by.value().second ? "ASC" : "DESC");
}
if (ctx_->limit.has_value()) {
query << " LIMIT " << ctx_->limit.value();
}
if (ctx_->offset.has_value()) {
query << " OFFSET " << ctx_->offset.value();
}
query << ';';
return query.str();
}
auto db_select::go() const -> db_result {
sqlite3_stmt *stmt_ptr{nullptr};
auto query_str = dump();
auto res =
sqlite3_prepare_v2(ctx_->db3, query_str.c_str(), -1, &stmt_ptr, nullptr);
auto stmt = db3_stmt_t{
stmt_ptr,
sqlite3_statement_deleter(),
};
if (res != SQLITE_OK) {
return {std::move(stmt), res};
}
if (not ctx_->where_data) {
return {std::move(stmt), res};
}
for (std::int32_t idx = 0;
idx < static_cast<std::int32_t>(ctx_->where_data->values.size());
idx++) {
res =
std::visit(overloaded{
[&idx, &stmt](std::int64_t data) -> std::int32_t {
return sqlite3_bind_int64(stmt.get(), idx + 1, data);
},
[&idx, &stmt](const std::string &data) -> std::int32_t {
return sqlite3_bind_text(stmt.get(), idx + 1,
data.c_str(), -1, nullptr);
},
},
ctx_->where_data->values.at(static_cast<std::size_t>(idx)));
if (res != SQLITE_OK) {
return {std::move(stmt), res};
}
}
return {std::move(stmt), res};
}
auto db_select::group(context::w_t::group_func_t func) -> context::w_t::wn_t {
if (not ctx_->where_data) {
ctx_->where_data = std::make_unique<context::wd_t>(context::wd_t{
context::w_t{0U, ctx_},
{},
{},
});
}
return ctx_->where_data->base.group(std::move(func));
}
auto db_select::group_by(std::string column_name) -> db_select {
ctx_->group_by.emplace_back(std::move(column_name));
return *this;
}
auto db_select::limit(std::int32_t value) -> db_select {
ctx_->limit = value;
return *this;
}
auto db_select::offset(std::int32_t value) -> db_select {
ctx_->offset = value;
return *this;
}
auto db_select::order_by(std::string column_name, bool ascending) -> db_select {
ctx_->order_by = {column_name, ascending};
return *this;
}
auto db_select::where(std::string column_name) const -> context::w_t::cn_t {
if (not ctx_->where_data) {
ctx_->where_data = std::make_unique<context::wd_t>(context::wd_t{
context::w_t{0U, ctx_},
{},
{},
});
}
return ctx_->where_data->base.where(column_name);
}
} // namespace repertory::utils::db::sqlite
#endif // defined(PROJECT_ENABLE_SQLITE)

View File

@ -0,0 +1,189 @@
/*
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.
*/
#if defined(PROJECT_ENABLE_SQLITE)
#include "utils/db/sqlite/db_update.hpp"
namespace repertory::utils::db::sqlite {
auto db_update::context::db_update_op_t::dump() const -> std::string {
return db_update{ctx}.dump();
}
auto db_update::context::db_update_op_t::go() const -> db_result {
return db_update{ctx}.go();
}
auto db_update::context::db_update_op_t::limit(std::int32_t value)
-> db_update::context::db_update_op_t {
db_update{ctx}.limit(value);
return *this;
}
auto db_update::context::db_update_op_t::order_by(std::string column_name,
bool ascending)
-> db_update::context::db_update_op_t {
db_update{ctx}.order_by(column_name, ascending);
return *this;
}
auto db_update::column_value(std::string column_name, db_types_t value)
-> db_update {
ctx_->column_values[column_name] = value;
return *this;
}
auto db_update::dump() const -> std::string {
std::stringstream query;
query << "UPDATE \"" << ctx_->table_name << "\" SET ";
for (std::int32_t idx = 0;
idx < static_cast<std::int32_t>(ctx_->column_values.size()); idx++) {
if (idx > 0) {
query << ", ";
}
auto column = std::next(ctx_->column_values.begin(), idx);
query << '"' << column->first << "\"=?" + std::to_string(idx + 1);
}
if (ctx_->where_data) {
auto idx{static_cast<std::int32_t>(ctx_->column_values.size())};
query << " WHERE " << ctx_->where_data->base.dump(idx);
}
if (ctx_->order_by.has_value()) {
query << " ORDER BY \"" << ctx_->order_by.value().first << "\" ";
query << (ctx_->order_by.value().second ? "ASC" : "DESC");
}
if (ctx_->limit.has_value()) {
query << " LIMIT " << ctx_->limit.value();
}
query << ';';
return query.str();
}
auto db_update::go() const -> db_result {
sqlite3_stmt *stmt_ptr{nullptr};
auto query_str = dump();
auto res =
sqlite3_prepare_v2(ctx_->db3, query_str.c_str(), -1, &stmt_ptr, nullptr);
auto stmt = db3_stmt_t{
stmt_ptr,
sqlite3_statement_deleter(),
};
if (res != SQLITE_OK) {
return {std::move(stmt), res};
}
for (std::int32_t idx = 0;
idx < static_cast<std::int32_t>(ctx_->column_values.size()); idx++) {
res =
std::visit(overloaded{
[&idx, &stmt](std::int64_t data) -> std::int32_t {
return sqlite3_bind_int64(stmt.get(), idx + 1, data);
},
[&idx, &stmt](const std::string &data) -> std::int32_t {
return sqlite3_bind_text(stmt.get(), idx + 1,
data.c_str(), -1, nullptr);
},
},
std::next(ctx_->column_values.begin(), idx)->second);
if (res != SQLITE_OK) {
return {std::move(stmt), res};
}
}
if (not ctx_->where_data) {
return {std::move(stmt), res};
}
for (std::int32_t idx = 0;
idx < static_cast<std::int32_t>(ctx_->where_data->values.size());
idx++) {
res = std::visit(
overloaded{
[this, &idx, &stmt](std::int64_t data) -> std::int32_t {
return sqlite3_bind_int64(
stmt.get(),
idx + static_cast<std::int32_t>(ctx_->column_values.size()) +
1,
data);
},
[this, &idx, &stmt](const std::string &data) -> std::int32_t {
return sqlite3_bind_text(
stmt.get(),
idx + static_cast<std::int32_t>(ctx_->column_values.size()) +
1,
data.c_str(), -1, nullptr);
},
},
ctx_->where_data->values.at(static_cast<std::size_t>(idx)));
if (res != SQLITE_OK) {
return {std::move(stmt), res};
}
}
return {std::move(stmt), res};
}
auto db_update::group(context::w_t::group_func_t func) -> context::w_t::wn_t {
if (not ctx_->where_data) {
ctx_->where_data = std::make_unique<context::wd_t>(context::wd_t{
context::w_t{0U, ctx_},
{},
{},
});
}
return ctx_->where_data->base.group(std::move(func));
}
auto db_update::limit(std::int32_t value) -> db_update {
ctx_->limit = value;
return *this;
}
auto db_update::order_by(std::string column_name, bool ascending) -> db_update {
ctx_->order_by = {column_name, ascending};
return *this;
}
auto db_update::where(std::string column_name) const -> context::w_t::cn_t {
if (not ctx_->where_data) {
ctx_->where_data = std::make_unique<context::wd_t>(context::wd_t{
context::w_t{0U, ctx_},
{},
{},
});
}
return ctx_->where_data->base.where(column_name);
}
} // namespace repertory::utils::db::sqlite
#endif // defined(PROJECT_ENABLE_SQLITE)

View File

@ -0,0 +1,450 @@
/*
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.
*/
#if defined(PROJECT_ENABLE_LIBSODIUM) && defined(PROJECT_ENABLE_BOOST)
#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(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 {
REPERTORY_USES_FUNCTION_NAME();
if ((which & std::ios_base::in) != std::ios_base::in) {
throw utils::error::create_exception(function_name,
{
"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)) {
REPERTORY_USES_FUNCTION_NAME();
if (not *source_file_) {
throw utils::error::create_exception(function_name, {
"file open failed",
source_path,
});
}
data_buffer result;
utils::encryption::encrypt_data(
key_, reinterpret_cast<const unsigned char *>(file_name.data()),
file_name.size(), result);
encrypted_file_name_ = utils::collection::to_hex_string(result);
if (relative_parent_path.has_value()) {
for (auto &&part :
utils::string::split(relative_parent_path.value(),
utils::path::directory_seperator, false)) {
utils::encryption::encrypt_data(
key_, reinterpret_cast<const unsigned char *>(part.c_str()),
strnlen(part.c_str(), part.size()), result);
encrypted_file_path_ += '/' + utils::collection::to_hex_string(result);
}
encrypted_file_path_ += '/' + encrypted_file_name_;
}
auto opt_size = source_file_->size();
if (not opt_size.has_value()) {
throw utils::error::create_exception(function_name,
{
"failed to get file size",
source_file_->get_path(),
});
}
auto file_size = opt_size.value();
const auto total_chunks = utils::divide_with_ceiling(
file_size, static_cast<std::uint64_t>(data_chunk_size_));
total_size_ = file_size + (total_chunks * encryption_header_size);
last_data_chunk_ = total_chunks - 1U;
last_data_chunk_size_ = (file_size <= data_chunk_size_) ? file_size
: (file_size % data_chunk_size_) == 0U
? data_chunk_size_
: file_size % data_chunk_size_;
iv_list_.resize(total_chunks);
for (auto &iv : iv_list_) {
randombytes_buf(iv.data(), iv.size());
}
}
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)) {
REPERTORY_USES_FUNCTION_NAME();
if (not *source_file_) {
throw utils::error::create_exception(function_name, {
"file open failed",
source_path,
});
}
encrypted_file_path_ = encrypted_file_path;
encrypted_file_name_ = utils::path::strip_to_file_name(encrypted_file_path_);
auto opt_size = source_file_->size();
if (not opt_size.has_value()) {
throw utils::error::create_exception(function_name,
{
"failed to get file size",
source_file_->get_path(),
});
}
auto file_size = opt_size.value();
const auto total_chunks = utils::divide_with_ceiling(
file_size, static_cast<std::uint64_t>(data_chunk_size_));
total_size_ = file_size + (total_chunks * encryption_header_size);
last_data_chunk_ = total_chunks - 1U;
last_data_chunk_size_ = (file_size <= data_chunk_size_) ? file_size
: (file_size % data_chunk_size_) == 0U
? data_chunk_size_
: file_size % data_chunk_size_;
iv_list_.resize(total_chunks);
for (auto &iv : iv_list_) {
randombytes_buf(iv.data(), iv.size());
}
}
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)) {
REPERTORY_USES_FUNCTION_NAME();
if (not *source_file_) {
throw utils::error::create_exception(function_name, {
"file open failed",
source_path,
});
}
encrypted_file_path_ = encrypted_file_path;
encrypted_file_name_ = utils::path::strip_to_file_name(encrypted_file_path_);
auto opt_size = source_file_->size();
if (not opt_size.has_value()) {
throw utils::error::create_exception(
function_name, {
"get file size failed",
std::to_string(utils::get_last_error_code()),
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_ = 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_) {
REPERTORY_USES_FUNCTION_NAME();
if (not *source_file_) {
throw utils::error::create_exception(
function_name, {
"file open failed",
std::to_string(utils::get_last_error_code()),
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 {
REPERTORY_USES_FUNCTION_NAME();
auto opt_size = utils::file::file{source_path}.size();
if (not opt_size.has_value()) {
throw utils::error::create_exception(
function_name, {
"get file size failed",
std::to_string(utils::get_last_error_code()),
source_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_));
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 {
REPERTORY_USES_FUNCTION_NAME();
const auto read_size = static_cast<std::size_t>(std::min(
static_cast<std::uint64_t>(size * nitems), total_size_ - read_offset_));
auto 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

@ -0,0 +1,117 @@
/*
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.
*/
#if defined(PROJECT_ENABLE_LIBSODIUM) && defined(PROJECT_ENABLE_BOOST)
#include "utils/encryption.hpp"
#include "utils/collection.hpp"
#include "utils/encrypting_reader.hpp"
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;
}
file_path = decrypted_file_path;
return true;
}
auto decrypt_file_name(std::string_view encryption_token,
std::string &file_name) -> bool {
data_buffer buffer;
if (not utils::collection::from_hex_string(file_name, buffer)) {
return false;
}
file_name.clear();
if (not utils::encryption::decrypt_data(encryption_token, buffer,
file_name)) {
return false;
}
return true;
}
#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) && defined (PROJECT_ENABLE_BOOST)

View File

@ -0,0 +1,82 @@
/*
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/error.hpp"
namespace repertory::utils::error {
std::atomic<const i_exception_handler *> exception_handler{
&default_exception_handler};
auto create_error_message(std::vector<std::string_view> items) -> std::string {
std::stringstream stream{};
for (std::size_t idx = 0U; idx < items.size(); ++idx) {
if (idx > 0) {
stream << '|';
}
stream << items.at(idx);
}
return stream.str();
}
auto create_exception(std::string_view function_name,
std::vector<std::string_view> items)
-> std::runtime_error {
items.insert(items.begin(), function_name);
return std::runtime_error(create_error_message(items));
}
void handle_error(std::string_view function_name, std::string_view msg) {
const i_exception_handler *handler{exception_handler};
if (handler != nullptr) {
handler->handle_error(function_name, msg);
return;
}
default_exception_handler.handle_error(function_name, msg);
}
void handle_exception(std::string_view function_name) {
const i_exception_handler *handler{exception_handler};
if (handler != nullptr) {
handler->handle_exception(function_name);
return;
}
default_exception_handler.handle_exception(function_name);
}
void handle_exception(std::string_view function_name,
const std::exception &ex) {
const i_exception_handler *handler{exception_handler};
if (handler != nullptr) {
handler->handle_exception(function_name, ex);
return;
}
default_exception_handler.handle_exception(function_name, ex);
}
void set_exception_handler(const i_exception_handler *handler) {
exception_handler = handler;
}
} // namespace repertory::utils::error

713
support/src/utils/file.cpp Normal file
View File

@ -0,0 +1,713 @@
/*
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/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 change_to_process_directory() -> bool {
REPERTORY_USES_FUNCTION_NAME();
try {
#if defined(_WIN32)
std::string file_name;
file_name.resize(repertory::max_path_length + 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 utils::error::create_exception(
function_name, {
"failed to readlink",
std::to_string(utils::get_last_error_code()),
path,
});
}
#endif // defined(__APPLE__)
path = utils::path::get_parent_path(path);
res = chdir(path.c_str());
if (res != 0) {
throw utils::error::create_exception(
function_name, {
"failed to chdir",
std::to_string(utils::get_last_error_code()),
path,
});
}
#endif // defined(_WIN32)
return true;
} catch (const std::exception &e) {
utils::error::handle_exception(function_name, e);
} catch (...) {
utils::error::handle_exception(function_name);
}
return false;
}
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> {
REPERTORY_USES_FUNCTION_NAME();
try {
#if defined(_WIN32)
ULARGE_INTEGER li{};
if (not::GetDiskFreeSpaceEx(std::string{path}.c_str(), &li, nullptr,
nullptr)) {
throw utils::error::create_exception(
function_name, {
"failed to get free disk space",
std::to_string(utils::get_last_error_code()),
path,
});
}
return li.QuadPart;
#endif // defined(_WIN32)
#if defined(__linux__)
struct statfs64 st{};
if (statfs64(std::string{path}.c_str(), &st) != 0) {
throw utils::error::create_exception(
function_name, {
"failed to get free disk space",
std::to_string(utils::get_last_error_code()),
path,
});
}
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 utils::error::create_exception(
function_name, {
"failed to get free disk space",
std::to_string(utils::get_last_error_code()),
path,
});
}
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> {
REPERTORY_USES_FUNCTION_NAME();
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, FILE_FLAG_BACKUP_SEMANTICS, nullptr);
if (file_handle != INVALID_HANDLE_VALUE) {
std::array<FILETIME, 3U> times{};
auto res = ::GetFileTime(file_handle, &times.at(0U), &times.at(1U),
&times.at(2U));
::CloseHandle(file_handle);
if (res) {
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));
return ret;
}
}
struct _stat64 st{};
if (_stat64(std::string{path}.c_str(), &st) != 0) {
throw utils::error::create_exception(
function_name, {
"failed to get file times",
std::to_string(utils::get_last_error_code()),
path,
});
}
ret.accessed = utils::time::windows_time_t_to_unix_time(st.st_atime);
ret.created = utils::time::windows_time_t_to_unix_time(st.st_ctime);
ret.modified = utils::time::windows_time_t_to_unix_time(st.st_mtime);
ret.written = utils::time::windows_time_t_to_unix_time(st.st_mtime);
#else // !defined(_WIN32)
struct stat64 st{};
if (stat64(std::string{path}.c_str(), &st) != 0) {
throw utils::error::create_exception(
function_name, {
"failed to get file times",
std::to_string(utils::get_last_error_code()),
path,
});
}
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> {
REPERTORY_USES_FUNCTION_NAME();
try {
#if defined(_WIN32)
ULARGE_INTEGER li{};
if (not::GetDiskFreeSpaceEx(std::string{path}.c_str(), nullptr, &li,
nullptr)) {
throw utils::error::create_exception(
function_name, {
"failed to get total disk space",
std::to_string(utils::get_last_error_code()),
path,
});
}
return li.QuadPart;
#endif // defined(_WIN32)
#if defined(__linux__)
struct statfs64 st{};
if (statfs64(std::string{path}.c_str(), &st) != 0) {
throw utils::error::create_exception(
function_name, {
"failed to get total disk space",
std::to_string(utils::get_last_error_code()),
path,
});
}
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 utils::error::create_exception(
function_name, {
"failed to get total disk space",
std::to_string(utils::get_last_error_code()),
path,
});
}
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) {
*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;
}
return false;
}
#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,
std::optional<std::string_view> password) -> bool {
#else // !defined(PROJECT_ENABLE_LIBSODIUM) && defined(PROJECT_ENABLE_BOOST)
auto read_json_file(std::string_view path, nlohmann::json &data) -> bool {
#endif // defined(PROJECT_ENABLE_LIBSODIUM) && defined(PROJECT_ENABLE_BOOST)
REPERTORY_USES_FUNCTION_NAME();
try {
auto abs_path = utils::path::absolute(path);
auto file = file::open_file(abs_path);
if (not*file) {
return false;
}
try {
data_buffer buffer{};
if (not file->read_all(buffer, 0U)) {
return false;
}
#if defined(PROJECT_ENABLE_LIBSODIUM) && defined(PROJECT_ENABLE_BOOST)
if (password.has_value()) {
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)
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);
return false;
} catch (...) {
utils::error::handle_exception(function_name);
return false;
}
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) && defined(PROJECT_ENABLE_BOOST)
auto write_json_file(std::string_view path, const nlohmann::json &data,
std::optional<std::string_view> password) -> bool {
#else // !defined(PROJECT_ENABLE_LIBSODIUM) && defined(PROJECT_ENABLE_BOOST)
auto write_json_file(std::string_view path, const nlohmann::json &data)
-> bool {
#endif // defined(PROJECT_ENABLE_LIBSODIUM) && defined(PROJECT_ENABLE_BOOST)
REPERTORY_USES_FUNCTION_NAME();
try {
auto file = file::open_or_create_file(path);
if (not file->truncate()) {
throw utils::error::create_exception(
function_name, {
"failed to truncate file",
std::to_string(utils::get_last_error_code()),
path,
});
}
#if defined(PROJECT_ENABLE_LIBSODIUM) && defined(PROJECT_ENABLE_BOOST)
if (password.has_value()) {
const auto str_data = data.dump(2);
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)
auto json_str = data.dump(2);
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 (...) {
utils::error::handle_exception(function_name);
}
return false;
}
#if defined(PROJECT_ENABLE_LIBSODIUM) && defined(PROJECT_ENABLE_BOOST)
auto read_json_file(std::wstring_view path, nlohmann::json &data,
std::optional<std::wstring_view> password) -> bool {
if (password.has_value()) {
auto password_a = utils::string::to_utf8(*password);
return read_json_file(utils::string::to_utf8(path), data, password_a);
}
return read_json_file(utils::string::to_utf8(path), data, std::nullopt);
}
auto write_json_file(std::wstring_view path, const nlohmann::json &data,
std::optional<std::wstring_view> password) -> bool {
if (password.has_value()) {
auto password_a = utils::string::to_utf8(*password);
return write_json_file(utils::string::to_utf8(path), data, password_a);
}
return write_json_file(utils::string::to_utf8(path), data, std::nullopt);
}
#else // !defined(PROJECT_ENABLE_LIBSODIUM) && defined(PROJECT_ENABLE_BOOST)
auto read_json_file(std::wstring_view path, nlohmann::json &data) -> bool {
return read_json_file(utils::string::to_utf8(path), data);
}
auto write_json_file(std::wstring_view path, const nlohmann::json &data)
-> bool {
return write_json_file(utils::string::to_utf8(path), data);
}
#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 {
REPERTORY_USES_FUNCTION_NAME();
if (not validate_smb_path(smb_path)) {
throw utils::error::create_exception(function_name, {
"invalid smb path",
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);
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 utils::error::create_exception(function_name, {
"invalid smb path",
path,
});
}
return path;
}
auto smb_create_and_validate_relative_path(std::string_view smb_path,
std::string_view path)
-> std::string {
REPERTORY_USES_FUNCTION_NAME();
if (not validate_smb_path(smb_path)) {
throw utils::error::create_exception(function_name, {
"invalid smb path",
smb_path,
});
}
std::string dir_path;
if (utils::string::begins_with(path, "//")) {
if (not utils::file::smb_parent_is_same(smb_path, path)) {
throw utils::error::create_exception(function_name,
{
"failed to validate path",
"parent paths are not the same",
smb_path,
path,
});
}
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 {
REPERTORY_USES_FUNCTION_NAME();
if (not validate_smb_path(smb_path)) {
throw utils::error::create_exception(function_name, {
"invalid smb path",
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 {
REPERTORY_USES_FUNCTION_NAME();
if (not validate_smb_path(smb_path)) {
throw utils::error::create_exception(function_name, {
"invalid smb path",
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 {
REPERTORY_USES_FUNCTION_NAME();
if (not validate_smb_path(smb_path)) {
throw utils::error::create_exception(function_name, {
"invalid smb path",
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 utils::error::create_exception(function_name,
{
"invalid parent smb path",
parent_smb_path,
});
}
return parent_smb_path;
}
auto smb_get_root_path(std::string_view smb_path) -> std::string {
REPERTORY_USES_FUNCTION_NAME();
if (not validate_smb_path(smb_path)) {
throw utils::error::create_exception(function_name, {
"invalid smb path",
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 {
REPERTORY_USES_FUNCTION_NAME();
if (not validate_smb_path(smb_path)) {
throw utils::error::create_exception(function_name, {
"invalid smb path",
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 {
REPERTORY_USES_FUNCTION_NAME();
if (not validate_smb_path(smb_path)) {
throw utils::error::create_exception(function_name, {
"invalid smb path",
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 {
REPERTORY_USES_FUNCTION_NAME();
if (not validate_smb_path(smb_path)) {
throw utils::error::create_exception(function_name, {
"invalid smb path",
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,486 @@
/*
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_directory.hpp"
#include "utils/common.hpp"
#include "utils/error.hpp"
#include "utils/string.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,
repertory::stop_type *stop_requested) -> bool {
REPERTORY_USES_FUNCTION_NAME();
auto res{true};
const auto is_stop_requested = [&stop_requested]() -> bool {
return (stop_requested != nullptr && *stop_requested);
};
#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 repertory::utils::error::create_exception(
function_name,
{
"failed to open directory",
std::to_string(repertory::utils::get_last_error_code()),
path,
});
}
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}),
stop_requested,
});
}
} else {
res = file_action(repertory::utils::file::file(
repertory::utils::path::combine(path, {fd.cFileName})));
}
} while (res && (::FindNextFileA(find, &fd) != 0) && !is_stop_requested());
::FindClose(find);
#else // !defined(_WIN32)
auto *root = opendir(std::string{path}.c_str());
if (root == nullptr) {
throw repertory::utils::error::create_exception(
function_name,
{
"failed to open directory",
std::to_string(repertory::utils::get_last_error_code()),
path,
});
}
struct dirent *de{nullptr};
while (res && (de = readdir(root)) && !is_stop_requested()) {
if (de->d_type == DT_DIR) {
if ((std::string_view(de->d_name) != ".") &&
(std::string_view(de->d_name) != "..")) {
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 {
REPERTORY_USES_FUNCTION_NAME();
try {
throw utils::error::create_exception(
function_name, {
"failed to copy directory",
"not implemented",
utils::string::from_bool(overwrite),
new_path,
path_,
});
} catch (const std::exception &e) {
utils::error::handle_exception(function_name, e);
} catch (...) {
utils::error::handle_exception(function_name);
}
return false;
}
auto directory::count(bool recursive) const -> std::uint64_t {
REPERTORY_USES_FUNCTION_NAME();
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;
},
stop_requested_);
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 {
REPERTORY_USES_FUNCTION_NAME();
try {
auto abs_path = utils::path::combine(path_, {path});
if (directory{abs_path, stop_requested_}.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 utils::error::create_exception(function_name,
{
"failed to create directory",
std::to_string(res),
abs_path,
});
}
#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;
!is_stop_requested() && 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 // defined(_WIN32)
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 {
REPERTORY_USES_FUNCTION_NAME();
try {
auto dir_path = utils::path::combine(path_, {path});
return fs_directory_t{
directory{dir_path, stop_requested_}.exists()
? new directory(dir_path, stop_requested_)
: 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> {
REPERTORY_USES_FUNCTION_NAME();
try {
std::vector<fs_directory_t> ret{};
traverse_directory(
path_,
[this, &ret](auto dir_item) -> bool {
ret.emplace_back(fs_directory_t{
new directory(dir_item.get_path(), stop_requested_),
});
return true;
},
[](auto /* file_item */) -> bool { return true; }, stop_requested_);
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 {
REPERTORY_USES_FUNCTION_NAME();
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 {
REPERTORY_USES_FUNCTION_NAME();
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> {
REPERTORY_USES_FUNCTION_NAME();
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;
},
stop_requested_);
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> {
REPERTORY_USES_FUNCTION_NAME();
try {
std::vector<fs_item_t> ret{};
traverse_directory(
path_,
[this, &ret](auto dir_item) -> bool {
ret.emplace_back(fs_item_t{
new directory(dir_item.get_path(), stop_requested_),
});
return true;
},
[&ret](auto file_item) -> bool {
ret.emplace_back(fs_item_t{
new file(file_item.get_path()),
});
return true;
},
stop_requested_);
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_stop_requested() const -> bool {
return (stop_requested_ != nullptr) && *stop_requested_;
}
auto directory::is_symlink() const -> bool {
REPERTORY_USES_FUNCTION_NAME();
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 {
REPERTORY_USES_FUNCTION_NAME();
try {
throw utils::error::create_exception(function_name,
{
"failed to move directory",
"not implemented",
new_path,
path_,
});
} catch (const std::exception &e) {
utils::error::handle_exception(function_name, e);
} catch (...) {
utils::error::handle_exception(function_name);
}
return false;
}
auto directory::remove() -> bool {
REPERTORY_USES_FUNCTION_NAME();
return utils::retry_action([this]() -> bool {
try {
#if defined(_WIN32)
auto ret = not exists() || (::RemoveDirectoryA(path_.c_str()) != 0);
#else // !defined(_WIN32)
auto ret = not exists() || (rmdir(path_.c_str()) == 0);
#endif // defined(_WIN32)
if (not ret) {
utils::error::handle_error(function_name,
utils::error::create_error_message({
"failed to remove directory",
path_,
}));
}
return ret;
} 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 {
REPERTORY_USES_FUNCTION_NAME();
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(); },
stop_requested_)) {
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 {
REPERTORY_USES_FUNCTION_NAME();
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;
},
stop_requested_);
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.
*/
#if defined(PROJECT_ENABLE_LIBSODIUM) && defined(PROJECT_ENABLE_BOOST)
#include "utils/file_enc_file.hpp"
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,594 @@
/*
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_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()) != 0) &&
(::PathIsDirectoryA(abs_path.c_str()) == 0));
#else // !defined(_WIN32)
struct stat64 st{};
return (stat64(abs_path.c_str(), &st) == 0 && not S_ISDIR(st.st_mode));
#endif // defined(_WIN32)
}
} // namespace
namespace repertory::utils::file {
// auto file::attach_file(native_handle handle,
// bool read_only) -> fs_file_t {
// REPERTORY_USES_FUNCTION_NAME();
//
// try {
// std::string path;
//
// #if defined(_WIN32)
// path.resize(repertory::max_path_length + 1U);
// ::GetFinalPathNameByHandleA(handle, path.data(),
// static_cast<DWORD>(path.size()),
// FILE_NAME_NORMALIZED | VOLUME_NAME_DOS);
// #else // !defined(_WIN32)
// path.resize(repertory::max_path_length + 1U);
//
// #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() {
REPERTORY_USES_FUNCTION_NAME();
if (not is_file(path_)) {
throw utils::error::create_exception(function_name, {
"file not found",
path_,
});
}
#if defined(_WIN32)
file_ = file_t{
_fsopen(path_.c_str(), read_only_ ? "rb" : "rb+", _SH_DENYNO),
file_deleter(),
};
#else // !defined(_WIN32)
file_ = file_t{
fopen(path_.c_str(), read_only_ ? "rb" : "rb+"),
file_deleter(),
};
#endif // defined(_WIN32)
}
auto file::open_file(std::string_view path, bool read_only) -> fs_file_t {
REPERTORY_USES_FUNCTION_NAME();
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 {
REPERTORY_USES_FUNCTION_NAME();
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) != 0;
#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 {
REPERTORY_USES_FUNCTION_NAME();
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 {
REPERTORY_USES_FUNCTION_NAME();
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) != 0;
#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 {
REPERTORY_USES_FUNCTION_NAME();
if (total_read != nullptr) {
(*total_read) = 0U;
}
try {
if (not file_) {
throw utils::error::create_exception(function_name,
{
"file is not open for reading",
path_,
});
}
if (fseeko(file_.get(), static_cast<std::int64_t>(offset), SEEK_SET) ==
-1) {
throw utils::error::create_exception(function_name,
{
"failed to seek before read",
path_,
});
}
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 utils::error::create_exception(function_name,
{
"failed to read file bytes",
path_,
});
}
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> {
REPERTORY_USES_FUNCTION_NAME();
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 utils::error::create_exception(function_name,
{
"failed to initialize sha256",
std::to_string(res),
path_,
});
}
{
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 utils::error::create_exception(function_name,
{
"failed to update sha256",
std::to_string(res),
path_,
});
}
}
}
std::array<unsigned char, crypto_hash_sha256_BYTES> out{};
res = crypto_hash_sha256_final(&state, out.data());
if (res != 0) {
throw utils::error::create_exception(function_name,
{
"failed to finalize sha256",
std::to_string(res),
path_,
});
}
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 {
REPERTORY_USES_FUNCTION_NAME();
close();
return utils::retry_action([this]() -> bool {
try {
#if defined(_WIN32)
auto ret = not exists() || (::DeleteFileA(path_.c_str()) != 0);
#else // !defined(_WIN32)
std::error_code ec{};
auto ret = not exists() || std::filesystem::remove(path_, ec);
#endif // defined(_WIN32)
if (not ret) {
utils::error::handle_error(function_name,
utils::error::create_error_message({
"failed to remove file",
path_,
}));
}
return ret;
} catch (const std::exception &e) {
utils::error::handle_exception(function_name, e);
} catch (...) {
utils::error::handle_exception(function_name);
}
return false;
});
}
auto file::truncate(std::size_t size) -> bool {
REPERTORY_USES_FUNCTION_NAME();
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 {
REPERTORY_USES_FUNCTION_NAME();
if (total_written != nullptr) {
(*total_written) = 0U;
}
try {
if (not file_) {
throw utils::error::create_exception(function_name,
{
"file is not open for writing",
path_,
});
}
auto res = fseeko(file_.get(), static_cast<std::int64_t>(offset), SEEK_SET);
if (res == -1) {
throw utils::error::create_exception(function_name,
{
"failed to seek before write",
path_,
});
}
std::size_t bytes_written{0U};
while (bytes_written != to_write) {
auto written =
fwrite(reinterpret_cast<const char *>(&data[bytes_written]), 1U,
to_write - bytes_written, file_.get());
if (not feof(file_.get()) && ferror(file_.get())) {
throw utils::error::create_exception(function_name,
{
"failed to write file bytes",
path_,
});
}
if (written == 0U) {
break;
}
bytes_written += written;
}
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> {
REPERTORY_USES_FUNCTION_NAME();
try {
if (file_) {
if (fseeko(file_.get(), 0, SEEK_END) == -1) {
throw utils::error::create_exception(function_name,
{
"failed to seek",
path_,
});
}
auto size = ftello(file_.get());
if (size == -1) {
throw utils::error::create_exception(function_name,
{
"failed to get position",
path_,
});
}
return static_cast<std::uint64_t>(size);
}
std::uint64_t size{};
if (not get_file_size(path_, size)) {
throw utils::error::create_exception(function_name,
{
"failed to get file size",
path_,
});
}
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,789 @@
/*
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.
*/
#if defined(PROJECT_ENABLE_LIBDSM)
#include "utils/file_smb_directory.hpp"
#include "utils/common.hpp"
#include "utils/error.hpp"
#include "utils/unix.hpp"
#include "utils/windows.hpp"
namespace repertory::utils::file {
auto smb_directory::open(std::string_view host, std::string_view user,
std::string_view password, std::string_view path,
stop_type *stop_requested) -> smb_directory_t {
REPERTORY_USES_FUNCTION_NAME();
try {
smb_session_t session{
smb_session_new(),
smb_session_deleter,
};
netbios_ns_t ns{
netbios_ns_new(),
netbios_ns_deleter(),
};
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 utils::error::create_exception(
function_name, {
"failed to resolve host",
std::to_string(utils::get_last_error_code()),
host,
});
}
}
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 utils::error::create_exception(function_name,
{
"failed to connect to host",
std::to_string(res),
host,
});
}
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 utils::error::create_exception(function_name,
{
"failed to logon to host",
std::to_string(res),
host,
user,
});
}
auto share_name = utils::string::split(path, '/', false).at(0U);
smb_tid tid{};
res = smb_tree_connect(session.get(), share_name.c_str(), &tid);
if (res != DSM_SUCCESS) {
throw utils::error::create_exception(function_name,
{
"failed to connect to share",
std::to_string(res),
share_name,
});
}
return smb_directory_t{
new smb_directory{
"//" + std::string{host} + "/" + std::string{path},
session,
share_name,
tid,
stop_requested,
},
};
} 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 path,
stop_type *stop_requested) -> smb_directory_t {
return open(utils::string::to_utf8(host), utils::string::to_utf8(user),
utils::string::to_utf8(password), utils::string::to_utf8(path),
stop_requested);
}
auto smb_directory::copy_to(std::string_view new_path,
bool overwrite) const -> bool {
REPERTORY_USES_FUNCTION_NAME();
try {
if (not session_) {
throw utils::error::create_exception(function_name,
{
"session not found",
path_,
});
}
// auto to_path = utils::path::absolute(new_path);
throw utils::error::create_exception(function_name,
{
"failed to copy directory",
"not implemented",
new_path,
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::count(bool recursive) const -> std::uint64_t {
REPERTORY_USES_FUNCTION_NAME();
try {
if (not session_) {
throw utils::error::create_exception(function_name,
{
"session not found",
path_,
});
}
smb_stat_list_t list{
smb_find(session_.get(), tid_, smb_create_search_path(path_).c_str()),
smb_stat_list_deleter(),
};
auto count = smb_stat_list_count(list.get());
if (not recursive) {
return count;
}
throw utils::error::create_exception(
function_name, {
"failed to get directory count recursively",
"not implemented",
utils::string::from_bool(recursive),
path_,
});
} 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 {
REPERTORY_USES_FUNCTION_NAME();
try {
if (not session_) {
throw utils::error::create_exception(function_name,
{
"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 utils::error::create_exception(function_name,
{
"failed to create directory",
std::to_string(res),
path_,
});
}
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 {
REPERTORY_USES_FUNCTION_NAME();
try {
if (not session_) {
throw utils::error::create_exception(function_name,
{
"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 utils::error::create_exception(function_name,
{
"failed to open existing file",
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 {
REPERTORY_USES_FUNCTION_NAME();
try {
if (not session_) {
throw utils::error::create_exception(function_name,
{
"session not found",
path_,
});
}
smb_stat_t st{
smb_fstat(session_.get(), tid_,
smb_create_relative_path(path_).c_str()),
smb_stat_deleter(),
};
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 {
REPERTORY_USES_FUNCTION_NAME();
try {
if (not session_) {
throw utils::error::create_exception(function_name,
{
"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()),
smb_stat_deleter(),
};
if (not st) {
throw utils::error::create_exception(function_name,
{
"failed to stat directory",
rel_path,
path_,
});
}
bool is_dir{smb_stat_get(st.get(), SMB_STAT_ISDIR) != 0U};
if (not is_dir) {
throw utils::error::create_exception(function_name,
{
"path is not a directory",
rel_path,
path_,
});
}
return smb_directory_t{
new smb_directory{
smb_create_smb_path(path_, rel_path),
session_,
share_name_,
tid_,
stop_requested_,
},
};
} 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> {
REPERTORY_USES_FUNCTION_NAME();
try {
if (not session_) {
throw utils::error::create_exception(function_name,
{
"session not found",
path_,
});
}
smb_stat_list_t list{
smb_find(session_.get(), tid_, smb_create_search_path(path_).c_str()),
smb_stat_list_deleter(),
};
if (not list) {
throw utils::error::create_exception(function_name,
{
"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; !is_stop_requested() && idx < count; ++idx) {
auto *item_st = smb_stat_list_at(list.get(), idx);
bool is_dir{smb_stat_get(item_st, SMB_STAT_ISDIR) != 0U};
if (not is_dir) {
continue;
}
std::string name{smb_stat_name(item_st)};
if (name == "." || name == "..") {
continue;
}
try {
ret.emplace_back(smb_directory_t{
new smb_directory{
smb_create_smb_path(path_, name),
session_,
share_name_,
tid_,
stop_requested_,
},
});
} catch (const std::exception &e) {
utils::error::handle_exception(function_name, e);
} catch (...) {
utils::error::handle_exception(function_name);
}
}
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 {
REPERTORY_USES_FUNCTION_NAME();
try {
if (not session_) {
throw utils::error::create_exception(function_name,
{
"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()),
smb_stat_deleter(),
};
if (not st) {
throw utils::error::create_exception(function_name,
{
"failed to stat file",
rel_path,
path_,
});
}
bool is_dir{smb_stat_get(st.get(), SMB_STAT_ISDIR) != 0U};
if (is_dir) {
throw utils::error::create_exception(function_name,
{
"path is not a file",
rel_path,
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> {
REPERTORY_USES_FUNCTION_NAME();
try {
if (not session_) {
throw utils::error::create_exception(function_name,
{
"session not found",
path_,
});
}
smb_stat_list_t list{
smb_find(session_.get(), tid_, smb_create_search_path(path_).c_str()),
smb_stat_list_deleter(),
};
if (not list) {
throw utils::error::create_exception(function_name,
{
"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; !is_stop_requested() && idx < count; ++idx) {
auto *item_st = smb_stat_list_at(list.get(), idx);
bool is_dir{smb_stat_get(item_st, SMB_STAT_ISDIR) != 0U};
if (is_dir) {
continue;
}
try {
std::string name{smb_stat_name(item_st)};
ret.emplace_back(std::make_unique<smb_file>(
std::nullopt, smb_create_smb_path(path_, 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 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> {
REPERTORY_USES_FUNCTION_NAME();
try {
if (not session_) {
throw utils::error::create_exception(function_name,
{
"session not found",
path_,
});
}
smb_stat_list_t list{
smb_find(session_.get(), tid_, smb_create_search_path(path_).c_str()),
smb_stat_list_deleter(),
};
if (not list) {
throw utils::error::create_exception(function_name,
{
"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; !is_stop_requested() && idx < count; ++idx) {
auto *item_st = smb_stat_list_at(list.get(), idx);
bool is_dir{smb_stat_get(item_st, SMB_STAT_ISDIR) != 0U};
std::string name{smb_stat_name(item_st)};
if (is_dir) {
if (name == "." || name == "..") {
continue;
}
try {
ret.emplace_back(smb_directory_t{
new smb_directory{
path_ + '/' + name,
session_,
share_name_,
tid_,
stop_requested_,
},
});
} catch (const std::exception &e) {
utils::error::handle_exception(function_name, e);
} catch (...) {
utils::error::handle_exception(function_name);
}
continue;
}
try {
ret.emplace_back(std::make_unique<smb_file>(
std::nullopt, smb_create_smb_path(path_, 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 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_time(time_type type) const
-> std::optional<std::uint64_t> {
return smb_file::get_time(session_.get(), tid_, path_, type);
}
auto smb_directory::is_stop_requested() const -> bool {
return (stop_requested_ != nullptr) && *stop_requested_;
}
auto smb_directory::is_symlink() const -> bool {
REPERTORY_USES_FUNCTION_NAME();
try {
if (not session_) {
throw utils::error::create_exception(function_name,
{
"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 {
REPERTORY_USES_FUNCTION_NAME();
try {
if (not session_) {
throw utils::error::create_exception(function_name,
{
"session not found",
path_,
});
}
throw utils::error::create_exception(function_name,
{
"failed to move directory",
"not implemented",
new_path,
path_,
});
} catch (const std::exception &e) {
utils::error::handle_exception(function_name, e);
} catch (...) {
utils::error::handle_exception(function_name);
}
return false;
}
auto smb_directory::remove() -> bool {
REPERTORY_USES_FUNCTION_NAME();
try {
if (not session_) {
throw utils::error::create_exception(function_name,
{
"session not found",
path_,
});
}
return utils::retry_action([this]() -> bool {
if (not exists()) {
return true;
}
try {
auto res = smb_directory_rm(session_.get(), tid_,
smb_create_relative_path(path_).c_str());
if (res != DSM_SUCCESS) {
throw utils::error::create_exception(function_name,
{
"failed to remove directory",
std::to_string(res),
path_,
});
}
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 {
REPERTORY_USES_FUNCTION_NAME();
try {
if (not session_) {
throw utils::error::create_exception(function_name,
{
"session not found",
path_,
});
}
if (not exists()) {
return true;
}
throw utils::error::create_exception(
function_name, {
"failed to remove directory recursively",
"not implemented",
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::size(bool /* recursive */) const -> std::uint64_t {
REPERTORY_USES_FUNCTION_NAME();
try {
if (not session_) {
throw utils::error::create_exception(function_name,
{
"session not found",
path_,
});
}
throw utils::error::create_exception(function_name,
{
"failed to get directory size",
"not implemented",
path_,
});
} 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,549 @@
/*
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.
*/
#if defined(PROJECT_ENABLE_LIBDSM)
#include "utils/file_smb_file.hpp"
#include "utils/common.hpp"
#include "utils/error.hpp"
#include "utils/string.hpp"
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 {
REPERTORY_USES_FUNCTION_NAME();
try {
if (not session_) {
throw utils::error::create_exception(function_name,
{
"session not found",
path_,
});
}
// auto to_path = utils::path::absolute(new_path);
throw utils::error::create_exception(function_name,
{
"failed to copy file",
"not implemented",
std::to_string(overwrite),
new_path,
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::exists() const -> bool {
REPERTORY_USES_FUNCTION_NAME();
try {
if (not session_) {
throw utils::error::create_exception(function_name,
{
"session not found",
path_,
});
}
smb_stat_t st{
smb_fstat(session_.get(), tid_,
smb_create_relative_path(path_).c_str()),
smb_stat_deleter(),
};
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 {
REPERTORY_USES_FUNCTION_NAME();
try {
throw utils::error::create_exception(function_name,
{
"failed to flush file",
"not implemented",
path_,
});
} 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> {
REPERTORY_USES_FUNCTION_NAME();
try {
if (session == nullptr) {
throw utils::error::create_exception(function_name,
{
"session not found",
path,
});
}
auto rel_path = smb_create_relative_path(path);
smb_stat_t st{
smb_fstat(session, tid, rel_path.c_str()),
smb_stat_deleter(),
};
if (not st) {
throw utils::error::create_exception(function_name,
{
"failed to stat file",
"not implemented",
rel_path,
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 {
REPERTORY_USES_FUNCTION_NAME();
try {
if (not session_) {
throw utils::error::create_exception(function_name,
{
"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 {
REPERTORY_USES_FUNCTION_NAME();
try {
if (utils::string::begins_with(new_path, "//")) {
throw utils::error::create_exception(function_name,
{
"failed to move file",
"new path must be in same share",
new_path,
path_,
});
}
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 utils::error::create_exception(function_name,
{
"failed to connect to share",
std::to_string(res),
share_name_,
path_,
});
}
res = smb_file_mv(session_.get(), tid_, from_path.c_str(), to_path.c_str());
if (res != DSM_SUCCESS) {
throw utils::error::create_exception(function_name,
{
"failed to move file",
std::to_string(res),
from_path,
to_path,
});
}
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 {
REPERTORY_USES_FUNCTION_NAME();
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 utils::error::create_exception(function_name,
{
"failed to connect to share",
std::to_string(res),
share_name_,
path_,
});
}
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 utils::error::create_exception(
function_name, {
"failed to open file",
std::to_string(res),
utils::string::from_bool(read_only),
rel_path,
path_,
});
}
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 {
REPERTORY_USES_FUNCTION_NAME();
try {
if (total_read != nullptr) {
(*total_read) = 0U;
}
if (not fd_.has_value()) {
throw utils::error::create_exception(function_name,
{
"failed to read file",
"file not open",
path_,
});
}
auto res = smb_fseek(session_.get(), *fd_, static_cast<off_t>(offset),
SMB_SEEK_SET);
if (res == -1) {
throw utils::error::create_exception(function_name,
{
"failed to seek file",
std::to_string(res),
std::to_string(offset),
path_,
});
}
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 utils::error::create_exception(function_name,
{
"failed to read file",
std::to_string(res),
std::to_string(offset),
std::to_string(to_read),
path_,
});
}
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 {
REPERTORY_USES_FUNCTION_NAME();
try {
close();
return utils::retry_action([this]() -> bool {
if (not exists()) {
return true;
}
try {
auto res = smb_tree_connect(session_.get(), share_name_.c_str(), &tid_);
if (res != DSM_SUCCESS) {
throw utils::error::create_exception(function_name,
{
"failed to connect to share",
std::to_string(res),
share_name_,
path_,
});
}
auto rel_path = smb_create_relative_path(path_);
res = smb_file_rm(session_.get(), tid_, rel_path.c_str());
if (res != DSM_SUCCESS) {
throw utils::error::create_exception(
function_name,
{
"failed to remove file",
std::to_string(res),
std::to_string(smb_session_get_nt_status(session_.get())),
rel_path,
path_,
});
}
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> {
REPERTORY_USES_FUNCTION_NAME();
try {
if (not session_) {
throw utils::error::create_exception(function_name,
{
"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()),
smb_stat_deleter(),
};
if (not st) {
throw utils::error::create_exception(function_name,
{
"failed to stat directory",
rel_path,
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 {
REPERTORY_USES_FUNCTION_NAME();
try {
throw utils::error::create_exception(function_name,
{
"failed to truncate file",
"not implemented",
std::to_string(size),
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::write(const unsigned char *data, std::size_t to_write,
std::size_t offset, std::size_t *total_written) -> bool {
REPERTORY_USES_FUNCTION_NAME();
try {
if (total_written != nullptr) {
(*total_written) = 0U;
}
if (not fd_.has_value()) {
throw utils::error::create_exception(function_name,
{
"failed to write file",
"file not open",
path_,
});
}
auto res = smb_fseek(session_.get(), *fd_, static_cast<off_t>(offset),
SMB_SEEK_SET);
if (res == -1) {
throw utils::error::create_exception(function_name,
{
"failed to seek file",
std::to_string(res),
std::to_string(offset),
path_,
});
}
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 utils::error::create_exception(function_name,
{
"failed to write file",
std::to_string(res),
std::to_string(offset),
std::to_string(to_write),
path_,
});
}
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,65 @@
/*
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_thread_file.hpp"
namespace repertory::utils::file {
// auto thread_file::attach_file(native_handle handle,
// bool read_only) -> fs_file_t {}
thread_file::thread_file(std::string_view path)
: file_(new repertory::utils::file::file(path)) {}
thread_file::thread_file(std::wstring_view path)
: file_(new repertory::utils::file::file(utils::string::to_utf8(path))) {}
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

189
support/src/utils/hash.cpp Normal file
View File

@ -0,0 +1,189 @@
/*
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.
*/
#if defined(PROJECT_ENABLE_LIBSODIUM)
#include "utils/hash.hpp"
#include "utils/error.hpp"
namespace repertory::utils::encryption {
auto create_hash_blake2b_256(std::string_view data) -> hash_256_t {
return create_hash_blake2b_t<hash_256_t>(
reinterpret_cast<const unsigned char *>(data.data()), data.size());
}
auto create_hash_blake2b_256(std::wstring_view data) -> hash_256_t {
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<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<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<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<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(
reinterpret_cast<const unsigned char *>(data.data()), data.size());
}
auto create_hash_sha256(std::wstring_view data) -> hash_256_t {
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(
reinterpret_cast<const unsigned char *>(data.data()), data.size());
}
auto create_hash_sha512(std::wstring_view data) -> hash_512_t {
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 {
REPERTORY_USES_FUNCTION_NAME();
hash_512_t hash{};
crypto_hash_sha512_state state{};
auto res = crypto_hash_sha512_init(&state);
if (res != 0) {
throw utils::error::create_exception(function_name,
{
"failed to initialize sha-512",
std::to_string(res),
});
}
res = crypto_hash_sha512_update(&state, data, data_size);
if (res != 0) {
throw utils::error::create_exception(function_name,
{
"failed to update sha-512",
std::to_string(res),
});
}
res = crypto_hash_sha512_final(&state, hash.data());
if (res != 0) {
throw utils::error::create_exception(function_name,
{
"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 {
REPERTORY_USES_FUNCTION_NAME();
hash_256_t hash{};
crypto_hash_sha256_state state{};
auto res = crypto_hash_sha256_init(&state);
if (res != 0) {
throw utils::error::create_exception(function_name,
{
"failed to initialize sha-256",
std::to_string(res),
});
}
res = crypto_hash_sha256_update(&state, data, data_size);
if (res != 0) {
throw utils::error::create_exception(function_name,
{
"failed to update sha-256",
std::to_string(res),
});
}
res = crypto_hash_sha256_final(&state, hash.data());
if (res != 0) {
throw utils::error::create_exception(function_name,
{
"failed to finalize sha-256",
std::to_string(res),
});
}
return hash;
}
} // namespace repertory::utils::encryption
#endif // defined(PROJECT_ENABLE_LIBSODIUM)

315
support/src/utils/path.cpp Normal file
View File

@ -0,0 +1,315 @@
/*
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/path.hpp"
#include "utils/common.hpp"
#include "utils/error.hpp"
#include "utils/file.hpp"
#include "utils/string.hpp"
#include "utils/unix.hpp"
namespace {
[[nodiscard]] auto resolve(std::string path) -> std::string {
REPERTORY_USES_FUNCTION_NAME();
#if defined(_WIN32)
if (repertory::utils::string::contains(path, "~\\")) {
repertory::utils::string::replace(path, "~\\", "%USERPROFILE%\\");
}
if (repertory::utils::string::contains(path, "~/")) {
repertory::utils::string::replace(path, "~/", "%USERPROFILE%\\");
}
if (repertory::utils::string::contains(path, "%")) {
auto size = ::ExpandEnvironmentStringsA(path.c_str(), nullptr, 0);
std::string dest;
dest.resize(size);
::ExpandEnvironmentStringsA(path.c_str(), dest.data(),
static_cast<DWORD>(dest.size()));
path = dest.c_str();
}
#else // !defined (_WIN32)
if (repertory::utils::string::contains(path, "~\\")) {
repertory::utils::string::replace(path, "~\\", "~/");
}
if (repertory::utils::string::contains(path, "~/")) {
std::string home{};
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 (not res) {
throw repertory::utils::error::create_exception(function_name,
{
"failed to getpwuid",
res.reason,
});
}
path = repertory::utils::string::replace(path, "~/", home + "/");
}
#endif // defined (_WIN32)
return repertory::utils::path::finalize(path);
}
} // namespace
namespace repertory::utils::path {
auto absolute(std::string_view path) -> std::string {
std::string abs_path{path};
if (abs_path.empty()) {
return abs_path;
}
abs_path = resolve(abs_path);
#if defined(_WIN32)
if (not utils::string::contains(abs_path, dot)) {
return abs_path;
}
std::string temp;
temp.resize(repertory::max_path_length + 1U);
::GetFullPathNameA(abs_path.c_str(), static_cast<DWORD>(temp.size()),
temp.data(), nullptr);
#else // !defined(_WIN32)
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 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{};
static std::unordered_map<std::string, std::string> found_items{};
mutex_lock lock(mtx);
if (found_items.contains(name_without_extension)) {
return found_items.at(name_without_extension);
}
auto path = utils::get_environment_variable("PATH");
if (path.empty()) {
return path;
}
#if defined(_WIN32)
static constexpr const std::array<std::string_view, 4U> extension_list{
".bat",
".cmd",
".exe",
".ps1",
};
static constexpr const auto split_char = ';';
#else // !defined(_WIN32)
static constexpr const std::array<std::string_view, 2U> extension_list{
"",
".sh",
};
static constexpr const auto split_char = ':';
#endif // defined(_WIN32)
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 (utils::file::file(exec_path).exists()) {
found_items[name_without_extension] = exec_path;
return exec_path;
}
}
}
return "";
}
[[nodiscard]] auto
find_program_in_path(std::wstring_view name_without_extension) -> std::wstring {
return utils::string::from_utf8(
find_program_in_path(utils::string::to_utf8(name_without_extension)));
}
auto get_parent_path(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)
abs_path = std::filesystem::path{abs_path}.parent_path().string();
#endif // defined(_WIN32)
return finalize(abs_path);
}
auto get_parent_path(std::wstring_view path) -> std::wstring {
return utils::string::from_utf8(
get_parent_path(utils::string::to_utf8(path)));
}
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 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, backslash, slash);
abs_path = std::string{slash} + abs_path;
#endif // defined(_WIN32)
return "file://" + abs_path;
}
auto make_file_uri(std::wstring_view path) -> std::wstring {
return utils::string::from_utf8(make_file_uri(utils::string::to_utf8(path)));
}
auto strip_to_file_name(std::string path) -> std::string {
if (path == "." || path == "..") {
return path;
}
#if defined(_WIN32)
return ::PathFindFileNameA(path.c_str());
#else // !defined(_WIN32)
return utils::string::contains(path, slash) ? basename(path.data()) : path;
#endif // defined(_WIN32)
}
auto strip_to_file_name(std::wstring path) -> std::wstring {
return utils::string::from_utf8(
strip_to_file_name(utils::string::to_utf8(path)));
}
auto unmake_file_uri(std::string_view uri) -> std::string {
static constexpr const std::array<std::string_view, 23U> escape_characters = {
{
" ", "<", ">", "#", "%", "+", "{", "}", "|", "\\", "^", "~",
"[", "]", "`", ";", "/", "?", ":", "@", "=", "&", "$",
}};
static constexpr const std::array<std::string_view, 23U>
escape_sequences_lower = {{
"%20", "%3c", "%3e", "%23", "%25", "%2b", "%7b", "%7d",
"%7c", "%5c", "%5e", "%7e", "%5b", "%5d", "%60", "%3b",
"%2f", "%3f", "%3a", "%40", "%3d", "%26", "%24",
}};
static constexpr const std::array<std::string_view, 23U>
escape_sequences_upper = {{
"%20", "%3C", "%3E", "%23", "%25", "%2B", "%7B", "%7D",
"%7C", "%5C", "%5E", "%7E", "%5B", "%5D", "%60", "%3B",
"%2F", "%3F", "%3A", "%40", "%3D", "%26", "%24",
}};
std::string ret_uri{uri};
#if defined(_WIN32)
ret_uri = ret_uri.substr(8U);
#else
ret_uri = ret_uri.substr(7U);
#endif
for (std::size_t idx = 0U; idx < escape_characters.size(); idx++) {
utils::string::replace(ret_uri, escape_sequences_lower.at(idx),
escape_characters.at(idx));
utils::string::replace(ret_uri, escape_sequences_upper.at(idx),
escape_characters.at(idx));
}
return absolute(ret_uri);
}
auto unmake_file_uri(std::wstring_view uri) -> std::wstring {
return utils::string::from_utf8(unmake_file_uri(utils::string::to_utf8(uri)));
}
} // namespace repertory::utils::path

View File

@ -0,0 +1,138 @@
/*
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/string.hpp"
namespace repertory::utils::string {
auto from_bool(bool val) -> std::string {
return std::to_string(static_cast<int>(val));
}
#if defined(PROJECT_ENABLE_BOOST)
auto from_dynamic_bitset(const boost::dynamic_bitset<> &bitset) -> std::string {
std::stringstream stream;
boost::archive::text_oarchive archive(stream);
archive << bitset;
return stream.str();
}
#endif // defined(PROJECT_ENABLE_BOOST)
auto from_utf8(std::string_view str) -> std::wstring {
return str.empty()
? L""
: std::wstring_convert<std::codecvt_utf8<wchar_t>, wchar_t>()
.from_bytes(std::string{str});
}
#if defined(PROJECT_ENABLE_SFML)
auto replace_sf(sf::String &src, const sf::String &find, const sf::String &with,
std::size_t start_pos) -> sf::String & {
if (not src.isEmpty() && (start_pos < src.getSize())) {
while ((start_pos = src.find(find, start_pos)) != std::string::npos) {
src.replace(start_pos, find.getSize(), with);
start_pos += with.getSize();
}
}
return src;
}
auto split_sf(sf::String str, wchar_t delim,
bool should_trim) -> std::vector<sf::String> {
auto result = std::views::split(str.toWideString(), delim);
std::vector<sf::String> ret{};
for (auto &&word : result) {
auto val = std::wstring{word.begin(), word.end()};
if (should_trim) {
trim(val);
}
ret.emplace_back(val);
}
return ret;
}
#endif // defined(PROJECT_ENABLE_SFML)
auto to_bool(std::string val) -> bool {
auto ret = false;
trim(val);
if (is_numeric(val)) {
if (contains(val, ".")) {
ret = (to_double(val) != 0.0);
} else {
std::istringstream(val) >> ret;
}
} else {
std::istringstream(to_lower(val)) >> std::boolalpha >> ret;
}
return ret;
}
auto to_double(const std::string &str) -> double { return std::stod(str); }
#if defined(PROJECT_ENABLE_BOOST)
auto to_dynamic_bitset(const std::string &val) -> boost::dynamic_bitset<> {
std::stringstream stream(val);
boost::dynamic_bitset<> bitset;
boost::archive::text_iarchive archive(stream);
archive >> bitset;
return bitset;
}
#endif // defined(PROJECT_ENABLE_BOOST)
auto to_int32(const std::string &val) -> std::int32_t { return std::stoi(val); }
auto to_int64(const std::string &val) -> std::int64_t {
return std::stoll(val);
}
auto to_size_t(const std::string &val) -> std::size_t {
return static_cast<std::size_t>(std::stoull(val));
}
auto to_uint8(const std::string &val) -> std::uint8_t {
return static_cast<std::uint8_t>(std::stoul(val));
}
auto to_uint16(const std::string &val) -> std::uint16_t {
return static_cast<std::uint16_t>(std::stoul(val));
}
auto to_uint32(const std::string &val) -> std::uint32_t {
return static_cast<std::uint32_t>(std::stoul(val));
}
auto to_uint64(const std::string &val) -> std::uint64_t {
return std::stoull(val);
}
auto to_utf8(std::string_view str) -> std::string { return std::string{str}; }
auto to_utf8(std::wstring_view str) -> std::string {
return str.empty()
? ""
: std::wstring_convert<std::codecvt_utf8<wchar_t>, wchar_t>()
.to_bytes(std::wstring{str});
}
} // namespace repertory::utils::string

View File

@ -0,0 +1,88 @@
/*
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/time.hpp"
namespace repertory::utils::time {
void get_local_time_now(struct tm &local_time) {
std::memset(&local_time, 0, sizeof(local_time));
const auto now =
std::chrono::system_clock::to_time_t(std::chrono::system_clock::now());
#if defined(_WIN32)
localtime_s(&local_time, &now);
#else // !defined(_WIN32)
localtime_r(&now, &local_time);
#endif // defined(_WIN32)
}
auto get_time_now() -> std::uint64_t {
return static_cast<std::uint64_t>(
std::chrono::duration_cast<std::chrono::nanoseconds>(
std::chrono::system_clock::now().time_since_epoch())
.count());
}
#if defined(_WIN32)
// https://stackoverflow.com/questions/321849/strptime-equivalent-on-windows
auto strptime(const char *s, const char *f, struct tm *tm) -> const char * {
std::istringstream input{s};
input.imbue(std::locale(setlocale(LC_ALL, nullptr)));
input >> std::get_time(tm, f);
if (input.fail()) {
return nullptr;
}
return reinterpret_cast<const char *>(s + input.tellg());
}
// https://www.frenk.com/2009/12/convert-filetime-to-unix-timestamp/
auto unix_time_to_filetime(std::uint64_t unix_time) -> FILETIME {
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_CONVERSION) * WIN32_TIME_NANOS_PER_TICK;
}
} // namespace repertory::utils::time

View File

@ -0,0 +1,83 @@
/*
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.
*/
#if !defined(_WIN32)
#include "utils/unix.hpp"
#include "utils/collection.hpp"
namespace repertory::utils {
#if !defined(__APPLE__)
auto convert_to_uint64(const pthread_t &thread) -> std::uint64_t {
return static_cast<std::uint64_t>(thread);
}
#endif // !defined(__APPLE__)
auto get_last_error_code() -> int { return errno; }
auto get_thread_id() -> std::uint64_t {
return convert_to_uint64(pthread_self());
}
auto is_uid_member_of_group(uid_t uid, gid_t gid) -> bool {
std::vector<gid_t> groups{};
auto res = use_getpwuid(uid, [&groups](struct passwd *pass) {
int group_count{};
if (getgrouplist(pass->pw_name, pass->pw_gid, nullptr, &group_count) < 0) {
groups.resize(static_cast<std::size_t>(group_count));
#if defined(__APPLE__)
getgrouplist(pass->pw_name, pass->pw_gid,
reinterpret_cast<int *>(groups.data()), &group_count);
#else // !defined(__APPLE__)
getgrouplist(pass->pw_name, pass->pw_gid, groups.data(), &group_count);
#endif // defined(__APPLE__)
}
});
if (not res) {
throw utils::error::create_exception(res.function_name,
{"use_getpwuid failed", res.reason});
}
return collection::includes(groups, gid);
}
auto use_getpwuid(uid_t uid, passwd_callback_t callback) -> result {
REPERTORY_USES_FUNCTION_NAME();
static std::mutex mtx{};
mutex_lock lock{mtx};
auto *temp_pw = getpwuid(uid);
if (temp_pw == nullptr) {
return {
std::string{function_name},
false,
"'getpwuid' returned nullptr",
};
}
callback(temp_pw);
return {std::string{function_name}};
}
} // namespace repertory::utils
#endif // !defined(_WIN32)

View File

@ -0,0 +1,144 @@
/*
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.
*/
#if defined(_WIN32)
#include "utils/windows.hpp"
#include "utils/com_init_wrapper.hpp"
#include "utils/error.hpp"
#include "utils/string.hpp"
namespace repertory::utils {
void create_console() {
if (::AllocConsole() == 0) {
return;
}
FILE *dummy{nullptr};
freopen_s(&dummy, "CONOUT$", "w", stdout);
freopen_s(&dummy, "CONOUT$", "w", stderr);
freopen_s(&dummy, "CONIN$", "r", stdin);
std::cout.clear();
std::clog.clear();
std::cerr.clear();
std::cin.clear();
auto *out_w = CreateFileW(L"CONOUT$", GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr,
OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr);
auto *in_w = CreateFileW(L"CONIN$", GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr,
OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr);
SetStdHandle(STD_OUTPUT_HANDLE, out_w);
SetStdHandle(STD_ERROR_HANDLE, out_w);
SetStdHandle(STD_INPUT_HANDLE, in_w);
std::wcout.clear();
std::wclog.clear();
std::wcerr.clear();
std::wcin.clear();
}
void free_console() { ::FreeConsole(); }
auto get_last_error_code() -> DWORD { return ::GetLastError(); }
auto get_local_app_data_directory() -> const std::string & {
REPERTORY_USES_FUNCTION_NAME();
static std::string app_data = ([]() -> std::string {
com_init_wrapper cw;
PWSTR local_app_data{};
if (SUCCEEDED(::SHGetKnownFolderPath(FOLDERID_LocalAppData, 0, nullptr,
&local_app_data))) {
auto ret = utils::string::to_utf8(local_app_data);
::CoTaskMemFree(local_app_data);
return ret;
}
throw utils::error::create_exception(
function_name, {
"unable to detect local application data folder",
});
})();
return app_data;
}
auto get_thread_id() -> std::uint64_t {
return static_cast<std::uint64_t>(::GetCurrentThreadId());
}
auto is_process_elevated() -> bool {
auto ret{false};
HANDLE token{INVALID_HANDLE_VALUE};
if (::OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &token)) {
TOKEN_ELEVATION te{};
DWORD sz = sizeof(te);
if (::GetTokenInformation(token, TokenElevation, &te, sizeof(te), &sz)) {
ret = (te.TokenIsElevated != 0);
}
}
if (token != INVALID_HANDLE_VALUE) {
::CloseHandle(token);
}
return ret;
}
auto run_process_elevated(std::vector<const char *> args) -> int {
std::cout << "Elevating Process" << std::endl;
std::string parameters{"-hidden"};
for (std::size_t idx = 1U; idx < args.size(); idx++) {
parameters +=
(parameters.empty() ? args.at(idx) : " " + std::string(args.at(idx)));
}
std::string full_path;
full_path.resize(repertory::max_path_length + 1);
if (::GetModuleFileNameA(nullptr, full_path.data(),
repertory::max_path_length)) {
SHELLEXECUTEINFOA sei{};
sei.fMask = SEE_MASK_NOCLOSEPROCESS;
sei.cbSize = sizeof(sei);
sei.lpVerb = "runas";
sei.lpFile = full_path.c_str();
sei.lpParameters = parameters.c_str();
sei.hwnd = nullptr;
sei.nShow = SW_NORMAL;
if (::ShellExecuteExA(&sei)) {
::WaitForSingleObject(sei.hProcess, INFINITE);
DWORD exit_code{};
::GetExitCodeProcess(sei.hProcess, &exit_code);
::CloseHandle(sei.hProcess);
return static_cast<int>(exit_code);
}
}
return static_cast<int>(::GetLastError());
}
void set_last_error_code(DWORD error_code) { ::SetLastError(error_code); }
} // namespace repertory::utils
#endif // defined(_WIN32)