// 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; UInt32 LzhCrc16Update(UInt32 crc, const void *data, size_t size); 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); } */ #define CRC16_INIT_VAL 0xFFFF #define Crc16Update(crc, data, size) LzhCrc16Update(crc, data, size) static UInt32 Crc16Calc(Byte const *data, size_t size) { return Crc16Update(CRC16_INIT_VAL, data, size); } #define EXT4_GOOD_OLD_INODE_SIZE 128 #define EXT_NODE_SIZE_MIN 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 char * const g_FeatureCompat_Flags[] = { "DIR_PREALLOC" , "IMAGIC_INODES" , "HAS_JOURNAL" , "EXT_ATTR" , "RESIZE_INODE" , "DIR_INDEX" , "LAZY_BG" // not in Linux , NULL // { 7, "EXCLUDE_INODE" // not used , NULL // { 8, "EXCLUDE_BITMAP" // not in kernel , "SPARSE_SUPER2" }; #define EXT4_FEATURE_INCOMPAT_FILETYPE (1 << 1) #define EXT4_FEATURE_INCOMPAT_64BIT (1 << 7) static const char * const g_FeatureIncompat_Flags[] = { "COMPRESSION" , "FILETYPE" , "RECOVER" /* Needs recovery */ , "JOURNAL_DEV" /* Journal device */ , "META_BG" , NULL , "EXTENTS" /* extents support */ , "64BIT" , "MMP" , "FLEX_BG" , "EA_INODE" /* EA in inode */ , NULL , "DIRDATA" /* data in dirent */ , "BG_USE_META_CSUM" /* use crc32c for bg */ , "LARGEDIR" /* >2GB or 3-lvl htree */ , "INLINE_DATA" /* data in inode */ , "ENCRYPT" // 16 }; static const UInt32 RO_COMPAT_GDT_CSUM = 1 << 4; static const UInt32 RO_COMPAT_METADATA_CSUM = 1 << 10; static const char * const g_FeatureRoCompat_Flags[] = { "SPARSE_SUPER" , "LARGE_FILE" , "BTREE_DIR" , "HUGE_FILE" , "GDT_CSUM" , "DIR_NLINK" , "EXTRA_ISIZE" , "HAS_SNAPSHOT" , "QUOTA" , "BIGALLOC" , "METADATA_CSUM" , "REPLICA" , "READONLY" // 12 }; static const UInt32 k_NodeFlags_HUGE = (UInt32)1 << 18; static const UInt32 k_NodeFlags_EXTENTS = (UInt32)1 << 19; static const char * const g_NodeFlags[] = { "SECRM" , "UNRM" , "COMPR" , "SYNC" , "IMMUTABLE" , "APPEND" , "NODUMP" , "NOATIME" , "DIRTY" , "COMPRBLK" , "NOCOMPR" , "ENCRYPT" , "INDEX" , "IMAGIC" , "JOURNAL_DATA" , "NOTAIL" , "DIRSYNC" , "TOPDIR" , "HUGE_FILE" , "EXTENTS" , NULL , "EA_INODE" , "EOFBLOCKS" , NULL , NULL , NULL , NULL , NULL , "INLINE_DATA" // 28 }; 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; } UInt64 GetPhySize() const { return NumBlocks << BlockBits; } bool Parse(const Byte *p); }; static int inline GetLog(UInt32 num) { for (unsigned i = 0; i < 32; i++) if (((UInt32)1 << i) == num) return (int)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) || InodeSize < EXT_NODE_SIZE_MIN || 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(0x150, NumBlocks) // HI_32(0x154, 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 = (UInt16)(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[] , (set as >=0 only for if (IsDir()) int SymLinkIndex; // in _symLinks[] int DirIndex; // in _dirs[] UInt16 Mode; UInt32 Uid; // fixed 21.02 UInt32 Gid; // fixed 21.02 // UInt16 Checksum; UInt64 FileSize; CExtTime MTime; CExtTime ATime; CExtTime CTime; CExtTime ChangeTime; // CExtTime DTime; UInt64 NumBlocks; UInt32 NumLinks; UInt32 Flags; UInt32 NumLinksCalced; Byte Block[kNodeBlockFieldSize]; CNode(): ParentNode(-1), ItemIndex(-1), SymLinkIndex(-1), DirIndex(-1), 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; ChangeTime.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, ChangeTime.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) { // InodeSize is power of 2, so the following check is not required: // if (_h.InodeSize < 128 + 2) return false; 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, ChangeTime.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 Z7_CLASS_IMP_CHandler_IInArchive_2( IArchiveGetRawProps, IInArchiveGetStream ) CObjectVector _items; CIntVector _refs; // iNode -> (index in _nodes). if (_refs[iNode] < 0), that node is not filled 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; } 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; }; 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 = (int)_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) const unsigned nameLen = p[6]; const 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 = (int)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) { // 21.07 : we force UTF8 // _isUTF = CheckUTF8_AString(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; } const int nodeIndex = _refs[iNode]; if (nodeIndex < 0) return S_FALSE; CNode &node = _nodes[nodeIndex]; if (_h.IsThereFileType() && type != 0) { if (type >= Z7_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 = (int)iNode; if (nodeDir.ParentNode < 0) nodeDir.ParentNode = (int)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 = (int)iNodeDir; else if ((unsigned)node.ParentNode != iNodeDir) return S_FALSE; const unsigned itemIndex = _items.Size(); dir.Add(itemIndex); node.ItemIndex = (int)itemIndex; } _items.Add(item); } if (parentNode < 0 || !isThereSelfLink) return S_FALSE; return S_OK; } static int CompareItemsNames(const unsigned *p1, const unsigned *p2, void *param) { const CObjectVector &items = *(const CObjectVector *)param; return strcmp(items[*p1].Name, items[*p2].Name); } 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]]; const int slash = path.Find('/', pos); if (slash < 0) { s = path.Ptr(pos); pos = path.Len(); } else { s.SetFrom(path.Ptr(pos), (unsigned)slash - pos); pos = (unsigned)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 = (unsigned)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; } } */ unsigned left = 0, right = dir.Size(); for (;;) { if (left == right) return -1; const unsigned mid = (unsigned)(((size_t)left + (size_t)right) / 2); const CItem &item = _items[dir[mid]]; const int comp = strcmp(s, item.Name); if (comp == 0) { iNode = item.Node; break; } if (comp < 0) right = mid; else left = mid + 1; } } 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_SeekSet(inStream, (UInt64)block << _h.BlockBits)) _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; { const 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.GetPhySize(); if (_openCallback) { RINOK(_openCallback->SetTotal(NULL, &_phySize)) } UInt64 fileSize = 0; RINOK(InStream_GetSize_SeekToEnd(inStream, fileSize)) CRecordVector groups; { // ---------- Read groups ---------- CByteBuffer gdBuf; const 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); const 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; const 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; } const 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((int)_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) { const int nodeIndex = _refs[i]; { if (nodeIndex < 0) continue; const CNode &node = _nodes[nodeIndex]; if (!node.IsDir()) continue; } RINOK(ExtractNode((unsigned)nodeIndex, dataBuf)) if (dataBuf.Size() == 0) { // _headersError = true; return S_FALSE; } else { RINOK(ParseDir(dataBuf, dataBuf.Size(), i)) } RINOK(CheckProgress()) } const int ref = _refs[k_INODE_ROOT]; if (ref < 0 || _nodes[ref].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 (;;) { const 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] = (int)i; if (node.ParentNode < 0 || node.ParentNode == k_INODE_ROOT) break; if ((unsigned)node.ParentNode == i) return S_FALSE; c = (unsigned)node.ParentNode; } } } { // ---------- Fill SymLinks data ---------- AString s; CByteBuffer data; unsigned i; for (i = 0; i < _refs.Size(); i++) { const 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((unsigned)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 = (int)_symLinks.Add(s); RINOK(CheckProgress()) } } for (i = 0; i < _dirs.Size(); i++) { _dirs[i].Sort(CompareItemsNames, (void *)&_items); } unsigned prev = 0; unsigned complex = 0; for (i = 0; i < _items.Size(); i++) { CItem &item = _items[i]; const int sym = _nodes[_refs[item.Node]].SymLinkIndex; if (sym >= 0 && item.ParentNode >= 0) { item.SymLinkItemIndex = FindTargetItem_for_SymLink((unsigned)item.ParentNode, _symLinks[sym]); if (_openCallback) { complex++; if (complex - prev >= (1 << 10)) { prev = complex; RINOK(CheckProgress2()) } } } } } { // ---------- Add items and aux folders for unreferenced files ---------- bool useSys = false; bool useUnknown = false; FOR_VECTOR (i, _refs) { const 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 < Z7_ARRAY_SIZE(k_SysInode_Names)) item.Name = k_SysInode_Names[item.Node]; useSys = true; } else useUnknown = true; if (item.Name.IsEmpty()) item.Name.Add_UInt32(item.Node); _items.Add(item); } } if (useSys) _auxSysIndex = (int)_auxItems.Add((AString)"[SYS]"); if (useUnknown) _auxUnknownIndex = (int)_auxItems.Add((AString)"[UNKNOWN]"); } return S_OK; } Z7_COM7F_IMF(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; } Z7_COM7F_IMF(CHandler::Close()) { _totalRead = 0; _totalReadPrev = 0; _phySize = 0; _isArc = false; _headersError = false; _headersWarning = false; _linksError = false; _isUTF = true; ClearRefs(); return S_OK; } static void ChangeSeparatorsInName(char *s, unsigned num) { for (unsigned i = 0; i < num; i++) { char c = s[i]; if (c == CHAR_PATH_SEPARATOR || c == '/') s[i] = '_'; } } 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); // 18.06 ChangeSeparatorsInName(s.GetBuf(), item.Name.Len()); 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 = (unsigned)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; */ } Z7_COM7F_IMF(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, kpidUserId, kpidGroupId }; 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) PropVariant_SetFrom_UnixTime(prop, val); } Z7_COM7F_IMF(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: { TYPE_TO_PROP(kHostOS, _h.CreatorOs, prop); break; } case kpidRevision: prop = _h.RevLevel; break; case kpidINodeSize: prop = (UInt32)_h.InodeSize; break; case kpidId: { if (!IsEmptyData(_h.Uuid, sizeof(_h.Uuid))) { char s[sizeof(_h.Uuid) * 2 + 2]; ConvertDataToHex_Lower(s, _h.Uuid, sizeof(_h.Uuid)); 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, }; */ Z7_COM7F_IMF(CHandler::GetNumRawProps(UInt32 *numProps)) { // *numProps = Z7_ARRAY_SIZE(kRawProps); *numProps = 0; return S_OK; } Z7_COM7F_IMF(CHandler::GetRawPropInfo(UInt32 /* index */, BSTR *name, PROPID *propID)) { // *propID = kRawProps[index]; *propID = 0; *name = NULL; return S_OK; } Z7_COM7F_IMF(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) { const int aux = GetParentAux(item); if (aux >= 0) *parent = _items.Size() + (unsigned)aux; } else { const int itemIndex = _nodes[_refs[item.ParentNode]].ItemIndex; if (itemIndex >= 0) *parent = (unsigned)itemIndex; } return S_OK; } Z7_COM7F_IMF(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) { if (t.Val == 0 && t.Extra == 0) return; FILETIME ft; unsigned low100ns = 0; // if (t.Extra != 0) { // 1901-2446 : Int64 v = (Int64)(Int32)t.Val; v += (UInt64)(t.Extra & 3) << 32; // 2 low bits are offset for main timestamp UInt64 ft64 = NTime::UnixTime64_To_FileTime64(v); const UInt32 ns = (t.Extra >> 2); if (ns < 1000000000) { ft64 += ns / 100; low100ns = (unsigned)(ns % 100); } ft.dwLowDateTime = (DWORD)ft64; ft.dwHighDateTime = (DWORD)(ft64 >> 32); } /* else { // 1901-2038 : that code is good for ext4 and compatibility with Extra NTime::UnixTime64ToFileTime((Int32)t.Val, ft); // for // 1970-2106 : that code is good if timestamp is used as unsigned 32-bit // are there such systems? // NTime::UnixTimeToFileTime(t.Val, ft); // for } */ prop.SetAsTimeFrom_FT_Prec_Ns100(ft, k_PropVar_TimePrec_1ns, low100ns); } Z7_COM7F_IMF(CHandler::GetProperty(UInt32 index, PROPID propID, PROPVARIANT *value)) { COM_TRY_BEGIN NCOM::CPropVariant prop; if (index >= _items.Size()) { switch (propID) { case kpidPath: case kpidName: { prop = _auxItems[index - _items.Size()]; 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 < Z7_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.ChangeTime, prop); break; case kpidUserId: prop = (UInt32)node.Uid; break; case kpidGroupId: 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 } Z7_CLASS_IMP_IInStream(CClusterInStream2 ) UInt64 _virtPos; UInt64 _physPos; UInt32 _curRem; public: unsigned BlockBits; UInt64 Size; CMyComPtr Stream; CRecordVector Vector; HRESULT SeekToPhys() { return InStream_SeekSet(Stream, _physPos); } HRESULT InitAndSeek() { _curRem = 0; _virtPos = 0; _physPos = 0; if (Vector.Size() > 0) { _physPos = (Vector[0] << BlockBits); return SeekToPhys(); } return S_OK; } }; Z7_COM7F_IMF(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; } Z7_COM7F_IMF(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 = (UInt64)offset; if (newPosition) *newPosition = (UInt64)offset; return S_OK; } Z7_CLASS_IMP_IInStream( CExtInStream ) UInt64 _virtPos; UInt64 _phyPos; public: unsigned BlockBits; UInt64 Size; CMyComPtr Stream; CRecordVector Extents; HRESULT StartSeek() { _virtPos = 0; _phyPos = 0; return InStream_SeekSet(Stream, _phyPos); } }; Z7_COM7F_IMF(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; } const UInt64 phyBlock = extent.PhyStart + bo; const UInt64 phy = (phyBlock << BlockBits) + offset; if (phy != _phyPos) { RINOK(InStream_SeekSet(Stream, phy)) _phyPos = phy; } UInt32 realProcessSize = 0; const HRESULT res = Stream->Read(data, size, &realProcessSize); _phyPos += realProcessSize; _virtPos += realProcessSize; if (processedSize) *processedSize = realProcessSize; return res; } } Z7_COM7F_IMF(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 = (UInt64)offset; if (newPosition) *newPosition = (UInt64)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; const 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); } Z7_COM7F_IMF(CHandler::Extract(const UInt32 *indices, UInt32 numItems, Int32 testMode, IArchiveExtractCallback *extractCallback)) { COM_TRY_BEGIN const 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++) { const 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; } RINOK(extractCallback->SetTotal(totalSize)) UInt64 totalPackSize; totalSize = totalPackSize = 0; CMyComPtr2_Create lps; lps->Init(extractCallback, false); CMyComPtr2_Create copyCoder; for (i = 0;; i++) { lps->InSize = totalPackSize; lps->OutSize = totalSize; RINOK(lps->SetCur()) if (i >= numItems) break; int opRes; { CMyComPtr outStream; const Int32 askMode = testMode ? NExtract::NAskMode::kTest : NExtract::NAskMode::kExtract; const 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)) opRes = NExtract::NOperationResult::kDataError; { CMyComPtr inSeqStream; HRESULT hres = GetStream(index, &inSeqStream); if (hres == S_FALSE || !inSeqStream) { if (hres == E_OUTOFMEMORY) return hres; opRes = NExtract::NOperationResult::kUnsupportedMethod; } else { RINOK(hres) { hres = copyCoder.Interface()->Code(inSeqStream, outStream, NULL, NULL, lps); if (hres == S_OK) { if (copyCoder->TotalSize == unpackSize) opRes = NExtract::NOperationResult::kOK; } else if (hres == E_NOTIMPL) { opRes = NExtract::NOperationResult::kUnsupportedMethod; } else if (hres != S_FALSE) { RINOK(hres) } } } } } RINOK(extractCallback->SetOperationResult(opRes)) } return S_OK; COM_TRY_END } Z7_COM7F_IMF(CHandler::GetStream(UInt32 index, ISequentialInStream **stream)) { *stream = NULL; if (index >= _items.Size()) return S_FALSE; return GetStream_Node((unsigned)_refs[_items[index].Node], stream); } API_FUNC_IsArc IsArc_Ext_PhySize(const Byte *p, size_t size, UInt64 *phySize); API_FUNC_IsArc IsArc_Ext_PhySize(const Byte *p, size_t size, UInt64 *phySize) { if (phySize) *phySize = 0; if (size < kHeaderSize) return k_IsArc_Res_NEED_MORE; CHeader h; if (!h.Parse(p + kHeaderDataOffset)) return k_IsArc_Res_NO; if (phySize) *phySize = h.GetPhySize(); return k_IsArc_Res_YES; } API_FUNC_IsArc IsArc_Ext(const Byte *p, size_t size); API_FUNC_IsArc IsArc_Ext(const Byte *p, size_t size) { return IsArc_Ext_PhySize(p, size, NULL); } static const Byte k_Signature[] = { 0x53, 0xEF }; REGISTER_ARC_I( "Ext", "ext ext2 ext3 ext4 img", NULL, 0xC7, k_Signature, 0x438, 0, IsArc_Ext) }}