Stroika Library 3.0d16
 
Loading...
Searching...
No Matches
SystemConfiguration.cpp
1/*
2 * Copyright(c) Sophist Solutions, Inc. 1990-2025. All rights reserved
3 */
4#include "Stroika/Foundation/StroikaPreComp.h"
5
6#include <climits>
7#include <filesystem>
8#include <thread>
9
10#if qStroika_Foundation_Common_Platform_POSIX
11#include <fstream>
12#include <unistd.h>
13#if qStroika_Foundation_Common_Platform_Linux
14#include <sys/sysinfo.h>
15#endif
16#include <utmpx.h>
17#elif qStroika_Foundation_Common_Platform_Windows
18#include <Windows.h>
19
20#include <VersionHelpers.h>
21#include <intrin.h>
22#endif
23
24#include "Stroika/Foundation/Characters/FloatConversion.h"
26#include "Stroika/Foundation/Characters/SDKString.h"
27#include "Stroika/Foundation/Characters/String2Int.h"
30#if qStroika_Foundation_Common_Platform_Windows
31#include "Stroika/Foundation/Common/Platform/Windows/Registry.h"
32#endif
33#include "Stroika/Foundation/Containers/Sequence.h"
34#include "Stroika/Foundation/Containers/Set.h"
35#include "Stroika/Foundation/Execution/Exceptions.h"
36#if qStroika_Foundation_Common_Platform_Windows
37#include "Stroika/Foundation/Execution/Platform/Windows/Exception.h"
38#endif
43
44#include "SystemConfiguration.h"
45
46#if qStroika_Foundation_Common_Platform_POSIX
48#include "Stroika/Foundation/Execution/ProcessRunner.h"
49#include "Stroika/Foundation/Streams/iostream/FStreamSupport.h"
50#endif
51
52using namespace Stroika::Foundation;
54using namespace Stroika::Foundation::Common;
56using namespace Stroika::Foundation::Streams;
57using namespace Stroika::Foundation::Time;
58
60
61// Comment this in to turn on aggressive noisy DbgTrace in this module
62//#define USE_NOISY_TRACE_IN_THIS_MODULE_ 1
63
64/*
65 ********************************************************************************
66 ***************** SystemConfiguration::BootInformation *************************
67 ********************************************************************************
68 */
69String SystemConfiguration::BootInformation::ToString () const
70{
72 sb << "{"sv;
73 sb << "Booted-At: "sv << fBootedAt;
74 sb << "}"sv;
75 return sb;
76};
77
78/*
79 ********************************************************************************
80 ***************** SystemConfiguration::CPU::CoreDetails ************************
81 ********************************************************************************
82 */
83
85{
87 sb << "{"sv;
88 sb << "Socket-ID: "sv << fSocketID;
89 sb << ", Model-Name: "sv << fModelName;
90 sb << "}"sv;
91 return sb;
92}
93
94/*
95 ********************************************************************************
96 ************************** SystemConfiguration::CPU ****************************
97 ********************************************************************************
98 */
99String SystemConfiguration::CPU::ToString () const
100{
101 StringBuilder sb;
102 sb << "{"sv;
103 sb << "Cores: "sv << fCores;
104 sb << "}"sv;
105 return sb;
106};
107
108/*
109 ********************************************************************************
110 ************************** SystemConfiguration::Memory *************************
111 ********************************************************************************
112 */
113String SystemConfiguration::Memory::ToString () const
114{
115 StringBuilder sb;
116 sb << "{"sv;
117 sb << "Page-Size: "sv << fPageSize;
118 sb << ", Total-Physical-RAM: "sv << fTotalPhysicalRAM;
119 sb << ", Total-Virtual-RAM: "sv << fTotalVirtualRAM;
120 sb << "}"sv;
121 return sb;
122};
123
124/*
125 ********************************************************************************
126 ********************** SystemConfiguration::ComputerNames **********************
127 ********************************************************************************
128 */
130{
131 StringBuilder sb;
132 sb << "{"sv;
133 sb << "Hostname: "sv << fHostname;
134 sb << "}"sv;
135 return sb;
136};
137
138/*
139 ********************************************************************************
140 ********************* SystemConfiguration::OperatingSystem *********************
141 ********************************************************************************
142 */
143String SystemConfiguration::OperatingSystem::ToString () const
144{
145 StringBuilder sb;
146 sb << "{"sv;
147 sb << "Token-Name: "sv + fTokenName;
148 sb << ", Short-Pretty-Name: "sv + fShortPrettyName;
149 sb << ", Pretty-Name-With-Major-Version: "sv + fPrettyNameWithMajorVersion;
150 sb << ", Pretty-Name-With-Details: "sv + fPrettyNameWithVersionDetails;
151 sb << ", Major-Minor-Version-String: "sv + fMajorMinorVersionString;
152 sb << ", RFC1945-Compat-Product-Token-With-Version: "sv + fRFC1945CompatProductTokenWithVersion;
153 sb << ", Bits: "sv << fBits;
154 if (fPreferredInstallerTechnology) {
155 sb << ", Preferred-Installer-Technology: "sv << *fPreferredInstallerTechnology;
156 }
157 sb << "}"sv;
158 return sb;
159};
160
161/*
162 ********************************************************************************
163 ******************************* SystemConfiguration ****************************
164 ********************************************************************************
165 */
166String SystemConfiguration::ToString () const
167{
168 StringBuilder sb;
169 sb << "{"sv;
170 sb << "Boot-Information: " << fBootInformation;
171 sb << ", CPU: "sv << fCPU;
172 sb << ", Memory: "sv << fMemory;
173 sb << ", Actual-Operating-System: "sv << fActualOperatingSystem;
174 sb << ", Apparent-Operating-System: "sv << fApparentOperatingSystem;
175 sb << ", Computer-Names: "sv << fComputerNames;
176 sb << "}"sv;
177 return sb;
178};
179
180/*
181 ********************************************************************************
182 ***************** Configuration::SystemConfiguration::CPU **********************
183 ********************************************************************************
184 */
185unsigned int SystemConfiguration::CPU::GetNumberOfSockets () const
186{
187 Set<unsigned int> socketIds;
188 for (const auto& i : fCores) {
189 socketIds.Add (i.fSocketID);
190 }
191 return static_cast<unsigned int> (socketIds.size ());
192}
193
194/*
195 ********************************************************************************
196 ****************** Common::GetSystemConfiguration_BootInformation **************
197 ********************************************************************************
198 */
199SystemConfiguration::BootInformation Common::GetSystemConfiguration_BootInformation ()
200{
201 // nb: this cannot change after app start, so cache it
202 static const SystemConfiguration::BootInformation kCachedResult_ = [] () {
203 SystemConfiguration::BootInformation result;
204#if qStroika_Foundation_Common_Platform_Linux
205 struct sysinfo info;
206 ::sysinfo (&info);
207 result.fBootedAt = DateTime::Now ().AddSeconds (-info.uptime);
208#elif qStroika_Foundation_Common_Platform_POSIX
209 {
210 // @todo - I don't think /proc/uptime is POSIX ... NOT SURE HOW TO DEFINE THIS - MAYBE ONLY .... on LINUX?
211 bool succeeded{false};
212 static const filesystem::path kProcUptimeFileName_{"/proc/uptime"};
213 if (IO::FileSystem::Default ().Access (kProcUptimeFileName_)) {
214 /*
215 * From https://www.centos.org/docs/5/html/5.1/Deployment_Guide/s2-proc-uptime.html
216 * "The first number is the total number of seconds the system has been up"
217 */
219 for (const String& line :
220 BinaryToText::Reader::New (IO::FileSystem::FileInputStream::New (kProcUptimeFileName_, IO::FileSystem::FileInputStream::eNotSeekable))
221 .ReadLines ()) {
222 Sequence<String> t = line.Tokenize ();
223 if (t.size () >= 2) {
224 result.fBootedAt = DateTime::Now ().AddSeconds (-Characters::FloatConversion::ToFloat<double> (t[0]));
225 succeeded = true;
226 }
227 break;
228 }
229 }
230 if (not succeeded) {
231 /*
232 * The hard way is to read /etc/utmp
233 *
234 * http://pubs.opengroup.org/onlinepubs/009695399/basedefs/utmpx.h.html
235 *
236 * This isn't threadsafe, or in any way reasonable. AIX has a non-standard threadsafe API, but I'm not sure of the
237 * need to fix this..????
238 * --LGP 2015-08-21
239 */
240 [[maybe_unused]] auto&& cleanup = Execution::Finally ([] () noexcept { ::endutxent (); });
241 ::setutxent ();
242 for (const utmpx* i = ::getutxent (); i != nullptr; i = ::getutxent ()) {
243 if (i->ut_type == BOOT_TIME) {
244 result.fBootedAt = DateTime{i->ut_tv};
245 succeeded = true;
246 }
247 }
248 }
249 Assert (succeeded); // not a real assert, but sort of a warning if this ever gets triggered
250 }
251#elif qStroika_Foundation_Common_Platform_Windows
252// ::GetTickCount () is defined to return #seconds since boot
253#if _WIN32_WINNT >= 0x0600
254 result.fBootedAt = DateTime::Now ().AddSeconds (-static_cast<int> (::GetTickCount64 () / 1000));
255#else
256 result.fBootedAt = DateTime::Now ().AddSeconds (-static_cast<int> (::GetTickCount () / 1000));
257#endif
258#else
260#endif
261 return result;
262 }();
263 return kCachedResult_;
264}
265
266/*
267 ********************************************************************************
268 ********************* Common::GetSystemConfiguration_CPU ***********************
269 ********************************************************************************
270 */
271SystemConfiguration::CPU Common::GetSystemConfiguration_CPU ()
272{
273 // @todo - basically all these implementations assume same # logical cores per physical CPU socket
274 // @todo - no API to capture (maybe not useful) # physical cores
275 using CPU = SystemConfiguration::CPU;
276 CPU result;
277#if qStroika_Foundation_Common_Platform_Linux
278 {
280 static const filesystem::path kProcCPUInfoFileName_{"/proc/cpuinfo"sv};
281 /*
282 * Example 1:
283 *
284 * #uname -a && cat /proc/cpuinfo
285 * Linux lewis-UbuntuDevVM3 3.19.0-58-generic #64-Ubuntu SMP Thu Mar 17 18:30:04 UTC 2016 x86_64 x86_64 x86_64 GNU/Linux
286 * processor : 0
287 * vendor_id : GenuineIntel
288 * cpu family : 6
289 * model : 60
290 * model name : Intel(R) Core(TM) i7-4810MQ CPU @ 2.80GHz
291 * stepping : 3
292 * cpu MHz : 2793.309
293 * cache size : 6144 KB
294 * physical id : 0
295 * siblings : 2
296 * core id : 0
297 * cpu cores : 2
298 * apicid : 0
299 * initial apicid : 0
300 * fpu : yes
301 * fpu_exception : yes
302 * cpuid level : 13
303 * wp : yes
304 * flags : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush mmx fxsr sse sse2 ht syscall nx rdtscp lm constant_tsc rep_good nopl xtopology nonstop_tsc pni pclmulqdq ssse3 cx16 sse4_1 sse4_2 movbe popcnt aes xsave avx rdrand lahf_lm abm
305 * bugs :
306 * bogomips : 5586.61
307 * clflush size : 64
308 * cache_alignment : 64
309 * address sizes : 39 bits physical, 48 bits virtual
310 * power management:
311 *
312 * processor : 1
313 * vendor_id : GenuineIntel
314 * cpu family : 6
315 * model : 60
316 * model name : Intel(R) Core(TM) i7-4810MQ CPU @ 2.80GHz
317 * stepping : 3
318 * cpu MHz : 2793.309
319 * cache size : 6144 KB
320 * physical id : 0
321 * siblings : 2
322 * core id : 1
323 * cpu cores : 2
324 * apicid : 1
325 * initial apicid : 1
326 * fpu : yes
327 * fpu_exception : yes
328 * cpuid level : 13
329 * wp : yes
330 * flags : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush mmx fxsr sse sse2 ht syscall nx rdtscp lm constant_tsc rep_good nopl xtopology nonstop_tsc pni pclmulqdq ssse3 cx16 sse4_1 sse4_2 movbe popcnt aes xsave avx rdrand lahf_lm abm
331 * bugs :
332 * bogomips : 5586.61
333 * clflush size : 64
334 * cache_alignment : 64
335 * address sizes : 39 bits physical, 48 bits virtual
336 * power management:
337 *
338 * Example 2:
339 *
340 * root@q7imx6:/opt/BLKQCL# uname -a
341 * Linux q7imx6 3.0.35.Q7_IMX6-14.03.01 #2 SMP PREEMPT Thu May 5 01:12:05 UTC 2016 armv7l GNU/Linux
342 * root@q7imx6:/opt/BLKQCL# cat /proc/cpuinfo
343 * Processor : ARMv7 Processor rev 10 (v7l)
344 * processor : 0
345 * BogoMIPS : 1988.28
346 *
347 * processor : 1
348 * BogoMIPS : 1988.28
349 *
350 * Features : swp half thumb fastmult vfp edsp neon vfpv3
351 * CPU implementer : 0x41
352 * CPU architecture: 7
353 * CPU variant : 0x2
354 * CPU part : 0xc09
355 * CPU revision : 10
356 *
357 * Hardware : MSC Q7-IMX6 Module
358 * Revision : 63000
359 * Serial : 0000000000000000
360 */
361 // Note - /procfs files always unseekable
362 optional<String> foundProcessor;
363 optional<unsigned int> currentProcessorID;
364 optional<String> currentModelName;
365 optional<unsigned int> currentSocketID;
366 for (const String& line :
367 BinaryToText::Reader::New (IO::FileSystem::FileInputStream::New (kProcCPUInfoFileName_, IO::FileSystem::FileInputStream::eNotSeekable))
368 .ReadLines ()) {
369#if USE_NOISY_TRACE_IN_THIS_MODULE_
370 DbgTrace ("in Configuration::GetSystemConfiguration_CPU capture_ line={}"_f, line);
371#endif
372 static const String kOldProcessorLabel_{"Processor"sv};
373 static const String kProcessorIDLabel_{"processor"sv};
374 static const String kModelNameLabel_{"model name"sv};
375 static const String kSocketIDLabel_{"physical id"sv}; // a bit of a guess?
376 if (not line.Trim ().empty ()) {
377 Sequence<String> lineTokens = line.Tokenize ({':'});
378 if (lineTokens.size () >= 2) {
379 String firstTrimedToken = lineTokens[0].Trim ();
380 size_t afterColon = *line.Find (':') + 1;
381 if (firstTrimedToken == kOldProcessorLabel_) {
382 foundProcessor = line.SubString (afterColon).Trim ();
383 }
384 else if (firstTrimedToken == kProcessorIDLabel_) {
385 currentProcessorID = String2Int<unsigned int> (line.SubString (afterColon).Trim ());
386 }
387 else if (firstTrimedToken == kModelNameLabel_) {
388 currentModelName = line.SubString (afterColon).Trim ();
389 }
390 else if (firstTrimedToken == kSocketIDLabel_) {
391 currentSocketID = String2Int<unsigned int> (line.SubString (afterColon).Trim ());
392 }
393 }
394
395 // ends each socket
396 if (currentProcessorID) {
397 String useModelName = Memory::NullCoalesce (foundProcessor);
398 Memory::CopyToIf (&useModelName, currentModelName); // currentModelName takes precedence but I doubt both present
399 result.fCores.Append (CPU::CoreDetails{Memory::NullCoalesce (currentSocketID), useModelName});
400 }
401 // intentionally don't clear foundProcessor cuz occurs once it appears
402 currentProcessorID = nullopt;
403 currentModelName = nullopt;
404 currentSocketID = nullopt;
405 }
406 }
407 if (currentProcessorID) {
408 String useModelName = Memory::NullCoalesce (foundProcessor);
409 Memory::CopyToIf (&useModelName, currentModelName); // currentModelName takes precedence but I doubt both present
410 result.fCores.Append (CPU::CoreDetails{Memory::NullCoalesce (currentSocketID), useModelName});
411 }
412 }
413#elif qStroika_Foundation_Common_Platform_Windows
414 /*
415 * Based on https://msdn.microsoft.com/en-us/library/ms683194?f=255&MSPPError=-2147217396
416 *
417 * I deleted code to capture NUMA nodes, and processor caches, and don't currently use number of physical cores, but could
418 * get that from original code.
419 */
420 DWORD logicalProcessorCount = 0;
421 DWORD processorCoreCount = 0;
422 DWORD processorPackageCount = 0;
423 {
424 auto countSetBits = [] (ULONG_PTR bitMask) -> DWORD {
425 DWORD LSHIFT = sizeof (ULONG_PTR) * 8 - 1;
426 DWORD bitSetCount = 0;
427 ULONG_PTR bitTest = (ULONG_PTR)1 << LSHIFT;
428 for (DWORD i = 0; i <= LSHIFT; ++i) {
429 bitSetCount += ((bitMask & bitTest) ? 1 : 0);
430 bitTest /= 2;
431 }
432 return bitSetCount;
433 };
434 typedef BOOL (WINAPI * LPFN_GLPI) (PSYSTEM_LOGICAL_PROCESSOR_INFORMATION, PDWORD);
435 DISABLE_COMPILER_MSC_WARNING_START (6387) // ignore check for null GetModuleHandle - if that fails - we have bigger problems and a crash sounds imminent
436 LPFN_GLPI glpi = (LPFN_GLPI)::GetProcAddress (::GetModuleHandle (TEXT ("kernel32")), "GetLogicalProcessorInformation");
437 DISABLE_COMPILER_MSC_WARNING_END (6387)
438 AssertNotNull (glpi); // assume at least OS WinXP...
439 StackBuffer<byte> buffer{Memory::eUninitialized, sizeof (PSYSTEM_LOGICAL_PROCESSOR_INFORMATION)};
440 DWORD returnLength = 0;
441 while (true) {
442 DWORD rc = glpi (reinterpret_cast<PSYSTEM_LOGICAL_PROCESSOR_INFORMATION> (buffer.begin ()), &returnLength);
443 if (FALSE == rc) {
444 if (GetLastError () == ERROR_INSUFFICIENT_BUFFER) {
445 buffer.GrowToSize_uninitialized (returnLength);
446 }
447 else {
449 }
450 }
451 else {
452 break;
453 }
454 }
455 PSYSTEM_LOGICAL_PROCESSOR_INFORMATION ptr = reinterpret_cast<PSYSTEM_LOGICAL_PROCESSOR_INFORMATION> (buffer.begin ());
456 DWORD byteOffset = 0;
457 while (byteOffset + sizeof (SYSTEM_LOGICAL_PROCESSOR_INFORMATION) <= returnLength) {
458 switch (ptr->Relationship) {
459 case RelationNumaNode:
460 break;
461 case RelationProcessorCore:
462 ++processorCoreCount;
463 // A hyperthreaded core supplies more than one logical processor.
464 logicalProcessorCount += countSetBits (ptr->ProcessorMask);
465 break;
466 case RelationCache:
467 break;
468 case RelationProcessorPackage:
469 // Logical processors share a physical package.
470 ++processorPackageCount;
471 break;
472 default:
473 DbgTrace ("Error: Unsupported LOGICAL_PROCESSOR_RELATIONSHIP value."_f);
474 break;
475 }
476 byteOffset += sizeof (SYSTEM_LOGICAL_PROCESSOR_INFORMATION);
477 ++ptr;
478 }
479 }
480
481 static const String kProcessorType_ = [] () {
482 int CPUInfo[4] = {-1};
483 char CPUBrandString[0x40];
484 // Get the information associated with each extended ID.
485 ::__cpuid (CPUInfo, 0x80000000);
486 uint32_t nExIds = CPUInfo[0];
487 for (uint32_t i = 0x80000000; i <= nExIds; ++i) {
488 ::__cpuid (CPUInfo, i);
489 // Interpret CPU brand string
490 if (i == 0x80000002)
491 (void)::memcpy (CPUBrandString, CPUInfo, sizeof (CPUInfo));
492 else if (i == 0x80000003)
493 (void)::memcpy (CPUBrandString + 16, CPUInfo, sizeof (CPUInfo));
494 else if (i == 0x80000004)
495 (void)::memcpy (CPUBrandString + 32, CPUInfo, sizeof (CPUInfo));
496 }
497 return String{CPUBrandString};
498 }();
499
501 ::SYSTEM_INFO sysInfo{}; // GetNativeSystemInfo cannot fail so no need to initialize data
502 ::GetNativeSystemInfo (&sysInfo);
503 Assert (sysInfo.dwNumberOfProcessors == logicalProcessorCount);
504 }
505 for (unsigned int socketNum = 0; socketNum < processorPackageCount; ++socketNum) {
506 unsigned int logProcessorsPerSocket = logicalProcessorCount / processorPackageCount;
507 for (DWORD i = 0; i < logProcessorsPerSocket; ++i) {
508 result.fCores.Append (CPU::CoreDetails{socketNum, kProcessorType_});
509 }
510 }
511
512#endif
513 return result;
514}
515
516/*
517 ********************************************************************************
518 ******************* Common::GetSystemConfiguration_Memory **********************
519 ********************************************************************************
520 */
521SystemConfiguration::Memory Common::GetSystemConfiguration_Memory ()
522{
523 using Memory = SystemConfiguration::Memory;
524 Memory result;
525#if qStroika_Foundation_Common_Platform_POSIX
526 // page size cannot change while running, but number of pages can
527 // (e.g. https://pubs.vmware.com/vsphere-50/index.jsp?topic=%2Fcom.vmware.vsphere.vm_admin.doc_50%2FGUID-0B4C3128-F854-43B9-9D80-A20C0C8B0FF7.html)
528 static const size_t kPageSize_{static_cast<size_t> (::sysconf (_SC_PAGESIZE))};
529 result.fPageSize = kPageSize_;
530 result.fTotalPhysicalRAM = ::sysconf (_SC_PHYS_PAGES) * kPageSize_;
531#elif qStroika_Foundation_Common_Platform_Windows
532 ::SYSTEM_INFO sysInfo{};
533 ::GetNativeSystemInfo (&sysInfo);
534 result.fPageSize = sysInfo.dwPageSize;
535
536 ::MEMORYSTATUSEX memStatus{};
537 memStatus.dwLength = sizeof (memStatus);
538 Verify (::GlobalMemoryStatusEx (&memStatus));
539 result.fTotalPhysicalRAM = memStatus.ullTotalPhys;
540 result.fTotalVirtualRAM = memStatus.ullTotalVirtual;
541#endif
542 return result;
543}
544
545/*
546 ********************************************************************************
547 *************** Common::GetSystemConfiguration_OperatingSystem *****************
548 ********************************************************************************
549 */
550SystemConfiguration::OperatingSystem Common::GetSystemConfiguration_ActualOperatingSystem ()
551{
552 using OperatingSystem = SystemConfiguration::OperatingSystem;
553 static const OperatingSystem kCachedResult_ = [] () -> OperatingSystem {
554 OperatingSystem tmp;
555#if qStroika_Foundation_Common_Platform_POSIX
556 tmp.fTokenName = "Unix"sv;
557 try {
558 tmp.fTokenName = get<0> (Execution::ProcessRunner{"uname"}.Run (String{})).Trim ();
559 }
560 catch (...) {
561 DbgTrace ("Failure running uname"_f);
562 }
563 try {
565 DataExchange::Variant::INI::Reader{}.ReadProfile (IO::FileSystem::FileInputStream::New ("/etc/os-release"sv));
566 tmp.fShortPrettyName = p.fUnnamedSection.fProperties.LookupValue ("NAME"sv);
567 tmp.fPrettyNameWithMajorVersion = p.fUnnamedSection.fProperties.LookupValue ("PRETTY_NAME"sv);
568 tmp.fMajorMinorVersionString = p.fUnnamedSection.fProperties.LookupValue ("VERSION_ID"sv);
569 }
570 catch (...) {
571 DbgTrace ("Failure reading /etc/os-release: {}"_f, current_exception ());
572 }
573 if (tmp.fShortPrettyName.empty ()) {
574 try {
575 String n = Streams::BinaryToText::Reader::New (IO::FileSystem::FileInputStream::New ("/etc/centos-release"sv)).ReadAll ().Trim ();
576 tmp.fShortPrettyName = "Centos"sv;
577 tmp.fPrettyNameWithMajorVersion = n;
578 Sequence<String> tokens = n.Tokenize ();
579 if (tokens.size () >= 3) {
580 tmp.fMajorMinorVersionString = tokens[2];
581 }
582 }
583 catch (...) {
584 DbgTrace ("Failure reading /etc/centos-release {}"_f, current_exception ());
585 }
586 }
587 if (tmp.fShortPrettyName.empty ()) {
588 try {
589 String n = Streams::BinaryToText::Reader::New (IO::FileSystem::FileInputStream::New ("/etc/redhat-release"sv)).ReadAll ().Trim ();
590 tmp.fShortPrettyName = "RedHat"sv;
591 tmp.fPrettyNameWithMajorVersion = n;
592 Sequence<String> tokens = n.Tokenize ();
593 if (tokens.size () >= 3) {
594 tmp.fMajorMinorVersionString = tokens[2];
595 }
596 }
597 catch (...) {
598 DbgTrace ("Failure reading /etc/redhat-release {}"_f, current_exception ());
599 }
600 }
601 if (tmp.fShortPrettyName.empty ()) {
602 tmp.fShortPrettyName = tmp.fTokenName;
603 }
604 if (tmp.fPrettyNameWithMajorVersion.empty ()) {
605 tmp.fPrettyNameWithMajorVersion = tmp.fShortPrettyName;
606 }
607 if (tmp.fPrettyNameWithVersionDetails.empty ()) {
608 tmp.fPrettyNameWithVersionDetails = tmp.fPrettyNameWithMajorVersion;
609 }
610 if (tmp.fRFC1945CompatProductTokenWithVersion.empty ()) {
611 tmp.fRFC1945CompatProductTokenWithVersion = tmp.fShortPrettyName.Trim ().ReplaceAll (" "sv, "-"sv);
612 if (not tmp.fMajorMinorVersionString.empty ()) {
613 tmp.fRFC1945CompatProductTokenWithVersion = tmp.fRFC1945CompatProductTokenWithVersion + "/"sv + tmp.fMajorMinorVersionString;
614 }
615 }
616
617#if defined(_POSIX_V6_LP64_OFF64)
618 //
619 // @todo FIX/FIND BETTER WAY!
620 //
621 //http://docs.oracle.com/cd/E36784_01/html/E36874/sysconf-3c.html
622 // Quite uncertain - this is not a good reference
623 // --LGP 2014-10-18
624 //
625 tmp.fBits = ::sysconf (_SC_V6_LP64_OFF64) == _POSIX_V6_LP64_OFF64 ? 64 : 32;
626#elif defined(_V6_LP64_OFF64)
627 //AIX? but maybe others??? -- LGP 2016-09-10 - not important to fix/remove
628 tmp.fBits = ::sysconf (_SC_V6_LP64_OFF64) == _V6_LP64_OFF64 ? 64 : 32;
629#else
630 // could be a C+++ const - let it not compile if not available, and we'll dig...
631 //tmp.fBits = ::sysconf (_SC_V6_LP64_OFF64) == _POSIX_V6_LP64_OFF64 ? 64 : 32;
632 DbgTrace ("_SC_V6_LP64_OFF64/_POSIX_V6_LP64_OFF64 not available - so assuming 32-bit..."_f);
633#endif
634
635 using Characters::CompareOptions;
636
637 // No good way I can find to tell...
638 if (not tmp.fPreferredInstallerTechnology.has_value ()) {
639 auto nameEqComparer = String::EqualsComparer{eCaseInsensitive};
640 if (nameEqComparer (tmp.fShortPrettyName, "Centos"sv) or nameEqComparer (tmp.fShortPrettyName, "RedHat"sv)) {
641 tmp.fPreferredInstallerTechnology = SystemConfiguration::OperatingSystem::InstallerTechnology::eRPM;
642 }
643 else if (nameEqComparer (tmp.fShortPrettyName, "Ubuntu"sv)) {
644 tmp.fPreferredInstallerTechnology = SystemConfiguration::OperatingSystem::InstallerTechnology::eDPKG;
645 }
646 }
647 // not a great way to test since some systems have both, like ubuntu
648 if (not tmp.fPreferredInstallerTechnology.has_value ()) {
649 try {
650 (void)Execution::ProcessRunner{"dpkg --help"}.Run (String{});
651 tmp.fPreferredInstallerTechnology = SystemConfiguration::OperatingSystem::InstallerTechnology::eDPKG;
652 }
653 catch (...) {
654 }
655 }
656 if (not tmp.fPreferredInstallerTechnology.has_value ()) {
657 try {
658 (void)Execution::ProcessRunner{"rpm --help"}.Run (String{});
659 tmp.fPreferredInstallerTechnology = SystemConfiguration::OperatingSystem::InstallerTechnology::eRPM;
660 }
661 catch (...) {
662 }
663 }
664#elif qStroika_Foundation_Common_Platform_Windows
665 tmp.fTokenName = "Windows"sv;
666
667 /*
668 * Best (of a bad bunch) official reference from Microsoft on finding version#s
669 * https://docs.microsoft.com/en-us/windows/desktop/SysInfo/operating-system-version
670 */
671
672 /*
673 * note
674 * Could consider using RtlGetVersion from https://stackoverflow.com/questions/36543301/detecting-windows-10-version
675 * except this only returns a #, and not the pretty name we get from the registry.
676 */
677
678 String kernelOSBuildVersion;
679 String kernelVersion;
680 {
681 /*
682 * How you do this seems to change alot. But as of 2019-03-16:
683 * from https://docs.microsoft.com/en-us/windows/desktop/sysinfo/getting-the-system-version
684 * To obtain the full version number for the operating system,
685 * call the GetFileVersionInfo function on one of the system DLLs,
686 * such as Kernel32.dll, then call VerQueryValue to obtain the
687 * \\StringFileInfo\\\\ProductVersion subblock of the file version information
688 */
689 const wchar_t* kkernel32_ = L"kernel32.dll";
690 DWORD dummy;
691 const auto cbInfo = ::GetFileVersionInfoSizeExW (FILE_VER_GET_NEUTRAL, kkernel32_, &dummy);
692 StackBuffer<char> buffer{Memory::eUninitialized, cbInfo};
693 Verify (::GetFileVersionInfoExW (FILE_VER_GET_NEUTRAL, kkernel32_, dummy, static_cast<DWORD> (buffer.size ()), &buffer[0]));
694 void* p = nullptr;
695 UINT size = 0;
696 Verify (::VerQueryValueW (buffer.data (), L"\\", &p, &size));
697 Assert (size >= sizeof (VS_FIXEDFILEINFO));
698 Assert (p != nullptr);
699 auto pFixed = static_cast<const VS_FIXEDFILEINFO*> (p);
700 kernelVersion = "{}.{}"_f(HIWORD (pFixed->dwFileVersionMS), LOWORD (pFixed->dwFileVersionMS));
701 kernelOSBuildVersion = "{}.{}"_f(HIWORD (pFixed->dwFileVersionLS), LOWORD (pFixed->dwFileVersionLS));
702 }
703
704 /*
705 * This trick is widely referenced on the internet, but does NOT appear in any official Microsoft documentaiton
706 * I can find, so is likely an implementation detail subject to change.
707 *
708 * However, it is referenced here for example:
709 * https://stackoverflow.com/questions/31072543/reliable-way-to-get-windows-version-from-registry
710 */
711 optional<String> platformVersion;
712 optional<String> productName;
713 optional<String> currentVersion; // windows major-minor version
714 try {
715 const Common::Platform::Windows::RegistryKey kWinVersionInfo_{HKEY_LOCAL_MACHINE, L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion"sv};
716 if (auto o = kWinVersionInfo_.Lookup ("ReleaseId"sv)) {
717 platformVersion = o.As<String> ();
718 }
719 if (auto o = kWinVersionInfo_.Lookup ("ProductName"sv)) {
720 productName = o.As<String> ();
721 }
722 // try to get current version from CurrentMajorVersionNumber/CurrentMinorVersionNumber which appears
723 // to be the new way
724 try {
725 if (auto oMajor = kWinVersionInfo_.Lookup ("CurrentMajorVersionNumber"sv)) {
726 if (auto oMinor = kWinVersionInfo_.Lookup ("CurrentMinorVersionNumber"sv)) {
727 currentVersion = oMajor.As<String> () + "." + oMinor.As<String> ();
728 }
729 }
730 }
731 catch (...) {
732 // ignore - older OS may not have this so fallthrough (though that shouldn't cause exception but in case)
733 }
734 if (not currentVersion) {
735 if (auto o = kWinVersionInfo_.Lookup ("CurrentVersion"sv)) {
736 currentVersion = o.As<String> ();
737 }
738 }
739 }
740 catch (...) {
741 DbgTrace ("Exception suppressed looking up windows version in registry: {}"_f, current_exception ());
742 }
743
744 // https://stackoverflow.com/questions/74645458/how-to-detect-windows-11-programmatically
745 if (currentVersion == "10.0"sv and Characters::FloatConversion::ToFloat<double> (kernelOSBuildVersion) >= 21996.0) {
746 currentVersion = "11.0"sv;
747 if (productName and productName->StartsWith ("Windows 10"sv)) {
748 productName = "Windows 11"sv + productName->SubString (10); // not sure this is best way to fix? --LGP 2024-07-24
749 }
750 platformVersion = nullopt; // in this case, the number doesn't appear to be meaningful --LGP 2024-07-24
751 }
752
753 if (tmp.fShortPrettyName.empty ()) {
754 tmp.fShortPrettyName = productName.value_or ("Windows"sv);
755 }
756 tmp.fPrettyNameWithMajorVersion = tmp.fShortPrettyName;
757
758 {
759 StringBuilder sb = tmp.fShortPrettyName;
760 if (platformVersion) {
761 sb << " Version "sv + *platformVersion;
762 }
763 if (not kernelVersion.empty ()) {
764 sb << " (OS Build "sv + kernelOSBuildVersion + ")"sv;
765 }
766 tmp.fPrettyNameWithVersionDetails = sb.str ();
767 }
768
769 tmp.fMajorMinorVersionString = currentVersion.value_or ("unknown"sv);
770 tmp.fRFC1945CompatProductTokenWithVersion = "Windows/"sv + tmp.fMajorMinorVersionString;
771 if constexpr (sizeof (void*) == 4) {
772 tmp.fBits = 32;
773 //IsWow64Process is not available on all supported versions of Windows.
774 //Use GetModuleHandle to get a handle to the DLL that contains the function
775 //and GetProcAddress to get a pointer to the function if available.
776 typedef BOOL (WINAPI * LPFN_ISWOW64PROCESS) (HANDLE, PBOOL);
777 DISABLE_COMPILER_MSC_WARNING_START (6387) // ignore check for null GetModuleHandle - if that fails - we have bigger problems and a crash sounds imminent
778 LPFN_ISWOW64PROCESS isWow64Process =
779 (LPFN_ISWOW64PROCESS)::GetProcAddress (::GetModuleHandle (TEXT ("kernel32")), "IsWow64Process");
780 DISABLE_COMPILER_MSC_WARNING_END (6387)
781 if (nullptr != isWow64Process) {
782 BOOL isWOW64 = false;
783 (void)isWow64Process (::GetCurrentProcess (), &isWOW64);
784 if (isWOW64) {
785 tmp.fBits = 64;
786 }
787 }
788 }
789 else {
790 // In windows, a 64 bit app cannot run on 32-bit windows
791 Assert (sizeof (void*) == 8);
792 tmp.fBits = 64;
793 }
794 tmp.fPreferredInstallerTechnology = SystemConfiguration::OperatingSystem::InstallerTechnology::eMSI;
795#else
797#endif
798 return tmp;
799 }();
800 return kCachedResult_;
801}
802
803#if qStroika_Foundation_Common_Platform_Windows
804#pragma comment(lib, "Mincore.lib") // for stuff like IsWindows10OrGreater
805#endif
806SystemConfiguration::OperatingSystem Common::GetSystemConfiguration_ApparentOperatingSystem ()
807{
808 using OperatingSystem = SystemConfiguration::OperatingSystem;
809 static const OperatingSystem kCachedResult_ = [] () -> OperatingSystem {
810 OperatingSystem tmp{GetSystemConfiguration_ActualOperatingSystem ()};
811 // not sure if/how to do this differently on linux? Probably pay MORE attention to stuff from uname and less to stuff like /etc/os-release
812#if qStroika_Foundation_Common_Platform_Windows
813 // Dizzy numbering - from https://docs.microsoft.com/en-us/windows/desktop/sysinfo/operating-system-version
814 optional<String> winCompatibilityVersionName;
815 optional<String> winCompatibilityVersionNumber;
816 {
817 if (not winCompatibilityVersionName and IsWindows10OrGreater ()) {
818 winCompatibilityVersionName = "10.0"sv;
819 winCompatibilityVersionNumber = "10.0"sv;
820 }
821 if (not winCompatibilityVersionName and IsWindows8Point1OrGreater ()) {
822 winCompatibilityVersionName = "8.1"sv;
823 winCompatibilityVersionNumber = "6.3"sv;
824 }
825 if (not winCompatibilityVersionName and IsWindows8OrGreater ()) {
826 winCompatibilityVersionName = "8.0"sv;
827 winCompatibilityVersionNumber = "6.2"sv;
828 }
829 if (not winCompatibilityVersionName and IsWindows7SP1OrGreater ()) {
830 // unclear cuz 7.1 not listed as operating system on that page???
831 winCompatibilityVersionName = "7.1"sv;
832 winCompatibilityVersionNumber = "6.2"sv;
833 }
834 if (not winCompatibilityVersionName and IsWindows7OrGreater ()) {
835 winCompatibilityVersionName = "7.0"sv;
836 winCompatibilityVersionNumber = "6.1"sv;
837 }
838 }
839 String useWinMajorMinorVersionNameStr = winCompatibilityVersionName.value_or ("unknown"sv);
840 String useWinMajorMinorVersionNumberStr = winCompatibilityVersionNumber.value_or ("unknown"sv);
841 tmp.fShortPrettyName = "Windows "sv + useWinMajorMinorVersionNameStr;
842 tmp.fPrettyNameWithMajorVersion = tmp.fShortPrettyName;
843 tmp.fPrettyNameWithVersionDetails = tmp.fShortPrettyName;
844 tmp.fMajorMinorVersionString = useWinMajorMinorVersionNumberStr;
845 tmp.fRFC1945CompatProductTokenWithVersion = "Windows/"sv + useWinMajorMinorVersionNumberStr;
846#endif
847 return tmp;
848 }();
849 return kCachedResult_;
850}
851
852/*
853 ********************************************************************************
854 ***************** GetSystemConfiguration_ComputerNames *************************
855 ********************************************************************************
856 */
857#if 0 && qStroika_Foundation_Common_Platform_POSIX
858// ALTERNATE APPROACH TO CONSIDER
859string name;
860{
861 struct addrinfo* res;
862 struct addrinfo hints {};
863 hints.ai_socktype = SOCK_STREAM;
864 hints.ai_flags = AI_CANONNAME;
865 int e = getaddrinfo(nullptr, nullptr, &hints, &res);
866 if (e != 0) {
867 //printf("failure %s\n", gai_strerror (e));
868 return String {};
869 }
870 int sock = -1;
871 for (struct addrinfo* r = res; r != NULL; r = r->ai_next) {
872 name = r->ai_canonname ;
873 break;
874 }
875 freeaddrinfo(res);
876}
877return String::FromSDKString (name);
878#endif
879SystemConfiguration::ComputerNames Common::GetSystemConfiguration_ComputerNames ()
880{
881 using ComputerNames = SystemConfiguration::ComputerNames;
882 ComputerNames result;
883#if qStroika_Foundation_Common_Platform_POSIX
884#if defined(HOST_NAME_MAX)
885 char nameBuf[HOST_NAME_MAX + 1]; // size from http://man7.org/linux/man-pages/man2/gethostname.2.html
886#else
887 char nameBuf[1024]; // Mac XCode 11 doesn't define HOST_NAME_MAX
888#endif
889 Execution::ThrowPOSIXErrNoIfNegative (::gethostname (nameBuf, Memory::NEltsOf (nameBuf)));
890 nameBuf[Memory::NEltsOf (nameBuf) - 1] = '\0'; // http://linux.die.net/man/2/gethostname says not necessarily nul-terminated
891 result.fHostname = String::FromNarrowSDKString (nameBuf);
892#elif qStroika_Foundation_Common_Platform_Windows
893 constexpr COMPUTER_NAME_FORMAT kUseNameFormat_ = ComputerNameNetBIOS; // total WAG -- LGP 2014-10-10
894 DWORD dwSize = 0;
895 (void)::GetComputerNameEx (kUseNameFormat_, nullptr, &dwSize);
896 StackBuffer<SDKChar> buf{Memory::eUninitialized, dwSize};
897 Execution::Platform::Windows::ThrowIfZeroGetLastError (::GetComputerNameEx (kUseNameFormat_, buf.data (), &dwSize));
898 result.fHostname = String::FromSDKString (buf);
899#else
901#endif
902 return result;
903}
904
905/*
906 ********************************************************************************
907 ************************ Common::GetNumberOfLogicalCPUCores ********************
908 ********************************************************************************
909 */
910unsigned int Common::GetNumberOfLogicalCPUCores (const chrono::duration<double>& allowedStaleness)
911{
912 [[maybe_unused]] static auto computeViaStdThreadHardwareConcurrency = [] () { return std::thread::hardware_concurrency (); };
913 [[maybe_unused]] static auto computeViaGetSystemConfiguration_CPU = [] () {
914 return Common::GetSystemConfiguration_CPU ().GetNumberOfLogicalCores ();
915 };
916#if qStroika_Foundation_Debug_AssertionsChecked
917 static auto compute = [=] () {
918 unsigned int hc = computeViaStdThreadHardwareConcurrency ();
919 unsigned int sysConfigLogCores = computeViaGetSystemConfiguration_CPU ();
920 WeakAssert (hc == sysConfigLogCores); // nice to test/find out if these ever differ
921 return sysConfigLogCores;
922 };
923#else
924 auto compute = computeViaGetSystemConfiguration_CPU; // maybe choose based on OS, etc???, like if I know which library does a good job with std::thread::hardware_concurrency
925#endif
926
927 static atomic<Time::TimePointSeconds> sCachedAt_ = Time::TimePointSeconds{};
928 static atomic<unsigned int> sCachedValue_ = compute ();
929 Time::TimePointSeconds now = Time::GetTickCount ();
930 if (now > sCachedAt_.load () + allowedStaleness) {
931 sCachedValue_ = compute ();
932 sCachedAt_ = now;
933 }
934 return sCachedValue_;
935}
936
937/*
938 ********************************************************************************
939 ****************** SystemConfiguration GetSystemConfiguration ******************
940 ********************************************************************************
941 */
942SystemConfiguration Common::GetSystemConfiguration ()
943{
944 return SystemConfiguration{GetSystemConfiguration_BootInformation (),
945 GetSystemConfiguration_CPU (),
946 GetSystemConfiguration_Memory (),
949 GetSystemConfiguration_ComputerNames ()};
950}
#define AssertNotNull(p)
Definition Assertions.h:333
#define AssertNotImplemented()
Definition Assertions.h:401
#define qStroika_Foundation_Debug_AssertionsChecked
The qStroika_Foundation_Debug_AssertionsChecked flag determines if assertions are checked and validat...
Definition Assertions.h:48
#define WeakAssert(c)
A WeakAssert() is for things that aren't guaranteed to be true, but are overwhelmingly likely to be t...
Definition Assertions.h:438
#define Verify(c)
Definition Assertions.h:419
time_point< RealtimeClock, DurationSeconds > TimePointSeconds
TimePointSeconds is a simpler approach to chrono::time_point, which doesn't require using templates e...
Definition Realtime.h:82
#define DbgTrace
Definition Trace.h:309
Similar to String, but intended to more efficiently construct a String. Mutable type (String is large...
String is like std::u32string, except it is much easier to use, often much more space efficient,...
Definition String.h:201
static String FromSDKString(const SDKChar *from)
Definition String.inl:447
static String FromNarrowSDKString(const char *from)
Definition String.inl:470
nonvirtual String Trim(bool(*shouldBeTrimmed)(Character)=Character::IsWhitespace) const
Definition String.cpp:1592
nonvirtual Containers::Sequence< String > Tokenize() const
Definition String.cpp:1234
A generalization of a vector: a container whose elements are keyed by the natural numbers.
Definition Sequence.h:187
Set<T> is a container of T, where once an item is added, additionally adds () do nothing.
Definition Set.h:105
nonvirtual void Add(ArgByValueType< value_type > item)
Definition Set.inl:138
nonvirtual Profile ReadProfile(const Streams::InputStream::Ptr< byte > &in)
though can read directly as VariantValue, reading as a Profile object maybe handier for this type of ...
Run the given command, and optionally support stdin/stdout/stderr as streams (either sync with Run,...
nonvirtual void Run(const Streams::InputStream::Ptr< byte > &in, const Streams::OutputStream::Ptr< byte > &out=nullptr, const Streams::OutputStream::Ptr< byte > &error=nullptr, ProgressMonitor::Updater progress=nullptr, Time::DurationSeconds timeout=Time::kInfinity)
Run the given external command/process (set by constructor) - with the given arguments,...
Logically halfway between std::array and std::vector; Smart 'direct memory array' - which when needed...
nonvirtual String ReadAll(size_t upTo=numeric_limits< size_t >::max()) const
nonvirtual Iterator< T > Find(THAT_FUNCTION &&that, Execution::SequencePolicy seq=Execution::SequencePolicy::eDEFAULT) const
Run the argument bool-returning function (or lambda) on each element of the container,...
nonvirtual size_t size() const
Returns the number of items contained.
Definition Iterable.inl:300
T String2Int(span< const CHAR_T > s)
SystemConfiguration::OperatingSystem GetSystemConfiguration_ActualOperatingSystem()
SystemConfiguration GetSystemConfiguration()
Get the System Configuration object - note not a system global - because the configuration can change...
SystemConfiguration::OperatingSystem GetSystemConfiguration_ApparentOperatingSystem()
unsigned int GetNumberOfLogicalCPUCores(const chrono::duration< double > &allowedStaleness=1min)
return the number of currently available CPU cores on this (virtual) machine
auto Finally(FUNCTION &&f) -> Private_::FinallySentry< FUNCTION >
Definition Finally.inl:31
INT_TYPE ThrowPOSIXErrNoIfNegative(INT_TYPE returnCode)
Ptr New(const InputStream::Ptr< byte > &src, optional< AutomaticCodeCvtFlags > codeCvtFlags={}, optional< SeekableFlag > seekable={}, ReadAhead readAhead=eReadAheadAllowed)
Create an InputStream::Ptr<Character> from the arguments (usually binary source) - which can be used ...