From ed8d5e30f05ad96e66ffdb209c7868f8ac8da4c1 Mon Sep 17 00:00:00 2001 From: gcontini <1121667+gcontini@users.noreply.github.com> Date: 周一, 24 2月 2020 05:20:25 +0800 Subject: [PATCH] windows progress --- src/library/locate/ExternalDefinition.cpp | 4 src/library/os/windows/isvm/BIOSReader.h | 21 src/library/os/windows/isvm/BIOSReader.cpp | 145 ++++++ src/library/os/windows/network.cpp | 113 ++++ src/library/hw_identifier/identification_strategy.hpp | 1 src/library/os/windows/cpu_info.cpp | 47 ++ src/library/os/CMakeLists.txt | 18 src/library/os/windows/execution_environment.cpp | 70 +++ src/library/os/windows/isvm/Native.cpp | 437 +++++++++++++++++++ src/library/os/windows/isvm/Native.h | 18 doc/snippets/hardware.cpp | 266 +++++++++++ /dev/null | 1 src/library/os/execution_environment.hpp | 5 src/library/os/linux/network.cpp | 20 doc/CREDITS.md | 9 src/library/os/windows/os-win.c | 106 ---- src/library/os/windows/isvm/main.cpp | 51 ++ src/library/hw_identifier/default_strategy.cpp | 2 18 files changed, 1,197 insertions(+), 137 deletions(-) diff --git a/doc/CREDITS.md b/doc/CREDITS.md index 918606c..74b8e5e 100644 --- a/doc/CREDITS.md +++ b/doc/CREDITS.md @@ -1,7 +1,10 @@ -The following code has been used in OpenLicenseManager. Many thanks to the authors +The following open source code has been used in OpenLicenseManager. +Thanks to every one of the authors of such projects. Without you open license manager would never have been completed. -======================================================== +## isVM +Thanks for the great smbios parsing code. It saved me days. + isvm : https://github.com/0of/isvm The MIT License (MIT) @@ -25,5 +28,5 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -============================= + diff --git a/doc/snippets/hardware.cpp b/doc/snippets/hardware.cpp new file mode 100644 index 0000000..bf4e0be --- /dev/null +++ b/doc/snippets/hardware.cpp @@ -0,0 +1,266 @@ +/********************* physical_processors.cpp ***************************** +* Author: Agner Fog +* Date created: 2019-10-29 +* Last modified: 2019-11-25 +* Version: 1.02 beta +* Project: vector class library +* Description: Detect number of physical and logical processors on CPU chip. +* Compile for C++11 or later +* +* (c) Copyright 2019 Agner Fog. +* Apache License version 2.0 or later. +******************************************************************************* +Some modern CPUs can run two threads in each CPU core when simultaneous +multithreading (SMT, called hyperthreading by Intel) is enabled. + +The number of physical processors is the number of CPU cores. +The number of logical processors is the same number multiplied by the number of +threads that can run simultaneously in each CPU core. + +Simultaneous multithreading will slow down performance when two CPU-intensive +threads running in the same physical processor (CPU core) are competing for the +same resources. Therefore, the optimal number of threads for CPU-intensive +tasks is most likely to be the number of physical processors. + +Tasks that are less CPU-intensive but limited by RAM access, disk access, +network, etc. may get an advantage by running as many threads as the number of +logical processors. This will be double the number of physical processors when +simultaneous multithreading is enabled. + +The physicalProcessors function detects the number of physical processors and +logical processors on an x86 computer. This is useful for determining the +optimal number of threads. + + +Note: There are several problems in detecting the number of physical processors: + +1. The CPUID instruction on Intel CPUs will return a wrong number of logical + processors when SMT (hyperthreading) is disabled. It may be necessary to + compare the number of processors returned by the CPUID instruction with the + number of processors reported by the operating system to detect if SMT is + enabled (AMD processors do not have this problem). + +2. It is necessary to rely on system functions to detect if there is more than + one CPU chip installed. It is assumed that the status of SMT is the same on + all CPU chips in a system. + +3. The behavior of VIA processors is undocumented. + +4. This function is not guaranteed to work on future CPUs. It may need updating + when new CPUs with different configurations or different CPUID functionality + appear. +******************************************************************************/ + +#include <thread> // std::thread functions + +#ifdef _MSC_VER +#include <intrin.h> // __cpuidex intrinsic function available on microsoft compilers +#endif + +// Define interface to CPUID instruction. +// input: leaf = eax, subleaf = ecx +// output: output[0] = eax, output[1] = ebx, output[2] = ecx, output[3] = edx +static inline void cpuid(int output[4], int leaf, int subleaf = 0) { +#if defined(__GNUC__) || defined(__clang__) // use inline assembly, Gnu/AT&T syntax + int a, b, c, d; + __asm("cpuid" : "=a"(a), "=b"(b), "=c"(c), "=d"(d) : "a"(leaf), "c"(subleaf) : ); + output[0] = a; + output[1] = b; + output[2] = c; + output[3] = d; + +#elif defined (_MSC_VER) // Microsoft compiler, intrin.h included + __cpuidex(output, leaf, subleaf); // intrinsic function for CPUID + +#else // unknown platform. try inline assembly with masm/intel syntax + __asm { + mov eax, leaf + mov ecx, subleaf + cpuid; + mov esi, output + mov[esi], eax + mov[esi + 4], ebx + mov[esi + 8], ecx + mov[esi + 12], edx + } +#endif +} + +// Function prototype: +int physicalProcessors(int * logical_processors = 0); + + +// Find the number of physical and logical processors supported by CPU +// Parameter: +// logical_processors: an optional pointer to an integer that will receive the number of logical processors. +// Return value: number of physical processors +int physicalProcessors(int * logical_processors) { + int vendor = 0; // CPU vendor: 1 = Intel, 2 = AMD, 3 = VIA, 0 = other + int logicalProc = 1; // number of logical processor cores + int physicalProc = 1; // number of physical processor cores + int procPerCore = 1; // logical cores per physical core + bool hyperthreadingSupported = false; // CPU supports hyperthreading / simultaneous multithreading + int systemProcessors = std::thread::hardware_concurrency(); // number of processors reported by operating system + + int abcd[4] = { 0,0,0,0 }; // CPUID output + cpuid(abcd, 0); // CPUID function 0 + + int maxLeaf = abcd[0]; // maximum eax input for CPUID + if (abcd[2] == 0x6C65746E) { // last 4 chars of "GenuineIntel" + vendor = 1; + } + else if (abcd[2] == 0x444D4163) { // last 4 chars of "AuthenticAMD" + vendor = 2; + } + else if (abcd[2] == 0x736C7561) { // last 4 chars of "CentaurHauls" + vendor = 3; + } + + if (maxLeaf >= 1) { + cpuid(abcd, 1); + if (abcd[3] & (1 << 28)) { // hyperthreading supported + hyperthreadingSupported = true; + } + } + + if (vendor == 1) { + ////////////////// + // Intel // + ////////////////// + + int hyper = 0; // hyperthreading status: 0 = unknown, 1 = disabled, 2 = enabled + if (maxLeaf >= 0xB) { // leaf 0xB or 0x1F: Extended Topology Enumeration + int num = 0xB; + // if (maxLeaf >= 0x1F) num = 0x1F; + + for (int c = 0; c < 5; c++) { + cpuid(abcd, num, c); // enumeration level c + int type = (abcd[2] >> 8) & 0xFF;// enumeration type at level c + if (type == 1) { // SMT level + procPerCore = abcd[1] & 0xFFFF; + } + else if (type >= 2) { // core level + logicalProc = abcd[1] & 0xFFFF; + } + else if (type == 0) break; + // There are more types/levels to consider if we use num = 0x1F. We may need + // to fix this in the future if CPUs with more complex configurations appear + } + physicalProc = logicalProc / procPerCore; + + // The number of performance monitor registers depends on hyperthreading status + // on Intel CPUs with performance monitoring version 3 or 4 + cpuid(abcd, 0xA, 0); // performance monitor counters information + int perfVersion = abcd[0] & 0xFF; // performance monitoring version + int perfNum = (abcd[0] >> 8) & 0xFF; // number of performance monitoring registers + if (perfVersion == 3 || perfVersion == 4) { + if (perfNum == 4) { + hyper = 2; // 4 performance registers when hyperthreading enabled + } + else if (perfNum == 8) { // 8 performance registers when hyperthreading disabled + hyper = 1; + procPerCore = 1; + logicalProc = physicalProc; // reduce the number of logical processors when hyperthreading is disabled + } + // hyper remains 0 in all other cases, indicating unknown status + } + } + else if (maxLeaf >= 4) { // CPUID function 4: cache parameters and cores + cpuid(abcd, 4); + logicalProc = (abcd[0] >> 26) + 1; + if (hyperthreadingSupported) { + // number of logical processors per core is not known. Assume 2 if hyperthreading supported + procPerCore = 2; + } + physicalProc = logicalProc / procPerCore; + } + else { + // no information. Assume 1 processor + } + if (systemProcessors > logicalProc) { + // Multiple CPU chips. Assume that chips are identical with respect to hypethreading + physicalProc = systemProcessors * physicalProc / logicalProc; + logicalProc = systemProcessors; + } + else if (logicalProc > systemProcessors && systemProcessors > 0 && hyper == 0) { + // Hyperthreading is disabled + logicalProc = systemProcessors; + physicalProc = systemProcessors; + } + } + else if (vendor == 2) { + + ////////////////// + // AMD // + ////////////////// + + cpuid(abcd, 0x80000000); // AMD specific CPUID functions + int maxLeaf8 = abcd[0] & 0xFFFF; // maximum eax 0x8000.... input for CPUID + + if (maxLeaf8 >= 8) { + cpuid(abcd, 0x80000008); + logicalProc = (abcd[2] & 0xFF) + 1; + + if (maxLeaf8 >= 0x1E) { + cpuid(abcd, 0x8000001E); + procPerCore = ((abcd[1] >> 8) & 0x03) + 1; + // procPerCore = 2 if simultaneous multithreading is enabled, 1 if disabled + } + else { + if (hyperthreadingSupported) { + procPerCore = 2; + } + else { + procPerCore = 1; + } + } + physicalProc = logicalProc / procPerCore; + } + else if (hyperthreadingSupported) { + // number of logical processors per core is not known. Assume 2 if SMT supported + logicalProc = 2; + physicalProc = 1; + } + if (systemProcessors > logicalProc) { + // Multiple CPU chips. Assume that chips are identical with respect to SMT + physicalProc = systemProcessors * physicalProc / logicalProc; + logicalProc = systemProcessors; + } + } + else { + + ////////////////////////////// + // VIA or unknown CPU // + ////////////////////////////// + + // The behavior of VIA processors is undocumented! It is not known how to detect threads on a VIA processor + physicalProc = logicalProc = systemProcessors; + if (hyperthreadingSupported && physicalProc > 1) { + physicalProc /= 2; + } + } + if (logical_processors) { + // return logical_processors if pointer is not null + *logical_processors = logicalProc; + } + return physicalProc; +} + +/* Uncomment this for testing: + +#include <stdio.h> + +int main() { + + int logicalProc = 0; + int physicalProc = physicalProcessors(&logicalProc); + + printf("\nlogical processors: %i", logicalProc); + printf("\nphysical processors: %i", physicalProc); + printf("\nlogical processors per core: %i", logicalProc / physicalProc); + int sysproc = std::thread::hardware_concurrency(); + printf("\nsystem processors: %i", sysproc); + + return 0; +} +*/ \ No newline at end of file diff --git a/src/library/hw_identifier/default_strategy.cpp b/src/library/hw_identifier/default_strategy.cpp index a770512..b46771c 100644 --- a/src/library/hw_identifier/default_strategy.cpp +++ b/src/library/hw_identifier/default_strategy.cpp @@ -14,7 +14,7 @@ namespace hw_identifier { static vector<LCC_API_IDENTIFICATION_STRATEGY> available_strategies() { - ExecutionEnvironment exec; + os::ExecutionEnvironment exec; VIRTUALIZATION virtualization = exec.getVirtualization(); vector<LCC_API_IDENTIFICATION_STRATEGY> strategy_to_try; if (virtualization == CONTAINER) { diff --git a/src/library/hw_identifier/identification_strategy.hpp b/src/library/hw_identifier/identification_strategy.hpp index ad3e2de..1b0985d 100644 --- a/src/library/hw_identifier/identification_strategy.hpp +++ b/src/library/hw_identifier/identification_strategy.hpp @@ -11,7 +11,6 @@ #include <licensecc/datatypes.h> #include <licensecc_properties.h> #include <vector> -#include <bits/unique_ptr.h> #include "../base/base.h" #include "hw_identifier.hpp" diff --git a/src/library/locate/ExternalDefinition.cpp b/src/library/locate/ExternalDefinition.cpp index 7add948..3ee7453 100644 --- a/src/library/locate/ExternalDefinition.cpp +++ b/src/library/locate/ExternalDefinition.cpp @@ -9,7 +9,7 @@ #include <cstring> #include <string> #include <vector> - +#include <stdexcept> #include <licensecc/datatypes.h> #include "../base/base64.h" @@ -21,7 +21,7 @@ namespace license { namespace locate { -using namespace std; + using namespace std; ExternalDefinition::ExternalDefinition(const LicenseLocation *location) : LocatorStrategy("ExternalDefinition"), m_location(location) {} diff --git a/src/library/os/CMakeLists.txt b/src/library/os/CMakeLists.txt index 986550f..ef0fd62 100644 --- a/src/library/os/CMakeLists.txt +++ b/src/library/os/CMakeLists.txt @@ -9,11 +9,25 @@ linux/os-linux.c) ELSE(UNIX) add_library(os OBJECT - cpu_info_common.cpp openssl/signature_verifier.cpp windows/os-win.c) + cpu_info_common.cpp windows/cpu_info.cpp + openssl/signature_verifier.cpp + windows/execution_environment.cpp + windows/isvm/Native.cpp + windows/isvm/BIOSReader.cpp + windows/os-win.c + windows/network.cpp) ENDIF(UNIX) ELSE(UNIX OR OPENSSL_FOUND) +#windows no openssl add_library(os OBJECT - cpu_info_common.cpp windows/signature_verifier.cpp windows/os-win.c) + cpu_info_common.cpp + windows/cpu_info.cpp + windows/signature_verifier.cpp + windows/execution_environment.cpp + windows/isvm/Native.cpp + windows/isvm/BIOSReader.cpp + windows/os-win.c + windows/network.cpp) ENDIF(UNIX OR OPENSSL_FOUND) if(CODE_COVERAGE AND UNIX) diff --git a/src/library/os/execution_environment.hpp b/src/library/os/execution_environment.hpp index ce3fb63..1c15ab4 100644 --- a/src/library/os/execution_environment.hpp +++ b/src/library/os/execution_environment.hpp @@ -9,7 +9,7 @@ #define SRC_LIBRARY_OS_VIRTUALIZATION_HPP_ namespace license { - +namespace os { typedef enum { NONE, CONTAINER, VM } VIRTUALIZATION; typedef enum { ON_PREMISE, @@ -25,7 +25,6 @@ ALI_CLOUD } CLOUD_PROVIDER; - class ExecutionEnvironment { public: ExecutionEnvironment(){}; @@ -35,7 +34,7 @@ bool is_docker(); CLOUD_PROVIDER getCloudProvider(); }; - +} // namespace os } // namespace license #endif /* SRC_LIBRARY_OS_VIRTUALIZATION_HPP_ */ diff --git a/src/library/os/linux/network.cpp b/src/library/os/linux/network.cpp index e456989..475463b 100644 --- a/src/library/os/linux/network.cpp +++ b/src/library/os/linux/network.cpp @@ -23,7 +23,6 @@ #include <ifaddrs.h> #include <linux/if_link.h> #include <netpacket/packet.h> -#include <string.h> #include <stdio.h> #include <unordered_map> @@ -35,25 +34,6 @@ namespace os { using namespace std; -/** - * - * @param ifnames - * @param ifname - * @param ifnames_max - * @return - */ - -static int ifname_position(char *ifnames, char *ifname, int ifnames_max) { - int i, position; - position = -1; - for (i = 0; i < ifnames_max; i++) { - if (strcmp(ifname, &ifnames[i * NI_MAXHOST]) == 0) { - position = i; - break; - } - } - return position; -} /** * diff --git a/src/library/os/windows/.gitignore b/src/library/os/windows/.gitignore deleted file mode 100644 index f3d0437..0000000 --- a/src/library/os/windows/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/isvm/ diff --git a/src/library/os/windows/cpu_info.cpp b/src/library/os/windows/cpu_info.cpp new file mode 100644 index 0000000..764b2fe --- /dev/null +++ b/src/library/os/windows/cpu_info.cpp @@ -0,0 +1,47 @@ +/* + * cpu_info.cpp + * + * Created on: Dec 14, 2019 + * Author: devel + */ + + +#include <intrin.h> +#include <string> +#include <unordered_set> +#include "../cpu_info.hpp" + +namespace license { +using namespace std; + +CpuInfo::CpuInfo() {} + +CpuInfo::~CpuInfo() {} +/** + * Detect Virtual machine using hypervisor bit. + * @return true if the cpu hypervisor bit is set to 1 + */ +bool CpuInfo::is_hypervisor_set() const { + int cpui[4] = {0}; + __cpuid(cpui, 0x1); + + return ((cpui[2] >> 31) & 1); +} + +uint32_t CpuInfo::model() const { + int cpui[4] = {0}; + __cpuid(cpui, 0x1); + // ax bits 0-3 stepping,4-7 model,8-11 family id,12-13 processor type + // 14-15 reserved, 16-19 extended model, 20-27 extended family, 27-31 reserved + // bx bits 0-7 brand index + return (cpui[0] & 0x3FFF) | (cpui[0] & 0x3FF8000) >> 2 | (cpui[1] & 0xff) << 24; +} + +string CpuInfo::vendor() const { + // hypervisor flag false, try to get the vendor name, see if it's a virtual cpu + int cpui[4] = {0}; + __cpuid(cpui, 0x0); + return string(reinterpret_cast<const char *>(cpui), 12); +} + +} /* namespace license */ diff --git a/src/library/os/windows/execution_environment.cpp b/src/library/os/windows/execution_environment.cpp new file mode 100644 index 0000000..b9d43ca --- /dev/null +++ b/src/library/os/windows/execution_environment.cpp @@ -0,0 +1,70 @@ +/* + * virtualization.cpp + * + * Created on: Dec 15, 2019 + * Author: GC + */ +#include <paths.h> +#include <sys/stat.h> +#include <unistd.h> +#include <fstream> +#include <iostream> +#include <stdio.h> +#include <string.h> +#include <dirent.h> +#include <sys/utsname.h> + +#include "isvm/BIOSReader.h" +#include "isvm/Native.h" +#include "../../base/base.h" +#include "../cpu_info.hpp" +#include "../execution_environment.hpp" + +namespace license { +using namespace std; + + + +VIRTUALIZATION ExecutionEnvironment::getVirtualization() { + VIRTUALIZATION result; + CpuInfo cpuInfo; + bool isContainer = false; + if (isContainer) { + result = CONTAINER; + } else if (cpuInfo.cpu_virtual() || is_cloud()) { + result = VM; + } else { + result = NONE; + } + return result; +} + +bool ExecutionEnvironment::is_cloud() { return getCloudProvider() != ON_PREMISE; } + +bool ExecutionEnvironment::is_docker() { return false; } + +CLOUD_PROVIDER ExecutionEnvironment::getCloudProvider() { + if (InitEntryPoints()) { + BIOSReader reader; + SystemInformation info = reader.readSystemInfo(); + + const char *vmVendors[] = { + "VMware", "Microsoft Corporation", "Virtual Machine", "innotek GmbH", "PowerVM", "Bochs", "KVM"}; + + const int count = _countof(vmVendors); + for (int i = 0; i != count; ++i) { + const char *vendor = vmVendors[i]; + + if (std::string::npos != info.Manufacturer.find(vendor) || + std::string::npos != info.ProductName.find(vendor) || + std::string::npos != info.SerialNum.find(vendor)) { + std::cout << "Inside virual machine!"; + return 1; + } + } + } else { + return -1; + } +} + +} // namespace license diff --git a/src/library/os/windows/isvm/BIOSReader.cpp b/src/library/os/windows/isvm/BIOSReader.cpp new file mode 100644 index 0000000..0974f1b --- /dev/null +++ b/src/library/os/windows/isvm/BIOSReader.cpp @@ -0,0 +1,145 @@ +#include "BIOSReader.h" + +#include <cstdint> + +#include "Native.h" + +struct smbios_structure_header +{ + uint8_t type; + uint8_t length; + uint16_t handle; +}; + +// +// System information +// +struct smbios_type_1 +{ + struct smbios_structure_header header; + uint8_t manufacturer_str; + uint8_t product_name_str; + uint8_t version_str; + uint8_t serial_number_str; + uint8_t uuid[16]; + uint8_t wake_up_type; + uint8_t sku_number_str; + uint8_t family_str; +}; + + + +#define _TYPE_COUNT1 6 + +#define _CONCATE(x,y) x##y +#define CONCATE(x,y) _CONCATE(x,y) +#define TYPE_COUNT(t) CONCATE(_TYPE_COUNT, t) + + +// +// Windows +// +#include <Windows.h> +#include <tchar.h> + + + + +int8_t * parse_smbiod_content(int8_t *addr, int8_t **indexes, int32_t *count) +{ + //! ignore 0 + int8_t parsed_count = 0; + + int8_t *raw_addr = addr; + + //! first one + if (indexes) + *indexes = raw_addr; + + bool reach_terminal = false; + + while (true) + { + if (0 == *raw_addr++) + { + if (reach_terminal) + break; + else + { + ++parsed_count; + + if (count && parsed_count < *count) + { + if (indexes) + *(indexes + parsed_count) = raw_addr; + } + + reach_terminal = true; + } + } + else + { + reach_terminal = false; + continue; + } + } + + if (count) + *count = parsed_count; + + return raw_addr; +} + +void read_smbios_type_1(int8_t *addr, SystemInformation *info) +{ + smbios_type_1 *t1 = (smbios_type_1 *)addr; + + int32_t offset = ((0x0F) & (t1->header.length >> 4)) * 16 + (t1->header.length & 0x0F); + + int8_t *string_addr[TYPE_COUNT(1)] = { 0 }; + + int32_t count = TYPE_COUNT(1); + parse_smbiod_content((int8_t *)t1 + offset, string_addr, &count); + + if (0 != t1->manufacturer_str) + info->Manufacturer = (std::string::traits_type::char_type *)string_addr[t1->manufacturer_str - 1]; + + if (0 != t1->product_name_str) + info->ProductName = (std::string::traits_type::char_type *)string_addr[t1->product_name_str - 1]; + + if (0 != t1->serial_number_str) + info->SerialNum = (std::string::traits_type::char_type *)string_addr[t1->serial_number_str - 1]; + + if (0 != t1->version_str) + info->SysVersion = (std::string::traits_type::char_type *)string_addr[t1->version_str - 1]; +} + +SystemInformation BIOSReader::readSystemInfo() +{ + SystemInformation info; + + uint32_t size = 0; + RawSMBIOSData *data = (RawSMBIOSData *)(LocateSMBIOS(&size)); + + if (NULL == data || 0 == size) + return info; + + smbios_structure_header *header = (smbios_structure_header *)(data->SMBIOSTableData); + + while (NULL != header) + { + if (1 == header->type) + { + read_smbios_type_1((int8_t *)header, &info); + header = NULL; //! stop + } + else + { + int32_t offset = ((0x0F) & (header->length >> 4)) * 16 + (header->length & 0x0F); + header = (smbios_structure_header *)parse_smbiod_content((int8_t *)header + offset, NULL, NULL); + } + } + + free(data); + return info; +} diff --git a/src/library/os/windows/isvm/BIOSReader.h b/src/library/os/windows/isvm/BIOSReader.h new file mode 100644 index 0000000..569facf --- /dev/null +++ b/src/library/os/windows/isvm/BIOSReader.h @@ -0,0 +1,21 @@ +#ifndef BIOSREADER_H +#define BIOSREADER_H + +#include <string> + +class SystemInformation +{ +public: + std::string Manufacturer; + std::string ProductName; + std::string SysVersion; + std::string SerialNum; +}; + +class BIOSReader +{ +public: + SystemInformation readSystemInfo(); +}; + +#endif diff --git a/src/library/os/windows/isvm/Native.cpp b/src/library/os/windows/isvm/Native.cpp new file mode 100644 index 0000000..99fbf8f --- /dev/null +++ b/src/library/os/windows/isvm/Native.cpp @@ -0,0 +1,437 @@ +#include "Native.h" + +#include <tchar.h> + +typedef LARGE_INTEGER PHYSICAL_ADDRESS, *PPHYSICAL_ADDRESS; // windbgkd + +typedef LONG NTSTATUS; +#define NT_SUCCESS(Status) ((NTSTATUS)(Status) >= 0) + +typedef struct _UNICODE_STRING +{ + USHORT Length; + USHORT MaximumLength; +#ifdef MIDL_PASS + [size_is(MaximumLength / 2), length_is((Length) / 2)] USHORT * Buffer; +#else // MIDL_PASS + PWSTR Buffer; +#endif // MIDL_PASS +} UNICODE_STRING; +typedef UNICODE_STRING *PUNICODE_STRING; + +typedef enum _SECTION_INHERIT +{ + ViewShare = 1, + ViewUnmap = 2 +} SECTION_INHERIT; + +#define OBJ_INHERIT 0x00000002L +#define OBJ_PERMANENT 0x00000010L +#define OBJ_EXCLUSIVE 0x00000020L +#define OBJ_CASE_INSENSITIVE 0x00000040L +#define OBJ_OPENIF 0x00000080L +#define OBJ_OPENLINK 0x00000100L +#define OBJ_VALID_ATTRIBUTES 0x000001F2L + +static bool bIsWindowsXPLater = false; +static DWORD dwPageSize = 0; + +#ifdef _UNICODE +#define GetVersionExProc "GetVersionExW" +#else +#define GetVersionExProc "GetVersionExA" +#endif + +typedef struct _OBJECT_ATTRIBUTES +{ + ULONG Length; + HANDLE RootDirectory; + PUNICODE_STRING ObjectName; + ULONG Attributes; + PVOID SecurityDescriptor; // Points to type SECURITY_DESCRIPTOR + PVOID SecurityQualityOfService; // Points to type SECURITY_QUALITY_OF_SERVICE +} OBJECT_ATTRIBUTES; +typedef OBJECT_ATTRIBUTES *POBJECT_ATTRIBUTES; + +typedef struct SMBIOSEntryPoint +{ + char EntryPointString[4]; + uint8_t Checksum; + uint8_t Length; + uint8_t MajorVersion; + uint8_t MinorVersion; + uint16_t MaxStructureSize; + uint8_t EntryPointRevision; + char FormattedArea[5]; + char EntryPointString2[5]; + uint8_t Checksum2; + uint16_t TableLength; + uint32_t TableAddress; + uint16_t NumberOfStructures; + uint8_t BCDRevision; +} SMBIOSEntryPoint, *PSMBIOSEntryPoint; + +#define InitializeObjectAttributes( p, n, a, r, s ) { \ + (p)->Length = sizeof(OBJECT_ATTRIBUTES); \ + (p)->RootDirectory = r; \ + (p)->Attributes = a; \ + (p)->ObjectName = n; \ + (p)->SecurityDescriptor = s; \ + (p)->SecurityQualityOfService = NULL; \ +} + +NTSTATUS(WINAPI *NtUnmapViewOfSection)( + IN HANDLE ProcessHandle, + IN PVOID BaseAddress + ); + +NTSTATUS(WINAPI *NtOpenSection)( + OUT PHANDLE SectionHandle, + IN ACCESS_MASK DesiredAccess, + IN POBJECT_ATTRIBUTES ObjectAttributes + ); + +NTSTATUS(WINAPI *NtMapViewOfSection)( + IN HANDLE SectionHandle, + IN HANDLE ProcessHandle, + IN OUT PVOID *BaseAddress, + IN ULONG ZeroBits, + IN ULONG CommitSize, + IN OUT PLARGE_INTEGER SectionOffset, /* optional */ + IN OUT PULONG ViewSize, + IN SECTION_INHERIT InheritDisposition, + IN ULONG AllocationType, + IN ULONG Protect + ); + +VOID(WINAPI *RtlInitUnicodeString)( + IN OUT PUNICODE_STRING DestinationString, + IN PCWSTR SourceString + ); + +ULONG(WINAPI *RtlNtStatusToDosError) ( + IN NTSTATUS Status + ); + +UINT(WINAPI *Win32GetSystemFirmwareTable)( + _In_ DWORD FirmwareTableProviderSignature, + _In_ DWORD FirmwareTableID, + _Out_writes_bytes_to_opt_(BufferSize, return) PVOID pFirmwareTableBuffer, + _In_ DWORD BufferSize); + +BOOL(WINAPI *Win32GetVersionEx)( + _Inout_ LPOSVERSIONINFO lpVersionInfo + ); + +VOID(WINAPI *Win32GetSystemInfo)( + _Out_ LPSYSTEM_INFO lpSystemInfo + ); + +BOOL(WINAPI *Win32VirtualProtect)( + _In_ LPVOID lpAddress, + _In_ SIZE_T dwSize, + _In_ DWORD flNewProtect, + _Out_ PDWORD lpflOldProtect + ); + +//---------------------------------------------------------------------- +// +// PrintError +// +// Formats an error message for the last error +// +// Mark Russinovich +// Systems Internals +// http://www.sysinternals.com +//---------------------------------------------------------------------- +void PrintError(char *message, NTSTATUS status) +{ + char *errMsg; + + FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, + NULL, RtlNtStatusToDosError(status), + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + (LPTSTR)&errMsg, 0, NULL); + LocalFree(errMsg); +} + +//-------------------------------------------------------- +// +// UnmapPhysicalMemory +// + +// Maps a view of a section. +// +// Mark Russinovich +// Systems Internals +// http://www.sysinternals.com +//-------------------------------------------------------- +static VOID UnmapPhysicalMemory(PVOID Address) +{ + NTSTATUS status; + + status = NtUnmapViewOfSection((HANDLE)-1, Address); + if (!NT_SUCCESS(status)) + { + PrintError("Unable to unmap view", status); + } +} + + +//-------------------------------------------------------- +// +// MapPhysicalMemory +// +// Maps a view of a section. +// +// Mark Russinovich +// Systems Internals +// http://www.sysinternals.com +//-------------------------------------------------------- +static BOOLEAN MapPhysicalMemory(HANDLE PhysicalMemory, + PVOID Address, PDWORD Length, + PVOID *VirtualAddress) +{ + NTSTATUS ntStatus; + PHYSICAL_ADDRESS viewBase; + char error[256]; + + viewBase.QuadPart = (ULONGLONG)(Address); + ntStatus = NtMapViewOfSection(PhysicalMemory, + (HANDLE)-1, + VirtualAddress, + 0L, *Length, + &viewBase, + Length, + ViewShare, + 0, + PAGE_READONLY); + + if (!NT_SUCCESS(ntStatus)) { + + PrintError(error, ntStatus); + return FALSE; + } + + return TRUE; +} + + +//-------------------------------------------------------- +// +// OpensPhysicalMemory +// +// This function opens the physical memory device. It +// uses the native API since +// +// Mark Russinovich +// Systems Internals +// http://www.sysinternals.com +//-------------------------------------------------------- +static HANDLE OpenPhysicalMemory() +{ + NTSTATUS status; + HANDLE physmem; + UNICODE_STRING physmemString; + OBJECT_ATTRIBUTES attributes; + WCHAR physmemName[] = L"\\device\\physicalmemory"; + + RtlInitUnicodeString(&physmemString, physmemName); + + InitializeObjectAttributes(&attributes, &physmemString, + OBJ_CASE_INSENSITIVE, NULL, NULL); + status = NtOpenSection(&physmem, SECTION_MAP_READ, &attributes); + + if (!NT_SUCCESS(status)) + { + PrintError("Could not open \\device\\physicalmemory", status); + return NULL; + } + + return physmem; +} + +static PVOID MapPhysicalMemoryWithBase(HANDLE hPhyHandle, PVOID pBase, PDWORD pLen, PVOID *pVirtualBase) +{ + DWORD dwOffset = (ULONGLONG)pBase % dwPageSize; + DWORD dwLen = *pLen + dwOffset; + + PVOID pVAddr = NULL; + + if (MapPhysicalMemory(hPhyHandle, pBase, &dwLen, &pVAddr)) + { + *pVirtualBase = pVAddr; + *pLen = dwLen; + + return (PBYTE)pVAddr + dwOffset; + } + else + { + return NULL; + } +} + +bool InitEntryPoints() +{ + Win32GetVersionEx = decltype(Win32GetVersionEx)(GetProcAddress(::GetModuleHandle(_T("kernel32.dll")), GetVersionExProc)); + if (!Win32GetVersionEx) + return false; + + Win32GetSystemInfo = decltype(Win32GetSystemInfo)(GetProcAddress(::GetModuleHandle(_T("kernel32.dll")), "GetSystemInfo")); + if (!Win32GetSystemInfo) + return false; + + OSVERSIONINFO osvi; + ZeroMemory(&osvi, sizeof(OSVERSIONINFO)); + osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFO); + + if (!Win32GetVersionEx(&osvi)) + return false; + + bIsWindowsXPLater = ((osvi.dwMajorVersion > 5) || ((osvi.dwMajorVersion == 5) && (osvi.dwMinorVersion > 1))); + + SYSTEM_INFO sysinfo; + ::ZeroMemory(&sysinfo, sizeof (SYSTEM_INFO)); + Win32GetSystemInfo(&sysinfo); + + dwPageSize = sysinfo.dwPageSize; + + if (bIsWindowsXPLater) + { + Win32GetSystemFirmwareTable = decltype(Win32GetSystemFirmwareTable)(GetProcAddress(GetModuleHandle(_T("kernel32.dll")), "GetSystemFirmwareTable")); + if (!Win32GetSystemFirmwareTable) + return false; + } + else + { + Win32VirtualProtect = decltype(Win32VirtualProtect)(GetProcAddress(GetModuleHandle(_T("kernel32.dll")), "VirtualProtect")); + if (!Win32VirtualProtect) + return false; + + RtlInitUnicodeString = (decltype(RtlInitUnicodeString))(GetProcAddress(GetModuleHandle(_T("ntdll.dll")), "RtlInitUnicodeString")); + if (!RtlInitUnicodeString) + return false; + + NtUnmapViewOfSection = (decltype(NtUnmapViewOfSection))(GetProcAddress(GetModuleHandle(_T("ntdll.dll")), "NtUnmapViewOfSection")); + if (!NtUnmapViewOfSection) + return false; + + NtOpenSection = (decltype(NtOpenSection))(GetProcAddress(GetModuleHandle(_T("ntdll.dll")), "NtOpenSection")); + if (!NtOpenSection) + return false; + + NtMapViewOfSection = (decltype(NtMapViewOfSection))(GetProcAddress(GetModuleHandle(_T("ntdll.dll")), "NtMapViewOfSection")); + if (!NtMapViewOfSection) + return false; + + RtlNtStatusToDosError = (decltype(RtlNtStatusToDosError))(GetProcAddress(GetModuleHandle(_T("ntdll.dll")), "RtlNtStatusToDosError")); + if (!NtMapViewOfSection) + return false; + } + + return true; +} + +void *LocateSMBIOS(uint32_t *smbios_size) +{ + void *buf = NULL; + if (bIsWindowsXPLater) + { + uint32_t size = 0; + size = Win32GetSystemFirmwareTable('RSMB', 0, buf, size); + if (0 == size) + { + return NULL; + } + + buf = malloc(size); + if (buf) + { + if (0 == Win32GetSystemFirmwareTable('RSMB', 0, buf, size)) + { + free(buf); + buf = NULL; + } + else + { + *smbios_size = size; + } + } + } + else + { + HANDLE hPhysMem = OpenPhysicalMemory(); + if (NULL == hPhysMem) + return NULL; + + DWORD dwReadLen = 0x10000; + DWORD dwActualLen = dwReadLen; + PVOID pBaseVAddr = NULL; + PVOID pVAddr = MapPhysicalMemoryWithBase(hPhysMem, (PVOID)0xF0000, &dwActualLen, &pBaseVAddr); + if (!pVAddr) + { + ::CloseHandle(hPhysMem); + return NULL; + } + + DWORD dwReadOffset = 0; + + PBYTE pbVAddr = (PBYTE)pVAddr; + PBYTE pbGuardVAddr = pbVAddr + dwReadLen; + + while (pbVAddr < pbGuardVAddr) + { + if (pbVAddr[0] == '_' && pbVAddr[1] == 'S' && pbVAddr[2] == 'M' && pbVAddr[3] == '_') + { + break; + } + + pbVAddr += 16; + } + + //! no SMBIOS found + if (pbVAddr >= pbGuardVAddr) + { + UnmapPhysicalMemory(pVAddr); + ::CloseHandle(hPhysMem); + + return NULL; + } + + PSMBIOSEntryPoint pEntryPoint = (PSMBIOSEntryPoint)pbVAddr; + + RawSMBIOSData *pData = (RawSMBIOSData *)::malloc(pEntryPoint->TableLength + sizeof(RawSMBIOSData)); + PVOID pTableBaseVAddr = NULL; + if (NULL != pData) + { + DWORD dwTableLen = pEntryPoint->TableLength; + PVOID pTableVAddr = MapPhysicalMemoryWithBase(hPhysMem, (PVOID)pEntryPoint->TableAddress, &dwTableLen, &pTableBaseVAddr); + if (!pTableVAddr) + { + UnmapPhysicalMemory(pBaseVAddr); + ::CloseHandle(hPhysMem); + return NULL; + } + + pData->Used20CallingMethod = 0; + pData->DmiRevision = 0; + pData->SMBIOSMajorVersion = pEntryPoint->MajorVersion; + pData->SMBIOSMinorVersion = pEntryPoint->MinorVersion; + pData->Length = pEntryPoint->TableLength; + + ::memcpy(pData->SMBIOSTableData, (PVOID)pTableVAddr, pEntryPoint->TableLength); + *smbios_size = pEntryPoint->TableLength; + } + + if (NULL != pTableBaseVAddr) + UnmapPhysicalMemory(pTableBaseVAddr); + if (NULL != pBaseVAddr) + UnmapPhysicalMemory(pBaseVAddr); + + ::CloseHandle(hPhysMem); + + buf = pData; + } + + return buf; +} diff --git a/src/library/os/windows/isvm/Native.h b/src/library/os/windows/isvm/Native.h new file mode 100644 index 0000000..5124c8d --- /dev/null +++ b/src/library/os/windows/isvm/Native.h @@ -0,0 +1,18 @@ +#pragma once + +#include <stdint.h> +#include <Windows.h> + +struct RawSMBIOSData +{ + BYTE Used20CallingMethod; + BYTE SMBIOSMajorVersion; + BYTE SMBIOSMinorVersion; + BYTE DmiRevision; + DWORD Length; + BYTE SMBIOSTableData[]; +}; + +bool InitEntryPoints(); +void *LocateSMBIOS(uint32_t *smbios_size); + diff --git a/src/library/os/windows/isvm/main.cpp b/src/library/os/windows/isvm/main.cpp new file mode 100644 index 0000000..105c105 --- /dev/null +++ b/src/library/os/windows/isvm/main.cpp @@ -0,0 +1,51 @@ +#include "intrin.h" + +#include <iostream> +#include "BIOSReader.h" +#include "Native.h" + +int _tmain(int argc, _TCHAR* argv[]) +{ + int cpui[4] = { 0 }; + __cpuid(cpui, 0x1); + + if ((cpui[2] >> 31) & 1) + { + std::cout << "Inside virual machine!"; + return 1; + } + + if (InitEntryPoints()) + { + BIOSReader reader; + SystemInformation info = reader.readSystemInfo(); + + const char *vmVendors[] = + { + "VMware", "Microsoft Corporation", "Virtual Machine", "innotek GmbH", "PowerVM", "Bochs", "KVM" + }; + + const int count = _countof(vmVendors); + for (int i = 0; i != count; ++i) + { + const char *vendor = vmVendors[i]; + + if (std::string::npos != info.Manufacturer.find(vendor) || + std::string::npos != info.ProductName.find(vendor) || + std::string::npos != info.SerialNum.find(vendor)) + { + std::cout << "Inside virual machine!"; + return 1; + } + } + } + else + { + return -1; + } + + std::cout << "Inside host machine!"; + + return 0; +} + diff --git a/src/library/os/windows/network.cpp b/src/library/os/windows/network.cpp new file mode 100644 index 0000000..fd111aa --- /dev/null +++ b/src/library/os/windows/network.cpp @@ -0,0 +1,113 @@ +/** + * @file network_id.c + * @date 16 Sep 2014 + * @brief File containing network interface detection functions for Windows. + * + * The only public function of this module is #getAdapterInfos(OsAdapterInfo *, + * size_t *), other functions are either static or inline. + * + * Responsibility of this module is to fill OsAdapterInfo structures, in a predictable way (skip loopback/vpn interfaces) + */ + +#ifdef _MSC_VER +#include <Windows.h> +#endif +#include <iphlpapi.h> +#include <unordered_map> +#include <stdio.h> +#pragma comment(lib, "IPHLPAPI.lib") + +#include "../../base/StringUtils.h" +#include "../../base/logger.h" +#include "../network.hpp" + +namespace license { +namespace os { +using namespace std; + +static int translate(char ipStringIn[16], unsigned char ipv4[4]) { + size_t index = 0; + + char *str2 = ipStringIn; /* save the pointer */ + while (*str2) { + if (isdigit((unsigned char)*str2)) { + ipv4[index] *= 10; + ipv4[index] += *str2 - '0'; + } else { + index++; + } + str2++; + } + return 0; +} +/** + * + * @param adapterInfos + * @param adapter_info_size + * @return + */ +FUNCTION_RETURN getAdapterInfos(vector<OsAdapterInfo> &adapterInfos) { + unordered_map<string, OsAdapterInfo> adapterByName; + FUNCTION_RETURN f_return = FUNC_RET_OK; + DWORD dwStatus; + int adapter_info_size; + PIP_ADAPTER_INFO pAdapterInfo; + DWORD dwBufLen = sizeof(IP_ADAPTER_INFO); + + unsigned int i = 3; + do { + pAdapterInfo = (PIP_ADAPTER_INFO)malloc(dwBufLen); + dwStatus = GetAdaptersInfo( // Call GetAdapterInfo + pAdapterInfo, // [out] buffer to receive data + &dwBufLen // [in] size of receive data buffer + ); + if (dwStatus != NO_ERROR && pAdapterInfo != nullptr) { + free(pAdapterInfo); + pAdapterInfo = nullptr; + } + } while (dwStatus == ERROR_BUFFER_OVERFLOW && i-- > 0); + + if (dwStatus == ERROR_BUFFER_OVERFLOW) { + return FUNC_RET_ERROR; + } + + adapter_info_size = dwBufLen / sizeof(IP_ADAPTER_INFO); + if (adapter_info_size == 0) { + return FUNC_RET_NOT_AVAIL; + } + + PIP_ADAPTER_INFO pAdapter = pAdapterInfo; + i = 0; + FUNCTION_RETURN result = FUNC_RET_OK; + while (pAdapter) { + OsAdapterInfo ai = {}; + strncpy(ai.description, pAdapter->Description, + min(sizeof(ai.description), MAX_ADAPTER_DESCRIPTION_LENGTH)); + memcpy(ai.mac_address, pAdapter->Address, 8); + translate(pAdapter->IpAddressList.IpAddress.String, ai.ipv4_address); + ai.type = IFACE_TYPE_ETHERNET; + i++; + pAdapter = pAdapter->Next; + if (i == adapter_info_size) { + result = FUNC_RET_BUFFER_TOO_SMALL; + break; + } + adapterByName[string(ai.description)] = ai; + } + free(pAdapterInfo); + + // FIXME sort by eth , enps, wlan + if (adapterByName.size() == 0) { + f_return = FUNC_RET_NOT_AVAIL; + } else { + f_return = FUNC_RET_OK; + adapterInfos.reserve(adapterByName.size()); + for (auto &it : adapterByName) { + adapterInfos.push_back(it.second); + } + } + return f_return; +} + +} // namespace os +} // namespace license diff --git a/src/library/os/windows/os-win.c b/src/library/os/windows/os-win.c index aa6c5f3..4a9e204 100644 --- a/src/library/os/windows/os-win.c +++ b/src/library/os/windows/os-win.c @@ -1,17 +1,13 @@ #ifdef _MSC_VER #include <Windows.h> #endif -#include "../base/logger.h" -#include "os.h" +#include "../../base/logger.h" +#include "../os.h" #include <iphlpapi.h> #include <stdio.h> #pragma comment(lib, "IPHLPAPI.lib") unsigned char* unbase64(const char* ascii, int len, int *flen); - -FUNCTION_RETURN getOsSpecificIdentifier(unsigned char identifier[6]) { - return FUNC_RET_NOT_AVAIL; -} FUNCTION_RETURN getMachineName(unsigned char identifier[6]) { FUNCTION_RETURN result = FUNC_RET_ERROR; @@ -100,79 +96,6 @@ return return_value; } -static int translate(char ipStringIn[16], unsigned char ipv4[4]) { - size_t index = 0; - - char* str2 = ipStringIn; /* save the pointer */ - while (*str2) { - if (isdigit((unsigned char) *str2)) { - ipv4[index] *= 10; - ipv4[index] += *str2 - '0'; - } else { - index++; - } - str2++; - } - return 0; -} - -//http://stackoverflow.com/questions/18046063/mac-address-using-c -//TODO: count only interfaces with type (MIB_IF_TYPE_ETHERNET IF_TYPE_IEEE80211) -FUNCTION_RETURN getAdapterInfos(OsAdapterInfo * adapterInfos, - size_t * adapter_info_size) { - DWORD dwStatus; - PIP_ADAPTER_INFO pAdapterInfo; - //IP_ADAPTER_INFO AdapterInfo[20]; // Allocate information for up to 16 NICs - DWORD dwBufLen = sizeof(IP_ADAPTER_INFO); //10 * sizeof(IP_ADAPTER_INFO); // Save the memory size of buffer - - unsigned int i = 3; - do { - pAdapterInfo = (PIP_ADAPTER_INFO) malloc(dwBufLen); - dwStatus = GetAdaptersInfo( // Call GetAdapterInfo - pAdapterInfo, // [out] buffer to receive data - &dwBufLen // [in] size of receive data buffer - ); - if (dwStatus != NO_ERROR) { - free(pAdapterInfo); - pAdapterInfo = NULL; - } - } while (dwStatus == ERROR_BUFFER_OVERFLOW && i-- > 0); - - if (dwStatus == ERROR_BUFFER_OVERFLOW) { - return FUNC_RET_ERROR; - } - - if (adapterInfos == NULL || *adapter_info_size == 0) { - *adapter_info_size = dwBufLen / sizeof(IP_ADAPTER_INFO); - if (pAdapterInfo != NULL){ - free(pAdapterInfo); - } - return FUNC_RET_OK; - } - - *adapter_info_size = dwBufLen / sizeof(IP_ADAPTER_INFO); - memset(adapterInfos, 0, dwBufLen); - PIP_ADAPTER_INFO pAdapter = pAdapterInfo; - i = 0; - FUNCTION_RETURN result = FUNC_RET_OK; - while (pAdapter) { - strncpy(adapterInfos[i].description, pAdapter->Description, - min(sizeof(adapterInfos->description), - MAX_ADAPTER_DESCRIPTION_LENGTH)); - memcpy(adapterInfos[i].mac_address, pAdapter->Address, 8); - translate(pAdapter->IpAddressList.IpAddress.String, - adapterInfos[i].ipv4_address); - adapterInfos[i].type = IFACE_TYPE_ETHERNET; - i++; - pAdapter = pAdapter->Next; - if (i == *adapter_info_size) { - result = FUNC_RET_BUFFER_TOO_SMALL; - break; - } - } - free(pAdapterInfo); - return result; -} FUNCTION_RETURN getModuleName(char buffer[MAX_PATH]) { FUNCTION_RETURN result = FUNC_RET_OK; @@ -183,29 +106,4 @@ return result; } -// TODO: remove unused -static void printHash(HCRYPTHASH* hHash) { - DWORD dwHashLen; - DWORD dwHashLenSize = sizeof(DWORD); - - if (CryptGetHashParam(*hHash, HP_HASHSIZE, (BYTE *) &dwHashLen, - &dwHashLenSize, 0)) { - BYTE* pbHash = (BYTE*)malloc(dwHashLen); - char* hashStr = (char*)malloc(dwHashLen * 2 + 1); - if (CryptGetHashParam(*hHash, HP_HASHVAL, pbHash, &dwHashLen, 0)) { - for (unsigned int i = 0; i < dwHashLen; i++) { - sprintf(&hashStr[i * 2], "%02x", pbHash[i]); - } LOG_DEBUG("Hash to verify: %s", hashStr); - } - free(pbHash); - free(hashStr); - } -} - -/** - * Not implemented yet. - */ -VIRTUALIZATION getVirtualization() { - return NONE; -} -- Gitblit v1.9.1