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