/************************************************************************* * UrBackup - Client/Server backup system * Copyright (C) 2021 Martin Raiber * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . **************************************************************************/ #include "vhdxfile.h" #include "../stringtools.h" #include #include #include "CompressedFile.h" #include "../urbackupcommon/os_functions.h" #include "FileWrapper.h" #include "ClientBitmap.h" #include "IFilesystem.h" #include "fs/ntfs.h" #define PAYLOAD_BLOCK_NOT_PRESENT 0 #define PAYLOAD_BLOCK_UNDEFINED 1 #define PAYLOAD_BLOCK_ZERO 2 #define PAYLOAD_BLOCK_UNMAPPED 3 #define PAYLOAD_BLOCK_FULLY_PRESENT 6 #define PAYLOAD_BLOCK_PARTIALLY_PRESENT 7 namespace { const int64 vhdx_header_length = 3 * 1024 * 1024; const int64 allocate_size_add_size = 100 * 1024 * 1024; const _u32 log_sector_size = 4096; template auto roundUp(T numToRound, T multiple) { return ((numToRound + multiple - 1) / multiple) * multiple; } std::vector getFileIdentifier() { std::vector ret; ret.resize(500); std::memcpy(ret.data(), "vhdxfile", 8); std::string creator = Server->ConvertToUTF16("UrBackup vhdx file"); std::memcpy(ret.data() + 8, creator.data(), creator.size()); return ret; } void secureRandomGuid(VhdxGUID& g) { Server->secureRandomFill(g, 16); g[6] = 0x40 | (g[6] & 0xf); g[8] = 0x80 | (g[8] & 0x3f); } void randomGuid(VhdxGUID& g) { Server->randomFill(g, 16); g[6] = 0x40 | (g[6] & 0xf); g[8] = 0x80 | (g[8] & 0x3f); } void zeroGUID(VhdxGUID& g) { memset(g, 0, 16); } bool equalsGUID(const VhdxGUID& a, const VhdxGUID& b) { return memcmp(a, b, sizeof(VhdxGUID)) == 0; } bool isZeroGUID(VhdxGUID& g) { VhdxGUID z = {}; return equalsGUID(g, z); } void copyGUID(const VhdxGUID& src, VhdxGUID& dst) { std::memcpy(dst, src, 16); } void reorderGUID(VhdxGUID& g) { *reinterpret_cast(&g[0]) = big_endian(*reinterpret_cast(&g[0])); *reinterpret_cast(&g[4]) = big_endian(*reinterpret_cast(&g[4])); *reinterpret_cast(&g[6]) = big_endian(*reinterpret_cast(&g[6])); } bool parseStrGuid(const std::string& str, VhdxGUID& g) { if (str.size() < 5) return false; if (str[0] != '{' || str[str.size() - 1] != '}') return false; std::string hb; for (size_t i = 1; i < str.size() - 1; ++i) { if(IsHex(str.substr(i, 1) ) ) hb+=str[i]; } if (hb.size() != 32) return false; for (size_t i = 0; i < hb.size(); i += 2) { std::string cb = hb.substr(i, 2); g[i/2] = static_cast(hexToULong(cb)); } reorderGUID(g); return true; } std::string strGUID(const VhdxGUID& g) { VhdxGUID tmp; copyGUID(g, tmp); reorderGUID(tmp); std::string ret = "{"; for (size_t i = 0; i < 16; ++i) { ret += byteToHex(tmp[i]); if (i == 3 || i==5 || i==7 || i==9) ret += "-"; } return ret + "}"; } unsigned int crc32c(unsigned char* data, size_t data_size) { unsigned int crc = 0xFFFFFFFF; for (size_t i = 0; i < data_size; ++i) { unsigned int b = data[i]; crc = crc ^ b; for (int j = 7; j >= 0; j--) { unsigned int mask = -1 * (crc & 1); crc = (crc >> 1) ^ (0x82F63B78 & mask); } } return ~crc; } std::vector getVhdxHeader(uint64 SequenceNumber) { std::vector ret; ret.resize(sizeof(VhdxHeader)); VhdxHeader* vhdxHeader = reinterpret_cast(ret.data()); std::memcpy(ret.data(), "head", 4); vhdxHeader->SequenceNumber = SequenceNumber; secureRandomGuid(vhdxHeader->FileWriteGuid); secureRandomGuid(vhdxHeader->DataWriteGuid); vhdxHeader->Version = 1; vhdxHeader->LogOffset = 1 * 1024 * 1024; vhdxHeader->LogLength = 1 * 1024 * 1024; vhdxHeader->Checksum = crc32c(reinterpret_cast(&ret[0]), ret.size()); return ret; } bool checkHeader(IFile* backing_file, VhdxHeader& header) { std::string ident(reinterpret_cast(&header), 4); if (ident != "head") { Server->Log("VHDX header tag wrong", LL_WARNING); return false; } _u32 ccrc = header.Checksum; header.Checksum = 0; if (crc32c(reinterpret_cast(&header), sizeof(header)) != ccrc) { header.Checksum = ccrc; Server->Log("VHDX header checksum wrong", LL_WARNING); return false; } header.Checksum = ccrc; return true; } #pragma pack(1) struct VhdxRegionTableHeader { _u32 Signature; _u32 Checksum; _u32 EntryCount; _u32 Reserved; }; #pragma pack() int64 getDataBlocks(int64 rawf_size, _u32 block_size) { int64 data_blocks = rawf_size / block_size; if (rawf_size % block_size != 0) ++data_blocks; return data_blocks; } _u32 getChunkRatio(_u32 block_size, _u32 sector_size) { return static_cast<_u32>((8388608LL * sector_size) / block_size); } _u32 getBatEntries(int64 size, _u32 block_size, _u32 sector_size) { int64 data_blocks = getDataBlocks(size, block_size); return static_cast<_u32>(data_blocks + (data_blocks - 1) / getChunkRatio(block_size, sector_size)); } _u32 getBatEntry(int64 pos, _u32 block_size, _u32 sector_size) { int64 data_blocks = pos / block_size; return static_cast<_u32>(data_blocks + (data_blocks - 1) / getChunkRatio(block_size, sector_size)); } _u32 getSectorBitmapEntry(int64 pos, _u32 block_size, _u32 sector_size) { int64 data_blocks = pos / block_size; _u32 chunk_ratio = getChunkRatio(block_size, sector_size); return static_cast<_u32>(data_blocks + (data_blocks - 1) / chunk_ratio + (chunk_ratio - data_blocks%chunk_ratio)); } _u32 getSectorBitmapOffset(int64 pos, _u32 block_size, _u32 sector_size) { int64 sector = pos / sector_size; return static_cast<_u32>(sector % 8388608LL); } bool isSectorSetInt(const char* sector_buf, int64 pos, _u32 block_size, _u32 sector_size) { _u32 offs = getSectorBitmapOffset(pos, block_size, sector_size); const char* byte = sector_buf + offs / 8; _u32 bitmap_bit = offs % 8; bool has_bit = (( (*byte) & (1 << bitmap_bit)) > 0); return has_bit; } void setSectorInt(char* sector_buf, int64 start, int64 end, _u32 block_size, _u32 sector_size) { while (start < end) { _u32 offs = getSectorBitmapOffset(start, block_size, sector_size); char* byte = sector_buf + offs / 8; _u32 bitmap_bit = offs % 8; *byte = *byte | (1 << bitmap_bit); start += sector_size; } } _u32 getBatLength(int64 rawf_size, _u32 block_size, _u32 sector_size) { int64 bat_entries = getBatEntries(rawf_size, block_size, sector_size); int64 mb_blocks = (bat_entries * sizeof(uint64)) / block_size; if (bat_entries % block_size != 0) ++mb_blocks; return static_cast<_u32>(mb_blocks * 1024 * 1024); } _u32 getSectorBitmapBlocksLength(int64 rawf_size, _u32 block_size, _u32 sector_size) { int64 data_blocks = getDataBlocks(rawf_size, block_size); _u32 chunk_ratio = getChunkRatio(block_size, sector_size); int64 sector_bitmaps = data_blocks / chunk_ratio; if (data_blocks % chunk_ratio != 0)++sector_bitmaps; return static_cast<_u32>(sector_bitmaps * 1 * 1024 * 1024); } int64 getMetadataSizeSize(int64 rawf_size, _u32 block_size, _u32 sector_size) { int64 data_blocks = getDataBlocks(rawf_size, block_size); _u32 chunk_ratio = getChunkRatio(block_size, sector_size); int64 sector_bitmaps = data_blocks / chunk_ratio; if (data_blocks % chunk_ratio != 0)++sector_bitmaps; // | -- HEADER -- | -- DATA BLOCKS -- | -- BAT -- | -- SECTOR BITMAP BLOCKS -- | return vhdx_header_length + data_blocks * block_size + getBatLength(rawf_size, block_size, sector_size) + getSectorBitmapBlocksLength(rawf_size, block_size, sector_size); } void makeMetaTableGUID(VhdxGUID& g) { unsigned char meta_guid[16] = { 0x8B, 0x7C, 0xA2, 0x06, 0x47, 0x90, 0x4B, 0x9A, 0xB8, 0xFE, 0x57, 0x5F, 0x05, 0x0F, 0x88, 0x6E }; std::memcpy(g, meta_guid, sizeof(meta_guid)); reorderGUID(g); } void makeBatGUID(VhdxGUID& g) { unsigned char bat_guid[16] = { 0x2D, 0xC2, 0x77, 0x66, 0xF6, 0x23, 0x42, 0x00, 0x9D, 0x64, 0x11, 0x5E, 0x9B, 0xFD, 0x4A, 0x08 }; std::memcpy(g, bat_guid, sizeof(bat_guid)); reorderGUID(g); } const uint64 meta_region_offset = 2 * 1024 * 1024; const uint64 bat_table_offset = meta_region_offset + 1 * 1024 * 1024; std::vector getVhdxRegionTable(int64 rawf_size, _u32 block_size, _u32 sector_size) { std::vector ret; ret.resize(64 * 1024); std::memcpy(ret.data(), "regi", 4); VhdxRegionTableHeader* header = reinterpret_cast(ret.data()); header->EntryCount = 2; VhdxRegionTableEntry* meta_entry = reinterpret_cast(ret.data() + sizeof(VhdxRegionTableHeader)); makeMetaTableGUID(meta_entry->Guid); meta_entry->FileOffset = meta_region_offset; meta_entry->Length = 1 * 1024 * 1024; meta_entry->Required = 1; VhdxRegionTableEntry* bat_entry = reinterpret_cast(ret.data() + sizeof(VhdxRegionTableHeader) + sizeof(VhdxRegionTableEntry)); makeBatGUID(bat_entry->Guid); bat_entry->FileOffset = bat_table_offset; bat_entry->Length = getBatLength(rawf_size, block_size, sector_size); bat_entry->Required = 1; header->Checksum = crc32c(reinterpret_cast(&ret[0]), ret.size()); return ret; } #pragma pack(1) struct VhdxMetadataTableHeader { uint64 Signature; unsigned short Reserved; unsigned short EntryCount; _u32 Reserved2[5]; }; struct VhdxMetadataTableEntry { VhdxGUID ItemId; _u32 Offset; _u32 Length; _u32 IsUser : 1; _u32 IsVirtualDisk : 1; _u32 IsRequired : 1; _u32 Reserved : 29; _u32 Reserved2; }; struct VhdxVirtualDiskSize { uint64 VirtualDiskSize; }; struct VhdxVirtualDiskLogicalSectorSize { _u32 LogicalSectorSize; }; struct VhdxPhysicalDiskSectorSize { _u32 PhysicalSectorSize; }; struct VhdxVirtualDiskId { VhdxGUID VirtualDiskId; }; struct VhdxParentLocatorHeader { VhdxGUID LocatorType; unsigned short Reserved; unsigned short KeyValueCount; }; struct VhdxParentLocatorEntry { _u32 KeyOffset; _u32 ValueOffset; unsigned short KeyLength; unsigned short ValueLength; }; #pragma pack() void makeFileParametersGUID(VhdxGUID& g) { unsigned char file_parameters_guid[16] = { 0xCA, 0xA1, 0x67, 0x37, 0xFA, 0x36, 0x4D, 0x43, 0xB3, 0xB6, 0x33, 0xF0, 0xAA, 0x44, 0xE7, 0x6B }; std::memcpy(&g, file_parameters_guid, sizeof(file_parameters_guid)); reorderGUID(g); } void makeVirtualDiskSizeGUID(VhdxGUID& g) { unsigned char virtual_disk_size_guid[16] = { 0x2F, 0xA5, 0x42, 0x24, 0xCD, 0x1B, 0x48, 0x76, 0xB2, 0x11, 0x5D, 0xBE, 0xD8, 0x3B, 0xF4, 0xB8 }; std::memcpy(&g, virtual_disk_size_guid, sizeof(virtual_disk_size_guid)); reorderGUID(g); } void makeLogicalSectorSizeGUID(VhdxGUID& g) { unsigned char logical_sector_size_guid[16] = { 0x81, 0x41, 0xBF, 0x1D, 0xA9, 0x6F, 0x47, 0x09, 0xBA, 0x47, 0xF2, 0x33, 0xA8, 0xFA, 0xAB, 0x5F }; std::memcpy(&g, logical_sector_size_guid, sizeof(logical_sector_size_guid)); reorderGUID(g); } void makePhysicalSectorSizeGUID(VhdxGUID& g) { unsigned char physical_sector_size_guid[16] = { 0xCD, 0xA3, 0x48, 0xC7, 0x44, 0x5D, 0x44, 0x71, 0x9C, 0xC9, 0xE9, 0x88, 0x52, 0x51, 0xC5, 0x56 }; std::memcpy(&g, physical_sector_size_guid, sizeof(physical_sector_size_guid)); reorderGUID(g); } void makeVirtualDiskIdGUID(VhdxGUID& g) { unsigned char page83_data_guid[16] = { 0xBE, 0xCA, 0x12, 0xAB, 0xB2, 0xE6, 0x45, 0x23, 0x93, 0xEF, 0xC3, 0x09, 0xE0, 0x00, 0xC7, 0x46 }; std::memcpy(&g, page83_data_guid, sizeof(page83_data_guid)); reorderGUID(g); } void makeParentLocatorGUID(VhdxGUID& g) { unsigned char parent_locator_guid[16] = { 0xA8, 0xD3, 0x5F, 0x2D, 0xB3, 0x0B, 0x45, 0x4D, 0xAB, 0xF7, 0xD3, 0xD8, 0x48, 0x34, 0xAB, 0x0C }; std::memcpy(&g, parent_locator_guid, sizeof(parent_locator_guid)); reorderGUID(g); } void makeVhdxParentLocatorGUID(VhdxGUID& g) { unsigned char vhdx_parent_locator_guid[16] = { 0xB0, 0x4A, 0xEF, 0xB7, 0xD1, 0x9E, 0x4A, 0x81, 0xB7, 0x89, 0x25, 0xB8, 0xE9, 0x44, 0x59, 0x13 }; std::memcpy(&g, vhdx_parent_locator_guid, sizeof(vhdx_parent_locator_guid)); reorderGUID(g); } std::vector getMetaRegion(int64 rawf_size, _u32 block_size, _u32 sector_size, std::string parent_data_uuid, std::string parent_rel_loc, std::string parent_abs_loc) { size_t parent_locator_size = 0; str_map parent_loc_entries; if (!parent_data_uuid.empty()) { parent_loc_entries[Server->ConvertToUTF16("parent_linkage")] = Server->ConvertToUTF16(parent_data_uuid); parent_loc_entries[Server->ConvertToUTF16("relative_path")] = Server->ConvertToUTF16(parent_rel_loc); parent_loc_entries[Server->ConvertToUTF16("absolute_win32_path")] = Server->ConvertToUTF16(parent_abs_loc); parent_locator_size = sizeof(VhdxParentLocatorHeader); parent_locator_size += sizeof(VhdxParentLocatorEntry) * parent_loc_entries.size(); for (auto it : parent_loc_entries) { parent_locator_size += it.first.size(); parent_locator_size += it.second.size(); } } std::vector ret; ret.resize(64 * 1024 + sizeof(VhdxFileParameters) + sizeof(VhdxVirtualDiskSize) + sizeof(VhdxVirtualDiskLogicalSectorSize) + sizeof(VhdxPhysicalDiskSectorSize) + sizeof(VhdxVirtualDiskId) + parent_locator_size); std::memcpy(ret.data(), "metadata", 8); VhdxMetadataTableHeader* header = reinterpret_cast(ret.data()); header->EntryCount = 5; VhdxMetadataTableEntry* file_parameters_entry = reinterpret_cast(ret.data() + sizeof(VhdxMetadataTableHeader)); makeFileParametersGUID(file_parameters_entry->ItemId); file_parameters_entry->Offset = 64 * 1024; file_parameters_entry->Length = sizeof(VhdxFileParameters); file_parameters_entry->IsRequired = 1; VhdxFileParameters* file_parameters = reinterpret_cast(ret.data() + file_parameters_entry->Offset); file_parameters->BlockSize = block_size; file_parameters->LeaveBlocksAllocated = 0; file_parameters->HasParent = parent_data_uuid.empty() ? 0 : 1; VhdxMetadataTableEntry* virtual_disk_size_entry = reinterpret_cast(ret.data() + sizeof(VhdxMetadataTableHeader) + sizeof(VhdxMetadataTableEntry)); makeVirtualDiskSizeGUID(virtual_disk_size_entry->ItemId); virtual_disk_size_entry->Offset = 64 * 1024 + sizeof(VhdxFileParameters); virtual_disk_size_entry->Length = sizeof(VhdxFileParameters); virtual_disk_size_entry->IsRequired = 1; virtual_disk_size_entry->IsVirtualDisk = 1; VhdxVirtualDiskSize* virtual_disk_size = reinterpret_cast(ret.data() + virtual_disk_size_entry->Offset); virtual_disk_size->VirtualDiskSize = rawf_size; VhdxMetadataTableEntry* logical_sector_size_entry = reinterpret_cast(ret.data() + sizeof(VhdxMetadataTableHeader) + 2 * sizeof(VhdxMetadataTableEntry)); makeLogicalSectorSizeGUID(logical_sector_size_entry->ItemId); logical_sector_size_entry->Offset = 64 * 1024 + sizeof(VhdxFileParameters) + sizeof(VhdxVirtualDiskSize); logical_sector_size_entry->Length = sizeof(VhdxVirtualDiskLogicalSectorSize); logical_sector_size_entry->IsRequired = 1; logical_sector_size_entry->IsVirtualDisk = 1; VhdxVirtualDiskLogicalSectorSize* logical_sector_size = reinterpret_cast(ret.data() + logical_sector_size_entry->Offset); logical_sector_size->LogicalSectorSize = sector_size; VhdxMetadataTableEntry* physical_sector_size_entry = reinterpret_cast(ret.data() + sizeof(VhdxMetadataTableHeader) + 3 * sizeof(VhdxMetadataTableEntry)); makePhysicalSectorSizeGUID(physical_sector_size_entry->ItemId); physical_sector_size_entry->Offset = 64 * 1024 + sizeof(VhdxFileParameters) + sizeof(VhdxVirtualDiskSize) + sizeof(VhdxVirtualDiskLogicalSectorSize); physical_sector_size_entry->Length = sizeof(VhdxPhysicalDiskSectorSize); physical_sector_size_entry->IsRequired = 1; physical_sector_size_entry->IsVirtualDisk = 1; VhdxPhysicalDiskSectorSize* physical_sector_size = reinterpret_cast(ret.data() + physical_sector_size_entry->Offset); physical_sector_size->PhysicalSectorSize = sector_size; VhdxMetadataTableEntry* page83_data_entry = reinterpret_cast(ret.data() + sizeof(VhdxMetadataTableHeader) + 4 * sizeof(VhdxMetadataTableEntry)); makeVirtualDiskIdGUID(page83_data_entry->ItemId); page83_data_entry->Offset = 64 * 1024 + sizeof(VhdxFileParameters) + sizeof(VhdxVirtualDiskSize) + sizeof(VhdxVirtualDiskLogicalSectorSize) + sizeof(VhdxPhysicalDiskSectorSize); page83_data_entry->Length = sizeof(VhdxVirtualDiskId); page83_data_entry->IsRequired = 1; page83_data_entry->IsVirtualDisk = 1; VhdxVirtualDiskId* virtual_disk_id = reinterpret_cast(ret.data() + page83_data_entry->Offset); secureRandomGuid(virtual_disk_id->VirtualDiskId); if (!parent_data_uuid.empty()) { ++header->EntryCount; VhdxMetadataTableEntry* parent_locator_entry = reinterpret_cast(ret.data() + sizeof(VhdxMetadataTableHeader) + 5 * sizeof(VhdxMetadataTableEntry)); makeParentLocatorGUID(parent_locator_entry->ItemId); parent_locator_entry->Offset = 64 * 1024 + sizeof(VhdxFileParameters) + sizeof(VhdxVirtualDiskSize) + sizeof(VhdxVirtualDiskLogicalSectorSize) + sizeof(VhdxPhysicalDiskSectorSize) + sizeof(VhdxVirtualDiskId); parent_locator_entry->Length = static_cast<_u32>(parent_locator_size); parent_locator_entry->IsRequired = 1; VhdxParentLocatorHeader* parent_locator_header = reinterpret_cast(ret.data() + parent_locator_entry->Offset); parent_locator_header->KeyValueCount = static_cast<_u16>(parent_loc_entries.size()); makeVhdxParentLocatorGUID(parent_locator_header->LocatorType); size_t entry_pos = parent_locator_entry->Offset + sizeof(VhdxParentLocatorHeader); size_t str_pos = parent_locator_entry->Offset + sizeof(VhdxParentLocatorHeader) + sizeof(VhdxParentLocatorEntry) * parent_loc_entries.size(); for (auto it: parent_loc_entries) { VhdxParentLocatorEntry* entry = reinterpret_cast(ret.data() + entry_pos); entry_pos += sizeof(VhdxParentLocatorEntry); entry->KeyOffset = static_cast<_u32>(str_pos - parent_locator_entry->Offset); entry->KeyLength = static_cast<_u16>(it.first.size()); std::memcpy(ret.data() + str_pos, it.first.data(), it.first.size()); str_pos += it.first.size(); entry->ValueOffset = static_cast<_u32>(str_pos - parent_locator_entry->Offset); entry->ValueLength = static_cast<_u16>(it.second.size()); std::memcpy(ret.data() + str_pos, it.second.data(), it.second.size()); str_pos += it.second.size(); } assert(str_pos == ret.size()); } return ret; } #pragma pack(1) struct LogEntryHeader { _u32 signature; _u32 Checksum; _u32 EntryLength; _u32 Tail; int64 SequenceNumber; _u32 DescriptorCount; _u32 Reserved; VhdxGUID LogGuid; int64 FlushedFileOffset; int64 LastFileOffset; }; struct LogZeroDescriptor { _u32 signature; _u32 Reserved; int64 ZeroLength; int64 FileOffset; int64 SequenceNumber; }; struct LogDataDescriptor { _u32 signature; char TrailingBytes[4]; char LeadingBytes[8]; int64 FileOffset; int64 SequenceNumber; }; struct LogDataSector { _u32 signature; _u32 SequenceHigh; char data[4084]; _u32 SequenceLow; }; #pragma pack() struct LogData { int64 offset; char data[4096]; }; struct LogEntry { std::vector to_zero; std::vector to_write; int64 sequence_number = -1; int64 length; int64 fsize; int64 new_fsize; uint64 tail_pos; }; typedef union { struct { _u32 LowPart; _u32 HighPart; }; struct { _u32 LowPart; _u32 HighPart; } u; int64 QuadPart; } SSequence; LogEntry readLogEntry(IFile* f, const VhdxGUID& log_guid, int64 off) { LogEntry loge; std::vector buf(4096); if (f->Read(off, buf.data(), static_cast<_u32>(buf.size())) != buf.size()) { Server->Log("Error reading log entry header. " + os_last_error_str(), LL_WARNING); return loge; } std::string signature(buf.data(), 4); if (signature != "loge") return loge; LogEntryHeader* header = reinterpret_cast(buf.data()); if (!equalsGUID(header->LogGuid, log_guid)) return loge; loge.length = header->EntryLength; std::vector entry_buf(header->EntryLength); if (f->Read(off, entry_buf.data(), static_cast<_u32>(entry_buf.size())) != entry_buf.size()) { Server->Log("Error reading log entry (size=" + std::to_string(header->EntryLength) + "). " + os_last_error_str(), LL_WARNING); return loge; } _u32 checksum = header->Checksum; header = reinterpret_cast(entry_buf.data()); header->Checksum = 0; _u32 checksum_calc = crc32c(reinterpret_cast(entry_buf.data()), entry_buf.size()); if (checksum_calc != checksum) { Server->Log("Log entry checksum is wrong", LL_WARNING); return loge; } int64 entry_seq = header->SequenceNumber; loge.fsize = header->FlushedFileOffset; loge.new_fsize = header->LastFileOffset; loge.tail_pos = header->Tail; int64 desc_off = 4096; if (header->DescriptorCount > 126) { desc_off += ((header->DescriptorCount - 126) / 128 ) *4096; if ( (header->DescriptorCount - 126) % 128 != 0) desc_off += 4096; } for (int64 i = 0; i < header->DescriptorCount; ++i) { char* desc_ptr = entry_buf.data() + 64 + i * 32; std::string desc_sig(desc_ptr, 4); if (desc_sig == "zero") { LogZeroDescriptor* zero_desc = reinterpret_cast(desc_ptr); if (entry_seq != zero_desc->SequenceNumber) { Server->Log("Zero log entry sequence number is wrong", LL_WARNING); return loge; } loge.to_zero.push_back(*zero_desc); } else if (desc_sig == "desc") { LogDataDescriptor* data_desc = reinterpret_cast(desc_ptr); if (entry_seq != data_desc->SequenceNumber) { Server->Log("Data log entry sequence number is wrong", LL_WARNING); return loge; } LogDataSector* data_sec = reinterpret_cast(entry_buf.data() + desc_off); std::string data_sec_sig(entry_buf.data() + desc_off, 4); if (data_sec_sig != "data") { Server->Log("Data log entry signature is wrong", LL_WARNING); return loge; } SSequence seq; seq.QuadPart = data_desc->SequenceNumber; if (data_sec->SequenceHigh != seq.HighPart) { Server->Log("Data log entry high sequence number is wrong", LL_WARNING); return loge; } if (data_sec->SequenceLow != seq.LowPart) { Server->Log("Data log entry low sequence number is wrong", LL_WARNING); return loge; } LogData log_data; log_data.offset = data_desc->FileOffset; std::memcpy(log_data.data, data_desc->LeadingBytes, 8); std::memcpy(log_data.data + 8, data_sec->data, sizeof(data_sec->data)); std::memcpy(log_data.data + 8 + sizeof(data_sec->data), data_desc->TrailingBytes, 4); loge.to_write.push_back(log_data); } else { Server->Log("Unknown log entry signature", LL_WARNING); return loge; } } loge.sequence_number = entry_seq; return loge; } } VHDXFile::VHDXFile(const std::string& fn, bool pRead_only, uint64 pDstsize, unsigned int pBlocksize, bool fast_mode, bool compress, size_t compress_n_threads) : dst_size(pDstsize), fast_mode(fast_mode), read_only(pRead_only) { is_open = open(fn, compress, compress_n_threads); } VHDXFile::VHDXFile(const std::string& fn, const std::string& parent_fn, bool pRead_only, bool fast_mode, bool compress, uint64 pDstsize, size_t compress_n_threads) : fast_mode(fast_mode), read_only(pRead_only), dst_size(pDstsize), parent_fn(parent_fn) { if (!FileExists(fn)) { parent = std::make_unique(parent_fn, true, 0); if (!parent->isOpen()) { Server->Log("Error opening VHDX parent at \"" + parent_fn + "\"", LL_ERROR); return; } dst_size = parent->getSize(); if (pDstsize > 0 && pDstsize != dst_size) { dst_size = pDstsize; } } is_open = open(fn, compress, compress_n_threads); } VHDXFile::~VHDXFile() { if (!is_open) return; if (!read_only) { finish(); } } bool VHDXFile::Seek(_i64 offset) { spos = offset; return true; } bool VHDXFile::Read(char* buffer, size_t bsize, size_t& read) { bool has_read_error = false; read = Read(spos, buffer, static_cast<_u32>(bsize), &has_read_error); spos += read; return !has_read_error; } _u32 VHDXFile::Write(const char* buffer, _u32 bsize, bool* has_error) { _u32 rc = Write(spos, buffer, bsize, has_error); spos += rc; return rc; } bool VHDXFile::isOpen(void) { return is_open; } uint64 VHDXFile::getSize(void) { return Size(); } uint64 VHDXFile::usedSize() { uint64 ret = 0; for (int64 i = 0; i < dst_size; i += block_size) { _u32 block = getBatEntry(spos, block_size, sector_size); VhdxBatEntry* bat_entry = reinterpret_cast(bat_buf.data()) + block; if (bat_entry->State == PAYLOAD_BLOCK_PARTIALLY_PRESENT || bat_entry->State == PAYLOAD_BLOCK_FULLY_PRESENT) ret += block_size; } return ret; } std::string VHDXFile::getFilename(void) { return file->getFilename(); } bool VHDXFile::has_sector(_i64 sector_size) { if (!has_sector_int(spos)) { if (parent.get() != nullptr) return parent->has_sector_int(spos); } return true; } bool VHDXFile::this_has_sector(_i64 sector_size) { return has_sector_int(spos); } unsigned int VHDXFile::getBlocksize() { return block_size; } bool VHDXFile::finish() { if (!finished) { finished = true; if (read_only) return true; bool ret = syncInt(true); if (ret && parent.get()!=nullptr) { ret = parent->finish(); } CompressedFile* compfile = dynamic_cast(file); if (compfile != nullptr) { if (compfile->finish()) { finished = true; return true; } } } return true; } bool VHDXFile::trimUnused(_i64 fs_offset, _i64 trim_blocksize, ITrimCallback* trim_callback) { return true; } bool VHDXFile::syncBitmap(_i64 fs_offset) { return true; } bool VHDXFile::makeFull(_i64 fs_offset, IVHDWriteCallback* write_callback) { FileWrapper devfile(this, fs_offset); std::unique_ptr bitmap_source; bitmap_source.reset(new ClientBitmap(backing_file->getFilename() + ".cbitmap")); if (bitmap_source->hasError()) { Server->Log("Error reading client bitmap. Falling back to reading bitmap from NTFS", LL_WARNING); bitmap_source.reset(new FSNTFS(&devfile, IFSImageFactory::EReadaheadMode_None, false, NULL)); } if (bitmap_source->hasError()) { Server->Log("Error opening NTFS bitmap. Cannot convert incremental to full image.", LL_WARNING); return false; } unsigned int bitmap_blocksize = static_cast(bitmap_source->getBlocksize()); std::vector buffer; buffer.resize(sector_size); int64 ntfs_blocks_per_vhd_sector = block_size / bitmap_blocksize; for (int64 ntfs_block = 0, n_ntfs_blocks = devfile.Size() / bitmap_blocksize; ntfs_block < n_ntfs_blocks; ntfs_block += ntfs_blocks_per_vhd_sector) { bool has_vhd_sector = false; for (int64 i = ntfs_block; i < ntfs_block + ntfs_blocks_per_vhd_sector && i < n_ntfs_blocks; ++i) { if (bitmap_source->hasBlock(i)) { has_vhd_sector = true; break; } } if (has_vhd_sector) { int64 block_pos = fs_offset + ntfs_block * bitmap_blocksize; int64 max_block_pos = (std::min)(fs_offset + ntfs_block * bitmap_blocksize + block_size, fs_offset + n_ntfs_blocks * bitmap_blocksize); for (int64 i = block_pos; i < max_block_pos; i += sector_size) { Seek(i); if (!has_block(false) && has_block(true)) { bool has_error = false; if (Read(buffer.data(), sector_size) != sector_size) { Server->Log("Error converting incremental to full image. Cannot read from parent VHDX file at position " + convert(i), LL_WARNING); return false; } if (!write_callback->writeVHD(i, buffer.data(), sector_size)) { Server->Log("Error converting incremental to full image. Cannot write to VHDX file at position " + convert(i), LL_WARNING); return false; } } } } else { int64 block_pos = ntfs_block * bitmap_blocksize; int64 max_block_pos = (std::min)(ntfs_block * bitmap_blocksize + block_size, n_ntfs_blocks * bitmap_blocksize); write_callback->emptyVHDBlock(block_pos, max_block_pos); } } parent.reset(); parent_fn.clear(); std::vector meta_region = getMetaRegion(dst_size, block_size, sector_size, std::string(), std::string(), std::string()); if (file->Write(meta_region_offset, meta_region.data(), static_cast<_u32>(meta_region.size())) != meta_region.size()) return false; return true; } bool VHDXFile::setUnused(_i64 unused_start, _i64 unused_end) { if (!Seek(unused_start)) { Server->Log("Error while sseking to " + convert(unused_end) + " in VHDX file." "Size is " + convert(dst_size) + " -2", LL_ERROR); return false; } if (read_only) { Server->Log("VHDX file is read only -2", LL_ERROR); return false; } if (unused_end > dst_size) { Server->Log("VHDX file is not large enough. Want to trim till " + convert(unused_end) + " but size is " + convert(dst_size), LL_ERROR); return false; } std::vector zero_buf; while (unused_start< unused_end) { int64 block = getBatEntry(unused_start, block_size, sector_size); VhdxBatEntry* bat_entry = reinterpret_cast(bat_buf.data()) + block; if (unused_start % block_size == 0 && unused_start + block_size <= unused_end) { bat_entry->State = PAYLOAD_BLOCK_ZERO; unused_start += block_size; continue; } _u32 curr_sector_size = sector_size; if (unused_start % sector_size != 0) { curr_sector_size = sector_size - unused_start % sector_size; } size_t wantwrite = (std::min)(static_cast(curr_sector_size), static_cast(unused_end - unused_start)); bool copy_prev = false; if (bat_entry->State == PAYLOAD_BLOCK_PARTIALLY_PRESENT) { bool set; if (!isSectorSet(unused_start, set)) { return false; } if (!set) { if (!setSector(unused_start)) { return false; } copy_prev = true; } else { if (zero_buf.size() != wantwrite) { zero_buf.resize(wantwrite); } _u32 rc = file->Write(bat_entry->FileOffsetMB * 1024 * 1024 + unused_start % block_size, zero_buf.data(), static_cast<_u32>(wantwrite)); } unused_start += wantwrite; } else if (bat_entry->State == PAYLOAD_BLOCK_UNDEFINED || bat_entry->State == PAYLOAD_BLOCK_UNMAPPED || bat_entry->State == PAYLOAD_BLOCK_NOT_PRESENT) { if (!allocateBatBlockFull(block)) { return false; } bat_entry->State = PAYLOAD_BLOCK_PARTIALLY_PRESENT; if (!setSector(unused_start)) { return false; } unused_start += wantwrite; copy_prev = true; } else if (bat_entry->State != PAYLOAD_BLOCK_ZERO) { Server->Log("Unknown bat entry state " + std::to_string(bat_entry->State), LL_ERROR); return false; } if (copy_prev && curr_sector_size < sector_size && parent.get()!=nullptr) { std::vector prev_buf(sector_size - curr_sector_size); int64 prev_pos = (unused_start / sector_size) * sector_size; _u32 rc = parent->Read(prev_pos, prev_buf.data(), static_cast<_u32>(prev_buf.size())); if (rc != prev_buf.size()) return false; rc = file->Write(bat_entry->FileOffsetMB * 1024 * 1024 + prev_pos % block_size, prev_buf.data(), static_cast<_u32>(prev_buf.size())); if (rc != prev_buf.size()) return false; } if (copy_prev && unused_start + wantwrite == unused_end && unused_end % sector_size != 0) { std::vector prev_buf(sector_size - unused_end%sector_size); int64 prev_pos = unused_end; _u32 rc = parent->Read(prev_pos, prev_buf.data(), static_cast<_u32>(prev_buf.size())); if (rc != prev_buf.size()) return false; rc = file->Write(bat_entry->FileOffsetMB * 1024 * 1024 + prev_pos % block_size, prev_buf.data(), static_cast<_u32>(prev_buf.size())); if (rc != prev_buf.size()) return false; } } return true; } bool VHDXFile::setBackingFileSize(_i64 fsize) { if (file != backing_file.get()) { return false; } fsize += 1 * 1024 * 1024; fsize += bat_region.Length; fsize += curr_header.LogLength; fsize += meta_table_region.Length; if (fsize > backing_file->Size()) { return backing_file->Resize(fsize); } return false; } std::string VHDXFile::Read(_u32 tr, bool* has_error) { std::string ret = Read(spos, tr, has_error); spos += ret.size(); return ret; } std::string VHDXFile::Read(int64 spos, _u32 tr, bool* has_error) { std::string ret; ret.resize(tr); _u32 rc = Read(spos, &ret[0], tr, has_error); if (rc < tr) ret.resize(rc); return ret; } _u32 VHDXFile::Read(char* buffer, _u32 bsize, bool* has_error) { _u32 rc = Read(spos, buffer, bsize, has_error); spos += rc; return rc; } _u32 VHDXFile::Read(int64 spos, char* buffer, _u32 bsize, bool* has_error) { if (spos> dst_size) { if (has_error != nullptr) *has_error = true; return 0; } else if (spos + bsize >= dst_size) { bsize = static_cast<_u32>(dst_size - spos); } _u32 read = 0; while (bsize - read > 0) { _u32 block = getBatEntry(spos, block_size, sector_size); VhdxBatEntry* bat_entry = reinterpret_cast(bat_buf.data()) + block; if (bat_entry->State == PAYLOAD_BLOCK_FULLY_PRESENT) { _u32 toread = (std::min)(block_size - static_cast<_u32>(spos % block_size), bsize - read); _u32 rc = file->Read(bat_entry->FileOffsetMB * 1024 * 1024 + spos % block_size, buffer + read, toread); read += rc; spos += rc; if (rc < toread) { if (has_error != nullptr) *has_error = true; return read; } continue; } if (parent.get()==nullptr) { _u32 toread = (std::min)(block_size - static_cast<_u32>(spos % block_size), bsize - read); if (bat_entry->State == PAYLOAD_BLOCK_PARTIALLY_PRESENT) { if (has_error != nullptr) *has_error = true; return read; } else if (bat_entry->State == PAYLOAD_BLOCK_NOT_PRESENT || bat_entry->State == PAYLOAD_BLOCK_UNDEFINED || bat_entry->State == PAYLOAD_BLOCK_ZERO || bat_entry->State == PAYLOAD_BLOCK_UNMAPPED) { toread = (std::min)(block_size - static_cast<_u32>(spos % block_size), bsize - read); memset(buffer+read, 0, toread); read += toread; spos += toread; } else { if (has_error != nullptr) *has_error = true; return read; } } else { _u32 toread; if (bat_entry->State != PAYLOAD_BLOCK_PARTIALLY_PRESENT) { toread = (std::min)(block_size - static_cast<_u32>(spos % block_size), bsize - read); } else { toread = (std::min)(sector_size - static_cast<_u32>(spos % sector_size), bsize - read); } if (bat_entry->State == PAYLOAD_BLOCK_PARTIALLY_PRESENT) { bool set; if (!isSectorSet(spos, set)) { if (has_error != nullptr) *has_error = true; return read; } _u32 rc; if (set) { rc = file->Read(bat_entry->FileOffsetMB * 1024 * 1024 + spos % block_size, buffer + read, toread); } else { rc = parent->Read(spos, buffer + read, toread); } read += rc; spos += rc; if (rc < toread) { if (has_error != nullptr) *has_error = true; return read; } } else if (bat_entry->State == PAYLOAD_BLOCK_UNDEFINED || bat_entry->State == PAYLOAD_BLOCK_ZERO || bat_entry->State == PAYLOAD_BLOCK_UNMAPPED) { memset(buffer + read, 0, toread); read += toread; spos += toread; } else if (bat_entry->State == PAYLOAD_BLOCK_NOT_PRESENT) { _u32 rc = parent->Read(spos, buffer + read, toread); read += rc; spos += rc; if (rc < toread) { if (has_error != nullptr) *has_error = true; return read; } } else { if (has_error != nullptr) *has_error = true; return read; } } } return read; } _u32 VHDXFile::Write(const std::string& tw, bool* has_error) { _u32 rc = Write(spos, tw.data(), static_cast<_u32>(tw.size()), has_error); spos += rc; return rc; } _u32 VHDXFile::Write(int64 spos, const std::string& tw, bool* has_error) { return Write(spos, tw.data(), static_cast<_u32>(tw.size()), has_error); } _u32 VHDXFile::Write(int64 spos, const char* buffer, _u32 bsize, bool* has_error) { if (spos > dst_size) { if (has_error != nullptr) *has_error = true; return 0; } else if (spos + bsize >= dst_size) { bsize = static_cast<_u32>(dst_size - spos); } if (!data_write_uuid_updated) { randomGuid(curr_header.DataWriteGuid); data_write_uuid_updated = true; if (!fast_mode && !updateHeader()) { if (has_error != nullptr) *has_error = true; return 0; } } _u32 written = 0; while (bsize - written > 0) { int64 block = getBatEntry(spos, block_size, sector_size); VhdxBatEntry* bat_entry = reinterpret_cast(bat_buf.data()) + block; if (bat_entry->State == PAYLOAD_BLOCK_FULLY_PRESENT) { _u32 towrite = (std::min)(block_size - static_cast<_u32>(spos % block_size), bsize - written); _u32 rc = file->Write(bat_entry->FileOffsetMB * 1024 * 1024 + spos % block_size, buffer + written, towrite); written += rc; spos += rc; if (rc < towrite) { if (has_error != nullptr) *has_error = true; return written; } continue; } if (parent.get()==nullptr) { _u32 towrite = (std::min)(block_size - static_cast<_u32>(spos % block_size), bsize - written); if (bat_entry->State == PAYLOAD_BLOCK_PARTIALLY_PRESENT) { if (has_error != nullptr) *has_error = true; return written; } else if (bat_entry->State != PAYLOAD_BLOCK_NOT_PRESENT && bat_entry->State != PAYLOAD_BLOCK_UNDEFINED && bat_entry->State != PAYLOAD_BLOCK_ZERO && bat_entry->State != PAYLOAD_BLOCK_UNMAPPED) { if (has_error != nullptr) *has_error = true; return written; } if (!allocateBatBlockFull(block)) { if (has_error != nullptr) *has_error = true; return written; } _u32 rc = file->Write(bat_entry->FileOffsetMB * 1024 * 1024 + spos % block_size, buffer + written, towrite); written += rc; spos += rc; if (rc < towrite) { if (has_error != nullptr) *has_error = true; return written; } } else { _u32 towrite = (std::min)(block_size - static_cast<_u32>(spos % block_size), bsize - written); if (bat_entry->State == PAYLOAD_BLOCK_PARTIALLY_PRESENT) { if (!setSector(spos, spos+towrite)) { if (has_error != nullptr) *has_error = true; return written; } } else if (bat_entry->State != PAYLOAD_BLOCK_NOT_PRESENT && bat_entry->State != PAYLOAD_BLOCK_UNDEFINED && bat_entry->State != PAYLOAD_BLOCK_ZERO && bat_entry->State != PAYLOAD_BLOCK_UNMAPPED) { if (has_error != nullptr) *has_error = true; return written; } else { if (!allocateBatBlockFull(block)) { if (has_error != nullptr) *has_error = true; return written; } bat_entry->State = PAYLOAD_BLOCK_PARTIALLY_PRESENT; if (!setSector(spos, spos+towrite)) { if (has_error != nullptr) *has_error = true; return written; } } _u32 rc = file->Write(bat_entry->FileOffsetMB * 1024 * 1024 + spos % block_size, buffer + written, towrite); written += rc; spos += rc; if (rc < towrite) { if (has_error != nullptr) *has_error = true; return written; } } } return written; } _i64 VHDXFile::Size(void) { return dst_size; } _i64 VHDXFile::RealSize() { return static_cast<_i64>(usedSize()); } bool VHDXFile::PunchHole(_i64 spos, _i64 size) { return false; } bool VHDXFile::Sync() { return syncInt(false); } bool VHDXFile::syncInt(bool full) { { std::lock_guard lock(pending_sector_bitmaps_mutex); for (_u32 sector_block : pending_sector_bitmaps) { VhdxBatEntry* sector_bat_entry = reinterpret_cast(bat_buf.data()) + sector_block; if (sector_bat_entry->State != PAYLOAD_BLOCK_FULLY_PRESENT) { Server->Log("Sector bitmap bat entry not fully present when syncing", LL_WARNING); return false; } auto it_sector_bitmap = sector_bitmap_bufs.find(sector_block); if (it_sector_bitmap == sector_bitmap_bufs.end()) { assert(false); return false; } if (file->Write(sector_bat_entry->FileOffsetMB * 1024 * 1024, it_sector_bitmap->second.data(), block_size) != block_size) { Server->Log("Error writing pending sector bitmap block. " + os_last_error_str(), LL_WARNING); return false; } } pending_sector_bitmaps.clear(); } bool retry; do { retry = false; std::unique_lock lock(log_mutex); int64 stop_idx = -1; if (!fast_mode) { int64 new_flushed_vhdx_size = file->Size(); if (flushed_vhdx_size != new_flushed_vhdx_size) { if (!file->Sync()) { Server->Log("Error syncing VHDX backing file -1. " + os_last_error_str(), LL_WARNING); return false; } flushed_vhdx_size = new_flushed_vhdx_size; } int64 b_idx = -1; int64 last_log_idx = -1; for (int64 entry_idx : pending_bat_entries) { int64 c_b_idx = (entry_idx * sizeof(int64)) / log_sector_size; if (b_idx != c_b_idx) { b_idx = c_b_idx; bool full = false; if (!logWrite(bat_region.FileOffset + c_b_idx * log_sector_size, bat_buf.data() + c_b_idx * log_sector_size, log_sector_size, -1, full)) { if (full) { stop_idx = entry_idx; retry = true; break; } else { Server->Log("Error logging VHDX BAT write", LL_WARNING); return false; } } } } if (!file->Sync()) { Server->Log("Error syncing VHDX backing file -2. " + os_last_error_str(), LL_WARNING); return false; } } int64 b_idx = -1; for (auto it = pending_bat_entries.begin(); it != pending_bat_entries.end();) { int64 entry_idx = *it; if (entry_idx == stop_idx) { break; } int64 c_b_idx = (entry_idx * sizeof(int64)) / log_sector_size; if (b_idx != c_b_idx) { b_idx = c_b_idx; _u32 rc = file->Write(bat_region.FileOffset + c_b_idx * log_sector_size, bat_buf.data() + c_b_idx * log_sector_size, log_sector_size); if (rc != log_sector_size) return false; } if (stop_idx != -1) { auto it_prev = it; ++it; pending_bat_entries.erase(it_prev); } else { ++it; } } if(stop_idx==-1) pending_bat_entries.clear(); if (fast_mode) { if (!file->Sync()) { Server->Log("Error syncing VHDX backing file -3. " + os_last_error_str(), LL_WARNING); return false; } } } while (retry); if (full && !fast_mode) { if (!file->Sync()) { Server->Log("Error syncing VHDX backing file -4. " + os_last_error_str(), LL_WARNING); return false; } zeroGUID(curr_header.LogGuid); if (!updateHeader()) return false; } return true; } void VHDXFile::getDataWriteGUID(VhdxGUID& g) { copyGUID(curr_header.DataWriteGuid, g); } bool VHDXFile::createNew() { memset(&curr_header, 0, sizeof(curr_header)); std::memcpy(&curr_header, "head", 4); curr_header.SequenceNumber = 1; secureRandomGuid(curr_header.FileWriteGuid); secureRandomGuid(curr_header.DataWriteGuid); data_write_uuid_updated = true; curr_header.Version = 1; curr_header.LogOffset = 1 * 1024 * 1024; curr_header.LogLength = 1 * 1024 * 1024; curr_header.Checksum = crc32c(reinterpret_cast(&curr_header), sizeof(curr_header)); log_pos = 0; log_start_pos = 0; block_size = 1 * 1024 * 1024; vhdx_params.BlockSize = block_size; sector_size = 512; std::vector ident = getFileIdentifier(); if (file->Write(0, ident.data(), static_cast<_u32>(ident.size())) != ident.size()) { Server->Log("Error writing new ident. " + os_last_error_str(), LL_WARNING); return false; } if (file->Write(64 * 1024, reinterpret_cast(&curr_header), sizeof(curr_header)) != sizeof(curr_header)) { Server->Log("Error writing new header 1. " + os_last_error_str(), LL_WARNING); return false; } if (file->Write(128 * 1024, reinterpret_cast(&curr_header), sizeof(curr_header)) != sizeof(curr_header)) { Server->Log("Error writing new header 2. " + os_last_error_str(), LL_WARNING); return false; } curr_header_pos = 64 * 1024; std::vector region_table = getVhdxRegionTable(dst_size, block_size, sector_size); meta_table_region.FileOffset = meta_region_offset; meta_table_region.Length = 1 * 1024 * 1024; bat_region.FileOffset = bat_table_offset; bat_region.Length = getBatLength(dst_size, block_size, sector_size); if (file->Write(192 * 1024, region_table.data(), static_cast<_u32>(region_table.size())) != region_table.size()) { Server->Log("Error writing new region table 1. " + os_last_error_str(), LL_WARNING); return false; } if (file->Write(256 * 1024, region_table.data(), static_cast<_u32>(region_table.size())) != region_table.size()) { Server->Log("Error writing new region table 2. " + os_last_error_str(), LL_WARNING); return false; } std::string parent_data_uuid; std::string parent_abs_path; std::string parent_rel_path; if (parent.get() != nullptr) { VhdxGUID g; parent->getDataWriteGUID(g); parent_data_uuid = strGUID(g); if (parent_fn.find("..") == 0) { parent_rel_path = greplace("/", "\\", parent_fn); std::string curr_dir = ExtractFilePath(file->getFilename()); parent_abs_path = parent_fn; while (next(parent_abs_path, 0, "..\\")) { curr_dir = ExtractFilePath(file->getFilename()); parent_abs_path.erase(0, 3); } if (!curr_dir.empty() && curr_dir[0] == '\\') { curr_dir.erase(0, 1); } parent_abs_path = os_file_prefix(parent_abs_path + "\\" + curr_dir); } else { parent_abs_path = os_file_prefix(parent_fn); std::string fn = file->getFilename(); std::string cparent_fn = parent_fn; while (fn.find("\\") != std::string::npos && cparent_fn.find("\\") != std::string::npos && getuntil("\\", fn)==getuntil("\\", cparent_fn)) { fn = getafter("\\", fn); cparent_fn = getafter("\\", cparent_fn); } parent_rel_path = cparent_fn; for (char ch : fn) { if (ch == '\\') parent_rel_path += "..\\"; } } } std::vector meta_region = getMetaRegion(dst_size, block_size, sector_size, parent_data_uuid, parent_rel_path, parent_abs_path); if (file->Write(meta_region_offset, meta_region.data(), static_cast<_u32>(meta_region.size())) != meta_region.size()) { Server->Log("Error writing new metadata region. " + os_last_error_str(), LL_WARNING); return false; } if (file == backing_file.get() && !backing_file->Resize(bat_region.FileOffset + bat_region.Length + allocate_size_add_size, false)) { Server->Log("Error writing new bat region. " + os_last_error_str(), LL_WARNING); return false; } allocated_size = file->Size(); next_payload_pos = bat_region.FileOffset + bat_region.Length; bat_buf.resize(bat_region.Length); return true; } bool VHDXFile::updateHeader() { ++curr_header.SequenceNumber; curr_header.Checksum = 0; curr_header.Checksum = crc32c(reinterpret_cast(&curr_header), sizeof(curr_header)); if (file->Write(curr_header_pos, reinterpret_cast(&curr_header), sizeof(curr_header)) != sizeof(curr_header)) { Server->Log("Error writing VHDX header to pos " + std::to_string(curr_header_pos) + ". " + os_last_error_str()); return false; } if (!file->Sync()) { Server->Log("Error syncing VHDX backing file after updating header. " + os_last_error_str(), LL_WARNING); return false; } if (curr_header_pos == 64 * 1024) curr_header_pos = 128 * 1024; else curr_header_pos = 64 * 1024; return true; } bool VHDXFile::replayLog() { VHDXFile::LogSequence seq = findLogSequence(); if (seq.max_sequence == 0) { Server->Log("Could not find VHDX log sequence -1", LL_WARNING); return false; } if (seq.entries.empty()) { Server->Log("Could not find VHDX log sequence -2", LL_WARNING); return false; } LogEntry head_entry = readLogEntry(file, curr_header.LogGuid, seq.entries[seq.entries.size()-1]); if (file->Size() < head_entry.fsize) { Server->Log("VHDX size smaller than expected from log expected="+std::to_string(head_entry.fsize) +" got="+std::to_string(file->Size()), LL_WARNING); return false; } for (int64 entry_pos : seq.entries) { LogEntry loge = readLogEntry(file, curr_header.LogGuid, entry_pos); if (loge.sequence_number == -1) { Server->Log("Error reading log entry while replaying log", LL_WARNING); return false; } if (file->Size() < loge.fsize) { Server->Log("VHDX size smaller than expected from log entry expected=" + std::to_string(loge.fsize) + " got=" + std::to_string(file->Size()), LL_WARNING); return false; } for (LogZeroDescriptor& zero_desc : loge.to_zero) { std::vector zero_buf(zero_desc.ZeroLength); if (file->Write(zero_desc.FileOffset, zero_buf.data(), static_cast<_u32>(zero_buf.size())) != zero_buf.size()) { Server->Log("Error writing zeroes from log. " + os_last_error_str(), LL_WARNING); return false; } } for (LogData& log_data : loge.to_write) { if (file->Write(log_data.offset, log_data.data, sizeof(log_data.data)) != sizeof(log_data.data)) { Server->Log("Error writing data from log. " + os_last_error_str(), LL_WARNING); return false; } } log_sequence_num = loge.sequence_number + 1; } int64 new_fsize = -1; if (file->Size() < head_entry.new_fsize && file == backing_file.get()) { if (backing_file->Resize(head_entry.new_fsize, false)) new_fsize = head_entry.new_fsize; } if (!file->Sync()) { Server->Log("Error syncing after writing log. " + os_last_error_str()); return false; } if (new_fsize >= 0) flushed_vhdx_size = new_fsize; zeroGUID(curr_header.LogGuid); return updateHeader(); } bool VHDXFile::readHeader() { std::string ident = file->Read(0LL, 8); if (ident != "vhdxfile") { Server->Log("VHDX header tag wrong", LL_WARNING); return false; } VhdxHeader header1, header2; if (file->Read(64LL * 1024, reinterpret_cast(&header1), sizeof(header1)) != sizeof(header1)) { Server->Log("Could not read VHDX header 1. " + os_last_error_str(), LL_WARNING); return false; } if (file->Read(128LL * 1024, reinterpret_cast(&header2), sizeof(header2)) != sizeof(header2)) { Server->Log("Could not read VHDX header 2. " + os_last_error_str(), LL_WARNING); return false; } VhdxHeader* sel_header = nullptr; if (checkHeader(file, header1)) { sel_header = &header1; curr_header_pos = 128LL * 1024; } if (checkHeader(file, header2) && header2.SequenceNumber > header1.SequenceNumber) { sel_header = &header2; curr_header_pos = 64LL * 1024; } if (sel_header == nullptr) { Server->Log("Both VHDX headers are invalid", LL_WARNING); return false; } std::memcpy(&curr_header, sel_header, sizeof(curr_header)); return true; } bool VHDXFile::readRegionTable(int64 off) { std::vector region_buf(64 * 1024); if (file->Read(off, region_buf.data(), static_cast<_u32>(region_buf.size())) != region_buf.size()) { Server->Log("Error reading VHDX region table. " + os_last_error_str(), LL_WARNING); return false; } if (std::string(region_buf.data(), 4) != "regi") { Server->Log("VHDX region table tag wrong", LL_WARNING); return false; } VhdxRegionTableHeader* header = reinterpret_cast(region_buf.data()); _u32 ccrc = header->Checksum; header->Checksum = 0; if (crc32c(reinterpret_cast(region_buf.data()), region_buf.size()) != ccrc) { Server->Log("VHDX region table checksum wrong", LL_WARNING); return false; } VhdxGUID meta_table_guid; makeMetaTableGUID(meta_table_guid); VhdxGUID bat_guid; makeBatGUID(bat_guid); unsigned int found = 0; for (_u32 i = 0; i < header->EntryCount; ++i) { VhdxRegionTableEntry* entry = reinterpret_cast(region_buf.data() + sizeof(VhdxRegionTableHeader) + i*sizeof(VhdxRegionTableEntry)); if (equalsGUID(entry->Guid, meta_table_guid)) { std::memcpy(&meta_table_region, entry, sizeof(meta_table_region)); if (found & 1) { Server->Log("Found metadata table region entry twice", LL_WARNING); return false; } found |= 1; } else if (equalsGUID(entry->Guid, bat_guid)) { std::memcpy(&bat_region, entry, sizeof(bat_region)); if (found & 2) { Server->Log("Found BAT table region entry twice", LL_WARNING); return false; } found |= 2; } else { Server->Log("Unknown region table entry " + strGUID(entry->Guid), LL_WARNING); return false; } } if ((found ^ (1 | 2)) != 0) { Server->Log("Did not find required region table entry. Found="+std::to_string(found), LL_WARNING); return false; } return true; } bool VHDXFile::readBat() { bat_buf.resize(bat_region.Length); const _u32 read_size = 512 * 1024; for (_u32 i = 0; i < bat_region.Length; i += read_size) { _u32 toread = (std::min)(read_size, bat_region.Length - i); if (file->Read(bat_region.FileOffset + i, bat_buf.data() + i, toread) != toread) { Server->Log("Error reading VHDX BAT at pos " + std::to_string(bat_region.FileOffset + i) + " toread " + std::to_string(toread) + ". " + os_last_error_str(), LL_WARNING); return false; } } return true; } bool VHDXFile::readMeta() { std::vector meta_table(64 * 1024); if (meta_table_region.Length < 64 * 1024) { Server->Log("Meta table region length smaller than 64KiB", LL_WARNING); return false; } if (file->Read(meta_table_region.FileOffset, meta_table.data(), static_cast<_u32>(meta_table.size())) != meta_table.size()) { Server->Log("Error reading VHDX meta table from pos " + std::to_string(meta_table_region.FileOffset) + ". " + os_last_error_str(), LL_WARNING); return false; } VhdxMetadataTableHeader* table_header = reinterpret_cast(meta_table.data()); std::string ident(meta_table.data(), 8); if (ident != "metadata") { Server->Log("Meta table ident wrong", LL_WARNING); return false; } sector_size = 0; physical_sector_size = 0; vhdx_params.BlockSize = 0; dst_size = -1; VhdxGUID parent_linkage_guid = {}; VhdxGUID file_parameters_guid, virtual_disk_size_guid, logical_sector_size_guid, physical_sector_size_guid, virtual_disk_id_guid, parent_locator_guid; makeFileParametersGUID(file_parameters_guid); makeVirtualDiskSizeGUID(virtual_disk_size_guid); makeLogicalSectorSizeGUID(logical_sector_size_guid); makePhysicalSectorSizeGUID(physical_sector_size_guid); makeVirtualDiskIdGUID(virtual_disk_id_guid); makeParentLocatorGUID(parent_locator_guid); std::string rel_parent_path; std::string volume_parent_path; std::string absolute_win32_parent_path; for (unsigned short i = 0; i < table_header->EntryCount; ++i) { if (32 + i * 32 + 32 > meta_table.size()) { Server->Log("Meta table not large enough", LL_WARNING); return false; } VhdxMetadataTableEntry* table_entry = reinterpret_cast(meta_table.data() + 32 + i * 32); if (table_entry->Offset < 64 * 1024) { Server->Log("Meta table offset wrong: " + std::to_string(table_entry->Offset), LL_WARNING); return false; } if (table_entry->Offset + table_entry->Length > meta_table_region.Length) { Server->Log("Meta table offset+length wrong: " + std::to_string(table_entry->Offset + table_entry->Length), LL_WARNING); return false; } std::vector entry_buf(table_entry->Length); if (file->Read(meta_table_region.FileOffset + table_entry->Offset, entry_buf.data(), static_cast<_u32>(entry_buf.size())) != entry_buf.size()) { Server->Log("Error reading meta table entry. " + os_last_error_str(), LL_WARNING); return false; } if (equalsGUID(table_entry->ItemId, file_parameters_guid)) { if (entry_buf.size() < sizeof(VhdxFileParameters)) { Server->Log("VhdxFileParameters entry not large enough", LL_WARNING); return false; } std::memcpy(&vhdx_params, entry_buf.data(), sizeof(vhdx_params)); } else if (equalsGUID(table_entry->ItemId, virtual_disk_size_guid)) { if (entry_buf.size() < sizeof(VhdxVirtualDiskSize)) { Server->Log("VhdxVirtualDiskSize entry not large enough", LL_WARNING); return false; } VhdxVirtualDiskSize* virtual_disk_size = reinterpret_cast(entry_buf.data()); dst_size = virtual_disk_size->VirtualDiskSize; } else if (equalsGUID(table_entry->ItemId, physical_sector_size_guid)) { if (entry_buf.size() < sizeof(VhdxPhysicalDiskSectorSize)) { Server->Log("VhdxPhysicalDiskSectorSize entry not large enough", LL_WARNING); return false; } VhdxPhysicalDiskSectorSize* physical_disk_sector_size = reinterpret_cast(entry_buf.data()); physical_sector_size = physical_disk_sector_size->PhysicalSectorSize; } else if (equalsGUID(table_entry->ItemId, logical_sector_size_guid)) { if (entry_buf.size() < sizeof(VhdxVirtualDiskLogicalSectorSize)) { Server->Log("VhdxVirtualDiskLogicalSectorSize entry not large enough", LL_WARNING); return false; } VhdxVirtualDiskLogicalSectorSize* logical_disk_sector_size = reinterpret_cast(entry_buf.data()); sector_size = logical_disk_sector_size->LogicalSectorSize; } else if (equalsGUID(table_entry->ItemId, virtual_disk_id_guid)) { if (entry_buf.size() < sizeof(VhdxVirtualDiskId)) { Server->Log("VhdxVirtualDiskId entry not large enough", LL_WARNING); return false; } VhdxVirtualDiskId* virtual_disk_id = reinterpret_cast(entry_buf.data()); } else if (equalsGUID(table_entry->ItemId, parent_locator_guid)) { if (entry_buf.size() < sizeof(VhdxParentLocatorHeader)) { Server->Log("Parent locator entry not large enough", LL_WARNING); return false; } VhdxParentLocatorHeader* parent_locator_header = reinterpret_cast(entry_buf.data()); VhdxGUID vhdx_parent_locator_guid; makeVhdxParentLocatorGUID(vhdx_parent_locator_guid); if (!equalsGUID(parent_locator_header->LocatorType, vhdx_parent_locator_guid)) { Server->Log("Unknown parent locator type " + strGUID(parent_locator_header->LocatorType), LL_WARNING); return false; } for (unsigned short i = 0; i < parent_locator_header->KeyValueCount; ++i) { VhdxParentLocatorEntry* parent_locator_entry = reinterpret_cast(entry_buf.data() + 20 + i * 12); if (parent_locator_entry->KeyOffset + parent_locator_entry->KeyLength >= entry_buf.size() || parent_locator_entry->KeyOffset>10*1024*1024) { Server->Log("Parent locator entry key offset not plausible: "+std::to_string(parent_locator_entry->KeyOffset)+ " length: "+std::to_string(parent_locator_entry->KeyLength), LL_WARNING); return false; } if (parent_locator_entry->ValueOffset + parent_locator_entry->ValueLength >= entry_buf.size() || parent_locator_entry->ValueOffset > 10 * 1024 * 1024) { Server->Log("Parent locator entry key offset not plausible: " + std::to_string(parent_locator_entry->ValueOffset)+ " length: " + std::to_string(parent_locator_entry->ValueLength), LL_WARNING); return false; } std::string key_vw(entry_buf.data() + parent_locator_entry->KeyOffset, parent_locator_entry->KeyLength); std::string value_vw(entry_buf.data() + parent_locator_entry->ValueOffset, parent_locator_entry->ValueLength); std::string key_v = Server->ConvertFromUTF16(key_vw); std::string value_v = Server->ConvertFromUTF16(value_vw); if (key_v == "parent_linkage") { if (!parseStrGuid(value_v, parent_linkage_guid)) { Server->Log("Error parsing parent linkage GUID " + value_v, LL_WARNING); return false; } } else if (key_v == "relative_path") { rel_parent_path = value_v; } else if (key_v == "volume_path") { volume_parent_path = value_v; } else if (key_v == "absolute_win32_path") { absolute_win32_parent_path = value_v; } } } else if(table_entry->IsRequired) { Server->Log("Required table entry " + strGUID(table_entry->ItemId) + " not suppoerted", LL_WARNING); return false; } } if (sector_size == 0 || physical_sector_size == 0 || vhdx_params.BlockSize == 0 || dst_size == -1) { Server->Log("Missing VHDX parameter. sector_size=" + std::to_string(sector_size) + " physical_sector_size=" + std::to_string(physical_sector_size) + " vhdx_params.BlockSize=" + std::to_string(vhdx_params.BlockSize)+ " dst_size=" + std::to_string(dst_size), LL_WARNING); return false; } block_size = vhdx_params.BlockSize; if (vhdx_params.HasParent) { if (isZeroGUID(parent_linkage_guid)) { Server->Log("Parent linkage GUID is zero", LL_WARNING); return false; } if (FileExists(absolute_win32_parent_path)) { parent.reset(new VHDXFile(absolute_win32_parent_path, true, 0)); } else if (FileExists(rel_parent_path)) { parent.reset(new VHDXFile(rel_parent_path, true, 0)); } if (parent.get() == nullptr || !parent->isOpen()) { Server->Log("Could not open parent vhdx at \"" + absolute_win32_parent_path + "\" or " "\"" + rel_parent_path + "\"", LL_WARNING); return false; } VhdxGUID dwg; parent->getDataWriteGUID(dwg); if (!equalsGUID(dwg, parent_linkage_guid)) { Server->Log("Parent linkage GUID differs. Got " + strGUID(dwg) + " expected " + strGUID(parent_linkage_guid), LL_WARNING); return false; } } return true; } bool VHDXFile::allocateBatBlockFull(int64 block) { VhdxBatEntry* bat_entry = reinterpret_cast(bat_buf.data()) + block; bat_entry->State = PAYLOAD_BLOCK_FULLY_PRESENT; int64 new_pos = next_payload_pos.fetch_add(block_size, std::memory_order_relaxed); if (new_pos > file->Size()) { allocated_size = new_pos + block_size + allocate_size_add_size; if (file == backing_file.get() && !backing_file->Resize(allocated_size, false)) { Server->Log("Error resizing backing file to new allocated size " + std::to_string(allocated_size) + ". " + os_last_error_str(), LL_WARNING); return false; } } assert(new_pos % (1 * 1024 * 1024) == 0); bat_entry->FileOffsetMB = new_pos / (1 * 1024 * 1024); bat_entry->Reserved = 0; { std::unique_lock lock(log_mutex); pending_bat_entries.insert(block); } return true; } void VHDXFile::calcNextPayloadPos() { int64 next_pos = 1 * 1024 * 1024; next_pos = (std::max)(next_pos, static_cast(bat_region.FileOffset + bat_region.Length)); next_pos = (std::max)(next_pos, static_cast(curr_header.LogOffset + curr_header.LogLength)); next_pos = (std::max)(next_pos, static_cast(meta_table_region.FileOffset + meta_table_region.Length)); _u32 bat_entries = getBatEntries(dst_size, block_size, sector_size); for (_u32 i = 0; i < bat_entries; ++i) { VhdxBatEntry* bat_entry = reinterpret_cast(bat_buf.data()) + i; next_pos = (std::max)(next_pos, static_cast(bat_entry->FileOffsetMB*1024*1024 + block_size)); } next_payload_pos = next_pos; } bool VHDXFile::open(const std::string& fn, bool compress, size_t compress_n_threads) { backing_file.reset(Server->openFile(fn, read_only ? MODE_READ : MODE_RW_CREATE)); if (backing_file.get() == nullptr) { Server->Log("Error opening VHDX backing file at \"" + fn + "\". " + os_last_error_str(), LL_WARNING); return false; } if (backing_file->Size() == 0) { if (read_only) { Server->Log("Read only vhdx file has zero size", LL_WARNING); return false; } if (compress) { compressed_file = std::make_unique(backing_file.get(), false, read_only, compress_n_threads); if (compressed_file->hasError()) { Server->Log("Error opening VHDX compressed file -1", LL_WARNING); return false; } file = compressed_file.get(); } else { file = backing_file.get(); } return createNew(); } else { if (check_if_compressed()) { compressed_file = std::make_unique(backing_file.get(), true, read_only, compress_n_threads); if (compressed_file->hasError()) { Server->Log("Error opening VHDX compressed file -2", LL_WARNING); return false; } file = compressed_file.get(); } else { file = backing_file.get(); } if (!readHeader()) { Server->Log("Error reading VHDX header", LL_WARNING); return false; } if (!readRegionTable(192 * 1024) && !readRegionTable(256 * 1024)) { Server->Log("Error reading any VHDX region table", LL_WARNING); return false; } if (!readBat()) { Server->Log("Error reading any VHDX bat", LL_WARNING); return false; } if (!readMeta()) { Server->Log("Error reading any VHDX metadata", LL_WARNING); return false; } if (read_only && !isZeroGUID(curr_header.LogGuid)) { Server->Log("VHDX is opened read only but has log entries", LL_WARNING); return false; } if (!read_only && !isZeroGUID(curr_header.LogGuid)) { if (!replayLog()) { Server->Log("Error replaying VHDX log", LL_WARNING); return false; } } calcNextPayloadPos(); allocated_size = backing_file->Size(); secureRandomGuid(curr_header.FileWriteGuid); flushed_vhdx_size = allocated_size; if (!fast_mode && !read_only && !updateHeader()) { return false; } return true; } } bool VHDXFile::has_sector_int(int64 spos) { if (spos >= dst_size) return true; _u32 block = getBatEntry(spos, block_size, sector_size); VhdxBatEntry* bat_entry = reinterpret_cast(bat_buf.data()) + block; return bat_entry->State == PAYLOAD_BLOCK_FULLY_PRESENT || bat_entry->State == PAYLOAD_BLOCK_PARTIALLY_PRESENT; } VHDXFile::LogSequence VHDXFile::findLogSequence() { LogSequence max_seq; max_seq.max_sequence = 0; for (uint64 log_pos = curr_header.LogOffset; log_pos < curr_header.LogOffset + curr_header.LogLength;) { LogSequence seq = findLogSequence(log_pos); if (seq.max_sequence > max_seq.max_sequence) max_seq = seq; } return max_seq; } VHDXFile::LogSequence& VHDXFile::validateSequence(LogSequence& seq) { if (seq.entries.empty()) return seq; LogEntry head = readLogEntry(file, curr_header.LogGuid, static_cast(seq.entries[seq.entries.size() - 1])); if (head.sequence_number == -1) { seq.entries.clear(); return seq; } if (curr_header.LogOffset + head.tail_pos != seq.entries[0]) { seq.entries.clear(); return seq; } return seq; } VHDXFile::LogSequence VHDXFile::findLogSequence(uint64& off) { int64 expected_seq = 0; VHDXFile::LogSequence seq; while (true) { LogEntry loge = readLogEntry(file, curr_header.LogGuid, static_cast(off)); if (loge.sequence_number == -1) { off += 4096; return validateSequence(seq); } if (expected_seq != 0 && expected_seq != loge.sequence_number) { return validateSequence(seq); } seq.entries.push_back(off); seq.max_sequence = loge.sequence_number; seq.fsize = loge.fsize; off += loge.length; off = (off - curr_header.LogOffset) % curr_header.LogLength + curr_header.LogOffset; expected_seq = loge.sequence_number + 1; } } bool VHDXFile::logWrite(int64 off, const char* buf, size_t bsize, int64 new_dst_size, bool& full) { if (bsize > 126 * log_sector_size) { assert(false); return false; } assert(bsize % log_sector_size == 0); if (isZeroGUID(curr_header.LogGuid)) { randomGuid(curr_header.LogGuid); log_pos = 0; log_start_pos = 0; if (!updateHeader()) return false; } size_t desc_count = roundUp(bsize, size_t{ log_sector_size }) / log_sector_size; std::vector log_entry(log_sector_size + roundUp(bsize, size_t{ log_sector_size } ) ); if (log_pos + log_entry.size() > curr_header.LogLength) { full = true; return false; } LogEntryHeader* header = reinterpret_cast(log_entry.data()); std::memcpy(&header->signature, "loge", 4); header->Checksum = 0; header->EntryLength = static_cast<_u32>(log_entry.size()); header->DescriptorCount = static_cast<_u32>(desc_count); header->Tail = static_cast<_u32>(log_start_pos); header->FlushedFileOffset = flushed_vhdx_size; if (new_dst_size <= 0) header->LastFileOffset = header->FlushedFileOffset; else header->LastFileOffset = new_dst_size; copyGUID(curr_header.LogGuid, header->LogGuid); header->SequenceNumber = log_sequence_num; ++log_sequence_num; for (size_t i = 0; i < desc_count; ++i) { LogDataDescriptor* data_desc = reinterpret_cast(log_entry.data() + 64 + i * 32); std::memcpy(&data_desc->signature, "desc", 4); data_desc->FileOffset = off + i * log_sector_size; std::memcpy(data_desc->LeadingBytes, buf + i * log_sector_size, 8); std::memcpy(data_desc->TrailingBytes, buf + i * log_sector_size + (log_sector_size - 4), 4); data_desc->SequenceNumber = header->SequenceNumber; } for (size_t i = 0; i < bsize; i += log_sector_size) { LogDataSector* data_sec = reinterpret_cast(log_entry.data() + log_sector_size + i * log_sector_size); std::memcpy(&data_sec->signature, "data", 4); SSequence seq; seq.QuadPart = header->SequenceNumber; data_sec->SequenceLow = seq.LowPart; data_sec->SequenceHigh = seq.HighPart; std::memcpy(data_sec->data, buf + i + 8, log_sector_size - 8 - 4); } header->Checksum = crc32c(reinterpret_cast(log_entry.data()), log_entry.size()); if (file->Write(curr_header.LogOffset + log_pos, log_entry.data(), static_cast<_u32>(log_entry.size())) != log_entry.size()) { Server->Log("Error writing VHDX log entry. " + os_last_error_str(), LL_WARNING); return false; } log_pos += log_entry.size(); return true; } char* VHDXFile::getSectorBitmap(_u32 sector_block, uint64 FileOffsetMB) { std::unique_lock lock(sector_bitmap_mutex); auto it_sector_bitmap = sector_bitmap_bufs.find(sector_block); if (it_sector_bitmap == sector_bitmap_bufs.end()) { lock.unlock(); std::vector sector_bitmap_buf(block_size); if (file->Read(FileOffsetMB * 1024 * 1024, sector_bitmap_buf.data(), static_cast<_u32>(sector_bitmap_buf.size())) != block_size) { Server->Log("Reading sector bitmap from mb offset " + std::to_string(FileOffsetMB) + " failed. " + os_last_error_str(), LL_ERROR); return nullptr; } lock.lock(); if (sector_bitmap_bufs.find(sector_block) == sector_bitmap_bufs.end()) { sector_bitmap_bufs[sector_block] = sector_bitmap_buf; } it_sector_bitmap = sector_bitmap_bufs.find(sector_block); lock.unlock(); } return it_sector_bitmap->second.data(); } char* VHDXFile::addZeroBitmap(_u32 sector_block) { std::unique_lock lock(sector_bitmap_mutex); auto it_sector_bitmap = sector_bitmap_bufs.find(sector_block); if (it_sector_bitmap == sector_bitmap_bufs.end()) { std::vector sector_bitmap_buf(block_size); return sector_bitmap_bufs.insert(std::make_pair(sector_block, sector_bitmap_buf)).first->second.data(); } return it_sector_bitmap->second.data(); } bool VHDXFile::isSectorSet(int64 spos, bool& set) { _u32 sector_block = getSectorBitmapEntry(spos, block_size, sector_size); VhdxBatEntry* sector_bat_entry = reinterpret_cast(bat_buf.data()) + sector_block; if (sector_bat_entry->State != PAYLOAD_BLOCK_FULLY_PRESENT) { Server->Log("Sector bitmap " + std::to_string(sector_block) + " not fully present", LL_WARNING); return false; } char* sector_bitmap = getSectorBitmap(sector_block, sector_bat_entry->FileOffsetMB); if (sector_bitmap == nullptr) { Server->Log("Error reading sector bitmap of sector block " + std::to_string(sector_block), LL_ERROR); return false; } set = isSectorSetInt(sector_bitmap, spos, block_size, sector_size); return true; } bool VHDXFile::setSector(int64 spos) { return setSector(spos, spos + sector_size); } bool VHDXFile::setSector(int64 start, int64 end) { _u32 sector_block = getSectorBitmapEntry(start, block_size, sector_size); VhdxBatEntry* sector_bat_entry = reinterpret_cast(bat_buf.data()) + sector_block; if (sector_bat_entry->State != PAYLOAD_BLOCK_FULLY_PRESENT && sector_bat_entry->State != PAYLOAD_BLOCK_NOT_PRESENT) { Server->Log("Sector bitmap " + std::to_string(sector_block) + " wrong state " +std::to_string(sector_bat_entry->State), LL_WARNING); return false; } char* sector_bitmap; if (sector_bat_entry->State == PAYLOAD_BLOCK_NOT_PRESENT) { if (!allocateBatBlockFull(sector_block)) return false; sector_bitmap = addZeroBitmap(sector_block); } else { sector_bitmap = getSectorBitmap(sector_block, sector_bat_entry->FileOffsetMB); } if (sector_bitmap == nullptr) return false; setSectorInt(sector_bitmap, start, end, block_size, sector_size); std::lock_guard lock(pending_sector_bitmaps_mutex); pending_sector_bitmaps.insert(sector_block); return true; } bool VHDXFile::check_if_compressed() { const char header_magic[] = "URBACKUP COMPRESSED FILE"; std::string magic = backing_file->Read(0LL, sizeof(header_magic) - 1); return magic == std::string(header_magic); } bool VHDXFile::has_block(bool use_parent) { if (!has_sector_int(spos)) { if (use_parent && parent.get() != nullptr) return parent->has_block(true); return false; } return true; }