// ExtHandler.cpp #include "StdAfx.h" // #define SHOW_DEBUG_INFO // #include // #define PRF2(x) x #define PRF2(x) #ifdef SHOW_DEBUG_INFO #include #define PRF(x) x #else #define PRF(x) #endif #include "../../../C/Alloc.h" #include "../../../C/CpuArch.h" #include "../../Common/ComTry.h" #include "../../Common/IntToString.h" #include "../../Common/MyLinux.h" #include "../../Common/StringConvert.h" #include "../../Common/UTFConvert.h" #include "../../Windows/PropVariantUtils.h" #include "../../Windows/TimeUtils.h" #include "../Common/ProgressUtils.h" #include "../Common/RegisterArc.h" #include "../Common/StreamObjects.h" #include "../Common/StreamUtils.h" #include "../Compress/CopyCoder.h" using namespace NWindows; namespace NArchive { namespace NExt { #define Get16(p) GetUi16(p) #define Get32(p) GetUi32(p) #define Get64(p) GetUi64(p) #define LE_16(offs, dest) dest = Get16(p + (offs)); #define LE_32(offs, dest) dest = Get32(p + (offs)); #define LE_64(offs, dest) dest = Get64(p + (offs)); #define HI_16(offs, dest) dest |= (((UInt32)Get16(p + (offs))) << 16); #define HI_32(offs, dest) dest |= (((UInt64)Get32(p + (offs))) << 32); /* static UInt32 g_Crc32CTable[256]; static struct CInitCrc32C { CInitCrc32C() { for (unsigned i = 0; i < 256; i++) { UInt32 r = i; unsigned j; for (j = 0; j < 8; j++) r = (r >> 1) ^ (0x82F63B78 & ~((r & 1) - 1)); g_Crc32CTable[i] = r; } } } g_InitCrc32C; #define CRC32C_INIT_VAL 0xFFFFFFFF #define CRC32C_GET_DIGEST(crc) ((crc) ^ CRC_INIT_VAL) #define CRC32C_UPDATE_BYTE(crc, b) (g_Crc32CTable[((crc) ^ (b)) & 0xFF] ^ ((crc) >> 8)) static UInt32 Crc32C_Update(UInt32 crc, Byte const *data, size_t size) { for (size_t i = 0; i < size; i++) crc = CRC32C_UPDATE_BYTE(crc, data[i]); return crc; } static UInt32 Crc32C_Calc(Byte const *data, size_t size) { return Crc32C_Update(CRC32C_INIT_VAL, data, size); } */ // CRC-16-ANSI. The poly is 0x8005 (x^16 + x^15 + x^2 + 1) static UInt16 g_Crc16Table[256]; static struct CInitCrc16 { CInitCrc16() { for (unsigned i = 0; i < 256; i++) { UInt32 r = i; unsigned j; for (j = 0; j < 8; j++) r = (r >> 1) ^ (0xA001 & ~((r & 1) - 1)); g_Crc16Table[i] = (UInt16)r; } } } g_InitCrc16; #define CRC16_UPDATE_BYTE(crc, b) (g_Crc16Table[((crc) ^ (b)) & 0xFF] ^ ((crc) >> 8)) #define CRC16_INIT_VAL 0xFFFF static UInt32 Crc16Update(UInt32 crc, Byte const *data, size_t size) { for (size_t i = 0; i < size; i++) crc = CRC16_UPDATE_BYTE(crc, data[i]); return crc; } static UInt32 Crc16Calc(Byte const *data, size_t size) { return Crc16Update(CRC16_INIT_VAL, data, size); } #define EXT4_GOOD_OLD_INODE_SIZE 128 // inodes numbers // #define k_INODE_BAD 1 // Bad blocks #define k_INODE_ROOT 2 // Root dir // #define k_INODE_USR_QUOTA 3 // User quota // #define k_INODE_GRP_QUOTA 4 // Group quota // #define k_INODE_BOOT_LOADER 5 // Boot loader // #define k_INODE_UNDEL_DIR 6 // Undelete dir #define k_INODE_RESIZE 7 // Reserved group descriptors // #define k_INODE_JOURNAL 8 // Journal // First non-reserved inode for old ext4 filesystems #define k_INODE_GOOD_OLD_FIRST 11 static const char * const k_SysInode_Names[] = { "0" , "Bad" , "Root" , "UserQuota" , "GroupQuota" , "BootLoader" , "Undelete" , "Resize" , "Journal" , "Exclude" , "Replica" }; static const char * const kHostOS[] = { "Linux" , "Hurd" , "Masix" , "FreeBSD" , "Lites" }; static const CUInt32PCharPair g_FeatureCompat_Flags[] = { { 0, "DIR_PREALLOC" }, { 1, "IMAGIC_INODES" }, { 2, "HAS_JOURNAL" }, { 3, "EXT_ATTR" }, { 4, "RESIZE_INODE" }, { 5, "DIR_INDEX" }, { 6, "LAZY_BG" }, // not in Linux // { 7, "EXCLUDE_INODE" }, // not used // { 8, "EXCLUDE_BITMAP" }, // not in kernel { 9, "SPARSE_SUPER2" } }; #define EXT4_FEATURE_INCOMPAT_FILETYPE (1 << 1) #define EXT4_FEATURE_INCOMPAT_64BIT (1 << 7) static const CUInt32PCharPair g_FeatureIncompat_Flags[] = { { 0, "COMPRESSION" }, { 1, "FILETYPE" }, { 2, "RECOVER" }, /* Needs recovery */ { 3, "JOURNAL_DEV" }, /* Journal device */ { 4, "META_BG" }, { 6, "EXTENTS" }, /* extents support */ { 7, "64BIT" }, { 8, "MMP" }, { 9, "FLEX_BG" }, { 10, "EA_INODE" }, /* EA in inode */ { 12, "DIRDATA" }, /* data in dirent */ { 13, "BG_USE_META_CSUM" }, /* use crc32c for bg */ { 14, "LARGEDIR" }, /* >2GB or 3-lvl htree */ { 15, "INLINE_DATA" }, /* data in inode */ { 16, "ENCRYPT" } }; static const UInt32 RO_COMPAT_GDT_CSUM = 1 << 4; static const UInt32 RO_COMPAT_METADATA_CSUM = 1 << 10; static const CUInt32PCharPair g_FeatureRoCompat_Flags[] = { { 0, "SPARSE_SUPER" }, { 1, "LARGE_FILE" }, { 2, "BTREE_DIR" }, { 3, "HUGE_FILE" }, { 4, "GDT_CSUM" }, { 5, "DIR_NLINK" }, { 6, "EXTRA_ISIZE" }, { 7, "HAS_SNAPSHOT" }, { 8, "QUOTA" }, { 9, "BIGALLOC" }, { 10, "METADATA_CSUM" }, { 11, "REPLICA" }, { 12, "READONLY" } }; static const UInt32 k_NodeFlags_HUGE = (UInt32)1 << 18; static const UInt32 k_NodeFlags_EXTENTS = (UInt32)1 << 19; static const CUInt32PCharPair g_NodeFlags[] = { { 0, "SECRM" }, { 1, "UNRM" }, { 2, "COMPR" }, { 3, "SYNC" }, { 4, "IMMUTABLE" }, { 5, "APPEND" }, { 6, "NODUMP" }, { 7, "NOATIME" }, { 8, "DIRTY" }, { 9, "COMPRBLK" }, { 10, "NOCOMPR" }, { 11, "ENCRYPT" }, { 12, "INDEX" }, { 13, "IMAGIC" }, { 14, "JOURNAL_DATA" }, { 15, "NOTAIL" }, { 16, "DIRSYNC" }, { 17, "TOPDIR" }, { 18, "HUGE_FILE" }, { 19, "EXTENTS" }, { 21, "EA_INODE" }, { 22, "EOFBLOCKS" }, { 28, "INLINE_DATA" } }; static inline char GetHex(unsigned t) { return (char)(((t < 10) ? ('0' + t) : ('A' + (t - 10)))); } static inline void PrintHex(unsigned v, char *s) { s[0] = GetHex((v >> 4) & 0xF); s[1] = GetHex(v & 0xF); } enum { k_Type_UNKNOWN, k_Type_REG_FILE, k_Type_DIR, k_Type_CHRDEV, k_Type_BLKDEV, k_Type_FIFO, k_Type_SOCK, k_Type_SYMLINK }; static const UInt16 k_TypeToMode[] = { 0, MY_LIN_S_IFREG, MY_LIN_S_IFDIR, MY_LIN_S_IFCHR, MY_LIN_S_IFBLK, MY_LIN_S_IFIFO, MY_LIN_S_IFSOCK, MY_LIN_S_IFLNK }; #define EXT4_GOOD_OLD_REV 0 // old (original) format // #define EXT4_DYNAMIC_REV 1 // V2 format with dynamic inode sizes struct CHeader { unsigned BlockBits; unsigned ClusterBits; UInt32 NumInodes; UInt64 NumBlocks; // UInt64 NumBlocksSuper; UInt64 NumFreeBlocks; UInt32 NumFreeInodes; // UInt32 FirstDataBlock; UInt32 BlocksPerGroup; UInt32 ClustersPerGroup; UInt32 InodesPerGroup; UInt32 MountTime; UInt32 WriteTime; // UInt16 NumMounts; // UInt16 NumMountsMax; // UInt16 State; // UInt16 Errors; // UInt16 MinorRevLevel; UInt32 LastCheckTime; // UInt32 CheckInterval; UInt32 CreatorOs; UInt32 RevLevel; // UInt16 DefResUid; // UInt16 DefResGid; UInt32 FirstInode; UInt16 InodeSize; UInt16 BlockGroupNr; UInt32 FeatureCompat; UInt32 FeatureIncompat; UInt32 FeatureRoCompat; Byte Uuid[16]; char VolName[16]; char LastMount[64]; // UInt32 BitmapAlgo; UInt32 JournalInode; UInt16 GdSize; // = 64 if 64-bit(); UInt32 CTime; UInt16 MinExtraISize; // UInt16 WantExtraISize; // UInt32 Flags; // Byte LogGroupsPerFlex; // Byte ChecksumType; UInt64 WrittenKB; bool IsOldRev() const { return RevLevel == EXT4_GOOD_OLD_REV; } UInt64 GetNumGroups() const { return (NumBlocks + BlocksPerGroup - 1) / BlocksPerGroup; } UInt64 GetNumGroups2() const { return ((UInt64)NumInodes + InodesPerGroup - 1) / InodesPerGroup; } bool IsThereFileType() const { return (FeatureIncompat & EXT4_FEATURE_INCOMPAT_FILETYPE) != 0; } bool Is64Bit() const { return (FeatureIncompat & EXT4_FEATURE_INCOMPAT_64BIT) != 0; } bool UseGdtChecksum() const { return (FeatureRoCompat & RO_COMPAT_GDT_CSUM) != 0; } bool UseMetadataChecksum() const { return (FeatureRoCompat & RO_COMPAT_METADATA_CSUM) != 0; } bool Parse(const Byte *p); }; static int inline GetLog(UInt32 num) { for (unsigned i = 0; i < 32; i++) if (((UInt32)1 << i) == num) return i; return -1; } static bool inline IsEmptyData(const Byte *data, unsigned size) { for (unsigned i = 0; i < size; i++) if (data[i] != 0) return false; return true; } bool CHeader::Parse(const Byte *p) { if (GetUi16(p + 0x38) != 0xEF53) return false; LE_32 (0x18, BlockBits); LE_32 (0x1C, ClusterBits); if (ClusterBits != 0 && BlockBits != ClusterBits) return false; if (BlockBits > 16 - 10) return false; BlockBits += 10; LE_32 (0x00, NumInodes); LE_32 (0x04, NumBlocks); // LE_32 (0x08, NumBlocksSuper); LE_32 (0x0C, NumFreeBlocks); LE_32 (0x10, NumFreeInodes); if (NumInodes < 2 || NumInodes <= NumFreeInodes) return false; UInt32 FirstDataBlock; LE_32 (0x14, FirstDataBlock); if (FirstDataBlock != (unsigned)(BlockBits == 10 ? 1 : 0)) return false; LE_32 (0x20, BlocksPerGroup); LE_32 (0x24, ClustersPerGroup); if (BlocksPerGroup != ClustersPerGroup) return false; if (BlocksPerGroup == 0) return false; if (BlocksPerGroup != ((UInt32)1 << (BlockBits + 3))) { // it's allowed in ext2 // return false; } LE_32 (0x28, InodesPerGroup); if (InodesPerGroup < 1 || InodesPerGroup > NumInodes) return false; LE_32 (0x2C, MountTime); LE_32 (0x30, WriteTime); // LE_16 (0x34, NumMounts); // LE_16 (0x36, NumMountsMax); // LE_16 (0x3A, State); // LE_16 (0x3C, Errors); // LE_16 (0x3E, MinorRevLevel); LE_32 (0x40, LastCheckTime); // LE_32 (0x44, CheckInterval); LE_32 (0x48, CreatorOs); LE_32 (0x4C, RevLevel); // LE_16 (0x50, DefResUid); // LE_16 (0x52, DefResGid); FirstInode = k_INODE_GOOD_OLD_FIRST; InodeSize = EXT4_GOOD_OLD_INODE_SIZE; if (!IsOldRev()) { LE_32 (0x54, FirstInode); LE_16 (0x58, InodeSize); if (FirstInode < k_INODE_GOOD_OLD_FIRST) return false; if (InodeSize > (UInt32)1 << BlockBits) return false; if (GetLog(InodeSize) < 0) return false; } LE_16 (0x5A, BlockGroupNr); LE_32 (0x5C, FeatureCompat); LE_32 (0x60, FeatureIncompat); LE_32 (0x64, FeatureRoCompat); memcpy(Uuid, p + 0x68, sizeof(Uuid)); memcpy(VolName, p + 0x78, sizeof(VolName)); memcpy(LastMount, p + 0x88, sizeof(LastMount)); // LE_32 (0xC8, BitmapAlgo); LE_32 (0xE0, JournalInode); LE_16 (0xFE, GdSize); LE_32 (0x108, CTime); if (Is64Bit()) { HI_32(150, NumBlocks); // HI_32(154, NumBlocksSuper); HI_32(0x158, NumFreeBlocks); } if (NumBlocks >= (UInt64)1 << (63 - BlockBits)) return false; LE_16(0x15C, MinExtraISize); // LE_16(0x15E, WantExtraISize); // LE_32(0x160, Flags); // LE_16(0x164, RaidStride); // LE_16(0x166, MmpInterval); // LE_64(0x168, MmpBlock); // LogGroupsPerFlex = p[0x174]; // ChecksumType = p[0x175]; LE_64 (0x178, WrittenKB); // LE_32(0x194, ErrorCount); // LE_32(0x198, ErrorTime); // LE_32(0x19C, ErrorINode); // LE_32(0x1A0, ErrorBlock); if (NumBlocks < 1) return false; if (NumFreeBlocks > NumBlocks) return false; if (GetNumGroups() != GetNumGroups2()) return false; return true; } struct CGroupDescriptor { UInt64 BlockBitmap; UInt64 InodeBitmap; UInt64 InodeTable; UInt32 NumFreeBlocks; UInt32 NumFreeInodes; UInt32 DirCount; UInt16 Flags; UInt64 ExcludeBitmap; UInt32 BlockBitmap_Checksum; UInt32 InodeBitmap_Checksum; UInt32 UnusedCount; UInt16 Checksum; void Parse(const Byte *p, unsigned size); }; void CGroupDescriptor::Parse(const Byte *p, unsigned size) { LE_32 (0x00, BlockBitmap); LE_32 (0x04, InodeBitmap); LE_32 (0x08, InodeTable); LE_16 (0x0C, NumFreeBlocks); LE_16 (0x0E, NumFreeInodes); LE_16 (0x10, DirCount); LE_16 (0x12, Flags); LE_32 (0x14, ExcludeBitmap); LE_16 (0x18, BlockBitmap_Checksum); LE_16 (0x1A, InodeBitmap_Checksum); LE_16 (0x1C, UnusedCount); LE_16 (0x1E, Checksum); if (size >= 64) { p += 0x20; HI_32 (0x00, BlockBitmap); HI_32 (0x04, InodeBitmap); HI_32 (0x08, InodeTable); HI_16 (0x0C, NumFreeBlocks); HI_16 (0x0E, NumFreeInodes); HI_16 (0x10, DirCount); HI_16 (0x12, UnusedCount); // instead of Flags HI_32 (0x14, ExcludeBitmap); HI_16 (0x18, BlockBitmap_Checksum); HI_16 (0x1A, InodeBitmap_Checksum); // HI_16 (0x1C, Reserved); } } static const unsigned kNodeBlockFieldSize = 60; struct CExtentTreeHeader { UInt16 NumEntries; UInt16 MaxEntries; UInt16 Depth; // UInt32 Generation; bool Parse(const Byte *p) { LE_16 (0x02, NumEntries); LE_16 (0x04, MaxEntries); LE_16 (0x06, Depth); // LE_32 (0x08, Generation); return Get16(p) == 0xF30A; // magic } }; struct CExtentIndexNode { UInt32 VirtBlock; UInt64 PhyLeaf; void Parse(const Byte *p) { LE_32 (0x00, VirtBlock); LE_32 (0x04, PhyLeaf); PhyLeaf |= (((UInt64)Get16(p + 8)) << 32); // unused 16-bit field (at offset 0x0A) can be not zero in some cases. Why? } }; struct CExtent { UInt32 VirtBlock; UInt16 Len; bool IsInited; UInt64 PhyStart; UInt32 GetVirtEnd() const { return VirtBlock + Len; } bool IsLenOK() const { return VirtBlock + Len >= VirtBlock; } void Parse(const Byte *p) { LE_32 (0x00, VirtBlock); LE_16 (0x04, Len); IsInited = true; if (Len > (UInt32)0x8000) { IsInited = false; Len -= (UInt32)0x8000; } LE_32 (0x08, PhyStart); UInt16 hi; LE_16 (0x06, hi); PhyStart |= ((UInt64)hi << 32); } }; struct CExtTime { UInt32 Val; UInt32 Extra; }; struct CNode { Int32 ParentNode; // in _refs[], -1 if not dir int ItemIndex; // in _items[] int SymLinkIndex; // in _symLinks[] int DirIndex; // in _dirs[] UInt16 Mode; UInt16 Uid; UInt16 Gid; // UInt16 Checksum; UInt64 FileSize; CExtTime MTime; CExtTime ATime; CExtTime CTime; // CExtTime InodeChangeTime; // CExtTime DTime; UInt64 NumBlocks; UInt32 NumLinks; UInt32 Flags; UInt32 NumLinksCalced; Byte Block[kNodeBlockFieldSize]; CNode(): ParentNode(-1), ItemIndex(-1), SymLinkIndex(-1), DirIndex(0), NumLinksCalced(0) {} bool IsFlags_HUGE() const { return (Flags & k_NodeFlags_HUGE) != 0; } bool IsFlags_EXTENTS() const { return (Flags & k_NodeFlags_EXTENTS) != 0; } bool IsDir() const { return MY_LIN_S_ISDIR(Mode); } bool IsRegular() const { return MY_LIN_S_ISREG(Mode); } bool IsLink() const { return MY_LIN_S_ISLNK(Mode); } bool Parse(const Byte *p, const CHeader &_h); }; bool CNode::Parse(const Byte *p, const CHeader &_h) { MTime.Extra = 0; ATime.Extra = 0; CTime.Extra = 0; CTime.Val = 0; // InodeChangeTime.Extra = 0; // DTime.Extra = 0; LE_16 (0x00, Mode); LE_16 (0x02, Uid); LE_32 (0x04, FileSize); LE_32 (0x08, ATime.Val); // LE_32 (0x0C, InodeChangeTime.Val); LE_32 (0x10, MTime.Val); // LE_32 (0x14, DTime.Val); LE_16 (0x18, Gid); LE_16 (0x1A, NumLinks); LE_32 (0x1C, NumBlocks); LE_32 (0x20, Flags); // LE_32 (0x24, Union osd1); memcpy(Block, p + 0x28, kNodeBlockFieldSize); // LE_32 (0x64, Generation); // File version (for NFS) // LE_32 (0x68, ACL); { UInt32 highSize; LE_32 (0x6C, highSize); // In ext2/3 this field was named i_dir_acl if (IsRegular()) // do we need that check ? FileSize |= ((UInt64)highSize << 32); } // UInt32 fragmentAddress; // LE_32 (0x70, fragmentAddress); // osd2 { // Linux; // ext2: // Byte FragmentNumber = p[0x74]; // Byte FragmentSize = p[0x74 + 1]; // ext4: UInt32 numBlocksHigh; LE_16 (0x74, numBlocksHigh); NumBlocks |= (UInt64)numBlocksHigh << 32; HI_16 (0x74 + 4, Uid); HI_16 (0x74 + 6, Gid); /* UInt32 checksum; LE_16 (0x74 + 8, checksum); checksum = checksum; */ } // 0x74: Byte Union osd2[12]; if (_h.InodeSize > 128) { UInt16 extra_isize; LE_16 (0x80, extra_isize); if (128 + extra_isize > _h.InodeSize) return false; if (extra_isize >= 0x1C) { // UInt16 checksumUpper; // LE_16 (0x82, checksumUpper); // LE_32 (0x84, InodeChangeTime.Extra); LE_32 (0x88, MTime.Extra); LE_32 (0x8C, ATime.Extra); LE_32 (0x90, CTime.Val); LE_32 (0x94, CTime.Extra); // LE_32 (0x98, VersionHi); } } PRF(printf("size = %5d", (unsigned)FileSize)); return true; } struct CItem { unsigned Node; // in _refs[] int ParentNode; // in _refs[] int SymLinkItemIndex; // in _items[], if the Node contains SymLink to existing dir Byte Type; AString Name; CItem(): Node(0), ParentNode(-1), SymLinkItemIndex(-1), Type(k_Type_UNKNOWN) {} void Clear() { Node = 0; ParentNode = -1; SymLinkItemIndex = -1; Type = k_Type_UNKNOWN; Name.Empty(); } bool IsDir() const { return Type == k_Type_DIR; } // bool IsNotDir() const { return Type != k_Type_DIR && Type != k_Type_UNKNOWN; } }; static const unsigned kNumTreeLevelsMax = 6; // must be >= 3 class CHandler: public IInArchive, public IArchiveGetRawProps, public IInArchiveGetStream, public CMyUnknownImp { CObjectVector _items; CIntVector _refs; CRecordVector _nodes; CObjectVector _dirs; // each CUIntVector contains indexes in _items[] only for dir items; AStringVector _symLinks; AStringVector _auxItems; int _auxSysIndex; int _auxUnknownIndex; CMyComPtr _stream; UInt64 _phySize; bool _isArc; bool _headersError; bool _headersWarning; bool _linksError; bool _isUTF; CHeader _h; IArchiveOpenCallback *_openCallback; UInt64 _totalRead; UInt64 _totalReadPrev; CByteBuffer _tempBufs[kNumTreeLevelsMax]; HRESULT CheckProgress2() { const UInt64 numFiles = _items.Size(); return _openCallback->SetCompleted(&numFiles, &_totalRead); } HRESULT CheckProgress() { HRESULT res = S_OK; if (_openCallback) { if (_totalRead - _totalReadPrev >= ((UInt32)1 << 20)) { _totalReadPrev = _totalRead; res = CheckProgress2(); } } return res; } const int GetParentAux(const CItem &item) const { if (item.Node < _h.FirstInode && _auxSysIndex >= 0) return _auxSysIndex; return _auxUnknownIndex; } HRESULT SeekAndRead(IInStream *inStream, UInt64 block, Byte *data, size_t size); HRESULT ParseDir(const Byte *data, size_t size, unsigned iNodeDir); int FindTargetItem_for_SymLink(unsigned dirNode, const AString &path) const; HRESULT FillFileBlocks2(UInt32 block, unsigned level, unsigned numBlocks, CRecordVector &blocks); HRESULT FillFileBlocks(const Byte *p, unsigned numBlocks, CRecordVector &blocks); HRESULT FillExtents(const Byte *p, size_t size, CRecordVector &extents, int parentDepth); HRESULT GetStream_Node(unsigned nodeIndex, ISequentialInStream **stream); HRESULT ExtractNode(unsigned nodeIndex, CByteBuffer &data); void ClearRefs(); HRESULT Open2(IInStream *inStream); void GetPath(unsigned index, AString &s) const; bool GetPackSize(unsigned index, UInt64 &res) const; public: CHandler() {} ~CHandler() {} MY_UNKNOWN_IMP3(IInArchive, IArchiveGetRawProps, IInArchiveGetStream) INTERFACE_IInArchive(;) INTERFACE_IArchiveGetRawProps(;) STDMETHOD(GetStream)(UInt32 index, ISequentialInStream **stream); }; HRESULT CHandler::ParseDir(const Byte *p, size_t size, unsigned iNodeDir) { bool isThereSelfLink = false; PRF(printf("\n\n========= node = %5d size = %5d", (unsigned)iNodeDir, (unsigned)size)); CNode &nodeDir = _nodes[_refs[iNodeDir]]; nodeDir.DirIndex = _dirs.Size(); CUIntVector &dir = _dirs.AddNew(); int parentNode = -1; CItem item; for (;;) { if (size == 0) break; if (size < 8) return S_FALSE; UInt32 iNode; LE_32 (0x00, iNode); unsigned recLen; LE_16 (0x04, recLen); unsigned nameLen = p[6]; Byte type = p[7]; if (recLen > size) return S_FALSE; if (nameLen + 8 > recLen) return S_FALSE; if (iNode >= _refs.Size()) return S_FALSE; item.Clear(); if (_h.IsThereFileType()) item.Type = type; else if (type != 0) return S_FALSE; item.ParentNode = iNodeDir; item.Node = iNode; item.Name.SetFrom_CalcLen((const char *)(p + 8), nameLen); p += recLen; size -= recLen; if (item.Name.Len() != nameLen) return S_FALSE; if (_isUTF) _isUTF = CheckUTF8(item.Name); if (iNode == 0) { /* ext3 deleted?? if (item.Name.Len() != 0) return S_FALSE; */ PRF(printf("\n EMPTY %6d %d %s", (unsigned)recLen, (unsigned)type, (const char *)item.Name)); if (type == 0xDE) { // checksum } continue; } int nodeIndex = _refs[iNode]; if (nodeIndex < 0) return S_FALSE; CNode &node = _nodes[nodeIndex]; if (_h.IsThereFileType() && type != 0) { if (type >= ARRAY_SIZE(k_TypeToMode)) return S_FALSE; if (k_TypeToMode[type] != (node.Mode & MY_LIN_S_IFMT)) return S_FALSE; } node.NumLinksCalced++; PRF(printf("\n%s %6d %s", item.IsDir() ? "DIR " : " ", (unsigned)item.Node, (const char *)item.Name)); if (item.Name[0] == '.') { if (item.Name[1] == 0) { if (isThereSelfLink) return S_FALSE; isThereSelfLink = true; if (iNode != iNodeDir) return S_FALSE; continue; } if (item.Name[1] == '.' && item.Name[2] == 0) { if (parentNode >= 0) return S_FALSE; if (!node.IsDir()) return S_FALSE; if (iNode == iNodeDir && iNode != k_INODE_ROOT) return S_FALSE; parentNode = iNode; if (nodeDir.ParentNode < 0) nodeDir.ParentNode = iNode; else if ((unsigned)nodeDir.ParentNode != iNode) return S_FALSE; continue; } } if (iNode == iNodeDir) return S_FALSE; if (parentNode < 0) return S_FALSE; if (node.IsDir()) { if (node.ParentNode < 0) node.ParentNode = iNodeDir; else if ((unsigned)node.ParentNode != iNodeDir) return S_FALSE; const unsigned itemIndex = _items.Size(); dir.Add(itemIndex); node.ItemIndex = itemIndex; } _items.Add(item); } if (parentNode < 0 || !isThereSelfLink) return S_FALSE; return S_OK; } int CHandler::FindTargetItem_for_SymLink(unsigned iNode, const AString &path) const { unsigned pos = 0; if (path.IsEmpty()) return -1; if (path[0] == '/') { iNode = k_INODE_ROOT; if (iNode >= _refs.Size()) return -1; pos = 1; } AString s; while (pos != path.Len()) { const CNode &node = _nodes[_refs[iNode]]; int slash = path.Find('/', pos); if (slash < 0) { s = path.Ptr(pos); pos = path.Len(); } else { s.SetFrom(path.Ptr(pos), slash - pos); pos = slash + 1; } if (s[0] == '.') { if (s[1] == 0) continue; else if (s[1] == '.' && s[2] == 0) { if (node.ParentNode < 0) return -1; if (iNode == k_INODE_ROOT) return -1; iNode = node.ParentNode; continue; } } if (node.DirIndex < 0) return -1; const CUIntVector &dir = _dirs[node.DirIndex]; for (unsigned i = 0;; i++) { if (i >= dir.Size()) return -1; const CItem &item = _items[dir[i]]; if (item.Name == s) { iNode = item.Node; break; } } } return _nodes[_refs[iNode]].ItemIndex; } HRESULT CHandler::SeekAndRead(IInStream *inStream, UInt64 block, Byte *data, size_t size) { if (block == 0 || block >= _h.NumBlocks) return S_FALSE; if (((size + ((size_t)1 << _h.BlockBits) - 1) >> _h.BlockBits) > _h.NumBlocks - block) return S_FALSE; RINOK(inStream->Seek((UInt64)block << _h.BlockBits, STREAM_SEEK_SET, NULL)); _totalRead += size; return ReadStream_FALSE(inStream, data, size); } static const unsigned kHeaderSize = 2 * 1024; static const unsigned kHeaderDataOffset = 1024; HRESULT CHandler::Open2(IInStream *inStream) { { Byte buf[kHeaderSize]; RINOK(ReadStream_FALSE(inStream, buf, kHeaderSize)); if (!_h.Parse(buf + kHeaderDataOffset)) return S_FALSE; if (_h.BlockGroupNr != 0) return S_FALSE; // it's just copy of super block } { // ---------- Read groups and nodes ---------- unsigned numGroups; { UInt64 numGroups64 = _h.GetNumGroups(); if (numGroups64 > (UInt32)1 << 31) return S_FALSE; numGroups = (unsigned)numGroups64; } unsigned gdBits = 5; if (_h.Is64Bit()) { if (_h.GdSize != 64) return S_FALSE; gdBits = 6; } _isArc = true; _phySize = _h.NumBlocks << _h.BlockBits; if (_openCallback) { RINOK(_openCallback->SetTotal(NULL, &_phySize)); } UInt64 fileSize = 0; RINOK(inStream->Seek(0, STREAM_SEEK_END, &fileSize)); CRecordVector groups; { // ---------- Read groups ---------- CByteBuffer gdBuf; size_t gdBufSize = (size_t)numGroups << gdBits; if ((gdBufSize >> gdBits) != numGroups) return S_FALSE; gdBuf.Alloc(gdBufSize); RINOK(SeekAndRead(inStream, (_h.BlockBits <= 10 ? 2 : 1), gdBuf, gdBufSize)); for (unsigned i = 0; i < numGroups; i++) { CGroupDescriptor gd; const Byte *p = gdBuf + ((size_t)i << gdBits); unsigned gd_Size = (unsigned)1 << gdBits; gd.Parse(p, gd_Size); if (_h.UseMetadataChecksum()) { // use CRC32c } else if (_h.UseGdtChecksum()) { UInt32 crc = Crc16Calc(_h.Uuid, sizeof(_h.Uuid)); Byte i_le[4]; SetUi32(i_le, i); crc = Crc16Update(crc, i_le, 4); crc = Crc16Update(crc, p, 32 - 2); if (gd_Size != 32) crc = Crc16Update(crc, p + 32, gd_Size - 32); if (crc != gd.Checksum) return S_FALSE; } groups.Add(gd); } } { // ---------- Read nodes ---------- if (_h.NumInodes < _h.NumFreeInodes) return S_FALSE; UInt32 numNodes = _h.InodesPerGroup; if (numNodes > _h.NumInodes) numNodes = _h.NumInodes; size_t nodesDataSize = (size_t)numNodes * _h.InodeSize; if (nodesDataSize / _h.InodeSize != numNodes) return S_FALSE; // that code to reduce false detecting cases if (nodesDataSize > fileSize) { if (numNodes > (1 << 24)) return S_FALSE; } UInt32 numReserveInodes = _h.NumInodes - _h.NumFreeInodes + 1; // numReserveInodes = _h.NumInodes + 1; if (numReserveInodes != 0) { _nodes.Reserve(numReserveInodes); _refs.Reserve(numReserveInodes); } CByteBuffer nodesData; nodesData.Alloc(nodesDataSize); CByteBuffer nodesMap; const size_t blockSize = (size_t)1 << _h.BlockBits; nodesMap.Alloc(blockSize); unsigned globalNodeIndex = 0; // unsigned numEmpty = 0; unsigned numEmpty_in_Maps = 0; FOR_VECTOR (gi, groups) { if (globalNodeIndex >= _h.NumInodes) break; const CGroupDescriptor &gd = groups[gi]; PRF(printf("\n\ng%6d block = %6x\n", gi, (unsigned)gd.InodeTable)); RINOK(SeekAndRead(inStream, gd.InodeBitmap, nodesMap, blockSize)); RINOK(SeekAndRead(inStream, gd.InodeTable, nodesData, nodesDataSize)); unsigned numEmpty_in_Map = 0; for (size_t n = 0; n < numNodes && globalNodeIndex < _h.NumInodes; n++, globalNodeIndex++) { if ((nodesMap[n >> 3] & ((unsigned)1 << (n & 7))) == 0) { numEmpty_in_Map++; continue; } const Byte *p = nodesData + (size_t)n * _h.InodeSize; if (IsEmptyData(p, _h.InodeSize)) { if (globalNodeIndex + 1 >= _h.FirstInode) { _headersError = true; // return S_FALSE; } continue; } CNode node; PRF(printf("\nnode = %5d ", (unsigned)n)); if (!node.Parse(p, _h)) return S_FALSE; // PRF(printf("\n %6d", (unsigned)n)); /* SetUi32(p + 0x7C, 0) SetUi32(p + 0x82, 0) UInt32 crc = Crc32C_Calc(_h.Uuid, sizeof(_h.Uuid)); Byte i_le[4]; SetUi32(i_le, n); crc = Crc32C_Update(crc, i_le, 4); crc = Crc32C_Update(crc, p, _h.InodeSize); if (crc != node.Checksum) return S_FALSE; */ while (_refs.Size() < globalNodeIndex + 1) { // numEmpty++; _refs.Add(-1); } _refs.Add(_nodes.Add(node)); } numEmpty_in_Maps += numEmpty_in_Map; if (numEmpty_in_Map != gd.NumFreeInodes) { _headersWarning = true; // return S_FALSE; } } if (numEmpty_in_Maps != _h.NumFreeInodes) { // some ext2 examples has incorrect value in _h.NumFreeInodes. // so we disable check; _headersWarning = true; } if (_refs.Size() <= k_INODE_ROOT) return S_FALSE; // printf("\n numReserveInodes = %6d, _refs.Size() = %d, numEmpty = %7d\n", numReserveInodes, _refs.Size(), (unsigned)numEmpty); } } _stream = inStream; // we need stream for dir nodes { // ---------- Read Dirs ---------- CByteBuffer dataBuf; FOR_VECTOR (i, _refs) { int nodeIndex = _refs[i]; { if (nodeIndex < 0) continue; const CNode &node = _nodes[nodeIndex]; if (!node.IsDir()) continue; } RINOK(ExtractNode(nodeIndex, dataBuf)); if (dataBuf.Size() == 0) { // _headersError = true; return S_FALSE; } else { RINOK(ParseDir(dataBuf, dataBuf.Size(), i)); } RINOK(CheckProgress()); } if (_nodes[_refs[k_INODE_ROOT]].ParentNode != k_INODE_ROOT) return S_FALSE; } { // ---------- Check NumLinks and unreferenced dir nodes ---------- FOR_VECTOR (i, _refs) { int nodeIndex = _refs[i]; if (nodeIndex < 0) continue; const CNode &node = _nodes[nodeIndex]; if (node.NumLinks != node.NumLinksCalced) { if (node.NumLinks != 1 || node.NumLinksCalced != 0 // ) && i >= _h.FirstInode ) { _linksError = true; // return S_FALSE; } } if (!node.IsDir()) continue; if (node.ParentNode < 0) { if (i >= _h.FirstInode) return S_FALSE; continue; } } } { // ---------- Check that there is no loops in parents list ---------- unsigned numNodes = _refs.Size(); CIntArr UsedByNode(numNodes); { { for (unsigned i = 0; i < numNodes; i++) UsedByNode[i] = -1; } } FOR_VECTOR (i, _refs) { { int nodeIndex = _refs[i]; if (nodeIndex < 0) continue; const CNode &node = _nodes[nodeIndex]; if (node.ParentNode < 0 // not dir || i == k_INODE_ROOT) continue; } unsigned c = i; for (;;) { int nodeIndex = _refs[c]; if (nodeIndex < 0) return S_FALSE; CNode &node = _nodes[nodeIndex]; if (UsedByNode[c] != -1) { if ((unsigned)UsedByNode[c] == i) return S_FALSE; break; } UsedByNode[c] = i; if (node.ParentNode < 0 || node.ParentNode == k_INODE_ROOT) break; if ((unsigned)node.ParentNode == i) return S_FALSE; c = node.ParentNode; } } } { // ---------- Fill SymLinks data ---------- AString s; CByteBuffer data; unsigned i; for (i = 0; i < _refs.Size(); i++) { int nodeIndex = _refs[i]; if (nodeIndex < 0) continue; CNode &node = _nodes[nodeIndex]; if (!node.IsLink()) continue; if (node.FileSize > ((UInt32)1 << 14)) continue; if (ExtractNode(nodeIndex, data) == S_OK && data.Size() != 0) { s.SetFrom_CalcLen((const char *)(const Byte *)data, (unsigned)data.Size()); if (s.Len() == data.Size()) node.SymLinkIndex = _symLinks.Add(s); RINOK(CheckProgress()); } } unsigned prev = 0; unsigned complex = 0; for (i = 0; i < _items.Size(); i++) { CItem &item = _items[i]; int sym = _nodes[_refs[item.Node]].SymLinkIndex; if (sym >= 0 && item.ParentNode >= 0) { item.SymLinkItemIndex = FindTargetItem_for_SymLink(item.ParentNode, _symLinks[sym]); if (_openCallback) { complex++; if (complex - prev >= (1 << 10)) { RINOK(CheckProgress2()); prev = complex; } } } } } { // ---------- Add items and aux folders for unreferenced files ---------- bool useSys = false; bool useUnknown = false; FOR_VECTOR (i, _refs) { int nodeIndex = _refs[i]; if (nodeIndex < 0) continue; const CNode &node = _nodes[nodeIndex]; if (node.NumLinksCalced == 0 /* || i > 100 && i < 150 */) // for debug { CItem item; item.Node = i; // we don't know how to work with k_INODE_RESIZE node (strange FileSize and Block values). // so we ignore it; if (i == k_INODE_RESIZE) continue; if (node.FileSize == 0) continue; if (i < _h.FirstInode) { if (item.Node < ARRAY_SIZE(k_SysInode_Names)) item.Name = k_SysInode_Names[item.Node]; useSys = true; } else useUnknown = true; if (item.Name.IsEmpty()) { char temp[16]; ConvertUInt32ToString(item.Node, temp); item.Name = temp; } _items.Add(item); } } if (useSys) _auxSysIndex = _auxItems.Add("[SYS]"); if (useUnknown) _auxUnknownIndex = _auxItems.Add("[UNKNOWN]"); } return S_OK; } STDMETHODIMP CHandler::Open(IInStream *stream, const UInt64 *, IArchiveOpenCallback *callback) { COM_TRY_BEGIN { Close(); HRESULT res; try { _openCallback = callback; res = Open2(stream); } catch(...) { ClearRefs(); throw; } if (res != S_OK) { ClearRefs(); return res; } _stream = stream; } return S_OK; COM_TRY_END } void CHandler::ClearRefs() { _stream.Release(); _items.Clear(); _nodes.Clear(); _refs.Clear(); _auxItems.Clear(); _symLinks.Clear(); _dirs.Clear(); _auxSysIndex = -1; _auxUnknownIndex = -1; } STDMETHODIMP CHandler::Close() { _totalRead = 0; _totalReadPrev = 0; _phySize = 0; _isArc = false; _headersError = false; _headersWarning = false; _linksError = false; _isUTF = true; ClearRefs(); return S_OK; } void CHandler::GetPath(unsigned index, AString &s) const { s.Empty(); if (index >= _items.Size()) { s = _auxItems[index - _items.Size()]; return; } for (;;) { const CItem &item = _items[index]; if (!s.IsEmpty()) s.InsertAtFront(CHAR_PATH_SEPARATOR); s.Insert(0, item.Name); if (item.ParentNode == k_INODE_ROOT) return; if (item.ParentNode < 0) { int aux = GetParentAux(item); if (aux < 0) break; s.InsertAtFront(CHAR_PATH_SEPARATOR); s.Insert(0, _auxItems[aux]); return; } const CNode &node = _nodes[_refs[item.ParentNode]]; if (node.ItemIndex < 0) return; index = node.ItemIndex; if (s.Len() > ((UInt32)1 << 16)) { s.Insert(0, "[LONG]" STRING_PATH_SEPARATOR); return; } } } bool CHandler::GetPackSize(unsigned index, UInt64 &totalPack) const { if (index >= _items.Size()) { totalPack = 0; return false; } const CItem &item = _items[index]; const CNode &node = _nodes[_refs[item.Node]]; // if (!node.IsFlags_EXTENTS()) { totalPack = (UInt64)node.NumBlocks << (node.IsFlags_HUGE() ? _h.BlockBits : 9); return true; } /* CExtentTreeHeader eth; if (!eth.Parse(node.Block)) return false; if (eth.NumEntries > 3) return false; if (!eth.Depth == 0) return false; UInt64 numBlocks = 0; { for (unsigned i = 0; i < eth.NumEntries; i++) { CExtent e; e.Parse(node.Block + 12 + i * 12); // const CExtent &e = node.leafs[i]; if (e.IsInited) numBlocks += e.Len; } } totalPack = numBlocks << _h.BlockBits; return true; */ } STDMETHODIMP CHandler::GetNumberOfItems(UInt32 *numItems) { *numItems = _items.Size() + _auxItems.Size(); return S_OK; } enum { kpidMountTime = kpidUserDefined, kpidLastCheckTime, kpidRevision, kpidINodeSize, kpidLastMount, kpidFeatureIncompat, kpidFeatureRoCompat, kpidWrittenKB // kpidGroupSize, // kpidChangeTime = kpidUserDefined + 256, // kpidDTime }; static const UInt32 kProps[] = { kpidPath, kpidIsDir, kpidSize, kpidPackSize, kpidPosixAttrib, kpidMTime, kpidCTime, kpidATime, // kpidChangeTime, // kpidDTime, kpidINode, kpidLinks, kpidSymLink, kpidCharacts, kpidUser, kpidGroup }; static const CStatProp kArcProps[] = { { NULL, kpidHeadersSize, VT_BSTR }, // { NULL, kpidFileSystem, VT_BSTR }, // kpidMethod, { NULL, kpidClusterSize, VT_UI4 }, // { "Group Size", kpidGroupSize, VT_UI8 }, { NULL, kpidFreeSpace, VT_UI8 }, { NULL, kpidMTime, VT_FILETIME }, { NULL, kpidCTime, VT_FILETIME }, { "Mount Time", kpidMountTime, VT_FILETIME }, { "Last Check Time", kpidLastCheckTime, VT_FILETIME }, { NULL, kpidHostOS, VT_BSTR}, { "Revision", kpidRevision, VT_UI4}, { "inode Size", kpidINodeSize, VT_UI4}, { NULL, kpidCodePage, VT_BSTR}, { NULL, kpidVolumeName, VT_BSTR}, { "Last Mounted", kpidLastMount, VT_BSTR}, { NULL, kpidId, VT_BSTR}, { NULL, kpidCharacts, VT_BSTR }, { "Incompatible Features", kpidFeatureIncompat, VT_BSTR }, { "Readonly-compatible Features", kpidFeatureRoCompat, VT_BSTR }, { "Written KiB", kpidWrittenKB, VT_UI8 } }; IMP_IInArchive_Props IMP_IInArchive_ArcProps_WITH_NAME static void StringToProp(bool isUTF, const char *s, unsigned size, NCOM::CPropVariant &prop) { UString u; AString a; a.SetFrom_CalcLen(s, size); if (!isUTF || !ConvertUTF8ToUnicode(a, u)) MultiByteToUnicodeString2(u, a); prop = u; } static void UnixTimeToProp(UInt32 val, NCOM::CPropVariant &prop) { if (val != 0) { FILETIME ft; NTime::UnixTimeToFileTime(val, ft); prop = ft; } } STDMETHODIMP CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value) { COM_TRY_BEGIN NCOM::CPropVariant prop; switch (propID) { /* case kpidFileSystem: { AString res = "Ext4"; prop = res; break; } */ case kpidIsTree: prop = true; break; case kpidIsAux: prop = true; break; case kpidINode: prop = true; break; case kpidClusterSize: prop = (UInt32)1 << _h.BlockBits; break; // case kpidGroupSize: prop = (UInt64)_h.BlocksPerGroup << _h.BlockBits; break; case kpidFreeSpace: prop = (UInt64)_h.NumFreeBlocks << _h.BlockBits; break; case kpidCTime: UnixTimeToProp(_h.CTime, prop); break; case kpidMTime: UnixTimeToProp(_h.WriteTime, prop); break; case kpidMountTime: UnixTimeToProp(_h.MountTime, prop); break; case kpidLastCheckTime: UnixTimeToProp(_h.LastCheckTime, prop); break; case kpidHostOS: { char temp[16]; const char *s = NULL; if (_h.CreatorOs < ARRAY_SIZE(kHostOS)) s = kHostOS[_h.CreatorOs]; else { ConvertUInt32ToString(_h.CreatorOs, temp); s = temp; } prop = s; break; } case kpidRevision: prop = _h.RevLevel; break; case kpidINodeSize: prop = _h.InodeSize; break; case kpidId: { if (!IsEmptyData(_h.Uuid, 16)) { char s[16 * 2 + 2]; for (unsigned i = 0; i < 16; i++) PrintHex(_h.Uuid[i], s + i * 2); s[16 * 2] = 0; prop = s; } break; } case kpidCodePage: if (_isUTF) prop = "UTF-8"; break; case kpidShortComment: case kpidVolumeName: StringToProp(_isUTF, _h.VolName, sizeof(_h.VolName), prop); break; case kpidLastMount: StringToProp(_isUTF, _h.LastMount, sizeof(_h.LastMount), prop); break; case kpidCharacts: FLAGS_TO_PROP(g_FeatureCompat_Flags, _h.FeatureCompat, prop); break; case kpidFeatureIncompat: FLAGS_TO_PROP(g_FeatureIncompat_Flags, _h.FeatureIncompat, prop); break; case kpidFeatureRoCompat: FLAGS_TO_PROP(g_FeatureRoCompat_Flags, _h.FeatureRoCompat, prop); break; case kpidWrittenKB: if (_h.WrittenKB != 0) prop = _h.WrittenKB; break; case kpidPhySize: prop = _phySize; break; case kpidErrorFlags: { UInt32 v = 0; if (!_isArc) v |= kpv_ErrorFlags_IsNotArc;; if (_linksError) v |= kpv_ErrorFlags_HeadersError; if (_headersError) v |= kpv_ErrorFlags_HeadersError; if (!_stream && v == 0 && _isArc) v = kpv_ErrorFlags_HeadersError; if (v != 0) prop = v; break; } case kpidWarningFlags: { UInt32 v = 0; if (_headersWarning) v |= kpv_ErrorFlags_HeadersError; if (v != 0) prop = v; break; } } prop.Detach(value); return S_OK; COM_TRY_END } /* static const Byte kRawProps[] = { // kpidSha1, }; */ STDMETHODIMP CHandler::GetNumRawProps(UInt32 *numProps) { // *numProps = ARRAY_SIZE(kRawProps); *numProps = 0; return S_OK; } STDMETHODIMP CHandler::GetRawPropInfo(UInt32 /* index */, BSTR *name, PROPID *propID) { // *propID = kRawProps[index]; *propID = 0; *name = 0; return S_OK; } STDMETHODIMP CHandler::GetParent(UInt32 index, UInt32 *parent, UInt32 *parentType) { *parentType = NParentType::kDir; *parent = (UInt32)(Int32)-1; if (index >= _items.Size()) return S_OK; const CItem &item = _items[index]; if (item.ParentNode < 0) { int aux = GetParentAux(item); if (aux >= 0) *parent = _items.Size() + aux; } else { int itemIndex = _nodes[_refs[item.ParentNode]].ItemIndex; if (itemIndex >= 0) *parent = itemIndex; } return S_OK; } STDMETHODIMP CHandler::GetRawProp(UInt32 index, PROPID propID, const void **data, UInt32 *dataSize, UInt32 *propType) { *data = NULL; *dataSize = 0; *propType = 0; if (propID == kpidName && _isUTF) { if (index < _items.Size()) { const AString &s = _items[index].Name; if (!s.IsEmpty()) { *data = (void *)(const char *)s; *dataSize = (UInt32)s.Len() + 1; *propType = NPropDataType::kUtf8z; } return S_OK; } else { const AString &s = _auxItems[index - _items.Size()]; { *data = (void *)(const char *)s; *dataSize = (UInt32)s.Len() + 1; *propType = NPropDataType::kUtf8z; } return S_OK; } } return S_OK; } static void ExtTimeToProp(const CExtTime &t, NCOM::CPropVariant &prop) { /* UInt32 nano = 0; if (t.Extra != 0) { UInt32 mask = t.Extra & 3; if (mask != 0) return; nano = t.Extra >> 2; } UInt64 v; if (t.Val == 0 && nano == 0) return; if (!NTime::UnixTime_to_FileTime64(t.Val, v)) return; if (nano != 0) v += nano / 100; FILETIME ft; ft.dwLowDateTime = (DWORD)v; ft.dwHighDateTime = (DWORD)(v >> 32); prop = ft; */ if (t.Val == 0) return; if (t.Extra != 0) { UInt32 mask = t.Extra & 3; if (mask != 0) return; } FILETIME ft; if (NTime::UnixTime64ToFileTime(t.Val, ft)) prop = ft; } STDMETHODIMP CHandler::GetProperty(UInt32 index, PROPID propID, PROPVARIANT *value) { COM_TRY_BEGIN NCOM::CPropVariant prop; if (index >= _items.Size()) { switch (propID) { case kpidPath: case kpidName: { AString s = _auxItems[index - _items.Size()]; prop = s; break; } case kpidIsDir: prop = true; break; case kpidIsAux: prop = true; break; } } else { const CItem &item = _items[index]; const CNode &node = _nodes[_refs[item.Node]]; bool isDir = node.IsDir(); switch (propID) { case kpidPath: { UString u; { AString s; GetPath(index, s); if (!_isUTF || !ConvertUTF8ToUnicode(s, u)) MultiByteToUnicodeString2(u, s); } prop = u; break; } case kpidName: { { UString u; { if (!_isUTF || !ConvertUTF8ToUnicode(item.Name, u)) MultiByteToUnicodeString2(u, item.Name); } prop = u; } break; } case kpidIsDir: { bool isDir2 = isDir; if (item.SymLinkItemIndex >= 0) isDir2 = _nodes[_refs[_items[item.SymLinkItemIndex].Node]].IsDir(); prop = isDir2; break; } case kpidSize: if (!isDir) prop = node.FileSize; break; case kpidPackSize: if (!isDir) { UInt64 size; if (GetPackSize(index, size)) prop = size; } break; case kpidPosixAttrib: { /* if (node.Type != 0 && node.Type < ARRAY_SIZE(k_TypeToMode)) prop = (UInt32)(node.Mode & 0xFFF) | k_TypeToMode[node.Type]; */ prop = (UInt32)(node.Mode); break; } case kpidMTime: ExtTimeToProp(node.MTime, prop); break; case kpidCTime: ExtTimeToProp(node.CTime, prop); break; case kpidATime: ExtTimeToProp(node.ATime, prop); break; // case kpidDTime: ExtTimeToProp(node.DTime, prop); break; // case kpidChangeTime: ExtTimeToProp(node.InodeChangeTime, prop); break; case kpidUser: prop = (UInt32)node.Uid; break; case kpidGroup: prop = (UInt32)node.Gid; break; case kpidLinks: prop = node.NumLinks; break; case kpidINode: prop = (UInt32)item.Node; break; case kpidStreamId: if (!isDir) prop = (UInt32)item.Node; break; case kpidCharacts: FLAGS_TO_PROP(g_NodeFlags, (UInt32)node.Flags, prop); break; case kpidSymLink: { if (node.SymLinkIndex >= 0) { UString u; { const AString &s = _symLinks[node.SymLinkIndex]; if (!_isUTF || !ConvertUTF8ToUnicode(s, u)) MultiByteToUnicodeString2(u, s); } prop = u; } break; } } } prop.Detach(value); return S_OK; COM_TRY_END } class CClusterInStream2: public IInStream, public CMyUnknownImp { UInt64 _virtPos; UInt64 _physPos; UInt32 _curRem; public: unsigned BlockBits; UInt64 Size; CMyComPtr Stream; CRecordVector Vector; HRESULT SeekToPhys() { return Stream->Seek(_physPos, STREAM_SEEK_SET, NULL); } HRESULT InitAndSeek() { _curRem = 0; _virtPos = 0; _physPos = 0; if (Vector.Size() > 0) { _physPos = (Vector[0] << BlockBits); return SeekToPhys(); } return S_OK; } MY_UNKNOWN_IMP2(ISequentialInStream, IInStream) STDMETHOD(Read)(void *data, UInt32 size, UInt32 *processedSize); STDMETHOD(Seek)(Int64 offset, UInt32 seekOrigin, UInt64 *newPosition); }; STDMETHODIMP CClusterInStream2::Read(void *data, UInt32 size, UInt32 *processedSize) { if (processedSize) *processedSize = 0; if (_virtPos >= Size) return S_OK; { UInt64 rem = Size - _virtPos; if (size > rem) size = (UInt32)rem; } if (size == 0) return S_OK; if (_curRem == 0) { const UInt32 blockSize = (UInt32)1 << BlockBits; const UInt32 virtBlock = (UInt32)(_virtPos >> BlockBits); const UInt32 offsetInBlock = (UInt32)_virtPos & (blockSize - 1); const UInt32 phyBlock = Vector[virtBlock]; if (phyBlock == 0) { UInt32 cur = blockSize - offsetInBlock; if (cur > size) cur = size; memset(data, 0, cur); _virtPos += cur; if (processedSize) *processedSize = cur; return S_OK; } UInt64 newPos = ((UInt64)phyBlock << BlockBits) + offsetInBlock; if (newPos != _physPos) { _physPos = newPos; RINOK(SeekToPhys()); } _curRem = blockSize - offsetInBlock; for (unsigned i = 1; i < 64 && (virtBlock + i) < (UInt32)Vector.Size() && phyBlock + i == Vector[virtBlock + i]; i++) _curRem += (UInt32)1 << BlockBits; } if (size > _curRem) size = _curRem; HRESULT res = Stream->Read(data, size, &size); if (processedSize) *processedSize = size; _physPos += size; _virtPos += size; _curRem -= size; return res; } STDMETHODIMP CClusterInStream2::Seek(Int64 offset, UInt32 seekOrigin, UInt64 *newPosition) { switch (seekOrigin) { case STREAM_SEEK_SET: break; case STREAM_SEEK_CUR: offset += _virtPos; break; case STREAM_SEEK_END: offset += Size; break; default: return STG_E_INVALIDFUNCTION; } if (offset < 0) return HRESULT_WIN32_ERROR_NEGATIVE_SEEK; if (_virtPos != (UInt64)offset) _curRem = 0; _virtPos = offset; if (newPosition) *newPosition = offset; return S_OK; } class CExtInStream: public IInStream, public CMyUnknownImp { UInt64 _virtPos; UInt64 _phyPos; public: unsigned BlockBits; UInt64 Size; CMyComPtr Stream; CRecordVector Extents; CExtInStream() {} HRESULT StartSeek() { _virtPos = 0; _phyPos = 0; return Stream->Seek(_phyPos, STREAM_SEEK_SET, NULL); } MY_UNKNOWN_IMP2(ISequentialInStream, IInStream) STDMETHOD(Read)(void *data, UInt32 size, UInt32 *processedSize); STDMETHOD(Seek)(Int64 offset, UInt32 seekOrigin, UInt64 *newPosition); }; STDMETHODIMP CExtInStream::Read(void *data, UInt32 size, UInt32 *processedSize) { if (processedSize) *processedSize = 0; if (_virtPos >= Size) return S_OK; { UInt64 rem = Size - _virtPos; if (size > rem) size = (UInt32)rem; } if (size == 0) return S_OK; UInt32 blockIndex = (UInt32)(_virtPos >> BlockBits); unsigned left = 0, right = Extents.Size(); for (;;) { unsigned mid = (left + right) / 2; if (mid == left) break; if (blockIndex < Extents[mid].VirtBlock) right = mid; else left = mid; } { const CExtent &extent = Extents[left]; if (blockIndex < extent.VirtBlock) return E_FAIL; UInt32 bo = blockIndex - extent.VirtBlock; if (bo >= extent.Len) return E_FAIL; UInt32 offset = ((UInt32)_virtPos & (((UInt32)1 << BlockBits) - 1)); UInt32 remBlocks = extent.Len - bo; UInt64 remBytes = ((UInt64)remBlocks << BlockBits); remBytes -= offset; if (size > remBytes) size = (UInt32)remBytes; if (!extent.IsInited) { memset(data, 0, size); _virtPos += size; if (processedSize) *processedSize = size; return S_OK; } UInt64 phyBlock = extent.PhyStart + bo; UInt64 phy = (phyBlock << BlockBits) + offset; if (phy != _phyPos) { RINOK(Stream->Seek(phy, STREAM_SEEK_SET, NULL)); _phyPos = phy; } UInt32 realProcessSize = 0; HRESULT res = Stream->Read(data, size, &realProcessSize); _phyPos += realProcessSize; _virtPos += realProcessSize; if (processedSize) *processedSize = realProcessSize; return res; } } STDMETHODIMP CExtInStream::Seek(Int64 offset, UInt32 seekOrigin, UInt64 *newPosition) { switch (seekOrigin) { case STREAM_SEEK_SET: break; case STREAM_SEEK_CUR: offset += _virtPos; break; case STREAM_SEEK_END: offset += Size; break; default: return STG_E_INVALIDFUNCTION; } if (offset < 0) return HRESULT_WIN32_ERROR_NEGATIVE_SEEK; _virtPos = offset; if (newPosition) *newPosition = offset; return S_OK; } HRESULT CHandler::FillFileBlocks2(UInt32 block, unsigned level, unsigned numBlocks, CRecordVector &blocks) { const size_t blockSize = (size_t)1 << _h.BlockBits; CByteBuffer &tempBuf = _tempBufs[level]; tempBuf.Alloc(blockSize); PRF2(printf("\n level = %d, block = %7d", level, (unsigned)block)); RINOK(SeekAndRead(_stream, block, tempBuf, blockSize)); const Byte *p = tempBuf; size_t num = (size_t)1 << (_h.BlockBits - 2); for (size_t i = 0; i < num; i++) { if (blocks.Size() == numBlocks) break; UInt32 val = GetUi32(p + 4 * i); if (val >= _h.NumBlocks) return S_FALSE; if (level != 0) { if (val == 0) { /* size_t num = (size_t)1 << ((_h.BlockBits - 2) * (level)); PRF2(printf("\n num empty = %3d", (unsigned)num)); for (size_t k = 0; k < num; k++) { blocks.Add(0); if (blocks.Size() == numBlocks) return S_OK; } continue; */ return S_FALSE; } RINOK(FillFileBlocks2(val, level - 1, numBlocks, blocks)); continue; } PRF2(printf("\n i = %3d, blocks.Size() = %6d, block = %5d ", i, blocks.Size(), (unsigned)val)); PRF(printf("\n i = %3d, start = %5d ", (unsigned)i, (unsigned)val)); blocks.Add(val); } return S_OK; } static const unsigned kNumDirectNodeBlocks = 12; HRESULT CHandler::FillFileBlocks(const Byte *p, unsigned numBlocks, CRecordVector &blocks) { // ext2 supports zero blocks (blockIndex == 0). blocks.ClearAndReserve(numBlocks); for (unsigned i = 0; i < kNumDirectNodeBlocks; i++) { if (i == numBlocks) return S_OK; UInt32 val = GetUi32(p + 4 * i); if (val >= _h.NumBlocks) return S_FALSE; blocks.Add(val); } for (unsigned level = 0; level < 3; level++) { if (blocks.Size() == numBlocks) break; UInt32 val = GetUi32(p + 4 * (kNumDirectNodeBlocks + level)); if (val >= _h.NumBlocks) return S_FALSE; if (val == 0) { /* size_t num = (size_t)1 << ((_h.BlockBits - 2) * (level + 1)); for (size_t k = 0; k < num; k++) { blocks.Add(0); if (blocks.Size() == numBlocks) return S_OK; } continue; */ return S_FALSE; } RINOK(FillFileBlocks2(val, level, numBlocks, blocks)); } return S_OK; } static void AddSkipExtents(CRecordVector &extents, UInt32 virtBlock, UInt32 numBlocks) { while (numBlocks != 0) { UInt32 len = numBlocks; const UInt32 kLenMax = (UInt32)1 << 15; if (len > kLenMax) len = kLenMax; CExtent e; e.VirtBlock = virtBlock; e.Len = (UInt16)len; e.IsInited = false; e.PhyStart = 0; extents.Add(e); virtBlock += len; numBlocks -= len; } } static bool UpdateExtents(CRecordVector &extents, UInt32 block) { if (extents.IsEmpty()) { if (block == 0) return true; AddSkipExtents(extents, 0, block); return true; } const CExtent &prev = extents.Back(); if (block < prev.VirtBlock) return false; UInt32 prevEnd = prev.GetVirtEnd(); if (block == prevEnd) return true; AddSkipExtents(extents, prevEnd, block - prevEnd); return true; } HRESULT CHandler::FillExtents(const Byte *p, size_t size, CRecordVector &extents, int parentDepth) { CExtentTreeHeader eth; if (!eth.Parse(p)) return S_FALSE; if (parentDepth >= 0 && eth.Depth != parentDepth - 1) // (eth.Depth >= parentDepth) return S_FALSE; if (12 + 12 * (size_t)eth.NumEntries > size) return S_FALSE; if (eth.Depth >= kNumTreeLevelsMax) return S_FALSE; if (eth.Depth == 0) { for (unsigned i = 0; i < eth.NumEntries; i++) { CExtent e; e.Parse(p + 12 + i * 12); if (e.PhyStart == 0 || e.PhyStart > _h.NumBlocks || e.PhyStart + e.Len > _h.NumBlocks || !e.IsLenOK()) return S_FALSE; if (!UpdateExtents(extents, e.VirtBlock)) return S_FALSE; extents.Add(e); } return S_OK; } const size_t blockSize = (size_t)1 << _h.BlockBits; CByteBuffer &tempBuf = _tempBufs[eth.Depth]; tempBuf.Alloc(blockSize); for (unsigned i = 0; i < eth.NumEntries; i++) { CExtentIndexNode e; e.Parse(p + 12 + i * 12); if (e.PhyLeaf == 0 || e.PhyLeaf >= _h.NumBlocks) return S_FALSE; if (!UpdateExtents(extents, e.VirtBlock)) return S_FALSE; RINOK(SeekAndRead(_stream, e.PhyLeaf, tempBuf, blockSize)); RINOK(FillExtents(tempBuf, blockSize, extents, eth.Depth)); } return S_OK; } HRESULT CHandler::GetStream_Node(unsigned nodeIndex, ISequentialInStream **stream) { COM_TRY_BEGIN *stream = NULL; const CNode &node = _nodes[nodeIndex]; if (!node.IsFlags_EXTENTS()) { // maybe sparse file can have (node.NumBlocks == 0) ? /* The following code doesn't work correctly for some CentOS images, where there are nodes with inline data and (node.NumBlocks != 0). If you know better way to detect inline data, please notify 7-Zip developers. */ if (node.NumBlocks == 0 && node.FileSize < kNodeBlockFieldSize) { Create_BufInStream_WithNewBuffer(node.Block, (size_t)node.FileSize, stream); return S_OK; } } if (node.FileSize >= ((UInt64)1 << 63)) return S_FALSE; CMyComPtr streamTemp; UInt64 numBlocks64 = (node.FileSize + (UInt64)(((UInt32)1 << _h.BlockBits) - 1)) >> _h.BlockBits; if (node.IsFlags_EXTENTS()) { if ((UInt32)numBlocks64 != numBlocks64) return S_FALSE; CExtInStream *streamSpec = new CExtInStream; streamTemp = streamSpec; streamSpec->BlockBits = _h.BlockBits; streamSpec->Size = node.FileSize; streamSpec->Stream = _stream; RINOK(FillExtents(node.Block, kNodeBlockFieldSize, streamSpec->Extents, -1)); UInt32 end = 0; if (!streamSpec->Extents.IsEmpty()) end = streamSpec->Extents.Back().GetVirtEnd(); if (end < numBlocks64) { AddSkipExtents(streamSpec->Extents, end, (UInt32)(numBlocks64 - end)); // return S_FALSE; } RINOK(streamSpec->StartSeek()); } else { { UInt64 numBlocks2 = numBlocks64; if (numBlocks64 > kNumDirectNodeBlocks) { UInt64 rem = numBlocks64 - kNumDirectNodeBlocks; const unsigned refBits = (_h.BlockBits - 2); const size_t numRefsInBlocks = (size_t)1 << refBits; numBlocks2++; if (rem > numRefsInBlocks) { numBlocks2++; const UInt64 numL2 = (rem - 1) >> refBits; numBlocks2 += numL2; if (numL2 > numRefsInBlocks) { numBlocks2++; numBlocks2 += (numL2 - 1) >> refBits; } } } const unsigned specBits = (node.IsFlags_HUGE() ? 0 : _h.BlockBits - 9); const UInt32 specMask = ((UInt32)1 << specBits) - 1;; if ((node.NumBlocks & specMask) != 0) return S_FALSE; const UInt64 numBlocks64_from_header = node.NumBlocks >> specBits; if (numBlocks64_from_header < numBlocks2) { // why (numBlocks64_from_header > numBlocks2) in some cases? // return S_FALSE; } } unsigned numBlocks = (unsigned)numBlocks64; if (numBlocks != numBlocks64) return S_FALSE; CClusterInStream2 *streamSpec = new CClusterInStream2; streamTemp = streamSpec; streamSpec->BlockBits = _h.BlockBits; streamSpec->Size = node.FileSize; streamSpec->Stream = _stream; RINOK(FillFileBlocks(node.Block, numBlocks, streamSpec->Vector)); streamSpec->InitAndSeek(); } *stream = streamTemp.Detach(); return S_OK; COM_TRY_END } HRESULT CHandler::ExtractNode(unsigned nodeIndex, CByteBuffer &data) { data.Free(); const CNode &node = _nodes[nodeIndex]; size_t size = (size_t)node.FileSize; if (size != node.FileSize) return S_FALSE; CMyComPtr inSeqStream; RINOK(GetStream_Node(nodeIndex, &inSeqStream)); if (!inSeqStream) return S_FALSE; data.Alloc(size); _totalRead += size; return ReadStream_FALSE(inSeqStream, data, size); } STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems, Int32 testMode, IArchiveExtractCallback *extractCallback) { COM_TRY_BEGIN bool allFilesMode = (numItems == (UInt32)(Int32)-1); if (allFilesMode) numItems = _items.Size() + _auxItems.Size(); if (numItems == 0) return S_OK; UInt64 totalSize = 0; UInt32 i; for (i = 0; i < numItems; i++) { UInt32 index = allFilesMode ? i : indices[i]; if (index >= _items.Size()) continue; const CItem &item = _items[index]; const CNode &node = _nodes[_refs[item.Node]]; if (!node.IsDir()) totalSize += node.FileSize; } extractCallback->SetTotal(totalSize); UInt64 totalPackSize; totalSize = totalPackSize = 0; NCompress::CCopyCoder *copyCoderSpec = new NCompress::CCopyCoder(); CMyComPtr copyCoder = copyCoderSpec; CLocalProgress *lps = new CLocalProgress; CMyComPtr progress = lps; lps->Init(extractCallback, false); for (i = 0;; i++) { lps->InSize = totalPackSize; lps->OutSize = totalSize; RINOK(lps->SetCur()); if (i == numItems) break; CMyComPtr outStream; Int32 askMode = testMode ? NExtract::NAskMode::kTest : NExtract::NAskMode::kExtract; UInt32 index = allFilesMode ? i : indices[i]; RINOK(extractCallback->GetStream(index, &outStream, askMode)); if (index >= _items.Size()) { RINOK(extractCallback->PrepareOperation(askMode)); RINOK(extractCallback->SetOperationResult(NExtract::NOperationResult::kOK)); continue; } const CItem &item = _items[index]; const CNode &node = _nodes[_refs[item.Node]]; if (node.IsDir()) { RINOK(extractCallback->PrepareOperation(askMode)); RINOK(extractCallback->SetOperationResult(NExtract::NOperationResult::kOK)); continue; } UInt64 unpackSize = node.FileSize; totalSize += unpackSize; UInt64 packSize; if (GetPackSize(index, packSize)) totalPackSize += packSize; if (!testMode && !outStream) continue; RINOK(extractCallback->PrepareOperation(askMode)); int res = NExtract::NOperationResult::kDataError; { CMyComPtr inSeqStream; HRESULT hres = GetStream(index, &inSeqStream); if (hres == S_FALSE || !inSeqStream) { if (hres == E_OUTOFMEMORY) return hres; res = NExtract::NOperationResult::kUnsupportedMethod; } else { RINOK(hres); { hres = copyCoder->Code(inSeqStream, outStream, NULL, NULL, progress); if (hres == S_OK) { if (copyCoderSpec->TotalSize == unpackSize) res = NExtract::NOperationResult::kOK; } else if (hres == E_NOTIMPL) { res = NExtract::NOperationResult::kUnsupportedMethod; } else if (hres != S_FALSE) { RINOK(hres); } } } } RINOK(extractCallback->SetOperationResult(res)); } return S_OK; COM_TRY_END } STDMETHODIMP CHandler::GetStream(UInt32 index, ISequentialInStream **stream) { *stream = NULL; if (index >= _items.Size()) return S_FALSE; return GetStream_Node(_refs[_items[index].Node], stream); } API_FUNC_static_IsArc IsArc_Ext(const Byte *p, size_t size) { if (size < kHeaderSize) return k_IsArc_Res_NEED_MORE; CHeader h; if (!h.Parse(p + kHeaderDataOffset)) return k_IsArc_Res_NO; return k_IsArc_Res_YES; } } static const Byte k_Signature[] = { 0x53, 0xEF }; REGISTER_ARC_I( "Ext", "ext ext2 ext3 ext4 img", 0, 0xC7, k_Signature, 0x438, 0, IsArc_Ext) }}