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