xref: /aosp_15_r20/external/lzma/CPP/7zip/Archive/MbrHandler.cpp (revision f6dc9357d832569d4d1f5d24eacdb3935a1ae8e6)
1 // MbrHandler.cpp
2 
3 #include "StdAfx.h"
4 
5 // #define SHOW_DEBUG_INFO
6 
7 #ifdef SHOW_DEBUG_INFO
8 #include <stdio.h>
9 #endif
10 
11 #include "../../../C/CpuArch.h"
12 
13 #include "../../Common/ComTry.h"
14 #include "../../Common/IntToString.h"
15 #include "../../Common/MyBuffer.h"
16 
17 #include "../../Windows/PropVariant.h"
18 
19 #include "../Common/RegisterArc.h"
20 #include "../Common/StreamUtils.h"
21 
22 #include "HandlerCont.h"
23 
24 #ifdef SHOW_DEBUG_INFO
25 #define PRF(x) x
26 #else
27 #define PRF(x)
28 #endif
29 
30 using namespace NWindows;
31 
32 namespace NArchive {
33 
34 namespace NFat {
35 API_FUNC_IsArc IsArc_Fat(const Byte *p, size_t size);
36 }
37 
38 namespace NMbr {
39 
40 struct CChs
41 {
42   Byte Head;
43   Byte SectCyl;
44   Byte Cyl8;
45 
GetSectorNArchive::NMbr::CChs46   UInt32 GetSector() const { return SectCyl & 0x3F; }
GetCylNArchive::NMbr::CChs47   UInt32 GetCyl() const { return ((UInt32)SectCyl >> 6 << 8) | Cyl8; }
48   void ToString(NCOM::CPropVariant &prop) const;
49 
ParseNArchive::NMbr::CChs50   void Parse(const Byte *p)
51   {
52     Head = p[0];
53     SectCyl = p[1];
54     Cyl8 = p[2];
55   }
CheckNArchive::NMbr::CChs56   bool Check() const { return GetSector() > 0; }
57 };
58 
59 
60 // Chs in some MBRs contains only low bits of "Cyl number". So we disable check.
61 /*
62 #define RINOZ(x) { int _t_ = (x); if (_t_ != 0) return _t_; }
63 static int CompareChs(const CChs &c1, const CChs &c2)
64 {
65   RINOZ(MyCompare(c1.GetCyl(), c2.GetCyl()));
66   RINOZ(MyCompare(c1.Head, c2.Head));
67   return MyCompare(c1.GetSector(), c2.GetSector());
68 }
69 */
70 
ToString(NCOM::CPropVariant & prop) const71 void CChs::ToString(NCOM::CPropVariant &prop) const
72 {
73   AString s;
74   s.Add_UInt32(GetCyl());
75   s.Add_Minus();
76   s.Add_UInt32(Head);
77   s.Add_Minus();
78   s.Add_UInt32(GetSector());
79   prop = s;
80 }
81 
82 struct CPartition
83 {
84   Byte Status;
85   CChs BeginChs;
86   Byte Type;
87   CChs EndChs;
88   UInt32 Lba;
89   UInt32 NumBlocks;
90 
CPartitionNArchive::NMbr::CPartition91   CPartition() { memset (this, 0, sizeof(*this)); }
92 
IsEmptyNArchive::NMbr::CPartition93   bool IsEmpty() const { return Type == 0; }
IsExtendedNArchive::NMbr::CPartition94   bool IsExtended() const { return Type == 5 || Type == 0xF; }
GetLimitNArchive::NMbr::CPartition95   UInt32 GetLimit() const { return Lba + NumBlocks; }
96   // bool IsActive() const { return Status == 0x80; }
GetPosNArchive::NMbr::CPartition97   UInt64 GetPos(unsigned sectorSizeLog) const { return (UInt64)Lba << sectorSizeLog; }
GetSizeNArchive::NMbr::CPartition98   UInt64 GetSize(unsigned sectorSizeLog) const { return (UInt64)NumBlocks << sectorSizeLog; }
99 
CheckLbaLimitsNArchive::NMbr::CPartition100   bool CheckLbaLimits() const { return (UInt32)0xFFFFFFFF - Lba >= NumBlocks; }
ParseNArchive::NMbr::CPartition101   bool Parse(const Byte *p)
102   {
103     Status = p[0];
104     BeginChs.Parse(p + 1);
105     Type = p[4];
106     EndChs.Parse(p + 5);
107     Lba = GetUi32(p + 8);
108     NumBlocks = GetUi32(p + 12);
109     if (Type == 0)
110       return true;
111     if (Status != 0 && Status != 0x80)
112       return false;
113     return BeginChs.Check()
114        && EndChs.Check()
115        // && CompareChs(BeginChs, EndChs) <= 0
116        && NumBlocks > 0
117        && CheckLbaLimits();
118   }
119 
120   #ifdef SHOW_DEBUG_INFO
PrintNArchive::NMbr::CPartition121   void Print() const
122   {
123     NCOM::CPropVariant prop, prop2;
124     BeginChs.ToString(prop);
125     EndChs.ToString(prop2);
126     printf("   %2x %2x %8X %8X %12S %12S", (int)Status, (int)Type, Lba, NumBlocks, prop.bstrVal, prop2.bstrVal);
127   }
128   #endif
129 };
130 
131 struct CPartType
132 {
133   UInt32 Id;
134   const char *Ext;
135   const char *Name;
136 };
137 
138 #define kFat "fat"
139 
140 /*
141 if we use "format" command in Windows 10 for existing partition:
142   - if we format to ExFAT, it sets type=7
143   - if we format to UDF, it doesn't change type from previous value.
144 */
145 
146 static const unsigned kType_Windows_NTFS = 7;
147 
148 static const CPartType kPartTypes[] =
149 {
150   { 0x01, kFat, "FAT12" },
151   { 0x04, kFat, "FAT16 DOS 3.0+" },
152   { 0x05, NULL, "Extended" },
153   { 0x06, kFat, "FAT16 DOS 3.31+" },
154   { 0x07, "ntfs", "NTFS" },
155   { 0x0B, kFat, "FAT32" },
156   { 0x0C, kFat, "FAT32-LBA" },
157   { 0x0E, kFat, "FAT16-LBA" },
158   { 0x0F, NULL, "Extended-LBA" },
159   { 0x11, kFat, "FAT12-Hidden" },
160   { 0x14, kFat, "FAT16-Hidden < 32 MB" },
161   { 0x16, kFat, "FAT16-Hidden >= 32 MB" },
162   { 0x1B, kFat, "FAT32-Hidden" },
163   { 0x1C, kFat, "FAT32-LBA-Hidden" },
164   { 0x1E, kFat, "FAT16-LBA-WIN95-Hidden" },
165   { 0x27, "ntfs", "NTFS-WinRE" },
166   { 0x82, NULL, "Solaris x86 / Linux swap" },
167   { 0x83, NULL, "Linux" },
168   { 0x8E, "lvm", "Linux LVM" },
169   { 0xA5, NULL, "BSD slice" },
170   { 0xBE, NULL, "Solaris 8 boot" },
171   { 0xBF, NULL, "New Solaris x86" },
172   { 0xC2, NULL, "Linux-Hidden" },
173   { 0xC3, NULL, "Linux swap-Hidden" },
174   { 0xEE, NULL, "GPT" },
175   { 0xEE, NULL, "EFI" }
176 };
177 
FindPartType(UInt32 type)178 static int FindPartType(UInt32 type)
179 {
180   for (unsigned i = 0; i < Z7_ARRAY_SIZE(kPartTypes); i++)
181     if (kPartTypes[i].Id == type)
182       return (int)i;
183   return -1;
184 }
185 
186 struct CItem
187 {
188   bool IsReal;
189   bool IsPrim;
190   bool WasParsed;
191   const char *FileSystem;
192   UInt64 Size;
193   CPartition Part;
194 
CItemNArchive::NMbr::CItem195   CItem():
196       WasParsed(false),
197       FileSystem(NULL)
198       {}
199 };
200 
201 
202 Z7_class_CHandler_final: public CHandlerCont
203 {
204   Z7_IFACE_COM7_IMP(IInArchive_Cont)
205 
206   CObjectVector<CItem> _items;
207   UInt64 _totalSize;
208   CByteBuffer _buffer;
209 
210   UInt32 _signature;
211   unsigned _sectorSizeLog;
212 
213   virtual int GetItem_ExtractInfo(UInt32 index, UInt64 &pos, UInt64 &size) const Z7_override Z7_final
214   {
215     const CItem &item = _items[index];
216     pos = item.Part.GetPos(_sectorSizeLog);
217     size = item.Size;
218     return NExtract::NOperationResult::kOK;
219   }
220 
221   HRESULT ReadTables(IInStream *stream, UInt32 baseLba, UInt32 lba, unsigned level);
222 };
223 
224 /*
225 static bool IsEmptyBuffer(const Byte *data, size_t size)
226 {
227   for (unsigned i = 0; i < size; i++)
228     if (data[i] != 0)
229       return false;
230   return true;
231 }
232 */
233 
234 const char *GetFileSystem(ISequentialInStream *stream, UInt64 partitionSize);
235 
236 HRESULT CHandler::ReadTables(IInStream *stream, UInt32 baseLba, UInt32 lba, unsigned level)
237 {
238   if (level >= 128 || _items.Size() >= 128)
239     return S_FALSE;
240 
241   const unsigned kNumHeaderParts = 4;
242   CPartition parts[kNumHeaderParts];
243 
244   if (level == 0)
245     _sectorSizeLog = 9;
246   const UInt32 kSectorSize = (UInt32)1 << _sectorSizeLog;
247   UInt32 bufSize  = kSectorSize;
248   if (level == 0 && _totalSize >= (1 << 12))
249     bufSize = (1 << 12);
250   _buffer.Alloc(bufSize);
251   {
252     Byte *buf = _buffer;
253     const UInt64 newPos = (UInt64)lba << _sectorSizeLog;
254     if (newPos + bufSize > _totalSize)
255       return S_FALSE;
256     RINOK(InStream_SeekSet(stream, newPos))
257     RINOK(ReadStream_FALSE(stream, buf, bufSize))
258     if (buf[0x1FE] != 0x55 || buf[0x1FF] != 0xAA)
259       return S_FALSE;
260     if (level == 0)
261       _signature = GetUi32(buf + 0x1B8);
262     for (unsigned i = 0; i < kNumHeaderParts; i++)
263       if (!parts[i].Parse(buf + 0x1BE + 16 * i))
264         return S_FALSE;
265   }
266 
267   // 23.02: now we try to detect 4kn format (4 KB sectors case)
268   if (level == 0)
269   // if (_totalSize >= (1 << 28)) // we don't expect small images with 4 KB sectors
270   if (bufSize >= (1 << 12))
271   {
272     UInt32 lastLim = 0;
273     UInt32 firstLba = 0;
274     UInt32 numBlocks = 0; // in first partition
275     for (unsigned i = 0; i < kNumHeaderParts; i++)
276     {
277       const CPartition &part = parts[i];
278       if (part.IsEmpty())
279         continue;
280       if (firstLba == 0 && part.NumBlocks != 0)
281       {
282         firstLba = part.Lba;
283         numBlocks = part.NumBlocks;
284       }
285       const UInt32 newLim = part.GetLimit();
286       if (newLim < lastLim)
287         return S_FALSE;
288       lastLim = newLim;
289     }
290     if (lastLim != 0)
291     {
292       const UInt64 lim12 = (UInt64)lastLim << 12;
293       if (lim12 <= _totalSize
294           // && _totalSize - lim12 < (1 << 28) // we can try to exclude false positive cases
295           )
296       // if (IsEmptyBuffer(&_buffer[(1 << 9)], (1 << 12) - (1 << 9)))
297       if (InStream_SeekSet(stream, (UInt64)firstLba << 12) == S_OK)
298       if (GetFileSystem(stream, (UInt64)numBlocks << 12))
299         _sectorSizeLog = 12;
300     }
301   }
302 
303   PRF(printf("\n# %8X", lba));
304 
305   UInt32 limLba = lba + 1;
306   if (limLba == 0)
307     return S_FALSE;
308 
309   for (unsigned i = 0; i < kNumHeaderParts; i++)
310   {
311     CPartition &part = parts[i];
312 
313     if (part.IsEmpty())
314       continue;
315     PRF(printf("\n   %2d ", (unsigned)level));
316     #ifdef SHOW_DEBUG_INFO
317     part.Print();
318     #endif
319 
320     unsigned numItems = _items.Size();
321     UInt32 newLba = lba + part.Lba;
322 
323     if (part.IsExtended())
324     {
325       // if (part.Type == 5) // Check it!
326       newLba = baseLba + part.Lba;
327       if (newLba < limLba)
328         return S_FALSE;
329       const HRESULT res = ReadTables(stream, level < 1 ? newLba : baseLba, newLba, level + 1);
330       if (res != S_FALSE && res != S_OK)
331         return res;
332     }
333     if (newLba < limLba)
334       return S_FALSE;
335     part.Lba = newLba;
336     if (!part.CheckLbaLimits())
337       return S_FALSE;
338 
339     CItem n;
340     n.Part = part;
341     bool addItem = false;
342     if (numItems == _items.Size())
343     {
344       n.IsPrim = (level == 0);
345       n.IsReal = true;
346       addItem = true;
347     }
348     else
349     {
350       const CItem &back = _items.Back();
351       const UInt32 backLimit = back.Part.GetLimit();
352       const UInt32 partLimit = part.GetLimit();
353       if (backLimit < partLimit)
354       {
355         n.IsReal = false;
356         n.Part.Lba = backLimit;
357         n.Part.NumBlocks = partLimit - backLimit;
358         addItem = true;
359       }
360     }
361     if (addItem)
362     {
363       if (n.Part.GetLimit() < limLba)
364         return S_FALSE;
365       limLba = n.Part.GetLimit();
366       n.Size = n.Part.GetSize(_sectorSizeLog);
367       _items.Add(n);
368     }
369   }
370   return S_OK;
371 }
372 
373 
374 static const Byte k_Ntfs_Signature[] = { 'N', 'T', 'F', 'S', ' ', ' ', ' ', ' ', 0 };
375 
376 static bool Is_Ntfs(const Byte *p)
377 {
378   if (p[0x1FE] != 0x55 || p[0x1FF] != 0xAA)
379     return false;
380   switch (p[0])
381   {
382     case 0xE9: /* codeOffset = 3 + (Int16)Get16(p + 1); */ break;
383     case 0xEB: if (p[2] != 0x90) return false; /* codeOffset = 2 + (int)(signed char)p[1]; */ break;
384     default: return false;
385   }
386   return memcmp(p + 3, k_Ntfs_Signature, Z7_ARRAY_SIZE(k_Ntfs_Signature)) == 0;
387 }
388 
389 static const Byte k_ExFat_Signature[] = { 'E', 'X', 'F', 'A', 'T', ' ', ' ', ' ' };
390 
391 static bool Is_ExFat(const Byte *p)
392 {
393   if (p[0x1FE] != 0x55 || p[0x1FF] != 0xAA)
394     return false;
395   if (p[0] != 0xEB || p[1] != 0x76 || p[2] != 0x90)
396     return false;
397   return memcmp(p + 3, k_ExFat_Signature, Z7_ARRAY_SIZE(k_ExFat_Signature)) == 0;
398 }
399 
400 static bool AllAreZeros(const Byte *p, size_t size)
401 {
402   for (size_t i = 0; i < size; i++)
403     if (p[i] != 0)
404       return false;
405   return true;
406 }
407 
408 static const UInt32 k_Udf_StartPos = 0x8000;
409 static const Byte k_Udf_Signature[] = { 0, 'B', 'E', 'A', '0', '1', 1, 0 };
410 
411 static bool Is_Udf(const Byte *p)
412 {
413   return memcmp(p + k_Udf_StartPos, k_Udf_Signature, sizeof(k_Udf_Signature)) == 0;
414 }
415 
416 
417 const char *GetFileSystem(ISequentialInStream *stream, UInt64 partitionSize)
418 {
419   const size_t kHeaderSize = 1 << 9;
420   if (partitionSize >= kHeaderSize)
421   {
422     Byte buf[kHeaderSize];
423     if (ReadStream_FAIL(stream, buf, kHeaderSize) == S_OK)
424     {
425       // NTFS is expected default filesystem for (Type == 7)
426       if (Is_Ntfs(buf))
427         return "NTFS";
428       if (Is_ExFat(buf))
429         return "exFAT";
430       if (NFat::IsArc_Fat(buf, kHeaderSize))
431         return "FAT";
432       const size_t kHeaderSize2 = k_Udf_StartPos + (1 << 9);
433       if (partitionSize >= kHeaderSize2)
434       {
435         if (AllAreZeros(buf, kHeaderSize))
436         {
437           CByteBuffer buffer(kHeaderSize2);
438           // memcpy(buffer, buf, kHeaderSize);
439           if (ReadStream_FAIL(stream, buffer + kHeaderSize, kHeaderSize2 - kHeaderSize) == S_OK)
440             if (Is_Udf(buffer))
441               return "UDF";
442         }
443       }
444     }
445   }
446   return NULL;
447 }
448 
449 
450 Z7_COM7F_IMF(CHandler::Open(IInStream *stream,
451     const UInt64 * /* maxCheckStartPosition */,
452     IArchiveOpenCallback * /* openArchiveCallback */))
453 {
454   COM_TRY_BEGIN
455   Close();
456   RINOK(InStream_GetSize_SeekToEnd(stream, _totalSize))
457   RINOK(ReadTables(stream, 0, 0, 0))
458   if (_items.IsEmpty())
459     return S_FALSE;
460   {
461     const UInt32 lbaLimit = _items.Back().Part.GetLimit();
462     const UInt64 lim = (UInt64)lbaLimit << _sectorSizeLog;
463     if (lim < _totalSize)
464     {
465       CItem n;
466       n.Part.Lba = lbaLimit;
467       n.Size = _totalSize - lim;
468       n.IsReal = false;
469       _items.Add(n);
470     }
471   }
472 
473   FOR_VECTOR (i, _items)
474   {
475     CItem &item = _items[i];
476     if (item.Part.Type != kType_Windows_NTFS)
477       continue;
478     if (InStream_SeekSet(stream, item.Part.GetPos(_sectorSizeLog)) != S_OK)
479       continue;
480     item.FileSystem = GetFileSystem(stream, item.Size);
481     item.WasParsed = true;
482   }
483 
484   _stream = stream;
485   return S_OK;
486   COM_TRY_END
487 }
488 
489 
490 Z7_COM7F_IMF(CHandler::Close())
491 {
492   _totalSize = 0;
493   _items.Clear();
494   _stream.Release();
495   return S_OK;
496 }
497 
498 
499 enum
500 {
501   kpidPrimary = kpidUserDefined,
502   kpidBegChs,
503   kpidEndChs
504 };
505 
506 static const CStatProp kProps[] =
507 {
508   { NULL, kpidPath, VT_BSTR},
509   { NULL, kpidSize, VT_UI8},
510   { NULL, kpidFileSystem, VT_BSTR},
511   { NULL, kpidOffset, VT_UI8},
512   { "Primary", kpidPrimary, VT_BOOL},
513   { "Begin CHS", kpidBegChs, VT_BSTR},
514   { "End CHS", kpidEndChs, VT_BSTR}
515 };
516 
517 static const Byte kArcProps[] =
518 {
519   kpidSectorSize,
520   kpidId
521 };
522 
523 IMP_IInArchive_Props_WITH_NAME
524 IMP_IInArchive_ArcProps
525 
526 
527 Z7_COM7F_IMF(CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value))
528 {
529   NCOM::CPropVariant prop;
530   switch (propID)
531   {
532     case kpidMainSubfile:
533     {
534       int mainIndex = -1;
535       FOR_VECTOR (i, _items)
536         if (_items[i].IsReal)
537         {
538           if (mainIndex >= 0)
539           {
540             mainIndex = -1;
541             break;
542           }
543           mainIndex = (int)i;
544         }
545       if (mainIndex >= 0)
546         prop = (UInt32)(Int32)mainIndex;
547       break;
548     }
549     case kpidPhySize: prop = _totalSize; break;
550     case kpidSectorSize: prop = (UInt32)((UInt32)1 << _sectorSizeLog); break;
551     case kpidId: prop = _signature; break;
552   }
553   prop.Detach(value);
554   return S_OK;
555 }
556 
557 Z7_COM7F_IMF(CHandler::GetNumberOfItems(UInt32 *numItems))
558 {
559   *numItems = _items.Size();
560   return S_OK;
561 }
562 
563 Z7_COM7F_IMF(CHandler::GetProperty(UInt32 index, PROPID propID, PROPVARIANT *value))
564 {
565   COM_TRY_BEGIN
566   NCOM::CPropVariant prop;
567 
568   const CItem &item = _items[index];
569   const CPartition &part = item.Part;
570   switch (propID)
571   {
572     case kpidPath:
573     {
574       AString s;
575       s.Add_UInt32(index);
576       if (item.IsReal)
577       {
578         s.Add_Dot();
579         const char *ext = NULL;
580         if (item.FileSystem)
581         {
582           ext = "";
583           AString fs (item.FileSystem);
584           fs.MakeLower_Ascii();
585           s += fs;
586         }
587         else if (!item.WasParsed)
588         {
589           const int typeIndex = FindPartType(part.Type);
590           if (typeIndex >= 0)
591             ext = kPartTypes[(unsigned)typeIndex].Ext;
592         }
593         if (!ext)
594           ext = "img";
595         s += ext;
596       }
597       prop = s;
598       break;
599     }
600     case kpidFileSystem:
601       if (item.IsReal)
602       {
603         char s[32];
604         ConvertUInt32ToString(part.Type, s);
605         const char *res = s;
606         if (item.FileSystem)
607         {
608           // strcat(s, "-");
609           // strcpy(s, item.FileSystem);
610           res = item.FileSystem;
611         }
612         else if (!item.WasParsed)
613         {
614           const int typeIndex = FindPartType(part.Type);
615           if (typeIndex >= 0 && kPartTypes[(unsigned)typeIndex].Name)
616             res = kPartTypes[(unsigned)typeIndex].Name;
617         }
618         prop = res;
619       }
620       break;
621     case kpidSize:
622     case kpidPackSize: prop = item.Size; break;
623     case kpidOffset: prop = part.GetPos(_sectorSizeLog); break;
624     case kpidPrimary: if (item.IsReal) prop = item.IsPrim; break;
625     case kpidBegChs: if (item.IsReal) part.BeginChs.ToString(prop); break;
626     case kpidEndChs: if (item.IsReal) part.EndChs.ToString(prop); break;
627   }
628   prop.Detach(value);
629   return S_OK;
630   COM_TRY_END
631 }
632 
633 
634   // 3, { 1, 1, 0 },
635   // 2, { 0x55, 0x1FF },
636 
637 REGISTER_ARC_I_NO_SIG(
638   "MBR", "mbr", NULL, 0xDB,
639   0,
640   NArcInfoFlags::kPureStartOpen,
641   NULL)
642 
643 }}
644