1 // SparseHandler.cpp 2 3 #include "StdAfx.h" 4 5 #include "../../../C/CpuArch.h" 6 7 #include "../../Common/ComTry.h" 8 9 #include "../../Windows/PropVariantUtils.h" 10 11 #include "../Common/RegisterArc.h" 12 #include "../Common/StreamUtils.h" 13 14 #include "HandlerCont.h" 15 16 #define Get16(p) GetUi16(p) 17 #define Get32(p) GetUi32(p) 18 19 #define G16(_offs_, dest) dest = Get16(p + (_offs_)); 20 #define G32(_offs_, dest) dest = Get32(p + (_offs_)); 21 22 using namespace NWindows; 23 24 namespace NArchive { 25 namespace NSparse { 26 27 // libsparse and simg2img 28 29 struct CHeader 30 { 31 // UInt32 magic; /* 0xed26ff3a */ 32 // UInt16 major_version; /* (0x1) - reject images with higher major versions */ 33 // UInt16 minor_version; /* (0x0) - allow images with higer minor versions */ 34 UInt16 file_hdr_sz; /* 28 bytes for first revision of the file format */ 35 UInt16 chunk_hdr_sz; /* 12 bytes for first revision of the file format */ 36 UInt32 BlockSize; /* block size in bytes, must be a multiple of 4 (4096) */ 37 UInt32 NumBlocks; /* total blocks in the non-sparse output image */ 38 UInt32 NumChunks; /* total chunks in the sparse input image */ 39 // UInt32 image_checksum; /* CRC32 checksum of the original data, counting "don't care" as 0. */ 40 ParseNArchive::NSparse::CHeader41 void Parse(const Byte *p) 42 { 43 // G16 (4, major_version); 44 // G16 (6, minor_version); 45 G16 (8, file_hdr_sz) 46 G16 (10, chunk_hdr_sz) 47 G32 (12, BlockSize) 48 G32 (16, NumBlocks) 49 G32 (20, NumChunks) 50 // G32 (24, image_checksum); 51 } 52 }; 53 54 // #define SPARSE_HEADER_MAGIC 0xed26ff3a 55 56 #define CHUNK_TYPE_RAW 0xCAC1 57 #define CHUNK_TYPE_FILL 0xCAC2 58 #define CHUNK_TYPE_DONT_CARE 0xCAC3 59 #define CHUNK_TYPE_CRC32 0xCAC4 60 61 #define MY_CHUNK_TYPE_FILL 0 62 #define MY_CHUNK_TYPE_DONT_CARE 1 63 #define MY_CHUNK_TYPE_RAW_START 2 64 65 static const char * const g_Methods[] = 66 { 67 "RAW" 68 , "FILL" 69 , "SPARSE" // "DONT_CARE" 70 , "CRC32" 71 }; 72 73 static const unsigned kFillSize = 4; 74 75 struct CChunk 76 { 77 UInt32 VirtBlock; 78 Byte Fill [kFillSize]; 79 UInt64 PhyOffset; 80 CChunkNArchive::NSparse::CChunk81 CChunk() 82 { 83 Fill[0] = 84 Fill[1] = 85 Fill[2] = 86 Fill[3] = 87 0; 88 } 89 }; 90 91 static const Byte k_Signature[] = { 0x3a, 0xff, 0x26, 0xed, 1, 0 }; 92 93 94 Z7_class_CHandler_final: public CHandlerImg 95 { 96 Z7_IFACE_COM7_IMP(IInArchive_Img) 97 98 Z7_IFACE_COM7_IMP(IInArchiveGetStream) 99 Z7_IFACE_COM7_IMP(ISequentialInStream) 100 101 CRecordVector<CChunk> Chunks; 102 UInt64 _virtSize_fromChunks; 103 unsigned _blockSizeLog; 104 UInt32 _chunkIndexPrev; 105 106 UInt64 _packSizeProcessed; 107 UInt64 _phySize; 108 UInt32 _methodFlags; 109 bool _isArc; 110 bool _headersError; 111 bool _unexpectedEnd; 112 // bool _unsupported; 113 UInt32 NumChunks; // from header 114 115 HRESULT Seek2(UInt64 offset) 116 { 117 _posInArc = offset; 118 return InStream_SeekSet(Stream, offset); 119 } 120 121 void InitSeekPositions() 122 { 123 /* (_virtPos) and (_posInArc) is used only in Read() (that calls ReadPhy()). 124 So we must reset these variables before first call of Read() */ 125 Reset_VirtPos(); 126 Reset_PosInArc(); 127 _chunkIndexPrev = 0; 128 _packSizeProcessed = 0; 129 } 130 131 // virtual functions 132 bool Init_PackSizeProcessed() Z7_override 133 { 134 _packSizeProcessed = 0; 135 return true; 136 } 137 bool Get_PackSizeProcessed(UInt64 &size) Z7_override 138 { 139 size = _packSizeProcessed; 140 return true; 141 } 142 143 HRESULT Open2(IInStream *stream, IArchiveOpenCallback *openCallback) Z7_override; 144 HRESULT ReadPhy(UInt64 offset, void *data, UInt32 size, UInt32 &processed); 145 }; 146 147 148 149 static const Byte kProps[] = 150 { 151 kpidSize, 152 kpidPackSize 153 }; 154 155 static const Byte kArcProps[] = 156 { 157 kpidClusterSize, 158 kpidNumBlocks, 159 kpidMethod 160 }; 161 162 IMP_IInArchive_Props 163 IMP_IInArchive_ArcProps 164 165 Z7_COM7F_IMF(CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value)) 166 { 167 COM_TRY_BEGIN 168 NCOM::CPropVariant prop; 169 170 switch (propID) 171 { 172 case kpidMainSubfile: prop = (UInt32)0; break; 173 case kpidClusterSize: prop = (UInt32)((UInt32)1 << _blockSizeLog); break; 174 case kpidNumBlocks: prop = (UInt32)NumChunks; break; 175 case kpidPhySize: if (_phySize != 0) prop = _phySize; break; 176 177 case kpidMethod: 178 { 179 FLAGS_TO_PROP(g_Methods, _methodFlags, prop); 180 break; 181 } 182 183 case kpidErrorFlags: 184 { 185 UInt32 v = 0; 186 if (!_isArc) v |= kpv_ErrorFlags_IsNotArc; 187 if (_headersError) v |= kpv_ErrorFlags_HeadersError; 188 if (_unexpectedEnd) v |= kpv_ErrorFlags_UnexpectedEnd; 189 // if (_unsupported) v |= kpv_ErrorFlags_UnsupportedMethod; 190 if (!Stream && v == 0 && _isArc) 191 v = kpv_ErrorFlags_HeadersError; 192 if (v != 0) 193 prop = v; 194 break; 195 } 196 } 197 198 prop.Detach(value); 199 return S_OK; 200 COM_TRY_END 201 } 202 203 204 Z7_COM7F_IMF(CHandler::GetProperty(UInt32 /* index */, PROPID propID, PROPVARIANT *value)) 205 { 206 COM_TRY_BEGIN 207 NCOM::CPropVariant prop; 208 209 switch (propID) 210 { 211 case kpidSize: prop = _size; break; 212 case kpidPackSize: prop = _phySize; break; 213 case kpidExtension: prop = (_imgExt ? _imgExt : "img"); break; 214 } 215 216 prop.Detach(value); 217 return S_OK; 218 COM_TRY_END 219 } 220 221 222 static unsigned GetLogSize(UInt32 size) 223 { 224 unsigned k; 225 for (k = 0; k < 32; k++) 226 if (((UInt32)1 << k) == size) 227 return k; 228 return k; 229 } 230 231 232 HRESULT CHandler::Open2(IInStream *stream, IArchiveOpenCallback *openCallback) 233 { 234 const unsigned kHeaderSize = 28; 235 const unsigned kChunkHeaderSize = 12; 236 CHeader h; 237 { 238 Byte buf[kHeaderSize]; 239 RINOK(ReadStream_FALSE(stream, buf, kHeaderSize)) 240 if (memcmp(buf, k_Signature, 6) != 0) 241 return S_FALSE; 242 h.Parse(buf); 243 } 244 245 if (h.file_hdr_sz != kHeaderSize || 246 h.chunk_hdr_sz != kChunkHeaderSize) 247 return S_FALSE; 248 249 NumChunks = h.NumChunks; 250 251 const unsigned logSize = GetLogSize(h.BlockSize); 252 if (logSize < 2 || logSize >= 32) 253 return S_FALSE; 254 _blockSizeLog = logSize; 255 256 _size = (UInt64)h.NumBlocks << logSize; 257 258 if (h.NumChunks >= (UInt32)(Int32)-2) // it's our limit 259 return S_FALSE; 260 261 _isArc = true; 262 Chunks.Reserve(h.NumChunks + 1); 263 UInt64 offset = kHeaderSize; 264 UInt32 virtBlock = 0; 265 UInt32 i; 266 267 for (i = 0; i < h.NumChunks; i++) 268 { 269 { 270 const UInt32 mask = ((UInt32)1 << 16) - 1; 271 if ((i & mask) == mask && openCallback) 272 { 273 RINOK(openCallback->SetCompleted(NULL, &offset)) 274 } 275 } 276 Byte buf[kChunkHeaderSize]; 277 { 278 size_t processed = kChunkHeaderSize; 279 RINOK(ReadStream(stream, buf, &processed)) 280 if (kChunkHeaderSize != processed) 281 { 282 offset += kChunkHeaderSize; 283 break; 284 } 285 } 286 const UInt32 type = Get32(&buf[0]); 287 const UInt32 numBlocks = Get32(&buf[4]); 288 UInt32 size = Get32(&buf[8]); 289 290 if (type < CHUNK_TYPE_RAW || 291 type > CHUNK_TYPE_CRC32) 292 return S_FALSE; 293 if (size < kChunkHeaderSize) 294 return S_FALSE; 295 CChunk c; 296 c.PhyOffset = offset + kChunkHeaderSize; 297 c.VirtBlock = virtBlock; 298 offset += size; 299 size -= kChunkHeaderSize; 300 _methodFlags |= ((UInt32)1 << (type - CHUNK_TYPE_RAW)); 301 302 if (numBlocks > h.NumBlocks - virtBlock) 303 return S_FALSE; 304 305 if (type == CHUNK_TYPE_CRC32) 306 { 307 // crc chunk must be last chunk (i == h.NumChunks -1); 308 if (size != kFillSize || numBlocks != 0) 309 return S_FALSE; 310 { 311 size_t processed = kFillSize; 312 RINOK(ReadStream(stream, c.Fill, &processed)) 313 if (kFillSize != processed) 314 break; 315 } 316 continue; 317 } 318 // else 319 { 320 if (numBlocks == 0) 321 return S_FALSE; 322 323 if (type == CHUNK_TYPE_DONT_CARE) 324 { 325 if (size != 0) 326 return S_FALSE; 327 c.PhyOffset = MY_CHUNK_TYPE_DONT_CARE; 328 } 329 else if (type == CHUNK_TYPE_FILL) 330 { 331 if (size != kFillSize) 332 return S_FALSE; 333 c.PhyOffset = MY_CHUNK_TYPE_FILL; 334 size_t processed = kFillSize; 335 RINOK(ReadStream(stream, c.Fill, &processed)) 336 if (kFillSize != processed) 337 break; 338 } 339 else if (type == CHUNK_TYPE_RAW) 340 { 341 /* Here we require (size == virtSize). 342 Probably original decoder also requires it. 343 But maybe size of last chunk can be non-aligned with blockSize ? */ 344 const UInt32 virtSize = (numBlocks << _blockSizeLog); 345 if (size != virtSize || numBlocks != (virtSize >> _blockSizeLog)) 346 return S_FALSE; 347 } 348 else 349 return S_FALSE; 350 351 virtBlock += numBlocks; 352 Chunks.AddInReserved(c); 353 if (type == CHUNK_TYPE_RAW) 354 RINOK(InStream_SeekSet(stream, offset)) 355 } 356 } 357 358 if (i != h.NumChunks) 359 _unexpectedEnd = true; 360 else if (virtBlock != h.NumBlocks) 361 _headersError = true; 362 363 _phySize = offset; 364 365 { 366 CChunk c; 367 c.VirtBlock = virtBlock; 368 c.PhyOffset = offset; 369 Chunks.AddInReserved(c); 370 } 371 _virtSize_fromChunks = (UInt64)virtBlock << _blockSizeLog; 372 373 Stream = stream; 374 return S_OK; 375 } 376 377 378 Z7_COM7F_IMF(CHandler::Close()) 379 { 380 Chunks.Clear(); 381 _isArc = false; 382 _virtSize_fromChunks = 0; 383 // _unsupported = false; 384 _headersError = false; 385 _unexpectedEnd = false; 386 _phySize = 0; 387 _methodFlags = 0; 388 389 _chunkIndexPrev = 0; 390 _packSizeProcessed = 0; 391 392 // CHandlerImg: 393 Clear_HandlerImg_Vars(); 394 Stream.Release(); 395 return S_OK; 396 } 397 398 399 Z7_COM7F_IMF(CHandler::GetStream(UInt32 /* index */, ISequentialInStream **stream)) 400 { 401 COM_TRY_BEGIN 402 *stream = NULL; 403 if (Chunks.Size() < 1) 404 return S_FALSE; 405 if (Chunks.Size() < 2 && _virtSize_fromChunks != 0) 406 return S_FALSE; 407 // if (_unsupported) return S_FALSE; 408 InitSeekPositions(); 409 CMyComPtr<ISequentialInStream> streamTemp = this; 410 *stream = streamTemp.Detach(); 411 return S_OK; 412 COM_TRY_END 413 } 414 415 416 417 HRESULT CHandler::ReadPhy(UInt64 offset, void *data, UInt32 size, UInt32 &processed) 418 { 419 processed = 0; 420 if (offset > _phySize || offset + size > _phySize) 421 { 422 // we don't expect these cases, if (_phySize) was set correctly. 423 return S_FALSE; 424 } 425 if (offset != _posInArc) 426 { 427 const HRESULT res = Seek2(offset); 428 if (res != S_OK) 429 { 430 Reset_PosInArc(); // we don't trust seek_pos in case of error 431 return res; 432 } 433 } 434 { 435 size_t size2 = size; 436 const HRESULT res = ReadStream(Stream, data, &size2); 437 processed = (UInt32)size2; 438 _packSizeProcessed += size2; 439 _posInArc += size2; 440 if (res != S_OK) 441 Reset_PosInArc(); // we don't trust seek_pos in case of reading error 442 return res; 443 } 444 } 445 446 447 Z7_COM7F_IMF(CHandler::Read(void *data, UInt32 size, UInt32 *processedSize)) 448 { 449 if (processedSize) 450 *processedSize = 0; 451 // const unsigned kLimit = (1 << 16) + 1; if (size > kLimit) size = kLimit; // for debug 452 if (_virtPos >= _virtSize_fromChunks) 453 return S_OK; 454 { 455 const UInt64 rem = _virtSize_fromChunks - _virtPos; 456 if (size > rem) 457 size = (UInt32)rem; 458 if (size == 0) 459 return S_OK; 460 } 461 462 UInt32 chunkIndex = _chunkIndexPrev; 463 if (chunkIndex + 1 >= Chunks.Size()) 464 return S_FALSE; 465 { 466 const UInt32 blockIndex = (UInt32)(_virtPos >> _blockSizeLog); 467 if (blockIndex < Chunks[chunkIndex ].VirtBlock || 468 blockIndex >= Chunks[chunkIndex + 1].VirtBlock) 469 { 470 unsigned left = 0, right = Chunks.Size() - 1; 471 for (;;) 472 { 473 const unsigned mid = (unsigned)(((size_t)left + (size_t)right) / 2); 474 if (mid == left) 475 break; 476 if (blockIndex < Chunks[mid].VirtBlock) 477 right = mid; 478 else 479 left = mid; 480 } 481 chunkIndex = left; 482 _chunkIndexPrev = chunkIndex; 483 } 484 } 485 486 const CChunk &c = Chunks[chunkIndex]; 487 const UInt64 offset = _virtPos - ((UInt64)c.VirtBlock << _blockSizeLog); 488 { 489 const UInt32 numBlocks = Chunks[chunkIndex + 1].VirtBlock - c.VirtBlock; 490 const UInt64 rem = ((UInt64)numBlocks << _blockSizeLog) - offset; 491 if (size > rem) 492 size = (UInt32)rem; 493 } 494 495 const UInt64 phyOffset = c.PhyOffset; 496 497 if (phyOffset >= MY_CHUNK_TYPE_RAW_START) 498 { 499 UInt32 processed = 0; 500 const HRESULT res = ReadPhy(phyOffset + offset, data, size, processed); 501 if (processedSize) 502 *processedSize = processed; 503 _virtPos += processed; 504 return res; 505 } 506 507 Byte b = 0; 508 509 if (phyOffset == MY_CHUNK_TYPE_FILL) 510 { 511 const Byte b0 = c.Fill [0]; 512 const Byte b1 = c.Fill [1]; 513 const Byte b2 = c.Fill [2]; 514 const Byte b3 = c.Fill [3]; 515 if (b0 != b1 || 516 b0 != b2 || 517 b0 != b3) 518 { 519 if (processedSize) 520 *processedSize = size; 521 _virtPos += size; 522 Byte *dest = (Byte *)data; 523 while (size >= 4) 524 { 525 dest[0] = b0; 526 dest[1] = b1; 527 dest[2] = b2; 528 dest[3] = b3; 529 dest += 4; 530 size -= 4; 531 } 532 if (size > 0) dest[0] = b0; 533 if (size > 1) dest[1] = b1; 534 if (size > 2) dest[2] = b2; 535 return S_OK; 536 } 537 b = b0; 538 } 539 else if (phyOffset != MY_CHUNK_TYPE_DONT_CARE) 540 return S_FALSE; 541 542 memset(data, b, size); 543 _virtPos += size; 544 if (processedSize) 545 *processedSize = size; 546 return S_OK; 547 } 548 549 REGISTER_ARC_I( 550 "Sparse", "simg img", NULL, 0xc2, 551 k_Signature, 552 0, 553 0, 554 NULL) 555 556 }} 557