From 3982458005c84ae5935e0a45f9bea1c668aa9fac Mon Sep 17 00:00:00 2001 From: Jim Monte Date: Tue, 10 Dec 2019 01:27:14 -0500 Subject: [PATCH] Fixed reporting of system information in Windows. --- src/frontend/com_sysinfo.c | 984 ++++++++++++++++++++++++++++--------- 1 file changed, 739 insertions(+), 245 deletions(-) diff --git a/src/frontend/com_sysinfo.c b/src/frontend/com_sysinfo.c index 2cd1c2509..8d1f817c0 100644 --- a/src/frontend/com_sysinfo.c +++ b/src/frontend/com_sysinfo.c @@ -12,40 +12,32 @@ #include "ngspice/fteext.h" #include "com_commands.h" -/* We might compile for Windows, but only as a console application (e.g. tcl) */ -#if defined(HAS_WINGUI) || defined(__MINGW32__) || defined(_MSC_VER) -#define HAVE_WIN32 -#endif - -#ifdef HAVE_WIN32 - -#define WIN32_LEAN_AND_MEAN +#ifdef _WIN32 +//#define WIN32_LEAN_AND_MEAN #undef BOOLEAN -#include "windows.h" +#include #include -#include -#include #endif -#include "stdio.h" -#include "stdlib.h" -#include "string.h" +#include +#include +#include +#include -#define tInt int -#define TesError int -#define TES_FAIL 1 -#define TES_SUCCESS 0 -#define TES_INVALID_PARAMS 1 +#include "ngspice/dstring.h" /* system info */ typedef struct TSI { char *cpuModelName; - unsigned numPhysicalProcessors; - unsigned numLogicalProcessors; char *osName; + unsigned int numPhysicalProcessors; + unsigned int numLogicalProcessors; } TesSystemInfo; +/* Struture with info about system */ +static TesSystemInfo system_info; + /* memory info */ struct sys_memory { unsigned long long size_m; /* Total memory size */ @@ -54,82 +46,132 @@ struct sys_memory { unsigned long long swap_f; /* Swap free */ }; -static struct sys_memory mem_t_act; - -TesError tesCreateSystemInfo(TesSystemInfo *info); +static void fprintmem(FILE *stream, unsigned long long memory); +static void free_static_system_info(void); static int get_sysmem(struct sys_memory *memall); +static void set_static_system_info(void); + +#ifdef _WIN32 +static inline void get_logical_processor_count(void); +static void get_os_info(void); +static void get_physical_processor_count(void); +static void get_processor_name(void); +#endif + + + +/* Print the available system info */ +void com_sysinfo(wordlist *wl) +{ + NG_IGNORE(wl); + + /* Invariant system data such as OS name */ + { + /* Flag that have at least some system info */ + bool f_have_system_info = FALSE; + + static bool f_first_call = TRUE; + if (f_first_call) { + /* Obtain the system info when this function is called the + * first time */ + set_static_system_info(); + + /* Free the allocations on exit. Not really necessary since they + * will be cleaned up then, but it may be useful when checking for + * memory leaks */ + if (atexit(&free_static_system_info) != 0) { + fprintf(cp_err, + "Unable to set handler to clean up system info.\n"); + } + + /* Mark that first-call init is done. Note that since the calls to + * set_static_system_info() and atexit define sequence points, the + * flag will not be set until after they complete, so the code is + * safe for reentrant calls. */ + f_first_call = FALSE; + } + + if (system_info.osName != (char *) NULL) { + fprintf(cp_out, "\nOS: %s\n", system_info.osName); + f_have_system_info = TRUE; + } + + if (system_info.cpuModelName != (char *) NULL) { + fprintf(cp_out, "CPU: %s\n", system_info.cpuModelName); + f_have_system_info = TRUE; + } + + if (system_info.numPhysicalProcessors > 0) { + fprintf(cp_out, "Physical processors: %u, ", + system_info.numPhysicalProcessors); + f_have_system_info = TRUE; + } + + if (system_info.numLogicalProcessors > 0) { + fprintf(cp_out, "Logical processors: %u\n", + system_info.numLogicalProcessors); + f_have_system_info = TRUE; + } + + /* Print something if no system info available */ + if (!f_have_system_info) { + fprintf(cp_err, "No system info available!\n"); + } + } /* end of block getting invariant system info */ + + /* Get memory information */ + { + struct sys_memory mem_t_act; + if (get_sysmem(&mem_t_act) == 0) { + /* get_sysmem returns bytes */ + fprintf(cp_out, "Total DRAM available = "); + fprintmem(cp_out, mem_t_act.size_m); + fprintf(cp_out, ".\n"); + + fprintf(cp_out, "DRAM currently available = "); + fprintmem(cp_out, mem_t_act.free_m); + fprintf(cp_out, ".\n\n"); + } + else { + fprintf(cp_err, "Memory info is unavailable! \n"); + } + } + + return; +} /* end of function com_sysinfo */ + + + +/* This function frees the buffers used to store system allocation strings */ +static void free_static_system_info(void) +{ + tfree(system_info.cpuModelName); + tfree(system_info.osName); +} /* end of fuction free_system_info */ + /* Print to stream the given memory size in a human friendly format */ -static void -fprintmem(FILE *stream, unsigned long long memory) +static void fprintmem(FILE *stream, unsigned long long memory) { - if (memory > 1048576) - fprintf(stream, "%8.6f MB", (double)memory /1048576.); - else if (memory > 1024) - fprintf(stream, "%5.3f kB", (double)memory / 1024.); - else - fprintf(stream, "%u bytes", (unsigned)memory); -} - - -static void -tesFreeSystemInfo(TesSystemInfo *info) -{ - if (info != NULL) { - tfree(info->cpuModelName); - tfree(info->osName); + if (memory > 1048576) { + fprintf(stream, "%8.6f MB", (double) memory /1048576.); } -} - - -/* print system info */ -void -com_sysinfo(wordlist *wl) -{ - int errorcode; - TesSystemInfo *info; - - NG_IGNORE(wl); - - info = TMALLOC(TesSystemInfo, 1); - - errorcode = tesCreateSystemInfo(info); - if (errorcode) { - fprintf(cp_err, "No system info available! \n"); - } else { - fprintf(cp_out, "\nOS: %s\n", info->osName); - fprintf(cp_out, "CPU: %s\n", info->cpuModelName); - if (info->numPhysicalProcessors > 0) - fprintf(cp_out, "Physical processors: %u, ", info->numPhysicalProcessors); - fprintf(cp_out, "Logical processors: %u\n", - info->numLogicalProcessors); + else if (memory > 1024) { + fprintf(stream, "%5.3f kB", (double) memory / 1024.); } -#if defined(HAVE_WIN32) || defined(HAVE__PROC_MEMINFO) - - get_sysmem(&mem_t_act); - - /* get_sysmem returns bytes */ - fprintf(cp_out, "Total DRAM available = "); - fprintmem(cp_out, mem_t_act.size_m); - fprintf(cp_out, ".\n"); - - fprintf(cp_out, "DRAM currently available = "); - fprintmem(cp_out, mem_t_act.free_m); - fprintf(cp_out, ".\n\n"); - -#endif - - tesFreeSystemInfo(info); - tfree(info); -} + else { + fprintf(stream, "%u bytes", (unsigned) memory); + } +} /* end of funtion fprintmem */ + +/*** Get processor and memory information as appropriate for the system ***/ #ifdef HAVE__PROC_MEMINFO /* Get memory information */ -static int -get_sysmem(struct sys_memory *memall) +static int get_sysmem(struct sys_memory *memall) { FILE *fp; char buffer[2048]; @@ -139,51 +181,51 @@ get_sysmem(struct sys_memory *memall) if ((fp = fopen("/proc/meminfo", "r")) == NULL) { perror("fopen(\"/proc/meminfo\")"); - return 0; + return -1; } bytes_read = fread(buffer, 1, sizeof(buffer), fp); fclose(fp); if (bytes_read == 0 || bytes_read == sizeof(buffer)) - return 0; + return -1; buffer[bytes_read] = '\0'; /* Search for string "MemTotal" */ match = strstr(buffer, "MemTotal"); if (match == NULL) /* not found */ - return 0; + return -1; sscanf(match, "MemTotal: %ld", &mem_got); memall->size_m = mem_got*1024; /* 1MB = 1024KB */ /* Search for string "MemFree" */ match = strstr(buffer, "MemFree"); if (match == NULL) /* not found */ - return 0; + return -1; sscanf(match, "MemFree: %ld", &mem_got); memall->free_m = mem_got*1024; /* 1MB = 1024KB */ /* Search for string "SwapTotal" */ match = strstr(buffer, "SwapTotal"); if (match == NULL) /* not found */ - return 0; + return -1; sscanf(match, "SwapTotal: %ld", &mem_got); memall->swap_t = mem_got*1024; /* 1MB = 1024KB */ /* Search for string "SwapFree" */ match = strstr(buffer, "SwapFree"); if (match == NULL) /* not found */ - return 0; + return -1; sscanf(match, "SwapFree: %ld", &mem_got); memall->swap_f = mem_got*1024; /* 1MB = 1024KB */ - return 1; + return 0; } /* Return length of first line in a string */ -static size_t -getLineLength(const char *str) +static inline size_t getLineLength(const char *str) { const char *p = str; - while (*p && (*p != '\n')) + while (*p && (*p != '\n')) { p++; + } return (size_t) (p - str); } @@ -191,8 +233,7 @@ getLineLength(const char *str) /* Checks if number 'match' is found in a vector 'set' of size 'size' Returns 1 if yes, otherwise, 0 */ -static tInt -searchInSet(const tInt *set, unsigned size, tInt match) +static int searchInSet(const int *set, unsigned size, int match) { unsigned index; for (index = 0; index < size; index++) @@ -203,17 +244,14 @@ searchInSet(const tInt *set, unsigned size, tInt match) /* Get system information */ -TesError -tesCreateSystemInfo(TesSystemInfo *info) +static void set_static_system_info(void) { FILE *file; - TesError error = TES_SUCCESS; - if (info == NULL) - return TES_INVALID_PARAMS; - info->cpuModelName = NULL; - info->osName = NULL; - info->numLogicalProcessors = info->numPhysicalProcessors = 0; + /* Init to all information unailable */ + system_info.cpuModelName = (char *) NULL; + system_info.osName = (char *) NULL; + system_info.numLogicalProcessors = system_info.numPhysicalProcessors = 0; /* get kernel version string */ file = fopen("/proc/version", "rb"); @@ -221,18 +259,18 @@ tesCreateSystemInfo(TesSystemInfo *info) size_t size; /* read bytes and find end of file */ - for (size = 0; ; size++) - if (EOF == fgetc(file)) + for (size = 0; ; size++) { + if (EOF == fgetc(file)) { break; + } + } - info->osName = TMALLOC(char, size); + system_info.osName = TMALLOC(char, size + 1); rewind(file); - fread(info->osName, sizeof(char), size, file); + fread(system_info.osName, sizeof(char), size, file); fclose(file); - info->osName[size-1] = '\0'; - } else { - error = TES_FAIL; + system_info.osName[size] = '\0'; } /* get cpu information */ @@ -242,9 +280,11 @@ tesCreateSystemInfo(TesSystemInfo *info) char *inStr; /* read bytes and find end of file */ - for (size = 0; ; size++) - if (EOF == fgetc(file)) + for (size = 0; ; size++) { + if (EOF == fgetc(file)) { break; + } + } /* get complete string */ inStr = TMALLOC(char, size+1); @@ -265,23 +305,20 @@ tesCreateSystemInfo(TesSystemInfo *info) if (numToEOL > 2) { /* skip ": "*/ numToEOL -= 2; - info->cpuModelName = TMALLOC(char, numToEOL+1); - memcpy(info->cpuModelName, modelPtr+2, numToEOL); - info->cpuModelName[numToEOL] = '\0'; + system_info.cpuModelName = TMALLOC(char, numToEOL+1); + memcpy(system_info.cpuModelName, modelPtr+2, numToEOL); + system_info.cpuModelName[numToEOL] = '\0'; } - } else { - error = TES_FAIL; } - } else { - error = TES_FAIL; } } + { const char *matchStrProc = "processor"; const char *matchStrPhys = "physical id"; char *strPtr = inStr; unsigned numProcs = 0; - tInt *physIDs; + int *physIDs; /* get number of logical processors */ while ((strPtr = strstr(strPtr, matchStrProc)) != NULL) { @@ -289,8 +326,8 @@ tesCreateSystemInfo(TesSystemInfo *info) strPtr += strlen(matchStrProc); if (isblank_c(*strPtr)) numProcs++; } - info->numLogicalProcessors = numProcs; - physIDs = TMALLOC(tInt, numProcs); + system_info.numLogicalProcessors = numProcs; + physIDs = TMALLOC(int, numProcs); /* get number of physical CPUs */ numProcs = 0; @@ -303,7 +340,7 @@ tesCreateSystemInfo(TesSystemInfo *info) /* go to ';' */ strPtr = strchr(strPtr, ':'); if (strPtr != NULL) { - tInt buffer = 0; + int buffer = 0; /* skip ": " */ strPtr += 2; /* get number */ @@ -314,21 +351,21 @@ tesCreateSystemInfo(TesSystemInfo *info) physIDs[numProcs] = buffer; numProcs++; } - } else { - // error = TES_FAIL; + } + else { break; } - } else { - // error = TES_FAIL; + } + else { break; } } - info->numPhysicalProcessors = numProcs; + system_info.numPhysicalProcessors = numProcs; tfree(physIDs); } /* another test to get number of logical processors - * if (info->numLogicalProcessors == 0) { + * if (system_info.numLogicalProcessors == 0) { * char *token; * char *cpustr = copy(inStr); * while (cpustr && !*cpustr) @@ -337,30 +374,31 @@ tesCreateSystemInfo(TesSystemInfo *info) * token = gettok(&cpustr); * } * - * info->numLogicalProcessors = atoi(token) + 1; + * system_info.numLogicalProcessors = atoi(token) + 1; * tfree(cpustr); * } */ tfree(inStr); fclose(file); - } else { - error = TES_FAIL; - } + } /* end of case that file was opened OK */ - return error; -} + return; +} /* end of function set_static_system_info */ -#elif defined(HAVE_WIN32) -/* get memory information */ -static int -get_sysmem(struct sys_memory *memall) + +#elif defined(_WIN32) + +/* Get memory information */ +static int get_sysmem(struct sys_memory *memall) { -#if (_WIN32_WINNT >= 0x0500) +#if (_WIN32_WINNT >= 0x0500) /* Windows 2000+ */ MEMORYSTATUSEX ms; ms.dwLength = sizeof(MEMORYSTATUSEX); - GlobalMemoryStatusEx(&ms); + if (GlobalMemoryStatusEx(&ms) == FALSE) { + return -1; + } memall->size_m = ms.ullTotalPhys; memall->free_m = ms.ullAvailPhys; memall->swap_t = ms.ullTotalPageFile; @@ -368,119 +406,575 @@ get_sysmem(struct sys_memory *memall) #else MEMORYSTATUS ms; ms.dwLength = sizeof(MEMORYSTATUS); - GlobalMemoryStatus(&ms); + if (GlobalMemoryStatus(&ms) == FALSE) { + return -1; + } memall->size_m = ms.dwTotalPhys; memall->free_m = ms.dwAvailPhys; memall->swap_t = ms.dwTotalPageFile; memall->swap_f = ms.dwAvailPageFile; #endif - return 1; -} + return 0; +} /* end of function get_sysmem */ -/* get system information */ -TesError -tesCreateSystemInfo(TesSystemInfo *info) + +/* This function gets system information about the version of Windows and + * the number processors available, and save this information in the static + * TesSystemInfo structure. If an item cannot be obtained, it is set to + * 0/NULL. This allows callers to check for valid data since neither of these + * values are valid */ +static void set_static_system_info(void) { - char *procStr, *freeStr; - char mversion[32]; - DWORD dwLen = 0; - HKEY hkBaseCPU; - LONG lResult; + get_processor_name(); /* name of processor */ + get_os_info(); /* name of OS with build and service pack, if any */ + get_logical_processor_count(); /* Get number of logical cores */ + get_physical_processor_count(); /* # hardware components */ + return; +} /* end of function set_static_system_info */ - SYSTEM_INFO sysinfo; - GetSystemInfo(&sysinfo); - info->numPhysicalProcessors = 0; - info->numLogicalProcessors = sysinfo.dwNumberOfProcessors; - info->osName = NULL; - mversion[0] = '\0'; - if (IsWindowsXPOrGreater()) { - strcpy(mversion, "Windows XP"); - } - - if (IsWindowsXPSP1OrGreater()) { - strcpy(mversion, "Windows XP Service Pack 1"); - } - - if (IsWindowsXPSP2OrGreater()) { - strcpy(mversion, "Windows XP Service Pack 2"); - } - - if (IsWindowsXPSP3OrGreater()) { - strcpy(mversion, "Windows XP Service Pack 3"); - } - - if (IsWindowsVistaOrGreater()) { - strcpy(mversion, "Windows Vista"); - } - - if (IsWindowsVistaSP1OrGreater()) { - strcpy(mversion, "Windows Vista Service Pack 1"); - } - - if (IsWindowsVistaSP2OrGreater()) { - strcpy(mversion, "Windows Vista Service Pack 2"); - } - - if (IsWindows7OrGreater()) { - strcpy(mversion, "Windows 7"); - } - - if (IsWindows7SP1OrGreater()) { - strcpy(mversion, "Windows 7 Service Pack 1"); - } - - if (IsWindows8OrGreater()) { - strcpy(mversion, "Windows 8"); - } - if (IsWindows8Point1OrGreater()) { - strcpy(mversion, "Windows 8.1"); - } - /* SDK has version 10 and thus IsWindows10OrGreater() */ -#if (VER_PRODUCTBUILD > 10000) - /* This will deliver the right OS version only if we have a manifest */ - if (IsWindows10OrGreater()) { - strcpy(mversion, "Windows 10"); - } -#endif - if (IsWindowsServer()) { - printf("Server\n"); - } - - if (mversion[0] != '\0') - info->osName = tprintf("%s", mversion); - - lResult = RegOpenKeyExA(HKEY_LOCAL_MACHINE, - "HARDWARE\\DESCRIPTION\\System\\CentralProcessor\\0", - 0, KEY_READ, &hkBaseCPU); - if (lResult != ERROR_SUCCESS) { - info->cpuModelName = NULL; - return TES_FAIL; - } - - RegQueryValueExA(hkBaseCPU, "ProcessorNameString", 0, 0, NULL, &dwLen); - freeStr = procStr = TMALLOC(char, dwLen + 1); - RegQueryValueExA(hkBaseCPU, "ProcessorNameString", 0, 0, (LPBYTE)procStr, &dwLen); - procStr[dwLen] = '\0'; - while (*procStr == ' ') - procStr++; - info->cpuModelName = copy(procStr); - tfree(freeStr); - - RegCloseKey(hkBaseCPU); - - return TES_SUCCESS; -} - -#else - -/* no Windows OS, no proc info file system */ -TesError -tesCreateSystemInfo(TesSystemInfo *info) +/* Copy data at HKLM/sz_subkey/sz_val_name to an allocated buffer that is + * 1 byte longer and always null-termianted, possibly with 2 nulls + * + * Parameters + * sz_subkey: Subkey string + * sz_val_name: Name of value to get + * p_ds: Address of dstring to receive data + * + * Return codes + * 0: Data obtained OK + * -1: Data not obtained. + */ +static int registry_value_to_ds(const char *sz_subkey, + const char *sz_val_name, DSTRING *p_ds) { - return 1; -} + int xrc = 0; + DWORD n_byte_data = 0; + HKEY hk; + bool f_key_open = FALSE; + + /* Opwn the key with the processor details */ + { + DWORD rc; + if ((rc = RegOpenKeyExA(HKEY_LOCAL_MACHINE, + sz_subkey, 0, KEY_READ, &hk)) != ERROR_SUCCESS) { + fprintf(cp_err, + "Unable to open key for registry data \"%s\". " + "System code = %lu\n", + sz_subkey, rc); + xrc = -1; + goto EXITPOINT; + } + } + f_key_open = TRUE; + + /* Get size of the name string. Strings in the registry need not be + * null-terminated, but if they are, the null is included in the + * size. */ + { + DWORD rc; + if ((rc = RegQueryValueExA(hk, sz_val_name, + 0, 0, NULL, &n_byte_data)) != ERROR_SUCCESS) { + fprintf(cp_err, + "Unable to get the size of value for \"%s\". " + "System code = %lu\n", + sz_val_name, rc); + xrc = -1; + goto EXITPOINT; + } + } + + /* Ensure dstring buffer is large enough for the data + 1 byte to add + * a null to the end */ + { + size_t n_byte_reserve = (size_t) n_byte_data + 1; + if (ds_reserve(p_ds, n_byte_reserve) != 0) { + (void) fprintf(cp_err, + "Unable to reserve a buffer of %u bytes for data.\n", + n_byte_reserve); + xrc = -1; + goto EXITPOINT; + } + } + + /* Retrieve the value using the dstring buffer to receive it */ + { + DWORD rc; + char *p_buf = ds_get_buf(p_ds); + if ((rc = RegQueryValueExA(hk, sz_val_name, 0, 0, + (LPBYTE) p_buf, &n_byte_data)) != ERROR_SUCCESS) { + (void) fprintf(cp_err, + "Unable to get the value for \"%s\". " + "System code = %lu\n", + sz_val_name, rc); + xrc = -1; + goto EXITPOINT; + } + } + + /* Set the dstring length */ + (void) ds_set_length(p_ds, n_byte_data); + +EXITPOINT: + /* Indicate error if failure */ + if (xrc != 0) { + ds_clear(p_ds); + } + + if (f_key_open) { /* close key if opened */ + RegCloseKey(hk); + } + + return xrc; +} /* end of function registry_value_to_ds */ + + + +/* Gets the name of the processor from the registry and sets field + * cpuModelName in system_info. On failure, the field is set to NULL */ +static void get_processor_name(void) +{ + DS_CREATE(ds, 200); + + system_info.cpuModelName = NULL; /* init in case of failure */ + if (registry_value_to_ds( + "HARDWARE\\DESCRIPTION\\System\\CentralProcessor\\0", + "ProcessorNameString", + &ds) != 0) { + (void) fprintf(cp_err, + "Unable to get processor name data from the registry.\n"); + return; + } + + /* Step past any leading blanks and copy name to cpuModelName */ + { + const char *proc_name = ds_get_buf(&ds); + + while (*proc_name == ' ') { + ++proc_name; + } /* end of loop finding first non-blank of processor name */ + + /* Make a copy of the string at cpuModelName field of system_info */ + system_info.cpuModelName = copy(proc_name); + } + + ds_free(&ds); /* Free resources */ + + return; +} /* end of function get_processor_name */ + + + +/* This function gets the release details to distinguish between + * 2016 and 2019 servers. If necessary, it can be extended to return + * codes for other servers in the future. + * + * See + * https://techcommunity.microsoft.com/t5/Windows-Server-Insiders/Windows-Server-2019-version-info/m-p/234472 + * + * Return codes + * -1: Failure + * +1: 2016 server + * +2: 2019 server (probably) + * + * Remarks + * Calling this function alone is not sufficient to identify a server. + * Rather it should be called given that a server OS is present to identify + * the serer version. + */ +static int get_server_id(void) +{ + DS_CREATE(ds, 25); + + if (registry_value_to_ds( + "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion", + "ReleaseId", + &ds) != 0) { + (void) fprintf(cp_err, + "Unable to get release ID data from the registry.\n"); + return -1; + } + + int id_code = -1; /* Set to failure until found */ + /* Convert the release ID to a number */ + { + char *p_end; + errno = 0; + const char *p_buf = ds_get_buf(&ds); + unsigned long id_val = strtoul(p_buf, &p_end, 10); + if (errno || *p_end != '\0') { + fprintf(cp_err, + "Unable to convert \"%s\" to a release ID number.\n", + p_buf); + goto EXITPOINT; + } + + if (id_val == 1607ul) { /* code for 2016 server */ + id_code = 1; + } + else if (id_code > 1607ul) { /* Probably 2019 server */ + id_code = 2; + } + /* Else unknown ID */ + } + +EXITPOINT: + ds_free(&ds); /* Free resources */ + + return id_code; +} /* end of function get_server_id */ + + + +/* This function creates a name of the form ' ' , + * allocates a buffer for it, and stores it in system_info.osNname. On + * failure an error is reported and the string is set to NULL. + * + * Remarks + * Getting the version has been complicated greatly in later versions of + * Windows. A good discussion of the issue can be found at + * https://stackoverflow.com/questions/47581146/getting-os-build-version-from-win32-api-c + * + * First, the function GetVersionEx() has been deprecated, so the + * straightforward call to retrieve the version is not the recommended + * approach any longer and will output a message to this effect during + * compilation. Also, it may be removed at some later time. Even if it is + * called, since Windows 8.0, the value returned depends not on the version + * of the OS, but the manifested version of the calling program. + * + * As an alternative function RtlGetVersion() always returns version info + * the same version as GetVersionEx() prior to Windows 8.1, and it + * is not deprecated. Unfortunately, the simple solution is made less + * simple because the header providing a prototype for RtlGetVersion() + * is part of the Windows DDK and the function is not directly exposed + * by ntdll.lib. Also, the DDK only works with UTF-16, so the name string + * must be converted. + * + * The following link has a table showing how to determine the all operating + * systems from Windows 2000 through Windows 10/Windows Server 2016. + * https://web.archive.org/web/20190501082653/https://docs.microsoft.com/en-us/windows/desktop/api/winnt/ns-winnt-_osversioninfoexa + * + * OS ver Other OSV=OSVERSIONINFOEX + * Windows Server 2016 10.0 OSV.wProductType != VER_NT_WORKSTATION + * Windows 10 10.0 OSV.wProductType == VER_NT_WORKSTATION + * Windows Server 2008 6.0 OSV.wProductType != VER_NT_WORKSTATION + * Windows Vista 6.0 OSV.wProductType == VER_NT_WORKSTATION + * Windows Server 2008 R2 6.1 OSV.wProductType != VER_NT_WORKSTATION + * Windows 7 6.1 OSV.wProductType == VER_NT_WORKSTATION + * Windows Server 2012 6.2 OSV.wProductType != VER_NT_WORKSTATION + * Windows 8 6.2 OSV.wProductType == VER_NT_WORKSTATION + * Windows Server 2012 R2 6.3 OSV.wProductType != VER_NT_WORKSTATION + * Windows 8.1 6.3 OSV.wProductType == VER_NT_WORKSTATION + * Windows 2000 5.0 Not applicable + * Windows XP 5.1 Not applicable + * Windows Home Server 5.2 OSV.wSuiteMask & VER_SUITE_WH_SERVER + * Windows XP Professional + * x64 Edition 5.2 (OSV.wProductType == VER_NT_WORKSTATION) && + * (SYSTEM_INFO.wProcessorArchitecture == + * PROESSOR_ARCHITECTURE_AMD64) + * Windows Server 2003 5.2 GetSystemMetrics(SM_SERVERR2) == 0 + * Windows Server 2003 R2 5.2 GetSystemMetrics(SM_SERVERR2) != 0 + + * Information on distinguishing between Windows Server 2016 and 2019 does + * not appear to have been provided as of early 2019: + * https://stackoverflow.com/questions/53393150/c-how-to-detect-windows-server-2019 + * Hopefully this issue will be resolved in the future. + */ +static void get_os_info(void) +{ + OSVERSIONINFOEXW ver_info; + + /* the name of the OS. Init to prevent compiler warning */ + const char *sz_os_name = NULL; + + /* Load library containing RtlGetVersion() */ + HMODULE lib = LoadLibraryExW(L"ntdll.dll", NULL, 0); + if (lib == (HMODULE) NULL) { /* Not loaded OK */ + (void) fprintf(cp_err, + "Unable to load ntdll.dll. " + "System code = %lu\n", + (unsigned long) GetLastError()); + system_info.osName = (char *) NULL; + return; + } + + /* Locate RtlGetVersion() */ + FARPROC p_get_ver = GetProcAddress(lib, "RtlGetVersion"); + if (p_get_ver == (FARPROC) NULL) { /* Did not get function addr OK */ + (void) fprintf(cp_err, + "Unable to locate function RtlGetVersion. " + "System code = %lu\n", + (unsigned long) GetLastError()); + system_info.osName = (char *) NULL; + return; + } + + /* Get version info. RtlGetVersion cannot fail. */ + ver_info.dwOSVersionInfoSize = sizeof(ver_info); + (void) ((DWORD (WINAPI *)(OSVERSIONINFOEXW *)) p_get_ver)( + &ver_info); + + switch (ver_info.dwMajorVersion) { + case 10: { + static const char OS_srvr[] = "Windows Server 2016/2019/other"; + static const char OS_10[] = "Windows 10"; + static const char OS_2016[] = "Windows Server 2016"; + static const char OS_2019[] = "Windows Server 2019"; + static const char * const p_str[] = { + OS_srvr, OS_10, OS_2016, OS_2019 + }; + + if (ver_info.dwMinorVersion != 0) { /* only know 10.0 */ + system_info.osName = (char *) NULL; + return; + } + sz_os_name = p_str[ver_info.wProductType == VER_NT_WORKSTATION ? + 1 : get_server_id() + 1]; + break; + } + case 6: { + static const char OS_2008[] = "Windows Server 2008"; + static const char OS_vista[] = "Windows Vista"; + static const char OS_2008R2[] = "Windows Server 2008 R2"; + static const char OS_7[] = "Windows 7"; + static const char OS_2012[] = "Windows Server 2012"; + static const char OS_8[] = "Windows 8"; + static const char OS_2012R2[] = "Windows Server 2012 R2"; + static const char OS_8_1[] = "Windows 8.1"; + static const char * const p_str[] = { + OS_2008, OS_vista, + OS_2008R2, OS_7, + OS_2012, OS_8, + OS_2012R2, OS_8_1 + }; + if (ver_info.dwMinorVersion > 3) { /* know 6.0 through 6.3 */ + (void) fprintf(cp_err, "Unknown Windows version 6.%lu. ", + (unsigned long) ver_info.dwMinorVersion); + system_info.osName = (char *) NULL; + return; + } + sz_os_name = p_str[2 * ver_info.dwMinorVersion + + ver_info.wProductType == VER_NT_WORKSTATION]; + break; + } + case 5: { /* an assortment of other conditions must be checked */ + + switch (ver_info.dwMinorVersion) { /* filter by minor verson */ + case 0: { + static const char OS_2k[] = "Windows 2000"; + sz_os_name = OS_2k; + break; + } + case 1: { + static const char OS_xp[] = "Windows XP"; + sz_os_name = OS_xp; + break; + } + case 2: + if (ver_info.wSuiteMask & VER_SUITE_WH_SERVER) { + static const char OS_home_server[] = "Windows Home Server"; + sz_os_name = OS_home_server; + } + else if (ver_info.wProductType == VER_NT_WORKSTATION) { + SYSTEM_INFO si; + GetSystemInfo(&si); + if (si.wProcessorArchitecture == + PROCESSOR_ARCHITECTURE_AMD64) { + static const char OS_xp64[] = + "Windows XP Professional x64 Edition"; + sz_os_name = OS_xp64; + } + } + else { /* Server 2003 or 2003 R2 */ + static const char OS_2003R2[] = "Windows Server 2003 R2"; + static const char OS_2003[] = "Windows Server 2003"; + static const char * const p_str[] = {OS_2003R2, OS_2003}; + sz_os_name = p_str[!GetSystemMetrics(SM_SERVERR2)]; + } + break; + default: + (void) fprintf(cp_err, "Unknown Windows version 5.%lu. ", + (unsigned long) ver_info.dwMinorVersion); + system_info.osName = (char *) NULL; + return; + } /* end of switch over minor version for major version 5 */ + break; + } + case 4: + switch (ver_info.dwMinorVersion) { + case 0: { + static const char OS_95[] = "Windows 95"; + static const char OS_nt4[] = "Windows NT 4.0"; + static const char * const p_str[] = {OS_95, OS_nt4}; + sz_os_name = p_str[ver_info.wProductType == VER_NT_WORKSTATION]; + } + case 10: { + static const char OS_98[] = "Windows 98"; + sz_os_name = OS_98; + break; + } + case 90: { + static const char OS_me[] = "Windows ME"; + sz_os_name = OS_me; + break; + } + default: + (void) fprintf(cp_err, "Unknown Windows version 4.%lu. ", + (unsigned long) ver_info.dwMinorVersion); + system_info.osName = (char *) NULL; + return; + } /* end of switch over minor version for major version 4 */ + default: + (void) fprintf(cp_err, "Unknown Windows version %lu.%lu. ", + (unsigned long) ver_info.dwMajorVersion, + (unsigned long) ver_info.dwMinorVersion); + system_info.osName = (char *) NULL; + return; + }/* end of switch over major version */ + + /* Have the base version name. Now must add service pack, if any */ + if (ver_info.wServicePackMajor == 0) { /* no service pack */ + system_info.osName = tprintf("%s, Build %lu", + sz_os_name, (unsigned long) ver_info.dwBuildNumber); + } + else if (ver_info.wServicePackMinor == 0) { /* major # only */ + system_info.osName = tprintf("%s, Build %lu, Service Pack %u", + sz_os_name, (unsigned long) ver_info.dwBuildNumber, + (unsigned) ver_info.wServicePackMajor); + } + else { /* service pack has major and minor versions */ + system_info.osName = tprintf("%s, Build %lu, Service Pack %u.%u", + sz_os_name, (unsigned long) ver_info.dwBuildNumber, + (unsigned) ver_info.wServicePackMajor, + (unsigned) ver_info.wServicePackMinor); + } + + return; +} /* end of function get_os_info */ + + + +/* This function sets the number of processors field in system_info */ + static inline void get_logical_processor_count(void) + { + SYSTEM_INFO si; + GetSystemInfo(&si); + system_info.numLogicalProcessors = si.dwNumberOfProcessors; +} /* end of function get_logical_processor_count */ + + + + /* This funtion sets the field storing the number of physical processors + * in system_info */ +typedef BOOL (WINAPI *glp_t)(LOGICAL_PROCESSOR_RELATIONSHIP, + PSYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX, PDWORD); +static void get_physical_processor_count(void) +{ + DWORD n_byte_buf = 0; + system_info.numPhysicalProcessors = 0; /* Init to 0 until found */ + + /* Get a handle to the DLL with the required function. Since the + * functdion GetModuleHandleW() is in kernel32.dll, it is safe to + * assume that kernel32.dll is already loaded. Not using + * LoadLibraryExW() simplifies error handling. */ + HMODULE lib = GetModuleHandleW(L"kernel32.dll"); + if (lib == (HMODULE) NULL) { /* Handle not obtained */ + (void) fprintf(cp_err, + "Unable to obtain a handle to kernel32.dll. " + "System code = %lu\n", + (unsigned long) GetLastError()); + return; + } + + /* Locate GetLogicalProcessorInformationEx(). It must be + * dynamically loaded since it is only present in + * Windows 7/Server 2008 R2 and later OS versions */ + FARPROC p_glp = GetProcAddress(lib, + "GetLogicalProcessorInformationEx"); + if (p_glp == (FARPROC) NULL) { /* Did not get function addr OK */ + (void) fprintf(cp_err, + "Unable to locate function " + "GetLogicalProcessorInformationEx. " + "System code = %lu\n", + (unsigned long) GetLastError()); + return; + } + + /* Find requried size. Should return FALSE/ERROR_INSUFFICIENT_BUFFER if + * working properly */ + if (((glp_t) (*p_glp))(RelationProcessorPackage, + NULL, &n_byte_buf) != 0) { + fprintf(cp_err, + "Unexpected error getting logical processor buffer size.\n"); + return; + } + + { + DWORD rc; + if ((rc = GetLastError()) != ERROR_INSUFFICIENT_BUFFER) { + fprintf(cp_err, + "Unable to get the logical processor bufer size. " + "System code = %lu.\n", + (unsigned long) rc); + return; + } + } + + /* Allocate buffer to get the info */ + SYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX * const buf = + (SYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX *) malloc(n_byte_buf); + if (buf == (SYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX *) NULL) { + fprintf(cp_err, + "Unable to allocate a buffer of %lu bytes " + "for logical processor information.\n", + n_byte_buf); + return; + } + + /* Try again with a buffer and the size obtained before */ + { + DWORD rc; + if ((rc = ((glp_t) (*p_glp))(RelationProcessorPackage, + buf, &n_byte_buf)) == 0) { + fprintf(cp_err, + "Unable to get the logical processor info. " + "System code = %lu.\n", + (unsigned long) rc); + return; + } + } + + /* Count the number of processor packages */ + { + SYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX * p_buf_cur = buf; + SYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX * const p_buf_end = + (SYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX *) + ((char *) buf + n_byte_buf); + unsigned int n_processor_package = 0; + for ( ; p_buf_cur < p_buf_end; + p_buf_cur = (SYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX *) + ((char *) p_buf_cur + p_buf_cur->Size)) { + ++n_processor_package; + } + system_info.numPhysicalProcessors = n_processor_package; + } + + return; +} /* end of function get_physical_processor_count */ + + + +#else /* no Windows OS, no proc info file system */ +void set_static_system_info(void) +{ + /* Set to no data available */ + system_info.osName = (char *) NULL; + system_info.cpuModelName = (char *) NULL; + system_info.numPhysicalProcessors = 0; + system_info.numLogicalProcessors = 0; + return; +} /* end of function set_static_system_info */ #endif