diff --git a/src/yycc/rust/env.cpp b/src/yycc/rust/env.cpp index 65a592d..5a80b84 100644 --- a/src/yycc/rust/env.cpp +++ b/src/yycc/rust/env.cpp @@ -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 #endif +// Path related functions required +#if defined(YYCC_OS_WINDOWS) +#include "../windows/winfct.hpp" +#else +#include +#include +#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 get_var(const std::u8string_view &name) { +#pragma region Environment Variable + + VarResult 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(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(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(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 set_var(const std::u8string_view &name, const std::u8string_view &value) { + VarResult 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 del_var(const std::u8string_view &name) { + VarResult 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 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(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(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(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 diff --git a/src/yycc/rust/env.hpp b/src/yycc/rust/env.hpp index a04e494..ed79b50 100644 --- a/src/yycc/rust/env.hpp +++ b/src/yycc/rust/env.hpp @@ -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 - using EnvResult = std::expected; + using VarResult = std::expected; /** * @brief Get the value of given environment variable name. * @param[in] name The name of environment variable * @return Gotten value, or error occurs. */ - EnvResult get_var(const std::u8string_view& name); + VarResult 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 set_var(const std::u8string_view& name, const std::u8string_view& value); + VarResult 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 del_var(const std::u8string_view& name); + VarResult 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 + using PathResult = std::expected; + + /** + * @brief Get the path of the current running executable. + * @return Gotten path (no absolute path guaranteed) or error occurs. + */ + PathResult current_exe(); + +#pragma endregion } // namespace yycc::rust::env diff --git a/test/yycc/rust/env.cpp b/test/yycc/rust/env.cpp index 38b111f..428397f 100644 --- a/test/yycc/rust/env.cpp +++ b/test/yycc/rust/env.cpp @@ -1,6 +1,8 @@ #include #include #include +#include +#include #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