From 8cd125a4b9b3f6f4dd574869c16c24e5e0ba0bcc Mon Sep 17 00:00:00 2001 From: yyc12345 Date: Fri, 12 Dec 2025 13:23:08 +0800 Subject: [PATCH] feat: finish env remains functions --- src/yycc/env.cpp | 320 +++++++++++++++++++++++++++++++++------ src/yycc/env.hpp | 2 + src/yycc/windows/com.hpp | 1 + 3 files changed, 280 insertions(+), 43 deletions(-) diff --git a/src/yycc/env.cpp b/src/yycc/env.cpp index 3b603ea..5dca976 100644 --- a/src/yycc/env.cpp +++ b/src/yycc/env.cpp @@ -1,26 +1,40 @@ #include "env.hpp" #include "macro/os_detector.hpp" +#include +#include +#include +#include +#include -// Environment variable required #if defined(YYCC_OS_WINDOWS) #include "encoding/windows.hpp" #include "num/safe_op.hpp" #include "num/safe_cast.hpp" +#include "windows/winfct.hpp" +#include "windows/import_guard_head.hpp" #include #include -#else +#include // For getting environment variables and commandline argument. +#include // For getting commandline argument. +#include "windows/import_guard_tail.hpp" +#elif defined(YYCC_OS_LINUX) #include "string/reinterpret.hpp" #include #include -#include -#endif - -// Path related functions required -#if defined(YYCC_OS_WINDOWS) -#include "windows/winfct.hpp" -#else +#include // For reading commandline argument. #include -#include +#include // For reading symlink target. +#elif defined(YYCC_OS_MACOS) +#include "string/reinterpret.hpp" +#include "num/safe_cast.hpp" +#include +#include +#include +#include +#include // For getting current exe path. +#include // For getting commandline argument. +#else +#error "Not supported OS" #endif #define SAFECAST ::yycc::num::safe_cast @@ -146,10 +160,76 @@ namespace yycc::env { #endif } +#if defined(YYCC_OS_WINDOWS) + class EnvironmentStringsDeleter { + public: + EnvironmentStringsDeleter() {} + void operator()(LPWCH ptr) { + if (ptr != nullptr) { + FreeEnvironmentStringsW(ptr); + } + } + }; + using SmartEnvironmentStrings = std::unique_ptr, EnvironmentStringsDeleter>; +#endif + std::vector get_vars() { - // TODO: finish this function according to Rust implementation. - // Considering whether replace return value with an iterator. - throw std::logic_error("not implemented"); + // TODO: Considering whether replace return value with an iterator. + std::vector rv; + +#if defined(YYCC_OS_WINDOWS) + // Reference: https://learn.microsoft.com/en-us/windows/win32/api/processenv/nf-processenv-getenvironmentstringsw + + SmartEnvironmentStrings env_block(GetEnvironmentStringsW()); + if (env_block == nullptr) throw std::runtime_error("GetEnvironmentStringsW call failed"); + + wchar_t *current = env_block.get(); + while (*current != L'\0') { + // Fetch current wide string + std::wstring_view entry(current); + + // Parse "KEY=VALUE" + size_t pos = entry.find(L'='); + if (pos != std::string::npos) { + auto key = entry.substr(0, pos); + auto value = entry.substr(pos + 1); + if (key.empty()) throw std::runtime_error("unexpected empty variable name"); + + auto u8key = ENC::to_utf8(key); + auto u8value = ENC::to_utf8(value); + if (u8key.has_value() && u8value.has_value()) { + rv.emplace_back(std::make_pair(std::move(u8key.value()), std::move(u8value.value()))); + } else { + throw std::runtime_error("bad encoding of variable"); + } + } else { + throw std::runtime_error("bad variable syntax"); + } + + // Increase the pointer + current += entry.length() + 1; + } + + env_block.reset(); +#else + // Reference: https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap08.html + + // POSIX (Linux, macOS, etc.) + for (char **env = environ; *env != nullptr; ++env) { + std::string_view entry(*env); + size_t pos = entry.find('='); + if (pos != std::string::npos) { + auto key = entry.substr(0, pos); + auto value = entry.substr(pos + 1); + if (key.empty()) throw std::runtime_error("unexpected empty variable name"); + rv.emplace_back(std::make_pair(REINTERPRET::as_utf8(key), REINTERPRET::as_utf8(value))); + } else { + throw std::runtime_error("bad variable syntax"); + } + } +#endif + + return rv; } #pragma endregion @@ -160,24 +240,38 @@ namespace yycc::env { return std::filesystem::current_path(); } - PathResult current_exe() { -#if defined(YYCC_OS_WINDOWS) - return WINFCT::get_module_file_name(NULL).transform_error([](auto e) { return PathError::SysCall; }).transform([](auto v) { - return std::filesystem::path(v); - }); -#elif defined(YYCC_OS_LINUX) +#if defined(YYCC_OS_LINUX) + + /// @brief All possible error occurs when reading Linux symlink target. + enum class ReadSymlinkTargetError { + SysCall, ///< Fail to call system call. + Truncated, ///< Expected path to target was truncated. + Others, ///< Any other errors + }; + + /** + * @brief An utility function for convenient reading symlink target on Linux. + * @param[in] link The path to symlink where the target is read. + * @return The read path to to target, or error occurs. + */ + static std::expected read_symlink_target(const std::string_view &link) { // Reference: https://www.man7.org/linux/man-pages/man2/readlink.2.html - // specify the path - constexpr char path[] = "/proc/self/exe"; + // String view is not NUL terminated. + // Create an string container for it. + std::string path(link); - // get the expected size + // Get the expected size. + // Query this symlink info first. struct stat sb; - if (lstat(path, &sb) != 0) { + if (lstat(path.c_str(), &sb) != 0) { + return std::unexpected(ReadSymlinkTargetError::SysCall); } + // Fetch the size of target path in gotten struct. + // And cast it into expected type. auto expected_size = SAFECAST::try_to(sb.st_size); if (!expected_size.has_value()) { - return std::unexpected(PathError::Others); + return std::unexpected(ReadSymlinkTargetError::Others); } auto buf_size = expected_size.value(); // Some magic symlinks under (for example) /proc and /sys report 'st_size' as zero. @@ -186,40 +280,96 @@ namespace yycc::env { buf_size = PATH_MAX; } - // prepare buffer and resize it; + // Prepare return value and allocate it with previous gotten size. std::u8string rv(u8'\0', buf_size); - // write data + // Copy data into result value. + // Add one to the link size, so that we can determine whether + // the buffer returned by readlink() was truncated. + // Also, due to the add operation, we need do overflow checks. auto passed_size = SAFEOP::checked_add(buf_size, 1); if (!passed_size.has_value()) { - return std::unexpected(PathError::Others); + return std::unexpected(ReadSymlinkTargetError::Others); } - ssize_t nbytes = readlink(path, REINTERPRET::as_ordinary(rv.data()), passed_size.value()); + // Read data into result value. + ssize_t nbytes = readlink(path.c_str(), REINTERPRET::as_ordinary(rv.data()), passed_size.value()); if (nbytes < 0) { - return std::unexpected(PathError::Others); + return std::unexpected(ReadSymlinkTargetError::SysCall); } - // check written size + // Check written size + // Cast it type into expected type. auto written_size = SAFECAST::try_to(nbytes); if (!written_size.has_value()) { - return std::unexpected(PathError::Others); + return std::unexpected(ReadSymlinkTargetError::Others); } + // If the return value was equal to the buffer size, then + // the link target was larger than expected (perhaps because the + // target was changed between the call to lstat() and the call to + // readlink()). Return error instead. if (written_size.value() != buf_size) { - return std::unexpected(PathError::Others); + // TODO: There must be a better solution to this truncated issue than simply return error. + return std::unexpected(ReadSymlinkTargetError::Truncated); } - // okey - return std::filesystem::path(rv); -#else - // TODO: Implement this in other OS. - // "/proc/self/exe" is Linux specific, not in POSIX standard. - // This method may need further patch when running on macOS. + // Everything is okey + return rv; + } + #endif + + PathResult current_exe() { + std::u8string rv; + +#if defined(YYCC_OS_WINDOWS) + auto file_name = WINFCT::get_module_file_name(NULL); + if (file_name.has_value()) rv = std::move(file_name.value()); + else return std::unexpected(PathError::SysCall); +#elif defined(YYCC_OS_LINUX) + // Reference: https://www.man7.org/linux/man-pages/man5/proc_pid_exe.5.html + auto target = read_symlink_target("/proc/self/exe"); + if () return rv = std::move(target.value()); + else return std::unexpected(PathError::SysCall); +#elif defined(YYCC_OS_MACOS) + // TODO: This is AI generated and don't have test and reference. + std::string buffer(PATH_MAX, '\0'); + auto rv_size = SAFECAST::try_to(buffer.size()); + if (!rv_size.has_value()) return std::unexpected(PathError::Others); + auto size = rv_size.value(); + if (_NSGetExecutablePath(buffer.data(), &size) != 0) { + // Buffer too small; resize and retry + buffer.resize(SAFECAST::to(size)); + if (_NSGetExecutablePath(buffer.data(), &size) != 0) { + return std::unexpected(PathError::SysCall); + } + } + buffer.resize(strlen(buffer.data())); + rv = REINTERPRET::as_utf8(buffer); +#else +#error "Not supported OS" +#endif + + return std::filesystem::path(rv); } PathResult home_dir() { - // TODO: finish this function according to Rust implementation. - throw std::logic_error("not implemented"); + std::u8string rv; + +#if defined(YYCC_OS_WINDOWS) + // USERPROFILE is introduced in Rust std::env::home_dir() document. + auto home = get_var(u8"USERPROFILE"); + if (home.has_value()) rv = std::move(home.value()); + else return std::unexpected(PathError::SysCall); +#else + // Reference: https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap08.html + + // HOME is an environment variable in POSIX standard. + auto home = get_var(u8"HOME"); + if (home.has_value()) rv = std::move(home.value()); + else return std::unexpected(PathError::SysCall); +#endif + + return std::filesystem::path(rv); } PathResult temp_dir() { @@ -230,10 +380,94 @@ namespace yycc::env { #pragma region Environment Argument +#if defined(YYCC_OS_WINDOWS) + + class CommandLineArgvDeleter { + public: + CommandLineArgvDeleter() {} + void operator()(LPWCH ptr) { + if (ptr != nullptr) { + LocalFree(ptr); + } + } + }; + using SmartCommandLineArgv = std::unique_ptr, CommandLineArgvDeleter>; + +#endif + std::vector get_args() { - // TODO: finish this function according to Rust implementation. - // Considering whether use iterator as return value. - throw std::logic_error("not implemented"); + // TODO: Considering whether use iterator as return value. + std::vector rv; + +#if defined(YYCC_OS_WINDOWS) + // Reference: https://learn.microsoft.com/en-us/windows/win32/api/shellapi/nf-shellapi-commandlinetoargvw + + // Fetch args from Win32 functions + int argc; + SmartCommandLineArgv argv(CommandLineToArgvW(GetCommandLineW(), &argc)); + if (argv == nullptr) throw std::runtime_error("unexpected blank command line tuple"); + + // Analyse it + for (int i = 1; i < argc; ++i) { // starts with 1 to remove first part (executable self) + auto arg = argv.get()[i]; + if (arg == nullptr) throw std::runtime_error("unexpected nullptr argument"); + + auto u8arg = ENC::to_utf8(arg); + if (u8arg.has_value()) { + rv.emplace_back(std::move(u8arg.value())); + } else { + throw std::runtime_error("bad encoding of argument"); + } + } + + // Free data + argv.reset(); +#elif defined(YYCC_OS_LINUX) + // Reference: https://www.man7.org/linux/man-pages/man5/proc_pid_cmdline.5.html + + // Open special file. + // Because we are in Linux so we do not need use UTF-8. + std::ifstream cmdline("/proc/self/cmdline", std::ios::binary); + // Check whether file is open. + if (cmdline.is_open()) { + // Prepare container for holding fetched argument. + std::string arg; + // Fetch arguments one by one. + while (true) { + // We use NUL as delimiter + std::getline(cmdline, arg, '\0'); + // Check whether reading is okey. + if (!cmdline.good()) throw std::runtime_error("bad reading"); + // If return string is empty, it means that we reach the tail. + if (arg.empty()) break; + + // Push this argument into result. + rv.emplace_back(REINTERPRET::as_utf8(arg)); + } + // Close file + cmdline.close(); + } else { + throw std::runtime_error("fail to open cmdline file"); + } + +#elif defined(YYCC_OS_MACOS) + // TODO: This is AI generated and don't have test and reference. + char ***apple_argv = _NSGetArgv(); + int *apple_argc = _NSGetArgc(); + if (apple_argv && apple_argc) { + for (int i = 0; i < *apple_argc; ++i) { + auto ptr = (*apple_argv)[i]; + if (ptr == nullptr) throw std::runtime_error("unexpected nullptr argument"); + else rv.emplace_back(REINTERPRET::as_utf8(ptr)); + } + } else { + throw std::runtime_error("fail to get pointer to argument data"); + } +#else +#error "Not supported OS" +#endif + + return rv; } #pragma endregion diff --git a/src/yycc/env.hpp b/src/yycc/env.hpp index 5d74b03..b2f6d75 100644 --- a/src/yycc/env.hpp +++ b/src/yycc/env.hpp @@ -72,6 +72,7 @@ namespace yycc::env { * @brief Returns an list of (variable, value) pairs of strings, * for all the environment variables of the current process. * @return The list holding all variables. + * @exception std::runtime_error Error occurs when getting variables. */ std::vector get_vars(); @@ -131,6 +132,7 @@ namespace yycc::env { /** * @brief Returns the arguments that this program was started with (normally passed via the command line). * @return The list holding all argument one by one. + * @exception std::runtime_error Error occurs when getting arguments. */ std::vector get_args(); diff --git a/src/yycc/windows/com.hpp b/src/yycc/windows/com.hpp index 9805264..50be12e 100644 --- a/src/yycc/windows/com.hpp +++ b/src/yycc/windows/com.hpp @@ -4,6 +4,7 @@ #if defined(YYCC_OS_WINDOWS) && defined(YYCC_STL_MSSTL) #include +#include #include "import_guard_head.hpp" #include