feat: finish env remains functions
This commit is contained in:
320
src/yycc/env.cpp
320
src/yycc/env.cpp
@ -1,26 +1,40 @@
|
|||||||
#include "env.hpp"
|
#include "env.hpp"
|
||||||
#include "macro/os_detector.hpp"
|
#include "macro/os_detector.hpp"
|
||||||
|
#include <string>
|
||||||
|
#include <string_view>
|
||||||
|
#include <memory>
|
||||||
|
#include <type_traits>
|
||||||
|
#include <stdexcept>
|
||||||
|
|
||||||
// Environment variable required
|
|
||||||
#if defined(YYCC_OS_WINDOWS)
|
#if defined(YYCC_OS_WINDOWS)
|
||||||
#include "encoding/windows.hpp"
|
#include "encoding/windows.hpp"
|
||||||
#include "num/safe_op.hpp"
|
#include "num/safe_op.hpp"
|
||||||
#include "num/safe_cast.hpp"
|
#include "num/safe_cast.hpp"
|
||||||
|
#include "windows/winfct.hpp"
|
||||||
|
#include "windows/import_guard_head.hpp"
|
||||||
#include <Windows.h>
|
#include <Windows.h>
|
||||||
#include <winbase.h>
|
#include <winbase.h>
|
||||||
#else
|
#include <processenv.h> // For getting environment variables and commandline argument.
|
||||||
|
#include <shellapi.h> // For getting commandline argument.
|
||||||
|
#include "windows/import_guard_tail.hpp"
|
||||||
|
#elif defined(YYCC_OS_LINUX)
|
||||||
#include "string/reinterpret.hpp"
|
#include "string/reinterpret.hpp"
|
||||||
#include <cstdlib>
|
#include <cstdlib>
|
||||||
#include <cerrno>
|
#include <cerrno>
|
||||||
#include <stdexcept>
|
#include <fstream> // For reading commandline argument.
|
||||||
#endif
|
|
||||||
|
|
||||||
// Path related functions required
|
|
||||||
#if defined(YYCC_OS_WINDOWS)
|
|
||||||
#include "windows/winfct.hpp"
|
|
||||||
#else
|
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
#include <sys/stat.h>
|
#include <sys/stat.h> // For reading symlink target.
|
||||||
|
#elif defined(YYCC_OS_MACOS)
|
||||||
|
#include "string/reinterpret.hpp"
|
||||||
|
#include "num/safe_cast.hpp"
|
||||||
|
#include <cstdlib>
|
||||||
|
#include <cerrno>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <cstring>
|
||||||
|
#include <mach-o/dyld.h> // For getting current exe path.
|
||||||
|
#include <crt_externs.h> // For getting commandline argument.
|
||||||
|
#else
|
||||||
|
#error "Not supported OS"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#define SAFECAST ::yycc::num::safe_cast
|
#define SAFECAST ::yycc::num::safe_cast
|
||||||
@ -146,10 +160,76 @@ namespace yycc::env {
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if defined(YYCC_OS_WINDOWS)
|
||||||
|
class EnvironmentStringsDeleter {
|
||||||
|
public:
|
||||||
|
EnvironmentStringsDeleter() {}
|
||||||
|
void operator()(LPWCH ptr) {
|
||||||
|
if (ptr != nullptr) {
|
||||||
|
FreeEnvironmentStringsW(ptr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
using SmartEnvironmentStrings = std::unique_ptr<std::remove_pointer_t<LPWCH>, EnvironmentStringsDeleter>;
|
||||||
|
#endif
|
||||||
|
|
||||||
std::vector<VarPair> get_vars() {
|
std::vector<VarPair> get_vars() {
|
||||||
// TODO: finish this function according to Rust implementation.
|
// TODO: Considering whether replace return value with an iterator.
|
||||||
// Considering whether replace return value with an iterator.
|
std::vector<VarPair> rv;
|
||||||
throw std::logic_error("not implemented");
|
|
||||||
|
#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
|
#pragma endregion
|
||||||
@ -160,24 +240,38 @@ namespace yycc::env {
|
|||||||
return std::filesystem::current_path();
|
return std::filesystem::current_path();
|
||||||
}
|
}
|
||||||
|
|
||||||
PathResult<std::filesystem::path> current_exe() {
|
#if defined(YYCC_OS_LINUX)
|
||||||
#if defined(YYCC_OS_WINDOWS)
|
|
||||||
return WINFCT::get_module_file_name(NULL).transform_error([](auto e) { return PathError::SysCall; }).transform([](auto v) {
|
/// @brief All possible error occurs when reading Linux symlink target.
|
||||||
return std::filesystem::path(v);
|
enum class ReadSymlinkTargetError {
|
||||||
});
|
SysCall, ///< Fail to call system call.
|
||||||
#elif defined(YYCC_OS_LINUX)
|
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<std::u8string, ReadSymlinkTargetError> read_symlink_target(const std::string_view &link) {
|
||||||
// Reference: https://www.man7.org/linux/man-pages/man2/readlink.2.html
|
// Reference: https://www.man7.org/linux/man-pages/man2/readlink.2.html
|
||||||
|
|
||||||
// specify the path
|
// String view is not NUL terminated.
|
||||||
constexpr char path[] = "/proc/self/exe";
|
// 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;
|
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<size_t>(sb.st_size);
|
auto expected_size = SAFECAST::try_to<size_t>(sb.st_size);
|
||||||
if (!expected_size.has_value()) {
|
if (!expected_size.has_value()) {
|
||||||
return std::unexpected(PathError::Others);
|
return std::unexpected(ReadSymlinkTargetError::Others);
|
||||||
}
|
}
|
||||||
auto buf_size = expected_size.value();
|
auto buf_size = expected_size.value();
|
||||||
// Some magic symlinks under (for example) /proc and /sys report 'st_size' as zero.
|
// 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;
|
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);
|
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<size_t>(buf_size, 1);
|
auto passed_size = SAFEOP::checked_add<size_t>(buf_size, 1);
|
||||||
if (!passed_size.has_value()) {
|
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) {
|
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<size_t>(nbytes);
|
auto written_size = SAFECAST::try_to<size_t>(nbytes);
|
||||||
if (!written_size.has_value()) {
|
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) {
|
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
|
// Everything is okey
|
||||||
return std::filesystem::path(rv);
|
return 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.
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
PathResult<std::filesystem::path> 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<uint32_t>(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_t>(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<std::filesystem::path> home_dir() {
|
PathResult<std::filesystem::path> home_dir() {
|
||||||
// TODO: finish this function according to Rust implementation.
|
std::u8string rv;
|
||||||
throw std::logic_error("not implemented");
|
|
||||||
|
#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<std::filesystem::path> temp_dir() {
|
PathResult<std::filesystem::path> temp_dir() {
|
||||||
@ -230,10 +380,94 @@ namespace yycc::env {
|
|||||||
|
|
||||||
#pragma region Environment Argument
|
#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<std::remove_pointer_t<LPWSTR *>, CommandLineArgvDeleter>;
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
std::vector<std::u8string> get_args() {
|
std::vector<std::u8string> get_args() {
|
||||||
// TODO: finish this function according to Rust implementation.
|
// TODO: Considering whether use iterator as return value.
|
||||||
// Considering whether use iterator as return value.
|
std::vector<std::u8string> rv;
|
||||||
throw std::logic_error("not implemented");
|
|
||||||
|
#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
|
#pragma endregion
|
||||||
|
|||||||
@ -72,6 +72,7 @@ namespace yycc::env {
|
|||||||
* @brief Returns an list of (variable, value) pairs of strings,
|
* @brief Returns an list of (variable, value) pairs of strings,
|
||||||
* for all the environment variables of the current process.
|
* for all the environment variables of the current process.
|
||||||
* @return The list holding all variables.
|
* @return The list holding all variables.
|
||||||
|
* @exception std::runtime_error Error occurs when getting variables.
|
||||||
*/
|
*/
|
||||||
std::vector<VarPair> get_vars();
|
std::vector<VarPair> 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).
|
* @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.
|
* @return The list holding all argument one by one.
|
||||||
|
* @exception std::runtime_error Error occurs when getting arguments.
|
||||||
*/
|
*/
|
||||||
std::vector<std::u8string> get_args();
|
std::vector<std::u8string> get_args();
|
||||||
|
|
||||||
|
|||||||
@ -4,6 +4,7 @@
|
|||||||
#if defined(YYCC_OS_WINDOWS) && defined(YYCC_STL_MSSTL)
|
#if defined(YYCC_OS_WINDOWS) && defined(YYCC_STL_MSSTL)
|
||||||
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
#include <type_traits>
|
||||||
|
|
||||||
#include "import_guard_head.hpp"
|
#include "import_guard_head.hpp"
|
||||||
#include <Windows.h>
|
#include <Windows.h>
|
||||||
|
|||||||
Reference in New Issue
Block a user