From d4288bbeab134277d20af672c0997ee23641d9aa Mon Sep 17 00:00:00 2001 From: cjihrig Date: Fri, 4 Jan 2019 14:04:32 -0500 Subject: [PATCH] unix,win: add uv_os_uname() Fixes: https://github.com/libuv/libuv/issues/2126 PR-URL: https://github.com/libuv/libuv/pull/2128 Reviewed-By: Ben Noordhuis Reviewed-By: Bartosz Sosnowski Reviewed-By: Santiago Gimeno Reviewed-By: Richard Lau --- CMakeLists.txt | 3 +- Makefile.am | 1 + docs/src/misc.rst | 22 +++++++ include/uv.h | 17 +++++- src/unix/core.c | 57 ++++++++++++++++++ src/win/util.c | 117 ++++++++++++++++++++++++++++++++++++ src/win/winapi.c | 4 ++ src/win/winapi.h | 4 ++ test/test-list.h | 2 + test/test-platform-output.c | 9 +++ test/test-uname.c | 69 +++++++++++++++++++++ test/test.gyp | 1 + 12 files changed, 303 insertions(+), 3 deletions(-) create mode 100644 test/test-uname.c diff --git a/CMakeLists.txt b/CMakeLists.txt index 9f1c0587a98..fa268a13ed6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -167,6 +167,7 @@ set(uv_test_sources test/test-udp-send-immediate.c test/test-udp-send-unreachable.c test/test-udp-try-send.c + test/test-uname.c test/test-walk-handles.c test/test-watcher-cross-stop.c) @@ -211,7 +212,7 @@ if(WIN32) else() list(APPEND uv_defines _FILE_OFFSET_BITS=64 _LARGEFILE_SOURCE) if(NOT CMAKE_SYSTEM_NAME STREQUAL "Android") - # Android has pthread as part of its c library, not as a separate + # Android has pthread as part of its c library, not as a separate # libpthread.so. list(APPEND uv_libraries pthread) endif() diff --git a/Makefile.am b/Makefile.am index f9c9c9a05d1..8fe31e2d753 100644 --- a/Makefile.am +++ b/Makefile.am @@ -293,6 +293,7 @@ test_run_tests_SOURCES = test/blackhole-server.c \ test/test-udp-send-immediate.c \ test/test-udp-send-unreachable.c \ test/test-udp-try-send.c \ + test/test-uname.c \ test/test-walk-handles.c \ test/test-watcher-cross-stop.c test_run_tests_LDADD = libuv.la diff --git a/docs/src/misc.rst b/docs/src/misc.rst index cf4a7895cd1..81c03a85303 100644 --- a/docs/src/misc.rst +++ b/docs/src/misc.rst @@ -145,6 +145,19 @@ Data types char* homedir; } uv_passwd_t; +.. c:type:: uv_utsname_t + + Data type for operating system name and version information. + + :: + + typedef struct uv_utsname_s { + char sysname[256]; + char release[256]; + char version[256]; + char machine[256]; + } uv_utsname_t; + API --- @@ -549,3 +562,12 @@ API for others it will be silently reduced to `PRIORITY_HIGH`. .. versionadded:: 1.23.0 + +.. c:function:: int uv_os_uname(uv_utsname_t* buffer) + + Retrieves system information in `buffer`. The populated data includes the + operating system name, release, version, and machine. On non-Windows + systems, `uv_os_uname()` is a thin wrapper around :man:`uname(3)`. Returns + zero on success, and a non-zero error value otherwise. + + .. versionadded:: 1.25.0 diff --git a/include/uv.h b/include/uv.h index 86a2ecc2d74..a46b229dfd1 100644 --- a/include/uv.h +++ b/include/uv.h @@ -234,6 +234,7 @@ typedef struct uv_cpu_info_s uv_cpu_info_t; typedef struct uv_interface_address_s uv_interface_address_t; typedef struct uv_dirent_s uv_dirent_t; typedef struct uv_passwd_s uv_passwd_t; +typedef struct uv_utsname_s uv_utsname_t; typedef enum { UV_LOOP_BLOCK_SIGNAL @@ -968,13 +969,13 @@ enum uv_process_flags { */ UV_PROCESS_WINDOWS_HIDE = (1 << 4), /* - * Hide the subprocess console window that would normally be created. This + * Hide the subprocess console window that would normally be created. This * option is only meaningful on Windows systems. On Unix it is silently * ignored. */ UV_PROCESS_WINDOWS_HIDE_CONSOLE = (1 << 5), /* - * Hide the subprocess GUI window that would normally be created. This + * Hide the subprocess GUI window that would normally be created. This * option is only meaningful on Windows systems. On Unix it is silently * ignored. */ @@ -1054,6 +1055,16 @@ struct uv_passwd_s { char* homedir; }; +struct uv_utsname_s { + char sysname[256]; + char release[256]; + char version[256]; + char machine[256]; + /* This struct does not contain the nodename and domainname fields present in + the utsname type. domainname is a GNU extension. Both fields are referred + to as meaningless in the docs. */ +}; + typedef enum { UV_DIRENT_UNKNOWN, UV_DIRENT_FILE, @@ -1135,6 +1146,8 @@ UV_EXTERN int uv_os_unsetenv(const char* name); UV_EXTERN int uv_os_gethostname(char* buffer, size_t* size); +UV_EXTERN int uv_os_uname(uv_utsname_t* buffer); + typedef enum { UV_FS_UNKNOWN = -1, diff --git a/src/unix/core.c b/src/unix/core.c index 0830c74a168..cd57ce20ba2 100644 --- a/src/unix/core.c +++ b/src/unix/core.c @@ -40,6 +40,7 @@ #include /* writev */ #include /* getrusage */ #include +#include #ifdef __sun # include /* MAXHOSTNAMELEN on Solaris */ @@ -1357,3 +1358,59 @@ int uv_os_setpriority(uv_pid_t pid, int priority) { return 0; } + + +int uv_os_uname(uv_utsname_t* buffer) { + struct utsname buf; + int r; + + if (buffer == NULL) + return UV_EINVAL; + + if (uname(&buf) == -1) { + r = UV__ERR(errno); + goto error; + } + + r = uv__strscpy(buffer->sysname, buf.sysname, sizeof(buffer->sysname)); + if (r == UV_E2BIG) + goto error; + +#ifdef _AIX + r = snprintf(buffer->release, + sizeof(buffer->release), + "%s.%s", + buf.version, + buf.release); + if (r >= sizeof(buffer->release)) { + r = UV_E2BIG; + goto error; + } +#else + r = uv__strscpy(buffer->release, buf.release, sizeof(buffer->release)); + if (r == UV_E2BIG) + goto error; +#endif + + r = uv__strscpy(buffer->version, buf.version, sizeof(buffer->version)); + if (r == UV_E2BIG) + goto error; + +#if defined(_AIX) || defined(__PASE__) + r = uv__strscpy(buffer->machine, "ppc64", sizeof(buffer->machine)); +#else + r = uv__strscpy(buffer->machine, buf.machine, sizeof(buffer->machine)); +#endif + + if (r == UV_E2BIG) + goto error; + + return 0; + +error: + buffer->sysname[0] = '\0'; + buffer->release[0] = '\0'; + buffer->version[0] = '\0'; + buffer->machine[0] = '\0'; + return r; +} diff --git a/src/win/util.c b/src/win/util.c index 6e81c26165f..923789129e9 100644 --- a/src/win/util.c +++ b/src/win/util.c @@ -1627,3 +1627,120 @@ int uv_os_setpriority(uv_pid_t pid, int priority) { 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; + 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. */ + #pragma warning(suppress : 4996) + if (GetVersionExW(&os_info) == 0) { + r = uv_translate_sys_error(GetLastError()); + goto error; + } + } + + /* Populate the version field. */ + if (WideCharToMultiByte(CP_UTF8, + 0, + os_info.szCSDVersion, + -1, + buffer->version, + sizeof(buffer->version), + 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(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(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; +} diff --git a/src/win/winapi.c b/src/win/winapi.c index 2c09b448a95..fbbbceed95e 100644 --- a/src/win/winapi.c +++ b/src/win/winapi.c @@ -26,6 +26,7 @@ /* Ntdll function pointers */ +sRtlGetVersion pRtlGetVersion; sRtlNtStatusToDosError pRtlNtStatusToDosError; sNtDeviceIoControlFile pNtDeviceIoControlFile; sNtQueryInformationFile pNtQueryInformationFile; @@ -55,6 +56,9 @@ void uv_winapi_init(void) { uv_fatal_error(GetLastError(), "GetModuleHandleA"); } + pRtlGetVersion = (sRtlGetVersion) GetProcAddress(ntdll_module, + "RtlGetVersion"); + pRtlNtStatusToDosError = (sRtlNtStatusToDosError) GetProcAddress( ntdll_module, "RtlNtStatusToDosError"); diff --git a/src/win/winapi.h b/src/win/winapi.h index 2a8adf6b262..82c5ed46711 100644 --- a/src/win/winapi.h +++ b/src/win/winapi.h @@ -4519,6 +4519,9 @@ typedef VOID (NTAPI *PIO_APC_ROUTINE) PIO_STATUS_BLOCK IoStatusBlock, ULONG Reserved); +typedef NTSTATUS (NTAPI *sRtlGetVersion) + (PRTL_OSVERSIONINFOW lpVersionInformation); + typedef ULONG (NTAPI *sRtlNtStatusToDosError) (NTSTATUS Status); @@ -4707,6 +4710,7 @@ typedef HWINEVENTHOOK (WINAPI *sSetWinEventHook) /* Ntdll function pointers */ +extern sRtlGetVersion pRtlGetVersion; extern sRtlNtStatusToDosError pRtlNtStatusToDosError; extern sNtDeviceIoControlFile pNtDeviceIoControlFile; extern sNtQueryInformationFile pNtQueryInformationFile; diff --git a/test/test-list.h b/test/test-list.h index 51c15e303db..61208a0c516 100644 --- a/test/test-list.h +++ b/test/test-list.h @@ -450,6 +450,7 @@ TEST_DECLARE (fork_threadpool_queue_work_simple) TEST_DECLARE (idna_toascii) TEST_DECLARE (utf8_decode1) +TEST_DECLARE (uname) TASK_LIST_START TEST_ENTRY_CUSTOM (platform_output, 0, 1, 5000) @@ -960,6 +961,7 @@ TASK_LIST_START #endif TEST_ENTRY (utf8_decode1) + TEST_ENTRY (uname) /* Doesn't work on z/OS because that platform uses EBCDIC, not ASCII. */ #ifndef __MVS__ diff --git a/test/test-platform-output.c b/test/test-platform-output.c index 43ed119debf..e651e5c5829 100644 --- a/test/test-platform-output.c +++ b/test/test-platform-output.c @@ -35,6 +35,7 @@ TEST_IMPL(platform_output) { uv_cpu_info_t* cpus; uv_interface_address_t* interfaces; uv_passwd_t pwd; + uv_utsname_t uname; int count; int i; int err; @@ -153,5 +154,13 @@ TEST_IMPL(platform_output) { ASSERT(ppid > 0); printf("uv_os_getppid: %d\n", (int) ppid); + err = uv_os_uname(&uname); + ASSERT(err == 0); + printf("uv_os_uname:\n"); + printf(" sysname: %s\n", uname.sysname); + printf(" release: %s\n", uname.release); + printf(" version: %s\n", uname.version); + printf(" machine: %s\n", uname.machine); + return 0; } diff --git a/test/test-uname.c b/test/test-uname.c new file mode 100644 index 00000000000..105a17fe677 --- /dev/null +++ b/test/test-uname.c @@ -0,0 +1,69 @@ +/* Copyright libuv project 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 "uv.h" +#include "task.h" +#include + +#ifndef _WIN32 +# include +#endif + +TEST_IMPL(uname) { +#ifndef _WIN32 + struct utsname buf; +#endif +#ifdef _AIX + char temp[256]; +#endif + uv_utsname_t buffer; + int r; + + /* Verify that NULL is handled properly. */ + r = uv_os_uname(NULL); + ASSERT(r == UV_EINVAL); + + /* Verify the happy path. */ + r = uv_os_uname(&buffer); + ASSERT(r == 0); + +#ifndef _WIN32 + ASSERT(uname(&buf) != -1); + ASSERT(strcmp(buffer.sysname, buf.sysname) == 0); + ASSERT(strcmp(buffer.version, buf.version) == 0); + +# ifdef _AIX + snprintf(temp, sizeof(temp), "%s.%s", buf.version, buf.release); + ASSERT(strcmp(buffer.release, temp) == 0); +# else + ASSERT(strcmp(buffer.release, buf.release) == 0); +# endif /* _AIX */ + +# if defined(_AIX) || defined(__PASE__) + ASSERT(strcmp(buffer.machine, "ppc64") == 0); +# else + ASSERT(strcmp(buffer.machine, buf.machine) == 0); +# endif /* defined(_AIX) || defined(__PASE__) */ + +#endif /* _WIN32 */ + + return 0; +} diff --git a/test/test.gyp b/test/test.gyp index ae7dc0d628f..604925861e5 100644 --- a/test/test.gyp +++ b/test/test.gyp @@ -154,6 +154,7 @@ 'test-udp-multicast-interface.c', 'test-udp-multicast-interface6.c', 'test-udp-try-send.c', + 'test-uname.c', ], 'conditions': [ [ 'OS=="win"', {