Cory McWilliams
f421606e21
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4308 ed5197a5-7fde-0310-b194-c3ffbd925b24
1868 lines
47 KiB
C
1868 lines
47 KiB
C
/* Copyright Joyent, Inc. and other Node contributors. All rights reserved.
|
|
*
|
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
* of this software and associated documentation files (the "Software"), to
|
|
* deal in the Software without restriction, including without limitation the
|
|
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
|
* sell copies of the Software, and to permit persons to whom the Software is
|
|
* furnished to do so, subject to the following conditions:
|
|
*
|
|
* The above copyright notice and this permission notice shall be included in
|
|
* all copies or substantial portions of the Software.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
|
* IN THE SOFTWARE.
|
|
*/
|
|
|
|
#include <assert.h>
|
|
#include <direct.h>
|
|
#include <limits.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <time.h>
|
|
#include <wchar.h>
|
|
|
|
#include "uv.h"
|
|
#include "internal.h"
|
|
|
|
/* clang-format off */
|
|
#include <sysinfoapi.h>
|
|
#include <winsock2.h>
|
|
#include <winperf.h>
|
|
#include <iphlpapi.h>
|
|
#include <psapi.h>
|
|
#include <tlhelp32.h>
|
|
#include <windows.h>
|
|
/* clang-format on */
|
|
#include <userenv.h>
|
|
#include <math.h>
|
|
|
|
/*
|
|
* Max title length; the only thing MSDN tells us about the maximum length
|
|
* of the console title is that it is smaller than 64K. However in practice
|
|
* it is much smaller, and there is no way to figure out what the exact length
|
|
* of the title is or can be, at least not on XP. To make it even more
|
|
* annoying, GetConsoleTitle fails when the buffer to be read into is bigger
|
|
* than the actual maximum length. So we make a conservative guess here;
|
|
* just don't put the novel you're writing in the title, unless the plot
|
|
* survives truncation.
|
|
*/
|
|
#define MAX_TITLE_LENGTH 8192
|
|
|
|
/* The number of nanoseconds in one second. */
|
|
#define UV__NANOSEC 1000000000
|
|
|
|
/* Max user name length, from iphlpapi.h */
|
|
#ifndef UNLEN
|
|
# define UNLEN 256
|
|
#endif
|
|
|
|
|
|
/* A RtlGenRandom() by any other name... */
|
|
extern BOOLEAN NTAPI SystemFunction036(PVOID Buffer, ULONG BufferLength);
|
|
|
|
/* Cached copy of the process title, plus a mutex guarding it. */
|
|
static char *process_title;
|
|
static CRITICAL_SECTION process_title_lock;
|
|
|
|
/* Frequency of the high-resolution clock. */
|
|
static uint64_t hrtime_frequency_ = 0;
|
|
|
|
|
|
/*
|
|
* One-time initialization code for functionality defined in util.c.
|
|
*/
|
|
void uv__util_init(void) {
|
|
LARGE_INTEGER perf_frequency;
|
|
|
|
/* Initialize process title access mutex. */
|
|
InitializeCriticalSection(&process_title_lock);
|
|
|
|
/* Retrieve high-resolution timer frequency
|
|
* and precompute its reciprocal.
|
|
*/
|
|
if (QueryPerformanceFrequency(&perf_frequency)) {
|
|
hrtime_frequency_ = perf_frequency.QuadPart;
|
|
} else {
|
|
uv_fatal_error(GetLastError(), "QueryPerformanceFrequency");
|
|
}
|
|
}
|
|
|
|
|
|
int uv_exepath(char* buffer, size_t* size_ptr) {
|
|
int utf8_len, utf16_buffer_len, utf16_len;
|
|
WCHAR* utf16_buffer;
|
|
int err;
|
|
|
|
if (buffer == NULL || size_ptr == NULL || *size_ptr == 0) {
|
|
return UV_EINVAL;
|
|
}
|
|
|
|
if (*size_ptr > 32768) {
|
|
/* Windows paths can never be longer than this. */
|
|
utf16_buffer_len = 32768;
|
|
} else {
|
|
utf16_buffer_len = (int) *size_ptr;
|
|
}
|
|
|
|
utf16_buffer = (WCHAR*) uv__malloc(sizeof(WCHAR) * utf16_buffer_len);
|
|
if (!utf16_buffer) {
|
|
return UV_ENOMEM;
|
|
}
|
|
|
|
/* Get the path as UTF-16. */
|
|
utf16_len = GetModuleFileNameW(NULL, utf16_buffer, utf16_buffer_len);
|
|
if (utf16_len <= 0) {
|
|
err = GetLastError();
|
|
goto error;
|
|
}
|
|
|
|
/* Convert to UTF-8 */
|
|
utf8_len = WideCharToMultiByte(CP_UTF8,
|
|
0,
|
|
utf16_buffer,
|
|
-1,
|
|
buffer,
|
|
(int) *size_ptr,
|
|
NULL,
|
|
NULL);
|
|
if (utf8_len == 0) {
|
|
err = GetLastError();
|
|
goto error;
|
|
}
|
|
|
|
uv__free(utf16_buffer);
|
|
|
|
/* utf8_len *does* include the terminating null at this point, but the
|
|
* returned size shouldn't. */
|
|
*size_ptr = utf8_len - 1;
|
|
return 0;
|
|
|
|
error:
|
|
uv__free(utf16_buffer);
|
|
return uv_translate_sys_error(err);
|
|
}
|
|
|
|
|
|
static int uv__cwd(WCHAR** buf, DWORD *len) {
|
|
WCHAR* p;
|
|
DWORD n;
|
|
DWORD t;
|
|
|
|
t = GetCurrentDirectoryW(0, NULL);
|
|
for (;;) {
|
|
if (t == 0)
|
|
return uv_translate_sys_error(GetLastError());
|
|
|
|
/* |t| is the size of the buffer _including_ nul. */
|
|
p = uv__malloc(t * sizeof(*p));
|
|
if (p == NULL)
|
|
return UV_ENOMEM;
|
|
|
|
/* |n| is the size of the buffer _excluding_ nul but _only on success_.
|
|
* If |t| was too small because another thread changed the working
|
|
* directory, |n| is the size the buffer should be _including_ nul.
|
|
* It therefore follows we must resize when n >= t and fail when n == 0.
|
|
*/
|
|
n = GetCurrentDirectoryW(t, p);
|
|
if (n > 0)
|
|
if (n < t)
|
|
break;
|
|
|
|
uv__free(p);
|
|
t = n;
|
|
}
|
|
|
|
/* The returned directory should not have a trailing slash, unless it points
|
|
* at a drive root, like c:\. Remove it if needed.
|
|
*/
|
|
t = n - 1;
|
|
if (p[t] == L'\\' && !(n == 3 && p[1] == L':')) {
|
|
p[t] = L'\0';
|
|
n = t;
|
|
}
|
|
|
|
*buf = p;
|
|
*len = n;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
int uv_cwd(char* buffer, size_t* size) {
|
|
DWORD utf16_len;
|
|
WCHAR *utf16_buffer;
|
|
int r;
|
|
|
|
if (buffer == NULL || size == NULL) {
|
|
return UV_EINVAL;
|
|
}
|
|
|
|
r = uv__cwd(&utf16_buffer, &utf16_len);
|
|
if (r < 0) {
|
|
return r;
|
|
}
|
|
|
|
/* Check how much space we need */
|
|
r = WideCharToMultiByte(CP_UTF8,
|
|
0,
|
|
utf16_buffer,
|
|
-1,
|
|
NULL,
|
|
0,
|
|
NULL,
|
|
NULL);
|
|
if (r == 0) {
|
|
uv__free(utf16_buffer);
|
|
return uv_translate_sys_error(GetLastError());
|
|
} else if (r > (int) *size) {
|
|
uv__free(utf16_buffer);
|
|
*size = r;
|
|
return UV_ENOBUFS;
|
|
}
|
|
|
|
/* Convert to UTF-8 */
|
|
r = WideCharToMultiByte(CP_UTF8,
|
|
0,
|
|
utf16_buffer,
|
|
-1,
|
|
buffer,
|
|
*size > INT_MAX ? INT_MAX : (int) *size,
|
|
NULL,
|
|
NULL);
|
|
uv__free(utf16_buffer);
|
|
|
|
if (r == 0) {
|
|
return uv_translate_sys_error(GetLastError());
|
|
}
|
|
|
|
*size = r - 1;
|
|
return 0;
|
|
}
|
|
|
|
|
|
int uv_chdir(const char* dir) {
|
|
WCHAR *utf16_buffer;
|
|
DWORD utf16_len;
|
|
WCHAR drive_letter, env_var[4];
|
|
int r;
|
|
|
|
if (dir == NULL) {
|
|
return UV_EINVAL;
|
|
}
|
|
|
|
utf16_len = MultiByteToWideChar(CP_UTF8,
|
|
0,
|
|
dir,
|
|
-1,
|
|
NULL,
|
|
0);
|
|
if (utf16_len == 0) {
|
|
return uv_translate_sys_error(GetLastError());
|
|
}
|
|
utf16_buffer = uv__malloc(utf16_len * sizeof(WCHAR));
|
|
if (utf16_buffer == NULL) {
|
|
return UV_ENOMEM;
|
|
}
|
|
|
|
if (MultiByteToWideChar(CP_UTF8,
|
|
0,
|
|
dir,
|
|
-1,
|
|
utf16_buffer,
|
|
utf16_len) == 0) {
|
|
uv__free(utf16_buffer);
|
|
return uv_translate_sys_error(GetLastError());
|
|
}
|
|
|
|
if (!SetCurrentDirectoryW(utf16_buffer)) {
|
|
uv__free(utf16_buffer);
|
|
return uv_translate_sys_error(GetLastError());
|
|
}
|
|
|
|
/* uv__cwd() will return a new buffer. */
|
|
uv__free(utf16_buffer);
|
|
utf16_buffer = NULL;
|
|
|
|
/* Windows stores the drive-local path in an "hidden" environment variable,
|
|
* which has the form "=C:=C:\Windows". SetCurrentDirectory does not update
|
|
* this, so we'll have to do it. */
|
|
r = uv__cwd(&utf16_buffer, &utf16_len);
|
|
if (r == UV_ENOMEM) {
|
|
/* When updating the environment variable fails, return UV_OK anyway.
|
|
* We did successfully change current working directory, only updating
|
|
* hidden env variable failed. */
|
|
return 0;
|
|
}
|
|
if (r < 0) {
|
|
return r;
|
|
}
|
|
|
|
if (utf16_len < 2 || utf16_buffer[1] != L':') {
|
|
/* Doesn't look like a drive letter could be there - probably an UNC path.
|
|
* TODO: Need to handle win32 namespaces like \\?\C:\ ? */
|
|
drive_letter = 0;
|
|
} else if (utf16_buffer[0] >= L'A' && utf16_buffer[0] <= L'Z') {
|
|
drive_letter = utf16_buffer[0];
|
|
} else if (utf16_buffer[0] >= L'a' && utf16_buffer[0] <= L'z') {
|
|
/* Convert to uppercase. */
|
|
drive_letter = utf16_buffer[0] - L'a' + L'A';
|
|
} else {
|
|
/* Not valid. */
|
|
drive_letter = 0;
|
|
}
|
|
|
|
if (drive_letter != 0) {
|
|
/* Construct the environment variable name and set it. */
|
|
env_var[0] = L'=';
|
|
env_var[1] = drive_letter;
|
|
env_var[2] = L':';
|
|
env_var[3] = L'\0';
|
|
|
|
SetEnvironmentVariableW(env_var, utf16_buffer);
|
|
}
|
|
|
|
uv__free(utf16_buffer);
|
|
return 0;
|
|
}
|
|
|
|
|
|
void uv_loadavg(double avg[3]) {
|
|
/* Can't be implemented */
|
|
avg[0] = avg[1] = avg[2] = 0;
|
|
}
|
|
|
|
|
|
uint64_t uv_get_free_memory(void) {
|
|
MEMORYSTATUSEX memory_status;
|
|
memory_status.dwLength = sizeof(memory_status);
|
|
|
|
if (!GlobalMemoryStatusEx(&memory_status)) {
|
|
return 0;
|
|
}
|
|
|
|
return (uint64_t)memory_status.ullAvailPhys;
|
|
}
|
|
|
|
|
|
uint64_t uv_get_total_memory(void) {
|
|
MEMORYSTATUSEX memory_status;
|
|
memory_status.dwLength = sizeof(memory_status);
|
|
|
|
if (!GlobalMemoryStatusEx(&memory_status)) {
|
|
return 0;
|
|
}
|
|
|
|
return (uint64_t)memory_status.ullTotalPhys;
|
|
}
|
|
|
|
|
|
uint64_t uv_get_constrained_memory(void) {
|
|
return 0; /* Memory constraints are unknown. */
|
|
}
|
|
|
|
|
|
uint64_t uv_get_available_memory(void) {
|
|
return uv_get_free_memory();
|
|
}
|
|
|
|
|
|
uv_pid_t uv_os_getpid(void) {
|
|
return GetCurrentProcessId();
|
|
}
|
|
|
|
|
|
uv_pid_t uv_os_getppid(void) {
|
|
int parent_pid = -1;
|
|
HANDLE handle;
|
|
PROCESSENTRY32 pe;
|
|
DWORD current_pid = GetCurrentProcessId();
|
|
|
|
pe.dwSize = sizeof(PROCESSENTRY32);
|
|
handle = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
|
|
|
|
if (Process32First(handle, &pe)) {
|
|
do {
|
|
if (pe.th32ProcessID == current_pid) {
|
|
parent_pid = pe.th32ParentProcessID;
|
|
break;
|
|
}
|
|
} while( Process32Next(handle, &pe));
|
|
}
|
|
|
|
CloseHandle(handle);
|
|
return parent_pid;
|
|
}
|
|
|
|
|
|
char** uv_setup_args(int argc, char** argv) {
|
|
return argv;
|
|
}
|
|
|
|
|
|
void uv__process_title_cleanup(void) {
|
|
}
|
|
|
|
|
|
int uv_set_process_title(const char* title) {
|
|
int err;
|
|
int length;
|
|
WCHAR* title_w = NULL;
|
|
|
|
uv__once_init();
|
|
|
|
/* Find out how big the buffer for the wide-char title must be */
|
|
length = MultiByteToWideChar(CP_UTF8, 0, title, -1, NULL, 0);
|
|
if (!length) {
|
|
err = GetLastError();
|
|
goto done;
|
|
}
|
|
|
|
/* Convert to wide-char string */
|
|
title_w = (WCHAR*)uv__malloc(sizeof(WCHAR) * length);
|
|
if (!title_w) {
|
|
uv_fatal_error(ERROR_OUTOFMEMORY, "uv__malloc");
|
|
}
|
|
|
|
length = MultiByteToWideChar(CP_UTF8, 0, title, -1, title_w, length);
|
|
if (!length) {
|
|
err = GetLastError();
|
|
goto done;
|
|
}
|
|
|
|
/* If the title must be truncated insert a \0 terminator there */
|
|
if (length > MAX_TITLE_LENGTH) {
|
|
title_w[MAX_TITLE_LENGTH - 1] = L'\0';
|
|
}
|
|
|
|
if (!SetConsoleTitleW(title_w)) {
|
|
err = GetLastError();
|
|
goto done;
|
|
}
|
|
|
|
EnterCriticalSection(&process_title_lock);
|
|
uv__free(process_title);
|
|
process_title = uv__strdup(title);
|
|
LeaveCriticalSection(&process_title_lock);
|
|
|
|
err = 0;
|
|
|
|
done:
|
|
uv__free(title_w);
|
|
return uv_translate_sys_error(err);
|
|
}
|
|
|
|
|
|
static int uv__get_process_title(void) {
|
|
WCHAR title_w[MAX_TITLE_LENGTH];
|
|
|
|
if (!GetConsoleTitleW(title_w, sizeof(title_w) / sizeof(WCHAR))) {
|
|
return -1;
|
|
}
|
|
|
|
if (uv__convert_utf16_to_utf8(title_w, -1, &process_title) != 0)
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
int uv_get_process_title(char* buffer, size_t size) {
|
|
size_t len;
|
|
|
|
if (buffer == NULL || size == 0)
|
|
return UV_EINVAL;
|
|
|
|
uv__once_init();
|
|
|
|
EnterCriticalSection(&process_title_lock);
|
|
/*
|
|
* If the process_title was never read before nor explicitly set,
|
|
* we must query it with getConsoleTitleW
|
|
*/
|
|
if (!process_title && uv__get_process_title() == -1) {
|
|
LeaveCriticalSection(&process_title_lock);
|
|
return uv_translate_sys_error(GetLastError());
|
|
}
|
|
|
|
assert(process_title);
|
|
len = strlen(process_title) + 1;
|
|
|
|
if (size < len) {
|
|
LeaveCriticalSection(&process_title_lock);
|
|
return UV_ENOBUFS;
|
|
}
|
|
|
|
memcpy(buffer, process_title, len);
|
|
LeaveCriticalSection(&process_title_lock);
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
/* https://github.com/libuv/libuv/issues/1674 */
|
|
int uv_clock_gettime(uv_clock_id clock_id, uv_timespec64_t* ts) {
|
|
FILETIME ft;
|
|
int64_t t;
|
|
|
|
if (ts == NULL)
|
|
return UV_EFAULT;
|
|
|
|
switch (clock_id) {
|
|
case UV_CLOCK_MONOTONIC:
|
|
uv__once_init();
|
|
t = uv__hrtime(UV__NANOSEC);
|
|
ts->tv_sec = t / 1000000000;
|
|
ts->tv_nsec = t % 1000000000;
|
|
return 0;
|
|
case UV_CLOCK_REALTIME:
|
|
GetSystemTimePreciseAsFileTime(&ft);
|
|
/* In 100-nanosecond increments from 1601-01-01 UTC because why not? */
|
|
t = (int64_t) ft.dwHighDateTime << 32 | ft.dwLowDateTime;
|
|
/* Convert to UNIX epoch, 1970-01-01. Still in 100 ns increments. */
|
|
t -= 116444736000000000ll;
|
|
/* Now convert to seconds and nanoseconds. */
|
|
ts->tv_sec = t / 10000000;
|
|
ts->tv_nsec = t % 10000000 * 100;
|
|
return 0;
|
|
}
|
|
|
|
return UV_EINVAL;
|
|
}
|
|
|
|
|
|
uint64_t uv_hrtime(void) {
|
|
uv__once_init();
|
|
return uv__hrtime(UV__NANOSEC);
|
|
}
|
|
|
|
|
|
uint64_t uv__hrtime(unsigned int scale) {
|
|
LARGE_INTEGER counter;
|
|
double scaled_freq;
|
|
double result;
|
|
|
|
assert(hrtime_frequency_ != 0);
|
|
assert(scale != 0);
|
|
if (!QueryPerformanceCounter(&counter)) {
|
|
uv_fatal_error(GetLastError(), "QueryPerformanceCounter");
|
|
}
|
|
assert(counter.QuadPart != 0);
|
|
|
|
/* Because we have no guarantee about the order of magnitude of the
|
|
* performance counter interval, integer math could cause this computation
|
|
* to overflow. Therefore we resort to floating point math.
|
|
*/
|
|
scaled_freq = (double) hrtime_frequency_ / scale;
|
|
result = (double) counter.QuadPart / scaled_freq;
|
|
return (uint64_t) result;
|
|
}
|
|
|
|
|
|
int uv_resident_set_memory(size_t* rss) {
|
|
HANDLE current_process;
|
|
PROCESS_MEMORY_COUNTERS pmc;
|
|
|
|
current_process = GetCurrentProcess();
|
|
|
|
if (!GetProcessMemoryInfo(current_process, &pmc, sizeof(pmc))) {
|
|
return uv_translate_sys_error(GetLastError());
|
|
}
|
|
|
|
*rss = pmc.WorkingSetSize;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
int uv_uptime(double* uptime) {
|
|
*uptime = GetTickCount64() / 1000.0;
|
|
return 0;
|
|
}
|
|
|
|
|
|
unsigned int uv_available_parallelism(void) {
|
|
SYSTEM_INFO info;
|
|
unsigned rc;
|
|
|
|
/* TODO(bnoordhuis) Use GetLogicalProcessorInformationEx() to support systems
|
|
* with > 64 CPUs? See https://github.com/libuv/libuv/pull/3458
|
|
*/
|
|
GetSystemInfo(&info);
|
|
|
|
rc = info.dwNumberOfProcessors;
|
|
if (rc < 1)
|
|
rc = 1;
|
|
|
|
return rc;
|
|
}
|
|
|
|
|
|
int uv_cpu_info(uv_cpu_info_t** cpu_infos_ptr, int* cpu_count_ptr) {
|
|
uv_cpu_info_t* cpu_infos;
|
|
SYSTEM_PROCESSOR_PERFORMANCE_INFORMATION* sppi;
|
|
DWORD sppi_size;
|
|
SYSTEM_INFO system_info;
|
|
DWORD cpu_count, i;
|
|
NTSTATUS status;
|
|
ULONG result_size;
|
|
int err;
|
|
uv_cpu_info_t* cpu_info;
|
|
|
|
cpu_infos = NULL;
|
|
cpu_count = 0;
|
|
sppi = NULL;
|
|
|
|
uv__once_init();
|
|
|
|
GetSystemInfo(&system_info);
|
|
cpu_count = system_info.dwNumberOfProcessors;
|
|
|
|
cpu_infos = uv__calloc(cpu_count, sizeof *cpu_infos);
|
|
if (cpu_infos == NULL) {
|
|
err = ERROR_OUTOFMEMORY;
|
|
goto error;
|
|
}
|
|
|
|
sppi_size = cpu_count * sizeof(*sppi);
|
|
sppi = uv__malloc(sppi_size);
|
|
if (sppi == NULL) {
|
|
err = ERROR_OUTOFMEMORY;
|
|
goto error;
|
|
}
|
|
|
|
status = pNtQuerySystemInformation(SystemProcessorPerformanceInformation,
|
|
sppi,
|
|
sppi_size,
|
|
&result_size);
|
|
if (!NT_SUCCESS(status)) {
|
|
err = pRtlNtStatusToDosError(status);
|
|
goto error;
|
|
}
|
|
|
|
assert(result_size == sppi_size);
|
|
|
|
for (i = 0; i < cpu_count; i++) {
|
|
WCHAR key_name[128];
|
|
HKEY processor_key;
|
|
DWORD cpu_speed;
|
|
DWORD cpu_speed_size = sizeof(cpu_speed);
|
|
WCHAR cpu_brand[256];
|
|
DWORD cpu_brand_size = sizeof(cpu_brand);
|
|
size_t len;
|
|
|
|
len = _snwprintf(key_name,
|
|
ARRAY_SIZE(key_name),
|
|
L"HARDWARE\\DESCRIPTION\\System\\CentralProcessor\\%d",
|
|
i);
|
|
|
|
assert(len > 0 && len < ARRAY_SIZE(key_name));
|
|
|
|
err = RegOpenKeyExW(HKEY_LOCAL_MACHINE,
|
|
key_name,
|
|
0,
|
|
KEY_QUERY_VALUE,
|
|
&processor_key);
|
|
if (err != ERROR_SUCCESS) {
|
|
goto error;
|
|
}
|
|
|
|
err = RegQueryValueExW(processor_key,
|
|
L"~MHz",
|
|
NULL,
|
|
NULL,
|
|
(BYTE*)&cpu_speed,
|
|
&cpu_speed_size);
|
|
if (err != ERROR_SUCCESS) {
|
|
RegCloseKey(processor_key);
|
|
goto error;
|
|
}
|
|
|
|
err = RegQueryValueExW(processor_key,
|
|
L"ProcessorNameString",
|
|
NULL,
|
|
NULL,
|
|
(BYTE*)&cpu_brand,
|
|
&cpu_brand_size);
|
|
RegCloseKey(processor_key);
|
|
if (err != ERROR_SUCCESS)
|
|
goto error;
|
|
|
|
cpu_info = &cpu_infos[i];
|
|
cpu_info->speed = cpu_speed;
|
|
cpu_info->cpu_times.user = sppi[i].UserTime.QuadPart / 10000;
|
|
cpu_info->cpu_times.sys = (sppi[i].KernelTime.QuadPart -
|
|
sppi[i].IdleTime.QuadPart) / 10000;
|
|
cpu_info->cpu_times.idle = sppi[i].IdleTime.QuadPart / 10000;
|
|
cpu_info->cpu_times.irq = sppi[i].InterruptTime.QuadPart / 10000;
|
|
cpu_info->cpu_times.nice = 0;
|
|
|
|
uv__convert_utf16_to_utf8(cpu_brand,
|
|
cpu_brand_size / sizeof(WCHAR),
|
|
&(cpu_info->model));
|
|
}
|
|
|
|
uv__free(sppi);
|
|
|
|
*cpu_count_ptr = cpu_count;
|
|
*cpu_infos_ptr = cpu_infos;
|
|
|
|
return 0;
|
|
|
|
error:
|
|
if (cpu_infos != NULL) {
|
|
/* This is safe because the cpu_infos array is zeroed on allocation. */
|
|
for (i = 0; i < cpu_count; i++)
|
|
uv__free(cpu_infos[i].model);
|
|
}
|
|
|
|
uv__free(cpu_infos);
|
|
uv__free(sppi);
|
|
|
|
return uv_translate_sys_error(err);
|
|
}
|
|
|
|
|
|
int uv_interface_addresses(uv_interface_address_t** addresses_ptr,
|
|
int* count_ptr) {
|
|
IP_ADAPTER_ADDRESSES* win_address_buf;
|
|
ULONG win_address_buf_size;
|
|
IP_ADAPTER_ADDRESSES* adapter;
|
|
|
|
uv_interface_address_t* uv_address_buf;
|
|
char* name_buf;
|
|
size_t uv_address_buf_size;
|
|
uv_interface_address_t* uv_address;
|
|
|
|
int count;
|
|
ULONG flags;
|
|
|
|
*addresses_ptr = NULL;
|
|
*count_ptr = 0;
|
|
|
|
flags = GAA_FLAG_SKIP_ANYCAST | GAA_FLAG_SKIP_MULTICAST |
|
|
GAA_FLAG_SKIP_DNS_SERVER;
|
|
|
|
/* Fetch the size of the adapters reported by windows, and then get the list
|
|
* itself. */
|
|
win_address_buf_size = 0;
|
|
win_address_buf = NULL;
|
|
|
|
for (;;) {
|
|
ULONG r;
|
|
|
|
/* If win_address_buf is 0, then GetAdaptersAddresses will fail with.
|
|
* ERROR_BUFFER_OVERFLOW, and the required buffer size will be stored in
|
|
* win_address_buf_size. */
|
|
r = GetAdaptersAddresses(AF_UNSPEC,
|
|
flags,
|
|
NULL,
|
|
win_address_buf,
|
|
&win_address_buf_size);
|
|
|
|
if (r == ERROR_SUCCESS)
|
|
break;
|
|
|
|
uv__free(win_address_buf);
|
|
|
|
switch (r) {
|
|
case ERROR_BUFFER_OVERFLOW:
|
|
/* This happens when win_address_buf is NULL or too small to hold all
|
|
* adapters. */
|
|
win_address_buf = uv__malloc(win_address_buf_size);
|
|
if (win_address_buf == NULL)
|
|
return UV_ENOMEM;
|
|
|
|
continue;
|
|
|
|
case ERROR_NO_DATA: {
|
|
/* No adapters were found. */
|
|
uv_address_buf = uv__malloc(1);
|
|
if (uv_address_buf == NULL)
|
|
return UV_ENOMEM;
|
|
|
|
*count_ptr = 0;
|
|
*addresses_ptr = uv_address_buf;
|
|
|
|
return 0;
|
|
}
|
|
|
|
case ERROR_ADDRESS_NOT_ASSOCIATED:
|
|
return UV_EAGAIN;
|
|
|
|
case ERROR_INVALID_PARAMETER:
|
|
/* MSDN says:
|
|
* "This error is returned for any of the following conditions: the
|
|
* SizePointer parameter is NULL, the Address parameter is not
|
|
* AF_INET, AF_INET6, or AF_UNSPEC, or the address information for
|
|
* the parameters requested is greater than ULONG_MAX."
|
|
* Since the first two conditions are not met, it must be that the
|
|
* adapter data is too big.
|
|
*/
|
|
return UV_ENOBUFS;
|
|
|
|
default:
|
|
/* Other (unspecified) errors can happen, but we don't have any special
|
|
* meaning for them. */
|
|
assert(r != ERROR_SUCCESS);
|
|
return uv_translate_sys_error(r);
|
|
}
|
|
}
|
|
|
|
/* Count the number of enabled interfaces and compute how much space is
|
|
* needed to store their info. */
|
|
count = 0;
|
|
uv_address_buf_size = 0;
|
|
|
|
for (adapter = win_address_buf;
|
|
adapter != NULL;
|
|
adapter = adapter->Next) {
|
|
IP_ADAPTER_UNICAST_ADDRESS* unicast_address;
|
|
int name_size;
|
|
|
|
/* Interfaces that are not 'up' should not be reported. Also skip
|
|
* interfaces that have no associated unicast address, as to avoid
|
|
* allocating space for the name for this interface. */
|
|
if (adapter->OperStatus != IfOperStatusUp ||
|
|
adapter->FirstUnicastAddress == NULL)
|
|
continue;
|
|
|
|
/* Compute the size of the interface name. */
|
|
name_size = WideCharToMultiByte(CP_UTF8,
|
|
0,
|
|
adapter->FriendlyName,
|
|
-1,
|
|
NULL,
|
|
0,
|
|
NULL,
|
|
FALSE);
|
|
if (name_size <= 0) {
|
|
uv__free(win_address_buf);
|
|
return uv_translate_sys_error(GetLastError());
|
|
}
|
|
uv_address_buf_size += name_size;
|
|
|
|
/* Count the number of addresses associated with this interface, and
|
|
* compute the size. */
|
|
for (unicast_address = (IP_ADAPTER_UNICAST_ADDRESS*)
|
|
adapter->FirstUnicastAddress;
|
|
unicast_address != NULL;
|
|
unicast_address = unicast_address->Next) {
|
|
count++;
|
|
uv_address_buf_size += sizeof(uv_interface_address_t);
|
|
}
|
|
}
|
|
|
|
/* Allocate space to store interface data plus adapter names. */
|
|
uv_address_buf = uv__malloc(uv_address_buf_size);
|
|
if (uv_address_buf == NULL) {
|
|
uv__free(win_address_buf);
|
|
return UV_ENOMEM;
|
|
}
|
|
|
|
/* Compute the start of the uv_interface_address_t array, and the place in
|
|
* the buffer where the interface names will be stored. */
|
|
uv_address = uv_address_buf;
|
|
name_buf = (char*) (uv_address_buf + count);
|
|
|
|
/* Fill out the output buffer. */
|
|
for (adapter = win_address_buf;
|
|
adapter != NULL;
|
|
adapter = adapter->Next) {
|
|
IP_ADAPTER_UNICAST_ADDRESS* unicast_address;
|
|
int name_size;
|
|
size_t max_name_size;
|
|
|
|
if (adapter->OperStatus != IfOperStatusUp ||
|
|
adapter->FirstUnicastAddress == NULL)
|
|
continue;
|
|
|
|
/* Convert the interface name to UTF8. */
|
|
max_name_size = (char*) uv_address_buf + uv_address_buf_size - name_buf;
|
|
if (max_name_size > (size_t) INT_MAX)
|
|
max_name_size = INT_MAX;
|
|
name_size = WideCharToMultiByte(CP_UTF8,
|
|
0,
|
|
adapter->FriendlyName,
|
|
-1,
|
|
name_buf,
|
|
(int) max_name_size,
|
|
NULL,
|
|
FALSE);
|
|
if (name_size <= 0) {
|
|
uv__free(win_address_buf);
|
|
uv__free(uv_address_buf);
|
|
return uv_translate_sys_error(GetLastError());
|
|
}
|
|
|
|
/* Add an uv_interface_address_t element for every unicast address. */
|
|
for (unicast_address = (IP_ADAPTER_UNICAST_ADDRESS*)
|
|
adapter->FirstUnicastAddress;
|
|
unicast_address != NULL;
|
|
unicast_address = unicast_address->Next) {
|
|
struct sockaddr* sa;
|
|
ULONG prefix_len;
|
|
|
|
sa = unicast_address->Address.lpSockaddr;
|
|
|
|
prefix_len =
|
|
((IP_ADAPTER_UNICAST_ADDRESS_LH*) unicast_address)->OnLinkPrefixLength;
|
|
|
|
memset(uv_address, 0, sizeof *uv_address);
|
|
|
|
uv_address->name = name_buf;
|
|
|
|
if (adapter->PhysicalAddressLength == sizeof(uv_address->phys_addr)) {
|
|
memcpy(uv_address->phys_addr,
|
|
adapter->PhysicalAddress,
|
|
sizeof(uv_address->phys_addr));
|
|
}
|
|
|
|
uv_address->is_internal =
|
|
(adapter->IfType == IF_TYPE_SOFTWARE_LOOPBACK);
|
|
|
|
if (sa->sa_family == AF_INET6) {
|
|
uv_address->address.address6 = *((struct sockaddr_in6 *) sa);
|
|
|
|
uv_address->netmask.netmask6.sin6_family = AF_INET6;
|
|
memset(uv_address->netmask.netmask6.sin6_addr.s6_addr, 0xff, prefix_len >> 3);
|
|
/* This check ensures that we don't write past the size of the data. */
|
|
if (prefix_len % 8) {
|
|
uv_address->netmask.netmask6.sin6_addr.s6_addr[prefix_len >> 3] =
|
|
0xff << (8 - prefix_len % 8);
|
|
}
|
|
|
|
} else {
|
|
uv_address->address.address4 = *((struct sockaddr_in *) sa);
|
|
|
|
uv_address->netmask.netmask4.sin_family = AF_INET;
|
|
uv_address->netmask.netmask4.sin_addr.s_addr = (prefix_len > 0) ?
|
|
htonl(0xffffffff << (32 - prefix_len)) : 0;
|
|
}
|
|
|
|
uv_address++;
|
|
}
|
|
|
|
name_buf += name_size;
|
|
}
|
|
|
|
uv__free(win_address_buf);
|
|
|
|
*addresses_ptr = uv_address_buf;
|
|
*count_ptr = count;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
void uv_free_interface_addresses(uv_interface_address_t* addresses,
|
|
int count) {
|
|
uv__free(addresses);
|
|
}
|
|
|
|
|
|
int uv_getrusage(uv_rusage_t *uv_rusage) {
|
|
FILETIME createTime, exitTime, kernelTime, userTime;
|
|
SYSTEMTIME kernelSystemTime, userSystemTime;
|
|
PROCESS_MEMORY_COUNTERS memCounters;
|
|
IO_COUNTERS ioCounters;
|
|
int ret;
|
|
|
|
ret = GetProcessTimes(GetCurrentProcess(), &createTime, &exitTime, &kernelTime, &userTime);
|
|
if (ret == 0) {
|
|
return uv_translate_sys_error(GetLastError());
|
|
}
|
|
|
|
ret = FileTimeToSystemTime(&kernelTime, &kernelSystemTime);
|
|
if (ret == 0) {
|
|
return uv_translate_sys_error(GetLastError());
|
|
}
|
|
|
|
ret = FileTimeToSystemTime(&userTime, &userSystemTime);
|
|
if (ret == 0) {
|
|
return uv_translate_sys_error(GetLastError());
|
|
}
|
|
|
|
ret = GetProcessMemoryInfo(GetCurrentProcess(),
|
|
&memCounters,
|
|
sizeof(memCounters));
|
|
if (ret == 0) {
|
|
return uv_translate_sys_error(GetLastError());
|
|
}
|
|
|
|
ret = GetProcessIoCounters(GetCurrentProcess(), &ioCounters);
|
|
if (ret == 0) {
|
|
return uv_translate_sys_error(GetLastError());
|
|
}
|
|
|
|
memset(uv_rusage, 0, sizeof(*uv_rusage));
|
|
|
|
uv_rusage->ru_utime.tv_sec = userSystemTime.wHour * 3600 +
|
|
userSystemTime.wMinute * 60 +
|
|
userSystemTime.wSecond;
|
|
uv_rusage->ru_utime.tv_usec = userSystemTime.wMilliseconds * 1000;
|
|
|
|
uv_rusage->ru_stime.tv_sec = kernelSystemTime.wHour * 3600 +
|
|
kernelSystemTime.wMinute * 60 +
|
|
kernelSystemTime.wSecond;
|
|
uv_rusage->ru_stime.tv_usec = kernelSystemTime.wMilliseconds * 1000;
|
|
|
|
uv_rusage->ru_majflt = (uint64_t) memCounters.PageFaultCount;
|
|
uv_rusage->ru_maxrss = (uint64_t) memCounters.PeakWorkingSetSize / 1024;
|
|
|
|
uv_rusage->ru_oublock = (uint64_t) ioCounters.WriteOperationCount;
|
|
uv_rusage->ru_inblock = (uint64_t) ioCounters.ReadOperationCount;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
int uv_os_homedir(char* buffer, size_t* size) {
|
|
uv_passwd_t pwd;
|
|
size_t len;
|
|
int r;
|
|
|
|
/* Check if the USERPROFILE environment variable is set first. The task of
|
|
performing input validation on buffer and size is taken care of by
|
|
uv_os_getenv(). */
|
|
r = uv_os_getenv("USERPROFILE", buffer, size);
|
|
|
|
/* Don't return an error if USERPROFILE was not found. */
|
|
if (r != UV_ENOENT)
|
|
return r;
|
|
|
|
/* USERPROFILE is not set, so call uv_os_get_passwd() */
|
|
r = uv_os_get_passwd(&pwd);
|
|
|
|
if (r != 0) {
|
|
return r;
|
|
}
|
|
|
|
len = strlen(pwd.homedir);
|
|
|
|
if (len >= *size) {
|
|
*size = len + 1;
|
|
uv_os_free_passwd(&pwd);
|
|
return UV_ENOBUFS;
|
|
}
|
|
|
|
memcpy(buffer, pwd.homedir, len + 1);
|
|
*size = len;
|
|
uv_os_free_passwd(&pwd);
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
int uv_os_tmpdir(char* buffer, size_t* size) {
|
|
wchar_t *path;
|
|
DWORD bufsize;
|
|
size_t len;
|
|
|
|
if (buffer == NULL || size == NULL || *size == 0)
|
|
return UV_EINVAL;
|
|
|
|
len = 0;
|
|
len = GetTempPathW(0, NULL);
|
|
if (len == 0) {
|
|
return uv_translate_sys_error(GetLastError());
|
|
}
|
|
/* Include space for terminating null char. */
|
|
len += 1;
|
|
path = uv__malloc(len * sizeof(wchar_t));
|
|
if (path == NULL) {
|
|
return UV_ENOMEM;
|
|
}
|
|
len = GetTempPathW(len, path);
|
|
|
|
if (len == 0) {
|
|
uv__free(path);
|
|
return uv_translate_sys_error(GetLastError());
|
|
}
|
|
|
|
/* The returned directory should not have a trailing slash, unless it points
|
|
* at a drive root, like c:\. Remove it if needed. */
|
|
if (path[len - 1] == L'\\' &&
|
|
!(len == 3 && path[1] == L':')) {
|
|
len--;
|
|
path[len] = L'\0';
|
|
}
|
|
|
|
/* Check how much space we need */
|
|
bufsize = WideCharToMultiByte(CP_UTF8, 0, path, -1, NULL, 0, NULL, NULL);
|
|
|
|
if (bufsize == 0) {
|
|
uv__free(path);
|
|
return uv_translate_sys_error(GetLastError());
|
|
} else if (bufsize > *size) {
|
|
uv__free(path);
|
|
*size = bufsize;
|
|
return UV_ENOBUFS;
|
|
}
|
|
|
|
/* Convert to UTF-8 */
|
|
bufsize = WideCharToMultiByte(CP_UTF8,
|
|
0,
|
|
path,
|
|
-1,
|
|
buffer,
|
|
*size,
|
|
NULL,
|
|
NULL);
|
|
uv__free(path);
|
|
|
|
if (bufsize == 0)
|
|
return uv_translate_sys_error(GetLastError());
|
|
|
|
*size = bufsize - 1;
|
|
return 0;
|
|
}
|
|
|
|
|
|
/*
|
|
* Converts a UTF-16 string into a UTF-8 one. The resulting string is
|
|
* null-terminated.
|
|
*
|
|
* If utf16 is null terminated, utf16len can be set to -1, otherwise it must
|
|
* be specified.
|
|
*/
|
|
int uv__convert_utf16_to_utf8(const WCHAR* utf16, int utf16len, char** utf8) {
|
|
DWORD bufsize;
|
|
|
|
if (utf16 == NULL)
|
|
return UV_EINVAL;
|
|
|
|
/* Check how much space we need */
|
|
bufsize = WideCharToMultiByte(CP_UTF8,
|
|
0,
|
|
utf16,
|
|
utf16len,
|
|
NULL,
|
|
0,
|
|
NULL,
|
|
NULL);
|
|
|
|
if (bufsize == 0)
|
|
return uv_translate_sys_error(GetLastError());
|
|
|
|
/* Allocate the destination buffer adding an extra byte for the terminating
|
|
* NULL. If utf16len is not -1 WideCharToMultiByte will not add it, so
|
|
* we do it ourselves always, just in case. */
|
|
*utf8 = uv__malloc(bufsize + 1);
|
|
|
|
if (*utf8 == NULL)
|
|
return UV_ENOMEM;
|
|
|
|
/* Convert to UTF-8 */
|
|
bufsize = WideCharToMultiByte(CP_UTF8,
|
|
0,
|
|
utf16,
|
|
utf16len,
|
|
*utf8,
|
|
bufsize,
|
|
NULL,
|
|
NULL);
|
|
|
|
if (bufsize == 0) {
|
|
uv__free(*utf8);
|
|
*utf8 = NULL;
|
|
return uv_translate_sys_error(GetLastError());
|
|
}
|
|
|
|
(*utf8)[bufsize] = '\0';
|
|
return 0;
|
|
}
|
|
|
|
|
|
/*
|
|
* Converts a UTF-8 string into a UTF-16 one. The resulting string is
|
|
* null-terminated.
|
|
*
|
|
* If utf8 is null terminated, utf8len can be set to -1, otherwise it must
|
|
* be specified.
|
|
*/
|
|
int uv__convert_utf8_to_utf16(const char* utf8, int utf8len, WCHAR** utf16) {
|
|
int bufsize;
|
|
|
|
if (utf8 == NULL)
|
|
return UV_EINVAL;
|
|
|
|
/* Check how much space we need */
|
|
bufsize = MultiByteToWideChar(CP_UTF8, 0, utf8, utf8len, NULL, 0);
|
|
|
|
if (bufsize == 0)
|
|
return uv_translate_sys_error(GetLastError());
|
|
|
|
/* Allocate the destination buffer adding an extra byte for the terminating
|
|
* NULL. If utf8len is not -1 MultiByteToWideChar will not add it, so
|
|
* we do it ourselves always, just in case. */
|
|
*utf16 = uv__malloc(sizeof(WCHAR) * (bufsize + 1));
|
|
|
|
if (*utf16 == NULL)
|
|
return UV_ENOMEM;
|
|
|
|
/* Convert to UTF-16 */
|
|
bufsize = MultiByteToWideChar(CP_UTF8, 0, utf8, utf8len, *utf16, bufsize);
|
|
|
|
if (bufsize == 0) {
|
|
uv__free(*utf16);
|
|
*utf16 = NULL;
|
|
return uv_translate_sys_error(GetLastError());
|
|
}
|
|
|
|
(*utf16)[bufsize] = L'\0';
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int uv__getpwuid_r(uv_passwd_t* pwd) {
|
|
HANDLE token;
|
|
wchar_t username[UNLEN + 1];
|
|
wchar_t *path;
|
|
DWORD bufsize;
|
|
int r;
|
|
|
|
if (pwd == NULL)
|
|
return UV_EINVAL;
|
|
|
|
/* Get the home directory using GetUserProfileDirectoryW() */
|
|
if (OpenProcessToken(GetCurrentProcess(), TOKEN_READ, &token) == 0)
|
|
return uv_translate_sys_error(GetLastError());
|
|
|
|
bufsize = 0;
|
|
GetUserProfileDirectoryW(token, NULL, &bufsize);
|
|
if (GetLastError() != ERROR_INSUFFICIENT_BUFFER) {
|
|
r = GetLastError();
|
|
CloseHandle(token);
|
|
return uv_translate_sys_error(r);
|
|
}
|
|
|
|
path = uv__malloc(bufsize * sizeof(wchar_t));
|
|
if (path == NULL) {
|
|
CloseHandle(token);
|
|
return UV_ENOMEM;
|
|
}
|
|
|
|
if (!GetUserProfileDirectoryW(token, path, &bufsize)) {
|
|
r = GetLastError();
|
|
CloseHandle(token);
|
|
uv__free(path);
|
|
return uv_translate_sys_error(r);
|
|
}
|
|
|
|
CloseHandle(token);
|
|
|
|
/* Get the username using GetUserNameW() */
|
|
bufsize = ARRAY_SIZE(username);
|
|
if (!GetUserNameW(username, &bufsize)) {
|
|
r = GetLastError();
|
|
uv__free(path);
|
|
|
|
/* This should not be possible */
|
|
if (r == ERROR_INSUFFICIENT_BUFFER)
|
|
return UV_ENOMEM;
|
|
|
|
return uv_translate_sys_error(r);
|
|
}
|
|
|
|
pwd->homedir = NULL;
|
|
r = uv__convert_utf16_to_utf8(path, -1, &pwd->homedir);
|
|
uv__free(path);
|
|
|
|
if (r != 0)
|
|
return r;
|
|
|
|
pwd->username = NULL;
|
|
r = uv__convert_utf16_to_utf8(username, -1, &pwd->username);
|
|
|
|
if (r != 0) {
|
|
uv__free(pwd->homedir);
|
|
return r;
|
|
}
|
|
|
|
pwd->shell = NULL;
|
|
pwd->uid = -1;
|
|
pwd->gid = -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
int uv_os_get_passwd(uv_passwd_t* pwd) {
|
|
return uv__getpwuid_r(pwd);
|
|
}
|
|
|
|
|
|
int uv_os_get_passwd2(uv_passwd_t* pwd, uv_uid_t uid) {
|
|
return UV_ENOTSUP;
|
|
}
|
|
|
|
|
|
int uv_os_get_group(uv_group_t* grp, uv_uid_t gid) {
|
|
return UV_ENOTSUP;
|
|
}
|
|
|
|
|
|
int uv_os_environ(uv_env_item_t** envitems, int* count) {
|
|
wchar_t* env;
|
|
wchar_t* penv;
|
|
int i, cnt;
|
|
uv_env_item_t* envitem;
|
|
|
|
*envitems = NULL;
|
|
*count = 0;
|
|
|
|
env = GetEnvironmentStringsW();
|
|
if (env == NULL)
|
|
return 0;
|
|
|
|
for (penv = env, i = 0; *penv != L'\0'; penv += wcslen(penv) + 1, i++);
|
|
|
|
*envitems = uv__calloc(i, sizeof(**envitems));
|
|
if (*envitems == NULL) {
|
|
FreeEnvironmentStringsW(env);
|
|
return UV_ENOMEM;
|
|
}
|
|
|
|
penv = env;
|
|
cnt = 0;
|
|
|
|
while (*penv != L'\0' && cnt < i) {
|
|
char* buf;
|
|
char* ptr;
|
|
|
|
if (uv__convert_utf16_to_utf8(penv, -1, &buf) != 0)
|
|
goto fail;
|
|
|
|
/* Using buf + 1 here because we know that `buf` has length at least 1,
|
|
* and some special environment variables on Windows start with a = sign. */
|
|
ptr = strchr(buf + 1, '=');
|
|
if (ptr == NULL) {
|
|
uv__free(buf);
|
|
goto do_continue;
|
|
}
|
|
|
|
*ptr = '\0';
|
|
|
|
envitem = &(*envitems)[cnt];
|
|
envitem->name = buf;
|
|
envitem->value = ptr + 1;
|
|
|
|
cnt++;
|
|
|
|
do_continue:
|
|
penv += wcslen(penv) + 1;
|
|
}
|
|
|
|
FreeEnvironmentStringsW(env);
|
|
|
|
*count = cnt;
|
|
return 0;
|
|
|
|
fail:
|
|
FreeEnvironmentStringsW(env);
|
|
|
|
for (i = 0; i < cnt; i++) {
|
|
envitem = &(*envitems)[cnt];
|
|
uv__free(envitem->name);
|
|
}
|
|
uv__free(*envitems);
|
|
|
|
*envitems = NULL;
|
|
*count = 0;
|
|
return UV_ENOMEM;
|
|
}
|
|
|
|
|
|
int uv_os_getenv(const char* name, char* buffer, size_t* size) {
|
|
wchar_t fastvar[512];
|
|
wchar_t* var;
|
|
DWORD varlen;
|
|
wchar_t* name_w;
|
|
DWORD bufsize;
|
|
size_t len;
|
|
int r;
|
|
|
|
if (name == NULL || buffer == NULL || size == NULL || *size == 0)
|
|
return UV_EINVAL;
|
|
|
|
r = uv__convert_utf8_to_utf16(name, -1, &name_w);
|
|
|
|
if (r != 0)
|
|
return r;
|
|
|
|
var = fastvar;
|
|
varlen = ARRAY_SIZE(fastvar);
|
|
|
|
for (;;) {
|
|
SetLastError(ERROR_SUCCESS);
|
|
len = GetEnvironmentVariableW(name_w, var, varlen);
|
|
|
|
if (len < varlen)
|
|
break;
|
|
|
|
/* Try repeatedly because we might have been preempted by another thread
|
|
* modifying the environment variable just as we're trying to read it.
|
|
*/
|
|
if (var != fastvar)
|
|
uv__free(var);
|
|
|
|
varlen = 1 + len;
|
|
var = uv__malloc(varlen * sizeof(*var));
|
|
|
|
if (var == NULL) {
|
|
r = UV_ENOMEM;
|
|
goto fail;
|
|
}
|
|
}
|
|
|
|
uv__free(name_w);
|
|
name_w = NULL;
|
|
|
|
if (len == 0) {
|
|
r = GetLastError();
|
|
if (r != ERROR_SUCCESS) {
|
|
r = uv_translate_sys_error(r);
|
|
goto fail;
|
|
}
|
|
}
|
|
|
|
/* Check how much space we need */
|
|
bufsize = WideCharToMultiByte(CP_UTF8, 0, var, -1, NULL, 0, NULL, NULL);
|
|
|
|
if (bufsize == 0) {
|
|
r = uv_translate_sys_error(GetLastError());
|
|
goto fail;
|
|
} else if (bufsize > *size) {
|
|
*size = bufsize;
|
|
r = UV_ENOBUFS;
|
|
goto fail;
|
|
}
|
|
|
|
/* Convert to UTF-8 */
|
|
bufsize = WideCharToMultiByte(CP_UTF8,
|
|
0,
|
|
var,
|
|
-1,
|
|
buffer,
|
|
*size,
|
|
NULL,
|
|
NULL);
|
|
|
|
if (bufsize == 0) {
|
|
r = uv_translate_sys_error(GetLastError());
|
|
goto fail;
|
|
}
|
|
|
|
*size = bufsize - 1;
|
|
r = 0;
|
|
|
|
fail:
|
|
|
|
if (name_w != NULL)
|
|
uv__free(name_w);
|
|
|
|
if (var != fastvar)
|
|
uv__free(var);
|
|
|
|
return r;
|
|
}
|
|
|
|
|
|
int uv_os_setenv(const char* name, const char* value) {
|
|
wchar_t* name_w;
|
|
wchar_t* value_w;
|
|
int r;
|
|
|
|
if (name == NULL || value == NULL)
|
|
return UV_EINVAL;
|
|
|
|
r = uv__convert_utf8_to_utf16(name, -1, &name_w);
|
|
|
|
if (r != 0)
|
|
return r;
|
|
|
|
r = uv__convert_utf8_to_utf16(value, -1, &value_w);
|
|
|
|
if (r != 0) {
|
|
uv__free(name_w);
|
|
return r;
|
|
}
|
|
|
|
r = SetEnvironmentVariableW(name_w, value_w);
|
|
uv__free(name_w);
|
|
uv__free(value_w);
|
|
|
|
if (r == 0)
|
|
return uv_translate_sys_error(GetLastError());
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
int uv_os_unsetenv(const char* name) {
|
|
wchar_t* name_w;
|
|
int r;
|
|
|
|
if (name == NULL)
|
|
return UV_EINVAL;
|
|
|
|
r = uv__convert_utf8_to_utf16(name, -1, &name_w);
|
|
|
|
if (r != 0)
|
|
return r;
|
|
|
|
r = SetEnvironmentVariableW(name_w, NULL);
|
|
uv__free(name_w);
|
|
|
|
if (r == 0)
|
|
return uv_translate_sys_error(GetLastError());
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
int uv_os_gethostname(char* buffer, size_t* size) {
|
|
WCHAR buf[UV_MAXHOSTNAMESIZE];
|
|
size_t len;
|
|
char* utf8_str;
|
|
int convert_result;
|
|
|
|
if (buffer == NULL || size == NULL || *size == 0)
|
|
return UV_EINVAL;
|
|
|
|
uv__once_init(); /* Initialize winsock */
|
|
|
|
if (pGetHostNameW == NULL)
|
|
return UV_ENOSYS;
|
|
|
|
if (pGetHostNameW(buf, UV_MAXHOSTNAMESIZE) != 0)
|
|
return uv_translate_sys_error(WSAGetLastError());
|
|
|
|
convert_result = uv__convert_utf16_to_utf8(buf, -1, &utf8_str);
|
|
|
|
if (convert_result != 0)
|
|
return convert_result;
|
|
|
|
len = strlen(utf8_str);
|
|
if (len >= *size) {
|
|
*size = len + 1;
|
|
uv__free(utf8_str);
|
|
return UV_ENOBUFS;
|
|
}
|
|
|
|
memcpy(buffer, utf8_str, len + 1);
|
|
uv__free(utf8_str);
|
|
*size = len;
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int uv__get_handle(uv_pid_t pid, int access, HANDLE* handle) {
|
|
int r;
|
|
|
|
if (pid == 0)
|
|
*handle = GetCurrentProcess();
|
|
else
|
|
*handle = OpenProcess(access, FALSE, pid);
|
|
|
|
if (*handle == NULL) {
|
|
r = GetLastError();
|
|
|
|
if (r == ERROR_INVALID_PARAMETER)
|
|
return UV_ESRCH;
|
|
else
|
|
return uv_translate_sys_error(r);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
int uv_os_getpriority(uv_pid_t pid, int* priority) {
|
|
HANDLE handle;
|
|
int r;
|
|
|
|
if (priority == NULL)
|
|
return UV_EINVAL;
|
|
|
|
r = uv__get_handle(pid, PROCESS_QUERY_LIMITED_INFORMATION, &handle);
|
|
|
|
if (r != 0)
|
|
return r;
|
|
|
|
r = GetPriorityClass(handle);
|
|
|
|
if (r == 0) {
|
|
r = uv_translate_sys_error(GetLastError());
|
|
} else {
|
|
/* Map Windows priority classes to Unix nice values. */
|
|
if (r == REALTIME_PRIORITY_CLASS)
|
|
*priority = UV_PRIORITY_HIGHEST;
|
|
else if (r == HIGH_PRIORITY_CLASS)
|
|
*priority = UV_PRIORITY_HIGH;
|
|
else if (r == ABOVE_NORMAL_PRIORITY_CLASS)
|
|
*priority = UV_PRIORITY_ABOVE_NORMAL;
|
|
else if (r == NORMAL_PRIORITY_CLASS)
|
|
*priority = UV_PRIORITY_NORMAL;
|
|
else if (r == BELOW_NORMAL_PRIORITY_CLASS)
|
|
*priority = UV_PRIORITY_BELOW_NORMAL;
|
|
else /* IDLE_PRIORITY_CLASS */
|
|
*priority = UV_PRIORITY_LOW;
|
|
|
|
r = 0;
|
|
}
|
|
|
|
CloseHandle(handle);
|
|
return r;
|
|
}
|
|
|
|
|
|
int uv_os_setpriority(uv_pid_t pid, int priority) {
|
|
HANDLE handle;
|
|
int priority_class;
|
|
int r;
|
|
|
|
/* Map Unix nice values to Windows priority classes. */
|
|
if (priority < UV_PRIORITY_HIGHEST || priority > UV_PRIORITY_LOW)
|
|
return UV_EINVAL;
|
|
else if (priority < UV_PRIORITY_HIGH)
|
|
priority_class = REALTIME_PRIORITY_CLASS;
|
|
else if (priority < UV_PRIORITY_ABOVE_NORMAL)
|
|
priority_class = HIGH_PRIORITY_CLASS;
|
|
else if (priority < UV_PRIORITY_NORMAL)
|
|
priority_class = ABOVE_NORMAL_PRIORITY_CLASS;
|
|
else if (priority < UV_PRIORITY_BELOW_NORMAL)
|
|
priority_class = NORMAL_PRIORITY_CLASS;
|
|
else if (priority < UV_PRIORITY_LOW)
|
|
priority_class = BELOW_NORMAL_PRIORITY_CLASS;
|
|
else
|
|
priority_class = IDLE_PRIORITY_CLASS;
|
|
|
|
r = uv__get_handle(pid, PROCESS_SET_INFORMATION, &handle);
|
|
|
|
if (r != 0)
|
|
return r;
|
|
|
|
if (SetPriorityClass(handle, priority_class) == 0)
|
|
r = uv_translate_sys_error(GetLastError());
|
|
|
|
CloseHandle(handle);
|
|
return r;
|
|
}
|
|
|
|
|
|
int uv_os_uname(uv_utsname_t* buffer) {
|
|
/* Implementation loosely based on
|
|
https://github.com/gagern/gnulib/blob/master/lib/uname.c */
|
|
OSVERSIONINFOW os_info;
|
|
SYSTEM_INFO system_info;
|
|
HKEY registry_key;
|
|
WCHAR product_name_w[256];
|
|
DWORD product_name_w_size;
|
|
int version_size;
|
|
int processor_level;
|
|
int r;
|
|
|
|
if (buffer == NULL)
|
|
return UV_EINVAL;
|
|
|
|
uv__once_init();
|
|
os_info.dwOSVersionInfoSize = sizeof(os_info);
|
|
os_info.szCSDVersion[0] = L'\0';
|
|
|
|
/* Try calling RtlGetVersion(), and fall back to the deprecated GetVersionEx()
|
|
if RtlGetVersion() is not available. */
|
|
if (pRtlGetVersion) {
|
|
pRtlGetVersion(&os_info);
|
|
} else {
|
|
/* Silence GetVersionEx() deprecation warning. */
|
|
#ifdef _MSC_VER
|
|
#pragma warning(suppress : 4996)
|
|
#endif
|
|
if (GetVersionExW(&os_info) == 0) {
|
|
r = uv_translate_sys_error(GetLastError());
|
|
goto error;
|
|
}
|
|
}
|
|
|
|
/* Populate the version field. */
|
|
version_size = 0;
|
|
r = RegOpenKeyExW(HKEY_LOCAL_MACHINE,
|
|
L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion",
|
|
0,
|
|
KEY_QUERY_VALUE,
|
|
®istry_key);
|
|
|
|
if (r == ERROR_SUCCESS) {
|
|
product_name_w_size = sizeof(product_name_w);
|
|
r = RegGetValueW(registry_key,
|
|
NULL,
|
|
L"ProductName",
|
|
RRF_RT_REG_SZ,
|
|
NULL,
|
|
(PVOID) product_name_w,
|
|
&product_name_w_size);
|
|
RegCloseKey(registry_key);
|
|
|
|
if (r == ERROR_SUCCESS) {
|
|
/* Windows 11 shares dwMajorVersion with Windows 10
|
|
* this workaround tries to disambiguate that by checking
|
|
* if the dwBuildNumber is from Windows 11 releases (>= 22000).
|
|
*
|
|
* This workaround replaces the ProductName key value
|
|
* from "Windows 10 *" to "Windows 11 *" */
|
|
if (os_info.dwMajorVersion == 10 &&
|
|
os_info.dwBuildNumber >= 22000 &&
|
|
product_name_w_size >= ARRAY_SIZE(L"Windows 10")) {
|
|
/* If ProductName starts with "Windows 10" */
|
|
if (wcsncmp(product_name_w, L"Windows 10", ARRAY_SIZE(L"Windows 10") - 1) == 0) {
|
|
/* Bump 10 to 11 */
|
|
product_name_w[9] = '1';
|
|
}
|
|
}
|
|
|
|
version_size = WideCharToMultiByte(CP_UTF8,
|
|
0,
|
|
product_name_w,
|
|
-1,
|
|
buffer->version,
|
|
sizeof(buffer->version),
|
|
NULL,
|
|
NULL);
|
|
if (version_size == 0) {
|
|
r = uv_translate_sys_error(GetLastError());
|
|
goto error;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Append service pack information to the version if present. */
|
|
if (os_info.szCSDVersion[0] != L'\0') {
|
|
if (version_size > 0)
|
|
buffer->version[version_size - 1] = ' ';
|
|
|
|
if (WideCharToMultiByte(CP_UTF8,
|
|
0,
|
|
os_info.szCSDVersion,
|
|
-1,
|
|
buffer->version + version_size,
|
|
sizeof(buffer->version) - version_size,
|
|
NULL,
|
|
NULL) == 0) {
|
|
r = uv_translate_sys_error(GetLastError());
|
|
goto error;
|
|
}
|
|
}
|
|
|
|
/* Populate the sysname field. */
|
|
#ifdef __MINGW32__
|
|
r = snprintf(buffer->sysname,
|
|
sizeof(buffer->sysname),
|
|
"MINGW32_NT-%u.%u",
|
|
(unsigned int) os_info.dwMajorVersion,
|
|
(unsigned int) os_info.dwMinorVersion);
|
|
assert((size_t)r < sizeof(buffer->sysname));
|
|
#else
|
|
uv__strscpy(buffer->sysname, "Windows_NT", sizeof(buffer->sysname));
|
|
#endif
|
|
|
|
/* Populate the release field. */
|
|
r = snprintf(buffer->release,
|
|
sizeof(buffer->release),
|
|
"%d.%d.%d",
|
|
(unsigned int) os_info.dwMajorVersion,
|
|
(unsigned int) os_info.dwMinorVersion,
|
|
(unsigned int) os_info.dwBuildNumber);
|
|
assert((size_t)r < sizeof(buffer->release));
|
|
|
|
/* Populate the machine field. */
|
|
GetSystemInfo(&system_info);
|
|
|
|
switch (system_info.wProcessorArchitecture) {
|
|
case PROCESSOR_ARCHITECTURE_AMD64:
|
|
uv__strscpy(buffer->machine, "x86_64", sizeof(buffer->machine));
|
|
break;
|
|
case PROCESSOR_ARCHITECTURE_IA64:
|
|
uv__strscpy(buffer->machine, "ia64", sizeof(buffer->machine));
|
|
break;
|
|
case PROCESSOR_ARCHITECTURE_INTEL:
|
|
uv__strscpy(buffer->machine, "i386", sizeof(buffer->machine));
|
|
|
|
if (system_info.wProcessorLevel > 3) {
|
|
processor_level = system_info.wProcessorLevel < 6 ?
|
|
system_info.wProcessorLevel : 6;
|
|
buffer->machine[1] = '0' + processor_level;
|
|
}
|
|
|
|
break;
|
|
case PROCESSOR_ARCHITECTURE_IA32_ON_WIN64:
|
|
uv__strscpy(buffer->machine, "i686", sizeof(buffer->machine));
|
|
break;
|
|
case PROCESSOR_ARCHITECTURE_MIPS:
|
|
uv__strscpy(buffer->machine, "mips", sizeof(buffer->machine));
|
|
break;
|
|
case PROCESSOR_ARCHITECTURE_ALPHA:
|
|
case PROCESSOR_ARCHITECTURE_ALPHA64:
|
|
uv__strscpy(buffer->machine, "alpha", sizeof(buffer->machine));
|
|
break;
|
|
case PROCESSOR_ARCHITECTURE_PPC:
|
|
uv__strscpy(buffer->machine, "powerpc", sizeof(buffer->machine));
|
|
break;
|
|
case PROCESSOR_ARCHITECTURE_SHX:
|
|
uv__strscpy(buffer->machine, "sh", sizeof(buffer->machine));
|
|
break;
|
|
case PROCESSOR_ARCHITECTURE_ARM:
|
|
uv__strscpy(buffer->machine, "arm", sizeof(buffer->machine));
|
|
break;
|
|
default:
|
|
uv__strscpy(buffer->machine, "unknown", sizeof(buffer->machine));
|
|
break;
|
|
}
|
|
|
|
return 0;
|
|
|
|
error:
|
|
buffer->sysname[0] = '\0';
|
|
buffer->release[0] = '\0';
|
|
buffer->version[0] = '\0';
|
|
buffer->machine[0] = '\0';
|
|
return r;
|
|
}
|
|
|
|
int uv_gettimeofday(uv_timeval64_t* tv) {
|
|
/* Based on https://doxygen.postgresql.org/gettimeofday_8c_source.html */
|
|
const uint64_t epoch = (uint64_t) 116444736000000000ULL;
|
|
FILETIME file_time;
|
|
ULARGE_INTEGER ularge;
|
|
|
|
if (tv == NULL)
|
|
return UV_EINVAL;
|
|
|
|
GetSystemTimeAsFileTime(&file_time);
|
|
ularge.LowPart = file_time.dwLowDateTime;
|
|
ularge.HighPart = file_time.dwHighDateTime;
|
|
tv->tv_sec = (int64_t) ((ularge.QuadPart - epoch) / 10000000L);
|
|
tv->tv_usec = (int32_t) (((ularge.QuadPart - epoch) % 10000000L) / 10);
|
|
return 0;
|
|
}
|
|
|
|
int uv__random_rtlgenrandom(void* buf, size_t buflen) {
|
|
if (buflen == 0)
|
|
return 0;
|
|
|
|
if (SystemFunction036(buf, buflen) == FALSE)
|
|
return UV_EIO;
|
|
|
|
return 0;
|
|
}
|
|
|
|
void uv_sleep(unsigned int msec) {
|
|
Sleep(msec);
|
|
}
|