1 // QcowHandler.cpp 2 3 #include "StdAfx.h" 4 5 // #include <stdio.h> 6 7 #include "../../../C/CpuArch.h" 8 9 #include "../../Common/ComTry.h" 10 #include "../../Common/IntToString.h" 11 #include "../../Common/MyBuffer2.h" 12 13 #include "../../Windows/PropVariant.h" 14 #include "../../Windows/PropVariantUtils.h" 15 16 #include "../Common/RegisterArc.h" 17 #include "../Common/StreamObjects.h" 18 #include "../Common/StreamUtils.h" 19 20 #include "../Compress/DeflateDecoder.h" 21 22 #include "HandlerCont.h" 23 24 #define Get32(p) GetBe32a(p) 25 #define Get64(p) GetBe64a(p) 26 27 using namespace NWindows; 28 29 namespace NArchive { 30 namespace NQcow { 31 32 static const Byte k_Signature[] = { 'Q', 'F', 'I', 0xFB, 0, 0, 0 }; 33 34 /* 35 VA to PA maps: 36 high bits (L1) : : index in L1 (_dir) : _dir[high_index] points to Table. 37 mid bits (L2) : _numMidBits : index in Table, Table[index] points to cluster start offset in arc file. 38 low bits : _clusterBits : offset inside cluster. 39 */ 40 41 Z7_class_CHandler_final: public CHandlerImg 42 { 43 Z7_IFACE_COM7_IMP(IInArchive_Img) 44 Z7_IFACE_COM7_IMP(IInArchiveGetStream) 45 Z7_IFACE_COM7_IMP(ISequentialInStream) 46 47 unsigned _clusterBits; 48 unsigned _numMidBits; 49 UInt64 _compressedFlag; 50 51 CObjArray2<UInt32> _dir; 52 CAlignedBuffer _table; 53 CByteBuffer _cache; 54 CByteBuffer _cacheCompressed; 55 UInt64 _cacheCluster; 56 57 UInt64 _comprPos; 58 size_t _comprSize; 59 60 bool _needCompression; 61 bool _isArc; 62 bool _unsupported; 63 Byte _compressionType; 64 65 UInt64 _phySize; 66 67 CMyComPtr2<ISequentialInStream, CBufInStream> _bufInStream; 68 CMyComPtr2<ISequentialOutStream, CBufPtrSeqOutStream> _bufOutStream; 69 CMyComPtr2<ICompressCoder, NCompress::NDeflate::NDecoder::CCOMCoder> _deflateDecoder; 70 71 UInt32 _version; 72 UInt32 _cryptMethod; 73 UInt64 _incompatFlags; 74 75 HRESULT Seek2(UInt64 offset) 76 { 77 _posInArc = offset; 78 return InStream_SeekSet(Stream, offset); 79 } 80 81 HRESULT InitAndSeek() 82 { 83 _virtPos = 0; 84 return Seek2(0); 85 } 86 87 HRESULT Open2(IInStream *stream, IArchiveOpenCallback *openCallback) Z7_override; 88 }; 89 90 91 static const UInt32 kEmptyDirItem = (UInt32)0 - 1; 92 93 Z7_COM7F_IMF(CHandler::Read(void *data, UInt32 size, UInt32 *processedSize)) 94 { 95 if (processedSize) 96 *processedSize = 0; 97 // printf("\nRead _virtPos = %6d size = %6d\n", (UInt32)_virtPos, size); 98 if (_virtPos >= _size) 99 return S_OK; 100 { 101 const UInt64 rem = _size - _virtPos; 102 if (size > rem) 103 size = (UInt32)rem; 104 if (size == 0) 105 return S_OK; 106 } 107 108 for (;;) 109 { 110 const UInt64 cluster = _virtPos >> _clusterBits; 111 const size_t clusterSize = (size_t)1 << _clusterBits; 112 const size_t lowBits = (size_t)_virtPos & (clusterSize - 1); 113 { 114 const size_t rem = clusterSize - lowBits; 115 if (size > rem) 116 size = (UInt32)rem; 117 } 118 if (cluster == _cacheCluster) 119 { 120 memcpy(data, _cache + lowBits, size); 121 break; 122 } 123 124 const UInt64 high = cluster >> _numMidBits; 125 126 if (high < _dir.Size()) 127 { 128 const UInt32 tabl = _dir[(size_t)high]; 129 if (tabl != kEmptyDirItem) 130 { 131 const size_t midBits = (size_t)cluster & (((size_t)1 << _numMidBits) - 1); 132 const Byte *p = _table + ((((size_t)tabl << _numMidBits) + midBits) << 3); 133 UInt64 v = Get64(p); 134 135 if (v) 136 { 137 if (v & _compressedFlag) 138 { 139 if (_version <= 1) 140 return E_FAIL; 141 /* 142 the example of table record for 12-bit clusters (4KB uncompressed): 143 2 bits : isCompressed status 144 (4 == _clusterBits - 8) bits : (num_sectors - 1) 145 packSize = num_sectors * 512; 146 it uses one additional bit over unpacked cluster_bits. 147 (49 == 61 - _clusterBits) bits : offset of 512-byte sector 148 9 bits : offset in 512-byte sector 149 */ 150 const unsigned numOffsetBits = 62 - (_clusterBits - 8); 151 const UInt64 offset = v & (((UInt64)1 << 62) - 1); 152 const size_t dataSize = ((size_t)(offset >> numOffsetBits) + 1) << 9; 153 UInt64 sectorOffset = offset & (((UInt64)1 << numOffsetBits) - (1 << 9)); 154 const UInt64 offset2inCache = sectorOffset - _comprPos; 155 156 // _comprPos is aligned for 512-bytes 157 // we try to use previous _cacheCompressed that contains compressed data 158 // that was read for previous unpacking 159 160 if (sectorOffset >= _comprPos && offset2inCache < _comprSize) 161 { 162 if (offset2inCache) 163 { 164 _comprSize -= (size_t)offset2inCache; 165 memmove(_cacheCompressed, _cacheCompressed + (size_t)offset2inCache, _comprSize); 166 _comprPos = sectorOffset; 167 } 168 sectorOffset += _comprSize; 169 } 170 else 171 { 172 _comprPos = sectorOffset; 173 _comprSize = 0; 174 } 175 176 if (dataSize > _comprSize) 177 { 178 if (sectorOffset != _posInArc) 179 { 180 // printf("\nDeflate-Seek %12I64x %12I64x\n", sectorOffset, sectorOffset - _posInArc); 181 RINOK(Seek2(sectorOffset)) 182 } 183 if (_cacheCompressed.Size() < dataSize) 184 return E_FAIL; 185 const size_t dataSize3 = dataSize - _comprSize; 186 size_t dataSize2 = dataSize3; 187 // printf("\n\n=======\nReadStream = %6d _comprPos = %6d \n", (UInt32)dataSize2, (UInt32)_comprPos); 188 const HRESULT hres = ReadStream(Stream, _cacheCompressed + _comprSize, &dataSize2); 189 _posInArc += dataSize2; 190 RINOK(hres) 191 if (dataSize2 != dataSize3) 192 return E_FAIL; 193 _comprSize += dataSize2; 194 } 195 196 const size_t kSectorMask = (1 << 9) - 1; 197 const size_t offsetInSector = (size_t)offset & kSectorMask; 198 _bufInStream->Init(_cacheCompressed + offsetInSector, dataSize - offsetInSector); 199 _cacheCluster = (UInt64)(Int64)-1; 200 if (_cache.Size() < clusterSize) 201 return E_FAIL; 202 _bufOutStream->Init(_cache, clusterSize); 203 // Do we need to use smaller block than clusterSize for last cluster? 204 const UInt64 blockSize64 = clusterSize; 205 HRESULT res = _deflateDecoder.Interface()->Code(_bufInStream, _bufOutStream, NULL, &blockSize64, NULL); 206 /* 207 if (_bufOutStreamSpec->GetPos() != clusterSize) 208 memset(_cache + _bufOutStreamSpec->GetPos(), 0, clusterSize - _bufOutStreamSpec->GetPos()); 209 */ 210 if (res == S_OK) 211 if (!_deflateDecoder->IsFinished() 212 || _bufOutStream->GetPos() != clusterSize) 213 res = S_FALSE; 214 RINOK(res) 215 _cacheCluster = cluster; 216 continue; 217 /* 218 memcpy(data, _cache + lowBits, size); 219 break; 220 */ 221 } 222 223 // version_3 supports zero clusters 224 if (((UInt32)v & 511) != 1) 225 { 226 v &= _compressedFlag - 1; 227 v += lowBits; 228 if (v != _posInArc) 229 { 230 // printf("\n%12I64x\n", v - _posInArc); 231 RINOK(Seek2(v)) 232 } 233 const HRESULT res = Stream->Read(data, size, &size); 234 _posInArc += size; 235 _virtPos += size; 236 if (processedSize) 237 *processedSize = size; 238 return res; 239 } 240 } 241 } 242 } 243 244 memset(data, 0, size); 245 break; 246 } 247 248 _virtPos += size; 249 if (processedSize) 250 *processedSize = size; 251 return S_OK; 252 } 253 254 255 static const Byte kProps[] = 256 { 257 kpidSize, 258 kpidPackSize 259 }; 260 261 static const Byte kArcProps[] = 262 { 263 kpidClusterSize, 264 kpidSectorSize, // actually we need variable to show table size 265 kpidHeadersSize, 266 kpidUnpackVer, 267 kpidMethod, 268 kpidCharacts 269 }; 270 271 IMP_IInArchive_Props 272 IMP_IInArchive_ArcProps 273 274 static const CUInt32PCharPair g_IncompatFlags_Characts[] = 275 { 276 { 0, "Dirty" }, 277 { 1, "Corrupt" }, 278 { 2, "External_Data_File" }, 279 { 3, "Compression" }, 280 { 4, "Extended_L2" } 281 }; 282 283 Z7_COM7F_IMF(CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value)) 284 { 285 COM_TRY_BEGIN 286 NCOM::CPropVariant prop; 287 288 switch (propID) 289 { 290 case kpidMainSubfile: prop = (UInt32)0; break; 291 case kpidClusterSize: prop = (UInt32)1 << _clusterBits; break; 292 case kpidSectorSize: prop = (UInt32)1 << (_numMidBits + 3); break; 293 case kpidHeadersSize: prop = _table.Size() + (UInt64)_dir.Size() * 8; break; 294 case kpidPhySize: if (_phySize) prop = _phySize; break; 295 case kpidUnpackVer: prop = _version; break; 296 case kpidCharacts: 297 { 298 if (_incompatFlags) 299 { 300 AString s ("incompatible: "); 301 // we need to show also high 32-bits. 302 s += FlagsToString(g_IncompatFlags_Characts, 303 Z7_ARRAY_SIZE(g_IncompatFlags_Characts), (UInt32)_incompatFlags); 304 prop = s; 305 } 306 break; 307 } 308 case kpidMethod: 309 { 310 AString s; 311 312 if (_compressionType) 313 { 314 if (_compressionType == 1) 315 s += "ZSTD"; 316 else 317 { 318 s += "Compression:"; 319 s.Add_UInt32(_compressionType); 320 } 321 } 322 else if (_needCompression) 323 s.Add_OptSpaced("Deflate"); 324 325 if (_cryptMethod) 326 { 327 s.Add_Space_if_NotEmpty(); 328 if (_cryptMethod == 1) 329 s += "AES"; 330 if (_cryptMethod == 2) 331 s += "LUKS"; 332 else 333 { 334 s += "Encryption:"; 335 s.Add_UInt32(_cryptMethod); 336 } 337 } 338 if (!s.IsEmpty()) 339 prop = s; 340 break; 341 } 342 343 case kpidErrorFlags: 344 { 345 UInt32 v = 0; 346 if (!_isArc) v |= kpv_ErrorFlags_IsNotArc; 347 if (_unsupported) v |= kpv_ErrorFlags_UnsupportedMethod; 348 // if (_headerError) v |= kpv_ErrorFlags_HeadersError; 349 if (!Stream && v == 0) 350 v = kpv_ErrorFlags_HeadersError; 351 if (v) 352 prop = v; 353 break; 354 } 355 } 356 357 prop.Detach(value); 358 return S_OK; 359 COM_TRY_END 360 } 361 362 363 Z7_COM7F_IMF(CHandler::GetProperty(UInt32 /* index */, PROPID propID, PROPVARIANT *value)) 364 { 365 COM_TRY_BEGIN 366 NCOM::CPropVariant prop; 367 368 switch (propID) 369 { 370 case kpidSize: prop = _size; break; 371 case kpidPackSize: prop = _phySize; break; 372 case kpidExtension: prop = (_imgExt ? _imgExt : "img"); break; 373 } 374 375 prop.Detach(value); 376 return S_OK; 377 COM_TRY_END 378 } 379 380 381 HRESULT CHandler::Open2(IInStream *stream, IArchiveOpenCallback *openCallback) 382 { 383 UInt64 buf64[0x70 / 8]; 384 RINOK(ReadStream_FALSE(stream, buf64, sizeof(buf64))) 385 const void *buf = (const void *)buf64; 386 // signature: { 'Q', 'F', 'I', 0xFB } 387 if (*(const UInt32 *)buf != Z7_CONV_BE_TO_NATIVE_CONST32(0x514649fb)) 388 return S_FALSE; 389 _version = Get32((const Byte *)(const void *)buf64 + 4); 390 if (_version < 1 || _version > 3) 391 return S_FALSE; 392 393 const UInt64 k_UncompressedSize_MAX = (UInt64)1 << 60; 394 const UInt64 k_CompressedSize_MAX = (UInt64)1 << 60; 395 396 _size = Get64((const Byte *)(const void *)buf64 + 0x18); 397 if (_size > k_UncompressedSize_MAX) 398 return S_FALSE; 399 size_t l1Size; 400 UInt32 headerSize; 401 402 if (_version == 1) 403 { 404 // _mTime = Get32((const Byte *)(const void *)buf64 + 0x14); // is unused in most images 405 _clusterBits = ((const Byte *)(const void *)buf64)[0x20]; 406 _numMidBits = ((const Byte *)(const void *)buf64)[0x21]; 407 if (_clusterBits < 9 || _clusterBits > 30) 408 return S_FALSE; 409 if (_numMidBits < 1 || _numMidBits > 28) 410 return S_FALSE; 411 _cryptMethod = Get32((const Byte *)(const void *)buf64 + 0x24); 412 const unsigned numBits2 = _clusterBits + _numMidBits; 413 const UInt64 l1Size64 = (_size + (((UInt64)1 << numBits2) - 1)) >> numBits2; 414 if (l1Size64 > ((UInt32)1 << 31)) 415 return S_FALSE; 416 l1Size = (size_t)l1Size64; 417 headerSize = 0x30; 418 } 419 else 420 { 421 _clusterBits = Get32((const Byte *)(const void *)buf64 + 0x14); 422 if (_clusterBits < 9 || _clusterBits > 30) 423 return S_FALSE; 424 _numMidBits = _clusterBits - 3; 425 _cryptMethod = Get32((const Byte *)(const void *)buf64 + 0x20); 426 l1Size = Get32((const Byte *)(const void *)buf64 + 0x24); 427 headerSize = 0x48; 428 if (_version >= 3) 429 { 430 _incompatFlags = Get64((const Byte *)(const void *)buf64 + 0x48); 431 // const UInt64 CompatFlags = Get64((const Byte *)(const void *)buf64 + 0x50); 432 // const UInt64 AutoClearFlags = Get64((const Byte *)(const void *)buf64 + 0x58); 433 // const UInt32 RefCountOrder = Get32((const Byte *)(const void *)buf64 + 0x60); 434 headerSize = 0x68; 435 const UInt32 headerSize2 = Get32((const Byte *)(const void *)buf64 + 0x64); 436 if (headerSize2 > (1u << 30)) 437 return S_FALSE; 438 if (headerSize < headerSize2) 439 headerSize = headerSize2; 440 if (headerSize2 >= 0x68 + 1) 441 _compressionType = ((const Byte *)(const void *)buf64)[0x68]; 442 } 443 444 const UInt64 refOffset = Get64((const Byte *)(const void *)buf64 + 0x30); // must be aligned for cluster 445 const UInt32 refClusters = Get32((const Byte *)(const void *)buf64 + 0x38); 446 // UInt32 numSnapshots = Get32((const Byte *)(const void *)buf64 + 0x3C); 447 // UInt64 snapshotsOffset = Get64((const Byte *)(const void *)buf64 + 0x40); // must be aligned for cluster 448 /* 449 if (numSnapshots) 450 return S_FALSE; 451 */ 452 if (refClusters) 453 { 454 if (refOffset > k_CompressedSize_MAX) 455 return S_FALSE; 456 const UInt64 numBytes = (UInt64)refClusters << _clusterBits; 457 const UInt64 end = refOffset + numBytes; 458 if (end > k_CompressedSize_MAX) 459 return S_FALSE; 460 /* 461 CByteBuffer refs; 462 refs.Alloc(numBytes); 463 RINOK(InStream_SeekSet(stream, refOffset)) 464 RINOK(ReadStream_FALSE(stream, refs, numBytes)); 465 */ 466 if (_phySize < end) 467 _phySize = end; 468 /* 469 for (size_t i = 0; i < numBytes; i += 2) 470 { 471 UInt32 v = GetBe16((const Byte *)refs + (size_t)i); 472 if (v == 0) 473 continue; 474 } 475 */ 476 } 477 } 478 479 const UInt64 l1Offset = Get64((const Byte *)(const void *)buf64 + 0x28); // must be aligned for cluster ? 480 if (l1Offset < headerSize || l1Offset > k_CompressedSize_MAX) 481 return S_FALSE; 482 if (_phySize < headerSize) 483 _phySize = headerSize; 484 485 _isArc = true; 486 { 487 const UInt64 backOffset = Get64((const Byte *)(const void *)buf64 + 8); 488 // UInt32 backSize = Get32((const Byte *)(const void *)buf64 + 0x10); 489 if (backOffset) 490 { 491 _unsupported = true; 492 return S_FALSE; 493 } 494 } 495 496 UInt64 fileSize = 0; 497 RINOK(InStream_GetSize_SeekToBegin(stream, fileSize)) 498 499 const size_t clusterSize = (size_t)1 << _clusterBits; 500 const size_t t1SizeBytes = (size_t)l1Size << 3; 501 { 502 const UInt64 end = l1Offset + t1SizeBytes; 503 if (end > k_CompressedSize_MAX) 504 return S_FALSE; 505 // we need to use align end for empty qcow files 506 // some files has no cluster alignment padding at the end 507 // but has sector alignment 508 // end = (end + clusterSize - 1) >> _clusterBits << _clusterBits; 509 if (_phySize < end) 510 _phySize = end; 511 if (end > fileSize) 512 return S_FALSE; 513 if (_phySize < fileSize) 514 { 515 const UInt64 end2 = (end + 511) & ~(UInt64)511; 516 if (end2 == fileSize) 517 _phySize = end2; 518 } 519 } 520 CObjArray<UInt64> table64(l1Size); 521 { 522 // if ((t1SizeBytes >> 3) != l1Size) return S_FALSE; 523 RINOK(InStream_SeekSet(stream, l1Offset)) 524 RINOK(ReadStream_FALSE(stream, table64, t1SizeBytes)) 525 } 526 527 _compressedFlag = (_version <= 1) ? ((UInt64)1 << 63) : ((UInt64)1 << 62); 528 const UInt64 offsetMask = _compressedFlag - 1; 529 const size_t midSize = (size_t)1 << (_numMidBits + 3); 530 size_t numTables = 0; 531 size_t i; 532 533 for (i = 0; i < l1Size; i++) 534 { 535 const UInt64 v = Get64(table64 + (size_t)i) & offsetMask; 536 if (!v) 537 continue; 538 numTables++; 539 const UInt64 end = v + midSize; 540 if (end > k_CompressedSize_MAX) 541 return S_FALSE; 542 if (_phySize < end) 543 _phySize = end; 544 if (end > fileSize) 545 return S_FALSE; 546 } 547 548 if (numTables) 549 { 550 const size_t size = (size_t)numTables << (_numMidBits + 3); 551 if (size >> (_numMidBits + 3) != numTables) 552 return E_OUTOFMEMORY; 553 _table.Alloc(size); 554 if (!_table.IsAllocated()) 555 return E_OUTOFMEMORY; 556 if (openCallback) 557 { 558 const UInt64 totalBytes = size; 559 RINOK(openCallback->SetTotal(NULL, &totalBytes)) 560 } 561 } 562 563 _dir.SetSize((unsigned)l1Size); 564 565 UInt32 curTable = 0; 566 567 for (i = 0; i < l1Size; i++) 568 { 569 Byte *buf2; 570 { 571 const UInt64 v = Get64(table64 + (size_t)i) & offsetMask; 572 if (v == 0) 573 { 574 _dir[i] = kEmptyDirItem; 575 continue; 576 } 577 _dir[i] = curTable; 578 const size_t tableOffset = (size_t)curTable << (_numMidBits + 3); 579 buf2 = (Byte *)_table + tableOffset; 580 curTable++; 581 if (openCallback && (tableOffset & 0xFFFFF) == 0) 582 { 583 const UInt64 numBytes = tableOffset; 584 RINOK(openCallback->SetCompleted(NULL, &numBytes)) 585 } 586 RINOK(InStream_SeekSet(stream, v)) 587 RINOK(ReadStream_FALSE(stream, buf2, midSize)) 588 } 589 590 for (size_t k = 0; k < midSize; k += 8) 591 { 592 const UInt64 v = Get64((const Byte *)buf2 + (size_t)k); 593 if (v == 0) 594 continue; 595 UInt64 offset = v & offsetMask; 596 size_t dataSize = clusterSize; 597 598 if (v & _compressedFlag) 599 { 600 if (_version <= 1) 601 { 602 const unsigned numOffsetBits = 63 - _clusterBits; 603 dataSize = ((size_t)(offset >> numOffsetBits) + 1) << 9; 604 offset &= ((UInt64)1 << numOffsetBits) - 1; 605 dataSize = 0; // why ? 606 // offset &= ~(((UInt64)1 << 9) - 1); 607 } 608 else 609 { 610 const unsigned numOffsetBits = 62 - (_clusterBits - 8); 611 dataSize = ((size_t)(offset >> numOffsetBits) + 1) << 9; 612 offset &= ((UInt64)1 << numOffsetBits) - (1 << 9); 613 } 614 _needCompression = true; 615 } 616 else 617 { 618 const UInt32 low = (UInt32)v & 511; 619 if (low) 620 { 621 // version_3 supports zero clusters 622 if (_version < 3 || low != 1) 623 { 624 _unsupported = true; 625 return S_FALSE; 626 } 627 } 628 } 629 630 const UInt64 end = offset + dataSize; 631 if (_phySize < end) 632 _phySize = end; 633 } 634 } 635 636 if (curTable != numTables) 637 return E_FAIL; 638 639 if (_cryptMethod) 640 _unsupported = true; 641 if (_needCompression && _version <= 1) // that case was not implemented 642 _unsupported = true; 643 if (_compressionType) 644 _unsupported = true; 645 646 Stream = stream; 647 return S_OK; 648 } 649 650 651 Z7_COM7F_IMF(CHandler::Close()) 652 { 653 _table.Free(); 654 _dir.Free(); 655 // _cache.Free(); 656 // _cacheCompressed.Free(); 657 _phySize = 0; 658 659 _cacheCluster = (UInt64)(Int64)-1; 660 _comprPos = 0; 661 _comprSize = 0; 662 663 _needCompression = false; 664 _isArc = false; 665 _unsupported = false; 666 667 _compressionType = 0; 668 _incompatFlags = 0; 669 670 // CHandlerImg: 671 Clear_HandlerImg_Vars(); 672 Stream.Release(); 673 return S_OK; 674 } 675 676 677 Z7_COM7F_IMF(CHandler::GetStream(UInt32 /* index */, ISequentialInStream **stream)) 678 { 679 COM_TRY_BEGIN 680 *stream = NULL; 681 if (_unsupported || !Stream) 682 return S_FALSE; 683 if (_needCompression) 684 { 685 if (_version <= 1 || _compressionType) 686 return S_FALSE; 687 _bufInStream.Create_if_Empty(); 688 _bufOutStream.Create_if_Empty(); 689 _deflateDecoder.Create_if_Empty(); 690 _deflateDecoder->Set_NeedFinishInput(true); 691 const size_t clusterSize = (size_t)1 << _clusterBits; 692 _cache.AllocAtLeast(clusterSize); 693 _cacheCompressed.AllocAtLeast(clusterSize * 2); 694 } 695 CMyComPtr<ISequentialInStream> streamTemp = this; 696 RINOK(InitAndSeek()) 697 *stream = streamTemp.Detach(); 698 return S_OK; 699 COM_TRY_END 700 } 701 702 703 REGISTER_ARC_I( 704 "QCOW", "qcow qcow2 qcow2c", NULL, 0xCA, 705 k_Signature, 706 0, 707 0, 708 NULL) 709 710 }} 711