From d45768de3e6f7b28bfecf4d19b192ccac9ce5dc2 Mon Sep 17 00:00:00 2001 From: Thijs Schreijer Date: Thu, 9 Nov 2023 23:03:21 +0100 Subject: feat(*): add environment variable and random functions --- src/Makefile | 4 +- src/compat.h | 27 +++++++++ src/core.c | 14 +++++ src/environment.c | 173 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/random.c | 117 ++++++++++++++++++++++++++++++++++++ src/time.c | 87 ++++++++++++++++++--------- 6 files changed, 391 insertions(+), 31 deletions(-) create mode 100644 src/environment.c create mode 100644 src/random.c (limited to 'src') diff --git a/src/Makefile b/src/Makefile index 10fc31a..119f95e 100644 --- a/src/Makefile +++ b/src/Makefile @@ -85,7 +85,7 @@ PLATFORM_win32?=Release CDIR_win32?=bin/lua/$(LUA_VERSION)/$(PLATFORM_win32) LDIR_win32?=bin/lua/$(LUA_VERSION)/$(PLATFORM_win32)/lua LUALIB_win32?=$(LUAPREFIX_win32)/lib/lua/$(LUA_VERSION)/$(PLATFORM_win32) -LUALIBNAME_win32?=lua$(subst .,,$(LUA_VERSION)).lib +LUALIBNAME_win32?=lua$(subst .,,$(LUA_VERSION)).lib # prefix: /usr/local /usr /opt/local /sw @@ -217,7 +217,7 @@ LUALIB= $(LUALIB_$(PLAT)) #------ # Objects # -OBJS=core.$(O) compat.$(O) time.$(O) +OBJS=core.$(O) compat.$(O) time.$(O) environment.$(O) random.$(O) #------ # Targets diff --git a/src/compat.h b/src/compat.h index f523fd9..5aca6df 100644 --- a/src/compat.h +++ b/src/compat.h @@ -8,4 +8,31 @@ void luaL_setfuncs(lua_State *L, const luaL_Reg *l, int nup); #endif +// Windows doesn't have ssize_t, so we define it here +#ifdef _WIN32 +#if SIZE_MAX == UINT_MAX +typedef int ssize_t; /* common 32 bit case */ +#define SSIZE_MIN INT_MIN +#define SSIZE_MAX INT_MAX +#elif SIZE_MAX == ULONG_MAX +typedef long ssize_t; /* linux 64 bits */ +#define SSIZE_MIN LONG_MIN +#define SSIZE_MAX LONG_MAX +#elif SIZE_MAX == ULLONG_MAX +typedef long long ssize_t; /* windows 64 bits */ +#define SSIZE_MIN LLONG_MIN +#define SSIZE_MAX LLONG_MAX +#elif SIZE_MAX == USHRT_MAX +typedef short ssize_t; /* is this even possible? */ +#define SSIZE_MIN SHRT_MIN +#define SSIZE_MAX SHRT_MAX +#elif SIZE_MAX == UINTMAX_MAX +typedef intmax_t ssize_t; /* last resort, chux suggestion */ +#define SSIZE_MIN INTMAX_MIN +#define SSIZE_MAX INTMAX_MAX +#else +#error platform has exotic SIZE_MAX +#endif +#endif + #endif diff --git a/src/core.c b/src/core.c index 6c46981..fffedd1 100644 --- a/src/core.c +++ b/src/core.c @@ -1,3 +1,6 @@ +/// Platform independent system calls for Lua. +// @module system + #include #include @@ -10,6 +13,8 @@ #endif void time_open(lua_State *L); +void environment_open(lua_State *L); +void random_open(lua_State *L); /*------------------------------------------------------------------------- * Initializes all library modules. @@ -18,7 +23,16 @@ LUAEXPORT int luaopen_system_core(lua_State *L) { lua_newtable(L); lua_pushstring(L, "_VERSION"); lua_pushstring(L, LUASYSTEM_VERSION); + lua_rawset(L, -3); + lua_pushstring(L, "windows"); +#ifdef _WIN32 + lua_pushboolean(L, 1); +#else + lua_pushboolean(L, 0); +#endif lua_rawset(L, -3); time_open(L); + random_open(L); + environment_open(L); return 1; } diff --git a/src/environment.c b/src/environment.c new file mode 100644 index 0000000..5f1c3da --- /dev/null +++ b/src/environment.c @@ -0,0 +1,173 @@ +/// @submodule system +#include +#include +#include "compat.h" +#include +#include + +#ifdef _WIN32 +#include "windows.h" +#endif + +/*** +Gets the value of an environment variable. + +__NOTE__: Windows has multiple copies of environment variables. For this reason, +the `setenv` function will not work with Lua's `os.getenv` on Windows. If you want +to use `setenv` then consider patching `os.getenv` with this implementation of `getenv`. +@function getenv +@tparam string name name of the environment variable +@treturn string|nil value of the environment variable, or nil if the variable is not set +*/ +static int lua_get_environment_variable(lua_State* L) { + const char* variableName = luaL_checkstring(L, 1); + +#ifdef _WIN32 + // On Windows, use GetEnvironmentVariable to retrieve the value + DWORD bufferSize = GetEnvironmentVariable(variableName, NULL, 0); + if (bufferSize > 0) { + char* buffer = (char*)malloc(bufferSize); + if (GetEnvironmentVariable(variableName, buffer, bufferSize) > 0) { + lua_pushstring(L, buffer); + free(buffer); + return 1; + } + free(buffer); + } +#else + // On non-Windows platforms, use getenv to retrieve the value + const char* variableValue = getenv(variableName); + if (variableValue != NULL) { + lua_pushstring(L, variableValue); + return 1; + } +#endif + + // If the variable is not set or an error occurs, push nil + lua_pushnil(L); + return 1; +} + + +/*** +Returns a table with all environment variables. +@function getenvs +@treturn table table with all environment variables and their values +*/ +static int lua_list_environment_variables(lua_State* L) { + lua_newtable(L); + +#ifdef _WIN32 + char* envStrings = GetEnvironmentStrings(); + char* envString = envStrings; + + if (envStrings == NULL) { + lua_pushnil(L); + return 1; + } + + while (*envString != '\0') { + const char* envVar = envString; + + // Split the environment variable into key and value + char* equals = strchr(envVar, '='); + if (equals != NULL) { + lua_pushlstring(L, envVar, equals - envVar); // Push the key + lua_pushstring(L, equals + 1); // Push the value + lua_settable(L, -3); // Set the key-value pair in the table + } + + envString += strlen(envString) + 1; + } + + FreeEnvironmentStrings(envStrings); +#else + extern char** environ; + + if (environ != NULL) { + for (char** envVar = environ; *envVar != NULL; envVar++) { + const char* envVarStr = *envVar; + + // Split the environment variable into key and value + char* equals = strchr(envVarStr, '='); + if (equals != NULL) { + lua_pushlstring(L, envVarStr, equals - envVarStr); // Push the key + lua_pushstring(L, equals + 1); // Push the value + lua_settable(L, -3); // Set the key-value pair in the table + } + } + } +#endif + + return 1; +} + + +/*** +Sets an environment variable. + +__NOTE__: Windows has multiple copies of environment variables. For this reason, the +`setenv` function will not work with Lua's `os.getenv` on Windows. If you want to use +it then consider patching `os.getenv` with the implementation of `system.getenv`. +@function setenv +@tparam string name name of the environment variable +@tparam[opt] string value value of the environment variable, if `nil` the variable will be deleted (on +Windows, setting an empty string, will also delete the variable) +@treturn boolean success +*/ +static int lua_set_environment_variable(lua_State* L) { + const char* variableName = luaL_checkstring(L, 1); + const char* variableValue = luaL_optstring(L, 2, NULL); + +#ifdef _WIN32 + // if (variableValue == NULL) { + // // If the value is nil, delete the environment variable + // if (SetEnvironmentVariable(variableName, NULL)) { + // lua_pushboolean(L, 1); + // } else { + // lua_pushboolean(L, 0); + // } + // } else { + // Set the environment variable with the provided value + if (SetEnvironmentVariable(variableName, variableValue)) { + lua_pushboolean(L, 1); + } else { + lua_pushboolean(L, 0); + } + // } +#else + if (variableValue == NULL) { + // If the value is nil, delete the environment variable + if (unsetenv(variableName) == 0) { + lua_pushboolean(L, 1); + } else { + lua_pushboolean(L, 0); + } + } else { + // Set the environment variable with the provided value + if (setenv(variableName, variableValue, 1) == 0) { + lua_pushboolean(L, 1); + } else { + lua_pushboolean(L, 0); + } + } +#endif + + return 1; +} + + + +static luaL_Reg func[] = { + { "getenv", lua_get_environment_variable }, + { "setenv", lua_set_environment_variable }, + { "getenvs", lua_list_environment_variables }, + { NULL, NULL } +}; + +/*------------------------------------------------------------------------- + * Initializes module + *-------------------------------------------------------------------------*/ +void environment_open(lua_State *L) { + luaL_setfuncs(L, func, 0); +} diff --git a/src/random.c b/src/random.c new file mode 100644 index 0000000..90fb3f2 --- /dev/null +++ b/src/random.c @@ -0,0 +1,117 @@ +/// @submodule system +#include +#include +#include "compat.h" +#include + +#ifdef _WIN32 +#include "windows.h" +#include "wincrypt.h" +#else +#include +#include +#include +#endif + + +/*** +Generate random bytes. +This uses `CryptGenRandom()` on Windows, and `/dev/urandom` on other platforms. It will return the +requested number of bytes, or an error, never a partial result. +@function random +@tparam[opt=1] int length number of bytes to get +@treturn[1] string string of random bytes +@treturn[2] nil +@treturn[2] string error message +*/ +static int lua_get_random_bytes(lua_State* L) { + int num_bytes = luaL_optinteger(L, 1, 1); // Number of bytes, default to 1 if not provided + + if (num_bytes <= 0) { + if (num_bytes == 0) { + lua_pushliteral(L, ""); + return 1; + } + lua_pushnil(L); + lua_pushstring(L, "invalid number of bytes, must not be less than 0"); + return 2; + } + + unsigned char* buffer = (unsigned char*)lua_newuserdata(L, num_bytes); + if (buffer == NULL) { + lua_pushnil(L); + lua_pushstring(L, "failed to allocate memory for random buffer"); + return 2; + } + + ssize_t n; + ssize_t total_read = 0; + +#ifdef _WIN32 + HCRYPTPROV hCryptProv; + if (!CryptAcquireContext(&hCryptProv, NULL, NULL, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT)) { + DWORD error = GetLastError(); + lua_pushnil(L); + lua_pushfstring(L, "failed to acquire cryptographic context: %lu", error); + return 2; + } + + if (!CryptGenRandom(hCryptProv, num_bytes, buffer)) { + DWORD error = GetLastError(); + lua_pushnil(L); + lua_pushfstring(L, "failed to get random data: %lu", error); + CryptReleaseContext(hCryptProv, 0); + return 2; + } + + CryptReleaseContext(hCryptProv, 0); +#else + + // for macOS/unixes use /dev/urandom for non-blocking + int fd = open("/dev/urandom", O_RDONLY | O_CLOEXEC); + if (fd < 0) { + lua_pushnil(L); + lua_pushstring(L, "failed opening /dev/urandom"); + return 2; + } + + while (total_read < num_bytes) { + n = read(fd, buffer + total_read, num_bytes - total_read); + + if (n < 0) { + if (errno == EINTR) { + continue; // Interrupted, retry + + } else { + lua_pushnil(L); + lua_pushfstring(L, "failed reading /dev/urandom: %s", strerror(errno)); + close(fd); + return 2; + } + } + + total_read += n; + } + + close(fd); +#endif + + lua_pushlstring(L, (const char*)buffer, num_bytes); + return 1; +} + + + +static luaL_Reg func[] = { + { "random", lua_get_random_bytes }, + { NULL, NULL } +}; + + + +/*------------------------------------------------------------------------- + * Initializes module + *-------------------------------------------------------------------------*/ +void random_open(lua_State *L) { + luaL_setfuncs(L, func, 0); +} diff --git a/src/time.c b/src/time.c index 8c6a4f2..5f0ead0 100644 --- a/src/time.c +++ b/src/time.c @@ -1,3 +1,4 @@ +/// @submodule system #include #include @@ -50,11 +51,8 @@ static double time_gettime(void) { } #endif -/*------------------------------------------------------------------------- - * Gets monotonic time in s - * Returns - * time in s. - *-------------------------------------------------------------------------*/ + + #ifdef _WIN32 WINBASEAPI ULONGLONG WINAPI GetTickCount64(VOID); @@ -70,53 +68,84 @@ static double time_monotime(void) { } #endif -/*------------------------------------------------------------------------- - * Returns the current system time, 1970 (UTC), in secconds. - *-------------------------------------------------------------------------*/ + + +/*** +Get system time. +The time is returned as the seconds since the epoch (1 January 1970 00:00:00). +@function gettime +@treturn number seconds (fractional) +*/ static int time_lua_gettime(lua_State *L) { lua_pushnumber(L, time_gettime()); return 1; } -/*------------------------------------------------------------------------- - * Returns the monotonic time the system has been up, in secconds. - *-------------------------------------------------------------------------*/ + + +/*** +Get monotonic time. +The time is returned as the seconds since system start. +@function monotime +@treturn number seconds (fractional) +*/ static int time_lua_monotime(lua_State *L) { lua_pushnumber(L, time_monotime()); return 1; } -/*------------------------------------------------------------------------- - * Sleep for n seconds. - *-------------------------------------------------------------------------*/ + + +/*** +Sleep without a busy loop. +This function will sleep, without doing a busy-loop and wasting CPU cycles. +@function sleep +@tparam number seconds seconds to sleep (fractional). +@tparam[opt=16] integer precision minimum stepsize in milliseconds (Windows only, ignored elsewhere) +@return `true` on success, or `nil+err` on failure +*/ #ifdef _WIN32 static int time_lua_sleep(lua_State *L) { double n = luaL_checknumber(L, 1); - if (n < 0.0) n = 0.0; - if (n < DBL_MAX/1000.0) n *= 1000.0; - if (n > INT_MAX) n = INT_MAX; - Sleep((int)n); - return 0; + + int precision = luaL_optinteger(L, 2, 16); + if (precision < 0 || precision > 16) precision = 16; + + if (n > 0.0) { + if (n < DBL_MAX/1000.0) n *= 1000.0; + if (n > INT_MAX) n = INT_MAX; + if (timeBeginPeriod(precision) != TIMERR_NOERROR) { + lua_pushnil(L); + lua_pushstring(L, "failed to set timer precision"); + return 2; + }; + Sleep((int)n); + timeEndPeriod(precision); + } + lua_pushboolean(L, 1); + return 1; } #else static int time_lua_sleep(lua_State *L) { double n = luaL_checknumber(L, 1); struct timespec t, r; - if (n < 0.0) n = 0.0; - if (n > INT_MAX) n = INT_MAX; - t.tv_sec = (int) n; - n -= t.tv_sec; - t.tv_nsec = (int) (n * 1000000000); - if (t.tv_nsec >= 1000000000) t.tv_nsec = 999999999; - while (nanosleep(&t, &r) != 0) { - t.tv_sec = r.tv_sec; - t.tv_nsec = r.tv_nsec; + if (n > 0.0) { + if (n > INT_MAX) n = INT_MAX; + t.tv_sec = (int) n; + n -= t.tv_sec; + t.tv_nsec = (int) (n * 1000000000); + if (t.tv_nsec >= 1000000000) t.tv_nsec = 999999999; + while (nanosleep(&t, &r) != 0) { + t.tv_sec = r.tv_sec; + t.tv_nsec = r.tv_nsec; + } } - return 0; + lua_pushboolean(L, 1); + return 1; } #endif -- cgit v1.2.3-55-g6feb