feat: add current_exe in rust env
This commit is contained in:
@ -1,6 +1,7 @@
|
||||
#include "env.hpp"
|
||||
#include "../macro/os_detector.hpp"
|
||||
|
||||
// Environment variable required
|
||||
#if defined(YYCC_OS_WINDOWS)
|
||||
#include "../encoding/windows.hpp"
|
||||
#include "../num/safe_op.hpp"
|
||||
@ -14,14 +15,25 @@
|
||||
#include <stdexcept>
|
||||
#endif
|
||||
|
||||
// Path related functions required
|
||||
#if defined(YYCC_OS_WINDOWS)
|
||||
#include "../windows/winfct.hpp"
|
||||
#else
|
||||
#include <unistd.h>
|
||||
#include <sys/stat.h>
|
||||
#endif
|
||||
|
||||
#define SAFECAST ::yycc::num::safe_cast
|
||||
#define SAFEOP ::yycc::num::safe_op
|
||||
#define ENC ::yycc::encoding::windows
|
||||
#define REINTERPRET ::yycc::string::reinterpret
|
||||
#define WINFCT ::yycc::windows::winfct
|
||||
|
||||
namespace yycc::rust::env {
|
||||
|
||||
EnvResult<std::u8string> get_var(const std::u8string_view &name) {
|
||||
#pragma region Environment Variable
|
||||
|
||||
VarResult<std::u8string> get_var(const std::u8string_view &name) {
|
||||
#if defined(YYCC_OS_WINDOWS)
|
||||
// Reference: https://learn.microsoft.com/en-us/windows/win32/api/processenv/nf-processenv-getenvironmentvariablew
|
||||
// Convert to wchar
|
||||
@ -38,22 +50,22 @@ namespace yycc::rust::env {
|
||||
// the size passed to this function must include NULL terminal.
|
||||
// So we forcely use checked add and sub for this bad behavior.
|
||||
auto fct_size = SAFEOP::checked_add<size_t>(wvalue.size(), 1);
|
||||
if (!fct_size.has_value()) return std::unexpected(EnvError::BadArithmetic);
|
||||
if (!fct_size.has_value()) return std::unexpected(VarError::BadArithmetic);
|
||||
auto rv = ::GetEnvironmentVariableW(wname.c_str(), wvalue.data(), fct_size.value());
|
||||
|
||||
// Check the return value
|
||||
if (rv == 0) {
|
||||
// Function failed. Extract error reason.
|
||||
auto ec = GetLastError();
|
||||
if (ec == ERROR_ENVVAR_NOT_FOUND) return std::unexpected(EnvError::NoSuchName);
|
||||
else return std::unexpected(EnvError::BadCall);
|
||||
if (ec == ERROR_ENVVAR_NOT_FOUND) return std::unexpected(VarError::NoSuchName);
|
||||
else return std::unexpected(VarError::BadCall);
|
||||
} else {
|
||||
// Function okey. Check the size.
|
||||
// Fetch function expected size.
|
||||
auto rv_size = SAFECAST::try_to<size_t>(rv);
|
||||
if (!rv_size.has_value()) return std::unexpected(EnvError::BadArithmetic);
|
||||
if (!rv_size.has_value()) return std::unexpected(VarError::BadArithmetic);
|
||||
auto exp_size = SAFEOP::checked_sub<size_t>(rv_size.value(), 1);
|
||||
if (!exp_size.has_value()) return std::unexpected(EnvError::BadArithmetic);
|
||||
if (!exp_size.has_value()) return std::unexpected(VarError::BadArithmetic);
|
||||
|
||||
// YYC MARK:
|
||||
// According to Microsoft, the return value of this function is just a bullshit.
|
||||
@ -75,7 +87,7 @@ namespace yycc::rust::env {
|
||||
}
|
||||
|
||||
// Convert back to UTF8 string and return.
|
||||
return ENC::to_utf8(wvalue).transform_error([](auto err) { return EnvError::BadEncoding; });
|
||||
return ENC::to_utf8(wvalue).transform_error([](auto err) { return VarError::BadEncoding; });
|
||||
#else
|
||||
// String view is not NULL-terminal-guaranted,
|
||||
// so we solve this when casting its type.
|
||||
@ -83,18 +95,18 @@ namespace yycc::rust::env {
|
||||
|
||||
// Fetch variable
|
||||
auto finder = std::getenv(ordinary_name.c_str());
|
||||
if (finder == nullptr) return std::unexpected(EnvError::NoSuchName);
|
||||
if (finder == nullptr) return std::unexpected(VarError::NoSuchName);
|
||||
else return REINTERPRET::as_utf8(finder);
|
||||
#endif
|
||||
}
|
||||
|
||||
EnvResult<void> set_var(const std::u8string_view &name, const std::u8string_view &value) {
|
||||
VarResult<void> set_var(const std::u8string_view &name, const std::u8string_view &value) {
|
||||
#if defined(YYCC_OS_WINDOWS)
|
||||
// Reference: https://learn.microsoft.com/en-us/windows/win32/api/processenv/nf-processenv-setenvironmentvariablew
|
||||
|
||||
// Convert to wchar, set variable, and check result.
|
||||
auto rv = ::SetEnvironmentVariableW(ENC::to_wchar(name).value().c_str(), ENC::to_wchar(value).value().c_str());
|
||||
if (!rv) return std::unexpected(EnvError::BadCall);
|
||||
if (!rv) return std::unexpected(VarError::BadCall);
|
||||
else return {};
|
||||
#else
|
||||
// Reference: https://pubs.opengroup.org/onlinepubs/9699919799/functions/setenv.html
|
||||
@ -106,19 +118,19 @@ namespace yycc::rust::env {
|
||||
if (rv == 0) return {};
|
||||
|
||||
// Check error type
|
||||
if (errno == EINVAL) return std::unexpected(EnvError::BadName);
|
||||
else if (errno == ENOMEM) return std::unexpected(EnvError::NoMemory);
|
||||
if (errno == EINVAL) return std::unexpected(VarError::BadName);
|
||||
else if (errno == ENOMEM) return std::unexpected(VarError::NoMemory);
|
||||
else throw std::runtime_error("impossible errno");
|
||||
#endif
|
||||
}
|
||||
|
||||
EnvResult<void> del_var(const std::u8string_view &name) {
|
||||
VarResult<void> del_var(const std::u8string_view &name) {
|
||||
#if defined(YYCC_OS_WINDOWS)
|
||||
// Reference: https://learn.microsoft.com/en-us/windows/win32/api/processenv/nf-processenv-setenvironmentvariablew
|
||||
|
||||
// Convert to wchar, delete variable, and check result.
|
||||
auto rv = ::SetEnvironmentVariableW(ENC::to_wchar(name).value().c_str(), NULL);
|
||||
if (!rv) return std::unexpected(EnvError::BadCall);
|
||||
if (!rv) return std::unexpected(VarError::BadCall);
|
||||
else return {};
|
||||
#else
|
||||
// Reference: https://pubs.opengroup.org/onlinepubs/9699919799/functions/unsetenv.html
|
||||
@ -129,9 +141,70 @@ namespace yycc::rust::env {
|
||||
if (rv == 0) return {};
|
||||
|
||||
// Check error type
|
||||
if (errno == EINVAL) return std::unexpected(EnvError::BadName);
|
||||
if (errno == EINVAL) return std::unexpected(VarError::BadName);
|
||||
else throw std::runtime_error("impossible errno");
|
||||
#endif
|
||||
}
|
||||
|
||||
#pragma endregion
|
||||
|
||||
#pragma region Environment
|
||||
|
||||
PathResult<std::u8string> current_exe() {
|
||||
#if defined(YYCC_OS_WINDOWS)
|
||||
return WINFCT::get_module_file_name(NULL).transform_error([](auto e) { return PathError::Win32; });
|
||||
#else
|
||||
// TODO:
|
||||
// "/proc/self/exe" is Linux specific, not in POSIX standard.
|
||||
// This method may need further patch when running on macOS.
|
||||
|
||||
// Reference: https://www.man7.org/linux/man-pages/man2/readlink.2.html
|
||||
|
||||
// specify the path
|
||||
constexpr char path[] = "/proc/self/exe";
|
||||
|
||||
// get the expected size
|
||||
struct stat sb;
|
||||
if (lstat(path, &sb) != 0) {
|
||||
}
|
||||
auto expected_size = SAFECAST::try_to<size_t>(sb.st_size);
|
||||
if (!expected_size.has_value()) {
|
||||
return std::unexpected(PathError::BadCast);
|
||||
}
|
||||
auto buf_size = expected_size.value();
|
||||
// Some magic symlinks under (for example) /proc and /sys report 'st_size' as zero.
|
||||
// In that case, take PATH_MAX as a "good enough" estimate.
|
||||
if (buf_size == 0) {
|
||||
buf_size = PATH_MAX;
|
||||
}
|
||||
|
||||
// prepare buffer and resize it;
|
||||
std::u8string rv(u8'\0', buf_size);
|
||||
|
||||
// write data
|
||||
auto passed_size = SAFEOP::checked_add<size_t>(buf_size, 1);
|
||||
if (!passed_size.has_value()) {
|
||||
return std::unexpected(PathError::Overflow);
|
||||
}
|
||||
ssize_t nbytes = readlink(path, REINTERPRET::as_ordinary(rv.data()), passed_size.value());
|
||||
if (nbytes < 0) {
|
||||
return std::unexpected(PathError::Posix);
|
||||
}
|
||||
|
||||
// check written size
|
||||
auto written_size = SAFECAST::try_to<size_t>(nbytes);
|
||||
if (!written_size.has_value()) {
|
||||
return std::unexpected(PathError::BadCast);
|
||||
}
|
||||
if (written_size.value() != buf_size) {
|
||||
return std::unexpected(PathError::BadSize);
|
||||
}
|
||||
|
||||
// okey
|
||||
return rv;
|
||||
#endif
|
||||
}
|
||||
|
||||
#pragma endregion
|
||||
|
||||
} // namespace yycc::rust::env
|
||||
|
@ -16,8 +16,10 @@
|
||||
*/
|
||||
namespace yycc::rust::env {
|
||||
|
||||
/// @brief The error occurs in this module.
|
||||
enum class EnvError {
|
||||
#pragma region Environment Variable
|
||||
|
||||
/// @brief The error occurs in environment variable operations.
|
||||
enum class VarError {
|
||||
NoSuchName, ///< The variable with given name is not presented.
|
||||
BadEncoding, ///< Error when performing encoding convertion.
|
||||
BadArithmetic, ///< Error when performing arithmetic operations.
|
||||
@ -26,16 +28,16 @@ namespace yycc::rust::env {
|
||||
NoMemory, ///< No enough memory to finish this operation.
|
||||
};
|
||||
|
||||
/// @brief The result type in this module.
|
||||
/// @brief The result type in environment variable operations.
|
||||
template<typename T>
|
||||
using EnvResult = std::expected<T, EnvError>;
|
||||
using VarResult = std::expected<T, VarError>;
|
||||
|
||||
/**
|
||||
* @brief Get the value of given environment variable name.
|
||||
* @param[in] name The name of environment variable
|
||||
* @return Gotten value, or error occurs.
|
||||
*/
|
||||
EnvResult<std::u8string> get_var(const std::u8string_view& name);
|
||||
VarResult<std::u8string> get_var(const std::u8string_view& name);
|
||||
|
||||
/**
|
||||
* @brief Set the value of given environment variable name.
|
||||
@ -47,7 +49,7 @@ namespace yycc::rust::env {
|
||||
* @param[in] value The value to be written into.
|
||||
* @return Nothing or error occurs.
|
||||
*/
|
||||
EnvResult<void> set_var(const std::u8string_view& name, const std::u8string_view& value);
|
||||
VarResult<void> set_var(const std::u8string_view& name, const std::u8string_view& value);
|
||||
|
||||
/**
|
||||
* @brief Delete environment variable with given name.
|
||||
@ -57,6 +59,31 @@ namespace yycc::rust::env {
|
||||
* @param[in] name The name of environment variable
|
||||
* @return Nothing, or error occurs.
|
||||
*/
|
||||
EnvResult<void> del_var(const std::u8string_view& name);
|
||||
VarResult<void> del_var(const std::u8string_view& name);
|
||||
|
||||
#pragma endregion
|
||||
|
||||
#pragma region Environment Path
|
||||
|
||||
/// @brief Error occurs when operating path related functions.
|
||||
enum class PathError {
|
||||
Win32, ///< Underlying Win32 function error.
|
||||
Posix, ///< Underlying POSIX failed.
|
||||
BadSize, ///< Written size if not matched with expected size.
|
||||
BadCast, ///< Error occurs when casting values.
|
||||
Overflow, ///< Some arithmetic operation overflow.
|
||||
};
|
||||
|
||||
/// @brief The result type used for path related functions;
|
||||
template<typename T>
|
||||
using PathResult = std::expected<T, PathError>;
|
||||
|
||||
/**
|
||||
* @brief Get the path of the current running executable.
|
||||
* @return Gotten path (no absolute path guaranteed) or error occurs.
|
||||
*/
|
||||
PathResult<std::u8string> current_exe();
|
||||
|
||||
#pragma endregion
|
||||
|
||||
} // namespace yycc::rust::env
|
||||
|
@ -1,6 +1,8 @@
|
||||
#include <gtest/gtest.h>
|
||||
#include <yycc.hpp>
|
||||
#include <yycc/rust/env.hpp>
|
||||
#include <yycc/macro/os_detector.hpp>
|
||||
#include <filesystem>
|
||||
|
||||
#define ENV ::yycc::rust::env
|
||||
|
||||
@ -9,7 +11,7 @@ namespace yycctest::rust::env {
|
||||
constexpr char8_t VAR_NAME[] = u8"HOMER";
|
||||
constexpr char8_t VAR_VALUE[] = u8"doh";
|
||||
|
||||
TEST(RustEnv, All) {
|
||||
TEST(RustEnv, EnvVar) {
|
||||
// Write a new variable should okey
|
||||
{
|
||||
auto rv = ENV::set_var(VAR_NAME, VAR_VALUE);
|
||||
@ -42,4 +44,19 @@ namespace yycctest::rust::env {
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
TEST(RustEnv, CurrentExe) {
|
||||
auto rv = ENV::current_exe();
|
||||
ASSERT_TRUE(rv.has_value());
|
||||
|
||||
std::filesystem::path p(rv.value());
|
||||
auto filename = p.filename().u8string();
|
||||
#if defined(YYCC_OS_WINDOWS)
|
||||
// Only Windows has special ext.
|
||||
EXPECT_EQ(filename, u8"YYCCTest.exe");
|
||||
#else
|
||||
// Executable in other system are all in plain name.
|
||||
EXPECT_EQ(filename, u8"YYCCTest");
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace yycctest::rust::env
|
||||
|
Reference in New Issue
Block a user