xref: /aosp_15_r20/external/lzma/CPP/7zip/Archive/SparseHandler.cpp (revision f6dc9357d832569d4d1f5d24eacdb3935a1ae8e6)
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