// SquashfsHandler.cpp #include "StdAfx.h" #include "../../../C/7zCrc.h" #include "../../../C/Alloc.h" #include "../../../C/CpuArch.h" #include "../../../C/Xz.h" #include "../../Common/ComTry.h" #include "../../Common/MyLinux.h" #include "../../Common/IntToString.h" #include "../../Common/StringConvert.h" #include "../../Windows/PropVariantUtils.h" #include "../../Windows/TimeUtils.h" #include "../Common/CWrappers.h" #include "../Common/LimitedStreams.h" #include "../Common/ProgressUtils.h" #include "../Common/RegisterArc.h" #include "../Common/StreamObjects.h" #include "../Common/StreamUtils.h" #include "../Compress/CopyCoder.h" #include "../Compress/ZlibDecoder.h" #include "../Compress/LzmaDecoder.h" namespace NArchive { namespace NSquashfs { static const UInt32 kNumFilesMax = (1 << 28); static const unsigned kNumDirLevelsMax = (1 << 10); // Layout: Header, Data, inodes, Directories, Fragments, UIDs, GIDs /* #define Get16(p) (be ? GetBe16(p) : GetUi16(p)) #define Get32(p) (be ? GetBe32(p) : GetUi32(p)) #define Get64(p) (be ? GetBe64(p) : GetUi64(p)) */ UInt16 Get16b(const Byte *p, bool be) { return be ? GetBe16(p) : GetUi16(p); } UInt32 Get32b(const Byte *p, bool be) { return be ? GetBe32(p) : GetUi32(p); } UInt64 Get64b(const Byte *p, bool be) { return be ? GetBe64(p) : GetUi64(p); } #define Get16(p) Get16b(p, be) #define Get32(p) Get32b(p, be) #define Get64(p) Get64b(p, be) #define LE_16(offs, dest) dest = GetUi16(p + (offs)); #define LE_32(offs, dest) dest = GetUi32(p + (offs)); #define LE_64(offs, dest) dest = GetUi64(p + (offs)); #define GET_16(offs, dest) dest = Get16(p + (offs)); #define GET_32(offs, dest) dest = Get32(p + (offs)); #define GET_64(offs, dest) dest = Get64(p + (offs)); static const UInt32 kSignature32_LE = 0x73717368; static const UInt32 kSignature32_BE = 0x68737173; static const UInt32 kSignature32_LZ = 0x71736873; #define kMethod_ZLIB 1 #define kMethod_LZMA 2 #define kMethod_LZO 3 #define kMethod_XZ 4 static const char * const k_Methods[] = { "Unknown" , "ZLIB" , "LZMA" , "LZO" , "XZ" }; static const UInt32 kMetadataBlockSizeLog = 13; static const UInt32 kMetadataBlockSize = (1 << kMetadataBlockSizeLog); enum { kType_IPC, kType_DIR, kType_FILE, kType_LNK, kType_BLK, kType_CHR, kType_FIFO, kType_SOCK }; static const UInt32 k_TypeToMode[] = { 0, MY_LIN_S_IFDIR, MY_LIN_S_IFREG, MY_LIN_S_IFLNK, MY_LIN_S_IFBLK, MY_LIN_S_IFCHR, MY_LIN_S_IFIFO, MY_LIN_S_IFSOCK, MY_LIN_S_IFDIR, MY_LIN_S_IFREG, MY_LIN_S_IFLNK, MY_LIN_S_IFBLK, MY_LIN_S_IFCHR, MY_LIN_S_IFIFO, MY_LIN_S_IFSOCK }; enum { kFlag_UNC_INODES, kFlag_UNC_DATA, kFlag_CHECK, kFlag_UNC_FRAGS, kFlag_NO_FRAGS, kFlag_ALWAYS_FRAG, kFlag_DUPLICATE, kFlag_EXPORT }; static const CUInt32PCharPair k_Flags[] = { { kFlag_UNC_INODES, "UNCOMPRESSED_INODES" }, { kFlag_UNC_DATA, "UNCOMPRESSED_DATA" }, { kFlag_CHECK, "CHECK" }, { kFlag_UNC_FRAGS, "UNCOMPRESSED_FRAGMENTS" }, { kFlag_NO_FRAGS, "NO_FRAGMENTS" }, { kFlag_ALWAYS_FRAG, "ALWAYS_FRAGMENTS" }, { kFlag_DUPLICATE, "DUPLICATES_REMOVED" }, { kFlag_EXPORT, "EXPORTABLE" } }; static const UInt32 kNotCompressedBit16 = (1 << 15); static const UInt32 kNotCompressedBit32 = (1 << 24); #define GET_COMPRESSED_BLOCK_SIZE(size) ((size) & ~kNotCompressedBit32) #define IS_COMPRESSED_BLOCK(size) (((size) & kNotCompressedBit32) == 0) static const UInt32 kHeaderSize1 = 0x33; static const UInt32 kHeaderSize2 = 0x3F; static const UInt32 kHeaderSize3 = 0x77; static const UInt32 kHeaderSize4 = 0x60; struct CHeader { bool be; bool SeveralMethods; Byte NumUids; Byte NumGids; UInt32 NumInodes; UInt32 CTime; UInt32 BlockSize; UInt32 NumFrags; UInt16 Method; UInt16 BlockSizeLog; UInt16 Flags; UInt16 NumIDs; UInt16 Major; UInt16 Minor; UInt64 RootInode; UInt64 Size; UInt64 UidTable; UInt64 GidTable; UInt64 XattrIdTable; UInt64 InodeTable; UInt64 DirTable; UInt64 FragTable; UInt64 LookupTable; void Parse3(const Byte *p) { Method = kMethod_ZLIB; GET_32 (0x08, Size); GET_32 (0x0C, UidTable); GET_32 (0x10, GidTable); GET_32 (0x14, InodeTable); GET_32 (0x18, DirTable); GET_16 (0x20, BlockSize); GET_16 (0x22, BlockSizeLog); Flags = p[0x24]; NumUids = p[0x25]; NumGids = p[0x26]; GET_32 (0x27, CTime); GET_64 (0x2B, RootInode); NumFrags = 0; FragTable = UidTable; if (Major >= 2) { GET_32 (0x33, BlockSize); GET_32 (0x37, NumFrags); GET_32 (0x3B, FragTable); if (Major == 3) { GET_64 (0x3F, Size); GET_64 (0x47, UidTable); GET_64 (0x4F, GidTable); GET_64 (0x57, InodeTable); GET_64 (0x5F, DirTable); GET_64 (0x67, FragTable); GET_64 (0x6F, LookupTable); } } } void Parse4(const Byte *p) { LE_32 (0x08, CTime); LE_32 (0x0C, BlockSize); LE_32 (0x10, NumFrags); LE_16 (0x14, Method); LE_16 (0x16, BlockSizeLog); LE_16 (0x18, Flags); LE_16 (0x1A, NumIDs); LE_64 (0x20, RootInode); LE_64 (0x28, Size); LE_64 (0x30, UidTable); LE_64 (0x38, XattrIdTable); LE_64 (0x40, InodeTable); LE_64 (0x48, DirTable); LE_64 (0x50, FragTable); LE_64 (0x58, LookupTable); GidTable = 0; } bool Parse(const Byte *p) { be = false; SeveralMethods = false; switch (GetUi32(p)) { case kSignature32_LE: break; case kSignature32_BE: be = true; break; case kSignature32_LZ: SeveralMethods = true; break; default: return false; } GET_32 (4, NumInodes); GET_16 (0x1C, Major); GET_16 (0x1E, Minor); if (Major <= 3) Parse3(p); else { if (be) return false; Parse4(p); } return InodeTable < DirTable && DirTable <= FragTable && FragTable <= Size && UidTable <= Size && BlockSizeLog >= 12 && BlockSizeLog < 31 && BlockSize == ((UInt32)1 << BlockSizeLog); } bool IsSupported() const { return Major > 0 && Major <= 4 && BlockSizeLog <= 23; } bool IsOldVersion() const { return Major < 4; } bool NeedCheckData() const { return (Flags & (1 << kFlag_CHECK)) != 0; } unsigned GetFileNameOffset() const { return Major <= 2 ? 3 : (Major == 3 ? 5 : 8); } unsigned GetSymLinkOffset() const { return Major <= 1 ? 5: (Major <= 2 ? 6: (Major == 3 ? 18 : 24)); } unsigned GetSpecGuidIndex() const { return Major <= 1 ? 0xF: 0xFF; } }; static const UInt32 kFrag_Empty = (UInt32)(Int32)-1; // static const UInt32 kXattr_Empty = (UInt32)(Int32)-1; struct CNode { UInt16 Type; UInt16 Mode; UInt16 Uid; UInt16 Gid; UInt32 Frag; UInt32 Offset; // UInt32 MTime; // UInt32 Number; // UInt32 NumLinks; // UInt32 RDev; // UInt32 Xattr; // UInt32 Parent; UInt64 FileSize; UInt64 StartBlock; // UInt64 Sparse; UInt32 Parse1(const Byte *p, UInt32 size, const CHeader &_h); UInt32 Parse2(const Byte *p, UInt32 size, const CHeader &_h); UInt32 Parse3(const Byte *p, UInt32 size, const CHeader &_h); UInt32 Parse4(const Byte *p, UInt32 size, const CHeader &_h); bool IsDir() const { return (Type == kType_DIR || Type == kType_DIR + 7); } bool IsLink() const { return (Type == kType_LNK || Type == kType_LNK + 7); } UInt64 GetSize() const { return IsDir() ? 0 : FileSize; } bool ThereAreFrags() const { return Frag != kFrag_Empty; } UInt64 GetNumBlocks(const CHeader &_h) const { return (FileSize >> _h.BlockSizeLog) + (!ThereAreFrags() && (FileSize & (_h.BlockSize - 1)) != 0); } }; UInt32 CNode::Parse1(const Byte *p, UInt32 size, const CHeader &_h) { const bool be = _h.be; if (size < 4) return 0; { const UInt32 t = Get16(p); if (be) { Type = (UInt16)(t >> 12); Mode = (UInt16)(t & 0xFFF); Uid = (UInt16)(p[2] >> 4); Gid = (UInt16)(p[2] & 0xF); } else { Type = (UInt16)(t & 0xF); Mode = (UInt16)(t >> 4); Uid = (UInt16)(p[2] & 0xF); Gid = (UInt16)(p[2] >> 4); } } // Xattr = kXattr_Empty; // MTime = 0; FileSize = 0; StartBlock = 0; Frag = kFrag_Empty; if (Type == 0) { Byte t = p[3]; if (be) { Type = (UInt16)(t >> 4); Offset = (UInt16)(t & 0xF); } else { Type = (UInt16)(t & 0xF); Offset = (UInt16)(t >> 4); } return (Type == kType_FIFO || Type == kType_SOCK) ? 4 : 0; } Type--; Uid = (UInt16)(Uid + (Type / 5) * 16); Type = (UInt16)((Type % 5) + 1); if (Type == kType_FILE) { if (size < 15) return 0; // GET_32 (3, MTime); GET_32 (7, StartBlock); UInt32 t; GET_32 (11, t); FileSize = t; UInt32 numBlocks = t >> _h.BlockSizeLog; if ((t & (_h.BlockSize - 1)) != 0) numBlocks++; UInt32 pos = numBlocks * 2 + 15; return (pos <= size) ? pos : 0; } if (Type == kType_DIR) { if (size < 14) return 0; UInt32 t = Get32(p + 3); if (be) { FileSize = t >> 13; Offset = t & 0x1FFF; } else { FileSize = t & 0x7FFFF; Offset = t >> 19; } // GET_32 (7, MTime); GET_32 (10, StartBlock); if (be) StartBlock &= 0xFFFFFF; else StartBlock >>= 8; return 14; } if (size < 5) return 0; if (Type == kType_LNK) { UInt32 len; GET_16 (3, len); FileSize = len; len += 5; return (len <= size) ? len : 0; } // GET_32 (3, RDev); return 5; } UInt32 CNode::Parse2(const Byte *p, UInt32 size, const CHeader &_h) { bool be = _h.be; if (size < 4) return 0; { const UInt32 t = Get16(p); if (be) { Type = (UInt16)(t >> 12); Mode = (UInt16)(t & 0xFFF); } else { Type = (UInt16)(t & 0xF); Mode = (UInt16)(t >> 4); } } Uid = p[2]; Gid = p[3]; // Xattr = kXattr_Empty; if (Type == kType_FILE) { if (size < 24) return 0; // GET_32 (4, MTime); GET_32 (8, StartBlock); GET_32 (12, Frag); GET_32 (16, Offset); UInt32 t; GET_32 (20, t); FileSize = t; UInt32 numBlocks = t >> _h.BlockSizeLog; if (!ThereAreFrags() && (t & (_h.BlockSize - 1)) != 0) numBlocks++; UInt32 pos = numBlocks * 4 + 24; return (pos <= size) ? (UInt32)pos : 0; } FileSize = 0; // MTime = 0; StartBlock = 0; Frag = kFrag_Empty; if (Type == kType_DIR) { if (size < 15) return 0; UInt32 t = Get32(p + 4); if (be) { FileSize = t >> 13; Offset = t & 0x1FFF; } else { FileSize = t & 0x7FFFF; Offset = t >> 19; } // GET_32 (8, MTime); GET_32 (11, StartBlock); if (be) StartBlock &= 0xFFFFFF; else StartBlock >>= 8; return 15; } if (Type == kType_DIR + 7) { if (size < 18) return 0; UInt32 t = Get32(p + 4); UInt32 t2 = Get16(p + 7); if (be) { FileSize = t >> 5; Offset = t2 & 0x1FFF; } else { FileSize = t & 0x7FFFFFF; Offset = t2 >> 3; } // GET_32 (9, MTime); GET_32 (12, StartBlock); if (be) StartBlock &= 0xFFFFFF; else StartBlock >>= 8; UInt32 iCount; GET_16 (16, iCount); UInt32 pos = 18; for (UInt32 i = 0; i < iCount; i++) { // 27 bits: index // 29 bits: startBlock if (pos + 8 > size) return 0; pos += 8 + (UInt32)p[pos + 7] + 1; // nameSize if (pos > size) return 0; } return pos; } if (Type == kType_FIFO || Type == kType_SOCK) return 4; if (size < 6) return 0; if (Type == kType_LNK) { UInt32 len; GET_16 (4, len); FileSize = len; len += 6; return (len <= size) ? len : 0; } if (Type == kType_BLK || Type == kType_CHR) { // GET_16 (4, RDev); return 6; } return 0; } UInt32 CNode::Parse3(const Byte *p, UInt32 size, const CHeader &_h) { bool be = _h.be; if (size < 12) return 0; { const UInt32 t = Get16(p); if (be) { Type = (UInt16)(t >> 12); Mode = (UInt16)(t & 0xFFF); } else { Type = (UInt16)(t & 0xF); Mode = (UInt16)(t >> 4); } } Uid = p[2]; Gid = p[3]; // GET_32 (4, MTime); // GET_32 (8, Number); // Xattr = kXattr_Empty; FileSize = 0; StartBlock = 0; if (Type == kType_FILE || Type == kType_FILE + 7) { UInt32 offset; if (Type == kType_FILE) { if (size < 32) return 0; GET_64 (12, StartBlock); GET_32 (20, Frag); GET_32 (24, Offset); GET_32 (28, FileSize); offset = 32; } else { if (size < 40) return 0; // GET_32 (12, NumLinks); GET_64 (16, StartBlock); GET_32 (24, Frag); GET_32 (28, Offset); GET_64 (32, FileSize); offset = 40; } UInt64 pos = GetNumBlocks(_h) * 4 + offset; return (pos <= size) ? (UInt32)pos : 0; } if (size < 16) return 0; // GET_32 (12, NumLinks); if (Type == kType_DIR) { if (size < 28) return 0; UInt32 t = Get32(p + 16); if (be) { FileSize = t >> 13; Offset = t & 0x1FFF; } else { FileSize = t & 0x7FFFF; Offset = t >> 19; } GET_32 (20, StartBlock); // GET_32 (24, Parent); return 28; } if (Type == kType_DIR + 7) { if (size < 31) return 0; UInt32 t = Get32(p + 16); UInt32 t2 = Get16(p + 19); if (be) { FileSize = t >> 5; Offset = t2 & 0x1FFF; } else { FileSize = t & 0x7FFFFFF; Offset = t2 >> 3; } GET_32 (21, StartBlock); UInt32 iCount; GET_16 (25, iCount); // GET_32 (27, Parent); UInt32 pos = 31; for (UInt32 i = 0; i < iCount; i++) { // UInt32 index // UInt32 startBlock if (pos + 9 > size) return 0; pos += 9 + (unsigned)p[pos + 8] + 1; // nameSize if (pos > size) return 0; } return pos; } if (Type == kType_FIFO || Type == kType_SOCK) return 16; if (size < 18) return 0; if (Type == kType_LNK) { UInt32 len; GET_16 (16, len); FileSize = len; len += 18; return (len <= size) ? len : 0; } if (Type == kType_BLK || Type == kType_CHR) { // GET_16 (16, RDev); return 18; } return 0; } UInt32 CNode::Parse4(const Byte *p, UInt32 size, const CHeader &_h) { if (size < 20) return 0; LE_16 (0, Type); LE_16 (2, Mode); LE_16 (4, Uid); LE_16 (6, Gid); // LE_32 (8, MTime); // LE_32 (12, Number); // Xattr = kXattr_Empty; FileSize = 0; StartBlock = 0; if (Type == kType_FILE || Type == kType_FILE + 7) { UInt32 offset; if (Type == kType_FILE) { if (size < 32) return 0; LE_32 (16, StartBlock); LE_32 (20, Frag); LE_32 (24, Offset); LE_32 (28, FileSize); offset = 32; } else { if (size < 56) return 0; LE_64 (16, StartBlock); LE_64 (24, FileSize); // LE_64 (32, Sparse); // LE_32 (40, NumLinks); LE_32 (44, Frag); LE_32 (48, Offset); // LE_32 (52, Xattr); offset = 56; } UInt64 pos = GetNumBlocks(_h) * 4 + offset; return (pos <= size) ? (UInt32)pos : 0; } if (Type == kType_DIR) { if (size < 32) return 0; LE_32 (16, StartBlock); // LE_32 (20, NumLinks); LE_16 (24, FileSize); LE_16 (26, Offset); // LE_32 (28, Parent); return 32; } // LE_32 (16, NumLinks); if (Type == kType_DIR + 7) { if (size < 40) return 0; LE_32 (20, FileSize); LE_32 (24, StartBlock); // LE_32 (28, Parent); UInt32 iCount; LE_16 (32, iCount); LE_16 (34, Offset); // LE_32 (36, Xattr); UInt32 pos = 40; for (UInt32 i = 0; i < iCount; i++) { // UInt32 index // UInt32 startBlock if (pos + 12 > size) return 0; UInt32 nameLen = GetUi32(p + pos + 8); pos += 12 + nameLen + 1; if (pos > size || nameLen > (1 << 10)) return 0; } return pos; } unsigned offset = 20; switch (Type) { case kType_FIFO: case kType_FIFO + 7: case kType_SOCK: case kType_SOCK + 7: break; case kType_LNK: case kType_LNK + 7: { if (size < 24) return 0; UInt32 len; LE_32 (20, len); FileSize = len; offset = len + 24; if (size < offset || len > (1 << 30)) return 0; break; } case kType_BLK: case kType_BLK + 7: case kType_CHR: case kType_CHR + 7: if (size < 24) return 0; // LE_32 (20, RDev); offset = 24; break; default: return 0; } if (Type >= 8) { if (size < offset + 4) return 0; // LE_32 (offset, Xattr); offset += 4; } return offset; } struct CItem { int Node; int Parent; UInt32 Ptr; CItem(): Node(-1), Parent(-1), Ptr(0) {} }; struct CData { CByteBuffer Data; CRecordVector PackPos; CRecordVector UnpackPos; // additional item at the end contains TotalUnpackSize UInt32 GetNumBlocks() const { return PackPos.Size(); } void Clear() { Data.Free(); PackPos.Clear(); UnpackPos.Clear(); } }; struct CFrag { UInt64 StartBlock; UInt32 Size; }; class CHandler: public IInArchive, public IInArchiveGetStream, public CMyUnknownImp { CRecordVector _items; CRecordVector _nodes; CRecordVector _nodesPos; CRecordVector _blockToNode; CData _inodesData; CData _dirs; CRecordVector _frags; // CByteBuffer _uids; // CByteBuffer _gids; CHeader _h; bool _noPropsLZMA; bool _needCheckLzma; CMyComPtr _stream; UInt64 _sizeCalculated; IArchiveOpenCallback *_openCallback; int _nodeIndex; CRecordVector _blockCompressed; CRecordVector _blockOffsets; CByteBuffer _cachedBlock; UInt64 _cachedBlockStartPos; UInt32 _cachedPackBlockSize; UInt32 _cachedUnpackBlockSize; CLimitedSequentialInStream *_limitedInStreamSpec; CMyComPtr _limitedInStream; CBufPtrSeqOutStream *_outStreamSpec; CMyComPtr _outStream; NCompress::NLzma::CDecoder *_lzmaDecoderSpec; CMyComPtr _lzmaDecoder; NCompress::NZlib::CDecoder *_zlibDecoderSpec; CMyComPtr _zlibDecoder; CXzUnpacker _xz; CByteBuffer _inputBuffer; CDynBufSeqOutStream *_dynOutStreamSpec; CMyComPtr _dynOutStream; void ClearCache() { _cachedBlockStartPos = 0; _cachedPackBlockSize = 0; _cachedUnpackBlockSize = 0; } HRESULT Decompress(ISequentialOutStream *outStream, Byte *outBuf, bool *outBufWasWritten, UInt32 *outBufWasWrittenSize, UInt32 inSize, UInt32 outSizeMax); HRESULT ReadMetadataBlock(UInt32 &packSize); HRESULT ReadData(CData &data, UInt64 start, UInt64 end); HRESULT OpenDir(int parent, UInt32 startBlock, UInt32 offset, unsigned level, int &nodeIndex); HRESULT ScanInodes(UInt64 ptr); // HRESULT ReadUids(UInt64 start, UInt32 num, CByteBuffer &ids); HRESULT Open2(IInStream *inStream); AString GetPath(int index) const; bool GetPackSize(int index, UInt64 &res, bool fillOffsets); public: CHandler(); ~CHandler() { XzUnpacker_Free(&_xz); } MY_UNKNOWN_IMP2(IInArchive, IInArchiveGetStream) INTERFACE_IInArchive(;) STDMETHOD(GetStream)(UInt32 index, ISequentialInStream **stream); HRESULT ReadBlock(UInt64 blockIndex, Byte *dest, size_t blockSize); }; CHandler::CHandler() { XzUnpacker_Construct(&_xz, &g_Alloc); _limitedInStreamSpec = new CLimitedSequentialInStream; _limitedInStream = _limitedInStreamSpec; _outStreamSpec = new CBufPtrSeqOutStream(); _outStream = _outStreamSpec; _dynOutStreamSpec = new CDynBufSeqOutStream; _dynOutStream = _dynOutStreamSpec; } static const Byte kProps[] = { kpidPath, kpidIsDir, kpidSize, kpidPackSize, kpidMTime, kpidPosixAttrib // kpidUser, // kpidGroup, // kpidLinks, // kpidOffset }; static const Byte kArcProps[] = { kpidHeadersSize, kpidFileSystem, kpidMethod, kpidClusterSize, kpidBigEndian, kpidCTime, kpidCharacts // kpidNumBlocks }; IMP_IInArchive_Props IMP_IInArchive_ArcProps static HRESULT LzoDecode(Byte *dest, SizeT *destLen, const Byte *src, SizeT *srcLen) { SizeT destRem = *destLen; SizeT srcRem = *srcLen; *destLen = 0; *srcLen = 0; const Byte *destStart = dest; const Byte *srcStart = src; unsigned mode = 2; { if (srcRem == 0) return S_FALSE; UInt32 b = *src; if (b > 17) { src++; srcRem--; b -= 17; mode = (b < 4 ? 0 : 1); if (b > srcRem || b > destRem) return S_FALSE; srcRem -= b; destRem -= b; do *dest++ = *src++; while (--b); } } for (;;) { if (srcRem < 3) return S_FALSE; UInt32 b = *src++; srcRem--; UInt32 len, back; if (b >= 64) { srcRem--; back = ((b >> 2) & 7) + ((UInt32)*src++ << 3); len = (b >> 5) + 1; } else if (b < 16) { if (mode == 2) { if (b == 0) { for (b = 15;; b += 255) { if (srcRem == 0) return S_FALSE; UInt32 b2 = *src++; srcRem--; if (b2 != 0) { b += b2; break; } } } b += 3; if (b > srcRem || b > destRem) return S_FALSE; srcRem -= b; destRem -= b; mode = 1; do *dest++ = *src++; while (--b); continue; } srcRem--; back = (b >> 2) + (*src++ << 2); len = 2; if (mode == 1) { back += (1 << 11); len = 3; } } else { UInt32 bOld = b; b = (b < 32 ? 7 : 31); len = bOld & b; if (len == 0) { for (len = b;; len += 255) { if (srcRem == 0) return S_FALSE; UInt32 b2 = *src++; srcRem--; if (b2 != 0) { len += b2; break; } } } len += 2; if (srcRem < 2) return S_FALSE; b = *src; back = (b >> 2) + ((UInt32)src[1] << 6); src += 2; srcRem -= 2; if (bOld < 32) { if (back == 0) { *destLen = dest - destStart; *srcLen = src - srcStart; return S_OK; } back += ((bOld & 8) << 11) + (1 << 14) - 1; } } back++; if (len > destRem || (size_t)(dest - destStart) < back) return S_FALSE; destRem -= len; Byte *destTemp = dest - back; dest += len; do { *(destTemp + back) = *destTemp; destTemp++; } while (--len); b &= 3; if (b == 0) { mode = 2; continue; } if (b > srcRem || b > destRem) return S_FALSE; srcRem -= b; destRem -= b; mode = 0; *dest++ = *src++; if (b > 1) { *dest++ = *src++; if (b > 2) *dest++ = *src++; } } } HRESULT CHandler::Decompress(ISequentialOutStream *outStream, Byte *outBuf, bool *outBufWasWritten, UInt32 *outBufWasWrittenSize, UInt32 inSize, UInt32 outSizeMax) { if (outBuf) { *outBufWasWritten = false; *outBufWasWrittenSize = 0; } UInt32 method = _h.Method; if (_h.SeveralMethods) { Byte b; RINOK(ReadStream_FALSE(_stream, &b, 1)); RINOK(_stream->Seek(-1, STREAM_SEEK_CUR, NULL)); method = (b == 0x5D ? kMethod_LZMA : kMethod_ZLIB); } if (method == kMethod_ZLIB && _needCheckLzma) { Byte b; RINOK(ReadStream_FALSE(_stream, &b, 1)); RINOK(_stream->Seek(-1, STREAM_SEEK_CUR, NULL)); if (b == 0) { _noPropsLZMA = true; method = _h.Method = kMethod_LZMA; } _needCheckLzma = false; } if (method == kMethod_ZLIB) { if (!_zlibDecoder) { _zlibDecoderSpec = new NCompress::NZlib::CDecoder(); _zlibDecoder = _zlibDecoderSpec; } RINOK(_zlibDecoder->Code(_limitedInStream, outStream, NULL, NULL, NULL)); if (inSize != _zlibDecoderSpec->GetInputProcessedSize()) return S_FALSE; } else if (method == kMethod_LZMA) { if (!_lzmaDecoder) { _lzmaDecoderSpec = new NCompress::NLzma::CDecoder(); _lzmaDecoderSpec->FinishStream = true; _lzmaDecoder = _lzmaDecoderSpec; } const UInt32 kPropsSize = LZMA_PROPS_SIZE + 8; Byte props[kPropsSize]; UInt32 propsSize; UInt64 outSize; if (_noPropsLZMA) { props[0] = 0x5D; SetUi32(&props[1], _h.BlockSize); propsSize = 0; outSize = outSizeMax; } else { RINOK(ReadStream_FALSE(_limitedInStream, props, kPropsSize)); propsSize = kPropsSize; outSize = GetUi64(&props[LZMA_PROPS_SIZE]); if (outSize > outSizeMax) return S_FALSE; } RINOK(_lzmaDecoderSpec->SetDecoderProperties2(props, LZMA_PROPS_SIZE)); RINOK(_lzmaDecoder->Code(_limitedInStream, outStream, NULL, &outSize, NULL)); if (inSize != propsSize + _lzmaDecoderSpec->GetInputProcessedSize()) return S_FALSE; } else { if (_inputBuffer.Size() < inSize) _inputBuffer.Alloc(inSize); RINOK(ReadStream_FALSE(_stream, _inputBuffer, inSize)); Byte *dest = outBuf; if (!outBuf) { dest = _dynOutStreamSpec->GetBufPtrForWriting(outSizeMax); if (!dest) return E_OUTOFMEMORY; } SizeT destLen = outSizeMax, srcLen = inSize; if (method == kMethod_LZO) { RINOK(LzoDecode(dest, &destLen, _inputBuffer, &srcLen)); } else { ECoderStatus status; XzUnpacker_Init(&_xz); SRes res = XzUnpacker_Code(&_xz, dest, &destLen, _inputBuffer, &srcLen, CODER_FINISH_END, &status); if (res != 0) return SResToHRESULT(res); if (status != CODER_STATUS_NEEDS_MORE_INPUT || !XzUnpacker_IsStreamWasFinished(&_xz)) return S_FALSE; } if (inSize != srcLen) return S_FALSE; if (outBuf) { *outBufWasWritten = true; *outBufWasWrittenSize = (UInt32)destLen; } else _dynOutStreamSpec->UpdateSize(destLen); } return S_OK; } HRESULT CHandler::ReadMetadataBlock(UInt32 &packSize) { Byte temp[3]; unsigned offset = _h.NeedCheckData() ? 3 : 2; if (offset > packSize) return S_FALSE; RINOK(ReadStream_FALSE(_stream, temp, offset)); // if (NeedCheckData && Major < 4) checkByte must be = 0xFF bool be = _h.be; UInt32 size = Get16(temp); bool isCompressed = ((size & kNotCompressedBit16) == 0); if (size != kNotCompressedBit16) size &= ~kNotCompressedBit16; if (size > kMetadataBlockSize || offset + size > packSize) return S_FALSE; packSize = offset + size; if (isCompressed) { _limitedInStreamSpec->Init(size); RINOK(Decompress(_dynOutStream, NULL, NULL, NULL, size, kMetadataBlockSize)); } else { // size != 0 here Byte *buf = _dynOutStreamSpec->GetBufPtrForWriting(size); if (!buf) return E_OUTOFMEMORY; RINOK(ReadStream_FALSE(_stream, buf, size)); _dynOutStreamSpec->UpdateSize(size); } return S_OK; } HRESULT CHandler::ReadData(CData &data, UInt64 start, UInt64 end) { if (end < start || end - start >= ((UInt64)1 << 32)) return S_FALSE; UInt32 size = (UInt32)(end - start); RINOK(_stream->Seek(start, STREAM_SEEK_SET, NULL)); _dynOutStreamSpec->Init(); UInt32 packPos = 0; while (packPos != size) { data.PackPos.Add(packPos); data.UnpackPos.Add((UInt32)_dynOutStreamSpec->GetSize()); if (packPos > size) return S_FALSE; UInt32 packSize = size - packPos; RINOK(ReadMetadataBlock(packSize)); if (_dynOutStreamSpec->GetSize() >= ((UInt64)1 << 32)) return S_FALSE; packPos += packSize; } data.UnpackPos.Add((UInt32)_dynOutStreamSpec->GetSize()); _dynOutStreamSpec->CopyToBuffer(data.Data); return S_OK; } struct CTempItem { UInt32 StartBlock; // UInt32 iNodeNumber1; UInt32 Offset; // UInt16 iNodeNumber2; UInt16 Type; }; HRESULT CHandler::OpenDir(int parent, UInt32 startBlock, UInt32 offset, unsigned level, int &nodeIndex) { if (level > kNumDirLevelsMax) return S_FALSE; int blockIndex = _inodesData.PackPos.FindInSorted(startBlock); if (blockIndex < 0) return S_FALSE; UInt32 unpackPos = _inodesData.UnpackPos[blockIndex] + offset; if (unpackPos < offset) return S_FALSE; nodeIndex = _nodesPos.FindInSorted(unpackPos, _blockToNode[blockIndex], _blockToNode[blockIndex + 1]); // nodeIndex = _nodesPos.FindInSorted(unpackPos); if (nodeIndex < 0) return S_FALSE; const CNode &n = _nodes[nodeIndex]; if (!n.IsDir()) return S_OK; blockIndex = _dirs.PackPos.FindInSorted((UInt32)n.StartBlock); if (blockIndex < 0) return S_FALSE; unpackPos = _dirs.UnpackPos[blockIndex] + n.Offset; if (unpackPos < n.Offset || unpackPos > _dirs.Data.Size()) return S_FALSE; UInt32 rem = (UInt32)_dirs.Data.Size() - unpackPos; const Byte *p = _dirs.Data + unpackPos; UInt32 fileSize = (UInt32)n.FileSize; // for some squashfs files: fileSize = rem + 3 !!! if (_h.Major >= 3) { if (fileSize < 3) return S_FALSE; fileSize -= 3; } if (fileSize > rem) return S_FALSE; rem = fileSize; CRecordVector tempItems; while (rem != 0) { bool be = _h.be; UInt32 count; CTempItem tempItem; if (_h.Major <= 2) { if (rem < 4) return S_FALSE; count = p[0]; tempItem.StartBlock = Get32(p); if (be) tempItem.StartBlock &= 0xFFFFFF; else tempItem.StartBlock >>= 8; p += 4; rem -= 4; } else { if (_h.Major == 3) { if (rem < 9) return S_FALSE; count = p[0]; p += 1; rem -= 1; } else { if (rem < 12) return S_FALSE; count = GetUi32(p); p += 4; rem -= 4; } GET_32 (0, tempItem.StartBlock); // GET_32 (4, tempItem.iNodeNumber1); p += 8; rem -= 8; } count++; for (UInt32 i = 0; i < count; i++) { if (rem == 0) return S_FALSE; UInt32 nameOffset = _h.GetFileNameOffset(); if (rem < nameOffset) return S_FALSE; if ((UInt32)_items.Size() >= kNumFilesMax) return S_FALSE; if (_openCallback) { UInt64 numFiles = _items.Size(); if ((numFiles & 0xFFFF) == 0) { RINOK(_openCallback->SetCompleted(&numFiles, NULL)); } } CItem item; item.Ptr = (UInt32)(p - _dirs.Data); UInt32 size; if (_h.IsOldVersion()) { UInt32 t = Get16(p); if (be) { tempItem.Offset = t >> 3; tempItem.Type = (UInt16)(t & 0x7); } else { tempItem.Offset = t & 0x1FFF; tempItem.Type = (UInt16)(t >> 13); } size = (UInt32)p[2]; /* if (_h.Major > 2) tempItem.iNodeNumber2 = Get16(p + 3); */ } else { GET_16 (0, tempItem.Offset); // GET_16 (2, tempItem.iNodeNumber2); GET_16 (4, tempItem.Type); GET_16 (6, size); } p += nameOffset; rem -= nameOffset; size++; if (rem < size) return S_FALSE; p += size; rem -= size; item.Parent = parent; _items.Add(item); tempItems.Add(tempItem); } } int startItemIndex = _items.Size() - tempItems.Size(); FOR_VECTOR (i, tempItems) { const CTempItem &tempItem = tempItems[i]; int index = startItemIndex + i; CItem &item = _items[index]; RINOK(OpenDir(index, tempItem.StartBlock, tempItem.Offset, level + 1, item.Node)); } return S_OK; } /* HRESULT CHandler::ReadUids(UInt64 start, UInt32 num, CByteBuffer &ids) { size_t size = num * 4; ids.SetCapacity(size); RINOK(_stream->Seek(start, STREAM_SEEK_SET, NULL)); return ReadStream_FALSE(_stream, ids, size); } */ HRESULT CHandler::Open2(IInStream *inStream) { { Byte buf[kHeaderSize3]; RINOK(ReadStream_FALSE(inStream, buf, kHeaderSize3)); if (!_h.Parse(buf)) return S_FALSE; if (!_h.IsSupported()) return E_NOTIMPL; _noPropsLZMA = false; _needCheckLzma = false; switch (_h.Method) { case kMethod_ZLIB: _needCheckLzma = true; break; case kMethod_LZMA: case kMethod_LZO: case kMethod_XZ: break; default: return E_NOTIMPL; } } _stream = inStream; if (_h.NumFrags != 0) { if (_h.NumFrags > kNumFilesMax) return S_FALSE; _frags.ClearAndReserve(_h.NumFrags); unsigned bigFrag = (_h.Major > 2); unsigned fragPtrsInBlockLog = kMetadataBlockSizeLog - (3 + bigFrag); UInt32 numBlocks = (_h.NumFrags + (1 << fragPtrsInBlockLog) - 1) >> fragPtrsInBlockLog; size_t numBlocksBytes = (size_t)numBlocks << (2 + bigFrag); CByteBuffer data(numBlocksBytes); RINOK(inStream->Seek(_h.FragTable, STREAM_SEEK_SET, NULL)); RINOK(ReadStream_FALSE(inStream, data, numBlocksBytes)); bool be = _h.be; for (UInt32 i = 0; i < numBlocks; i++) { UInt64 offset = bigFrag ? Get64(data + i * 8) : Get32(data + i * 4); RINOK(_stream->Seek(offset, STREAM_SEEK_SET, NULL)); _dynOutStreamSpec->Init(); UInt32 packSize = kMetadataBlockSize + 3; RINOK(ReadMetadataBlock(packSize)); UInt32 unpackSize = (UInt32)_dynOutStreamSpec->GetSize(); if (unpackSize != kMetadataBlockSize) if (i != numBlocks - 1 || unpackSize != ((_h.NumFrags << (3 + bigFrag)) & (kMetadataBlockSize - 1))) return S_FALSE; const Byte *buf = _dynOutStreamSpec->GetBuffer(); for (UInt32 j = 0; j < kMetadataBlockSize && j < unpackSize;) { CFrag frag; if (bigFrag) { frag.StartBlock = Get64(buf + j); frag.Size = Get32(buf + j + 8); // some archives contain nonzero in unused (buf + j + 12) j += 16; } else { frag.StartBlock = Get32(buf + j); frag.Size = Get32(buf + j + 4); j += 8; } _frags.Add(frag); } } if ((UInt32)_frags.Size() != _h.NumFrags) return S_FALSE; } // RINOK(inStream->Seek(_h.InodeTable, STREAM_SEEK_SET, NULL)); RINOK(ReadData(_inodesData, _h.InodeTable, _h.DirTable)); RINOK(ReadData(_dirs, _h.DirTable, _h.FragTable)); UInt64 absOffset = _h.RootInode >> 16; if (absOffset >= ((UInt64)1 << 32)) return S_FALSE; { UInt32 pos = 0; UInt32 totalSize = (UInt32)_inodesData.Data.Size(); _nodesPos.ClearAndReserve(_h.NumInodes); _nodes.ClearAndReserve(_h.NumInodes); // we use _blockToNode for binary search seed optimizations _blockToNode.ClearAndReserve(_inodesData.GetNumBlocks() + 1); int curBlock = 0; for (UInt32 i = 0; i < _h.NumInodes; i++) { CNode n; const Byte *p = _inodesData.Data + pos; UInt32 size = totalSize - pos; switch (_h.Major) { case 1: size = n.Parse1(p, size, _h); break; case 2: size = n.Parse2(p, size, _h); break; case 3: size = n.Parse3(p, size, _h); break; default: size = n.Parse4(p, size, _h); break; } if (size == 0) return S_FALSE; while (pos >= _inodesData.UnpackPos[curBlock]) { _blockToNode.Add(_nodesPos.Size()); curBlock++; } _nodesPos.AddInReserved(pos); _nodes.AddInReserved(n); pos += size; } _blockToNode.Add(_nodesPos.Size()); if (pos != totalSize) return S_FALSE; } int rootNodeIndex; RINOK(OpenDir(-1, (UInt32)absOffset, (UInt32)_h.RootInode & 0xFFFF, 0, rootNodeIndex)); /* if (_h.Major < 4) { RINOK(ReadUids(_h.UidTable, _h.NumUids, _uids)); RINOK(ReadUids(_h.GidTable, _h.NumGids, _gids)); } else { UInt32 size = _h.NumIDs * 4; _uids.SetCapacity(size); UInt32 numBlocks = (size + kMetadataBlockSize - 1) / kMetadataBlockSize; UInt32 numBlocksBytes = numBlocks << 3; CByteBuffer data; data.SetCapacity(numBlocksBytes); RINOK(inStream->Seek(_h.UidTable, STREAM_SEEK_SET, NULL)); RINOK(ReadStream_FALSE(inStream, data, numBlocksBytes)); for (UInt32 i = 0; i < numBlocks; i++) { UInt64 offset = GetUi64(data + i * 8); UInt32 unpackSize, packSize; RINOK(_stream->Seek(offset, STREAM_SEEK_SET, NULL)); RINOK(ReadMetadataBlock(NULL, _uids + kMetadataBlockSize * i, packSize, unpackSize)); if (unpackSize != kMetadataBlockSize) if (i != numBlocks - 1 || unpackSize != (size & (kMetadataBlockSize - 1))) return S_FALSE; } } */ { const UInt32 alignSize = 1 << 12; Byte buf[alignSize]; RINOK(inStream->Seek(_h.Size, STREAM_SEEK_SET, NULL)); UInt32 rem = (UInt32)(0 - _h.Size) & (alignSize - 1); _sizeCalculated = _h.Size; if (rem != 0) { if (ReadStream_FALSE(_stream, buf, rem) == S_OK) { size_t i; for (i = 0; i < rem && buf[i] == 0; i++); if (i == rem) _sizeCalculated = _h.Size + rem; } } } return S_OK; } AString CHandler::GetPath(int index) const { unsigned len = 0; int indexMem = index; bool be = _h.be; do { const CItem &item = _items[index]; index = item.Parent; const Byte *p = _dirs.Data + item.Ptr; unsigned size = (_h.IsOldVersion() ? (unsigned)p[2] : (unsigned)Get16(p + 6)) + 1; p += _h.GetFileNameOffset(); unsigned i; for (i = 0; i < size && p[i]; i++); len += i + 1; } while (index >= 0); len--; AString path; char *dest = path.GetBuf_SetEnd(len) + len; index = indexMem; for (;;) { const CItem &item = _items[index]; index = item.Parent; const Byte *p = _dirs.Data + item.Ptr; unsigned size = (_h.IsOldVersion() ? (unsigned)p[2] : (unsigned)Get16(p + 6)) + 1; p += _h.GetFileNameOffset(); unsigned i; for (i = 0; i < size && p[i]; i++); dest -= i; memcpy(dest, p, i); if (index < 0) break; *(--dest) = CHAR_PATH_SEPARATOR; } return path; } STDMETHODIMP CHandler::Open(IInStream *stream, const UInt64 *, IArchiveOpenCallback *callback) { COM_TRY_BEGIN { Close(); _limitedInStreamSpec->SetStream(stream); HRESULT res; try { _openCallback = callback; res = Open2(stream); } catch(...) { Close(); throw; } if (res != S_OK) { Close(); return res; } _stream = stream; } return S_OK; COM_TRY_END } STDMETHODIMP CHandler::Close() { _sizeCalculated = 0; _limitedInStreamSpec->ReleaseStream(); _stream.Release(); _items.Clear(); _nodes.Clear(); _nodesPos.Clear(); _blockToNode.Clear(); _frags.Clear(); _inodesData.Clear(); _dirs.Clear(); // _uids.Free(); // _gids.Free();; _cachedBlock.Free(); ClearCache(); return S_OK; } bool CHandler::GetPackSize(int index, UInt64 &totalPack, bool fillOffsets) { totalPack = 0; const CItem &item = _items[index]; const CNode &node = _nodes[item.Node]; UInt32 ptr = _nodesPos[item.Node]; const Byte *p = _inodesData.Data + ptr; bool be = _h.be; UInt32 type = node.Type; UInt32 offset; if (node.IsLink() || node.FileSize == 0) { totalPack = node.FileSize; return true; } UInt32 numBlocks = (UInt32)node.GetNumBlocks(_h); if (fillOffsets) { _blockOffsets.Clear(); _blockCompressed.Clear(); _blockOffsets.Add(totalPack); } if (_h.Major <= 1) { offset = 15; p += offset; for (UInt32 i = 0; i < numBlocks; i++) { UInt32 t = Get16(p + i * 2); if (fillOffsets) _blockCompressed.Add((t & kNotCompressedBit16) == 0); if (t != kNotCompressedBit16) t &= ~kNotCompressedBit16; totalPack += t; if (fillOffsets) _blockOffsets.Add(totalPack); } } else { if (_h.Major <= 2) offset = 24; else if (type == kType_FILE) offset = 32; else if (type == kType_FILE + 7) offset = (_h.Major <= 3 ? 40 : 56); else return false; p += offset; for (UInt64 i = 0; i < numBlocks; i++) { UInt32 t = Get32(p + i * 4); if (fillOffsets) _blockCompressed.Add(IS_COMPRESSED_BLOCK(t)); UInt32 size = GET_COMPRESSED_BLOCK_SIZE(t); if (size > _h.BlockSize) return false; totalPack += size; if (fillOffsets) _blockOffsets.Add(totalPack); } if (node.ThereAreFrags()) { if (node.Frag >= (UInt32)_frags.Size()) return false; const CFrag &frag = _frags[node.Frag]; if (node.Offset == 0) { UInt32 size = GET_COMPRESSED_BLOCK_SIZE(frag.Size); if (size > _h.BlockSize) return false; totalPack += size; } } } return true; } STDMETHODIMP CHandler::GetNumberOfItems(UInt32 *numItems) { *numItems = _items.Size(); return S_OK; } STDMETHODIMP CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value) { COM_TRY_BEGIN NWindows::NCOM::CPropVariant prop; switch (propID) { case kpidMethod: { const char *s; if (_noPropsLZMA) s = "LZMA Spec"; else if (_h.SeveralMethods) s = "LZMA ZLIB"; else { s = k_Methods[0]; if (_h.Method < ARRAY_SIZE(k_Methods)) s = k_Methods[_h.Method]; } prop = s; break; } case kpidFileSystem: { AString res = "SquashFS"; if (_h.SeveralMethods) res += "-LZMA"; res.Add_Space(); char s[16]; ConvertUInt32ToString(_h.Major, s); res += s; res += '.'; ConvertUInt32ToString(_h.Minor, s); res += s; prop = res; break; } case kpidClusterSize: prop = _h.BlockSize; break; case kpidBigEndian: prop = _h.be; break; case kpidCTime: if (_h.CTime != 0) { FILETIME ft; NWindows::NTime::UnixTimeToFileTime(_h.CTime, ft); prop = ft; } break; case kpidCharacts: FLAGS_TO_PROP(k_Flags, _h.Flags, prop); break; // case kpidNumBlocks: prop = _h.NumFrags; break; case kpidPhySize: prop = _sizeCalculated; break; case kpidHeadersSize: if (_sizeCalculated >= _h.InodeTable) prop = _sizeCalculated - _h.InodeTable; break; } prop.Detach(value); return S_OK; COM_TRY_END } STDMETHODIMP CHandler::GetProperty(UInt32 index, PROPID propID, PROPVARIANT *value) { COM_TRY_BEGIN NWindows::NCOM::CPropVariant prop; const CItem &item = _items[index]; const CNode &node = _nodes[item.Node]; bool isDir = node.IsDir(); bool be = _h.be; switch (propID) { case kpidPath: prop = MultiByteToUnicodeString(GetPath(index), CP_OEMCP); break; case kpidIsDir: prop = isDir; break; // case kpidOffset: if (!node.IsLink()) prop = (UInt64)node.StartBlock; break; case kpidSize: if (!isDir) prop = node.GetSize(); break; case kpidPackSize: if (!isDir) { UInt64 size; if (GetPackSize(index, size, false)) prop = size; } break; case kpidMTime: { UInt32 offset = 0; switch (_h.Major) { case 1: if (node.Type == kType_FILE) offset = 3; else if (node.Type == kType_DIR) offset = 7; break; case 2: if (node.Type == kType_FILE) offset = 4; else if (node.Type == kType_DIR) offset = 8; else if (node.Type == kType_DIR + 7) offset = 9; break; case 3: offset = 4; break; case 4: offset = 8; break; } if (offset != 0) { const Byte *p = _inodesData.Data + _nodesPos[item.Node] + offset; FILETIME ft; NWindows::NTime::UnixTimeToFileTime(Get32(p), ft); prop = ft; } break; } case kpidPosixAttrib: { if (node.Type != 0 && node.Type < ARRAY_SIZE(k_TypeToMode)) prop = (UInt32)(node.Mode & 0xFFF) | k_TypeToMode[node.Type]; break; } /* case kpidUser: { UInt32 offset = node.Uid * 4; if (offset < _uids.Size()) prop = (UInt32)Get32(_uids + offset); break; } case kpidGroup: { if (_h.Major == 4 || node.Gid == _h.GetSpecGuidIndex()) { UInt32 offset = node.Uid * 4; if (offset < _uids.Size()) prop = (UInt32)Get32(_uids + offset); } else { UInt32 offset = node.Gid * 4; if (offset < _gids.Size()) prop = (UInt32)Get32(_gids + offset); } break; } */ /* case kpidLinks: if (_h.Major >= 3 && node.Type != kType_FILE) prop = node.NumLinks; break; */ } prop.Detach(value); return S_OK; COM_TRY_END } class CSquashfsInStream: public CCachedInStream { HRESULT ReadBlock(UInt64 blockIndex, Byte *dest, size_t blockSize); public: CHandler *Handler; }; HRESULT CSquashfsInStream::ReadBlock(UInt64 blockIndex, Byte *dest, size_t blockSize) { return Handler->ReadBlock(blockIndex, dest, blockSize); } HRESULT CHandler::ReadBlock(UInt64 blockIndex, Byte *dest, size_t blockSize) { const CNode &node = _nodes[_nodeIndex]; UInt64 blockOffset; UInt32 packBlockSize; UInt32 offsetInBlock = 0; bool compressed; if (blockIndex < _blockCompressed.Size()) { compressed = _blockCompressed[(int)blockIndex]; blockOffset = _blockOffsets[(int)blockIndex]; packBlockSize = (UInt32)(_blockOffsets[(int)blockIndex + 1] - blockOffset); blockOffset += node.StartBlock; } else { if (!node.ThereAreFrags()) return S_FALSE; const CFrag &frag = _frags[node.Frag]; offsetInBlock = node.Offset; blockOffset = frag.StartBlock; packBlockSize = GET_COMPRESSED_BLOCK_SIZE(frag.Size); compressed = IS_COMPRESSED_BLOCK(frag.Size); } if (packBlockSize == 0) { // sparse file ??? memset(dest, 0, blockSize); return S_OK; } if (blockOffset != _cachedBlockStartPos || packBlockSize != _cachedPackBlockSize) { ClearCache(); RINOK(_stream->Seek(blockOffset, STREAM_SEEK_SET, NULL)); _limitedInStreamSpec->Init(packBlockSize); if (compressed) { _outStreamSpec->Init((Byte *)_cachedBlock, _h.BlockSize); bool outBufWasWritten; UInt32 outBufWasWrittenSize; HRESULT res = Decompress(_outStream, _cachedBlock, &outBufWasWritten, &outBufWasWrittenSize, packBlockSize, _h.BlockSize); if (outBufWasWritten) _cachedUnpackBlockSize = outBufWasWrittenSize; else _cachedUnpackBlockSize = (UInt32)_outStreamSpec->GetPos(); RINOK(res); } else { RINOK(ReadStream_FALSE(_limitedInStream, _cachedBlock, packBlockSize)); _cachedUnpackBlockSize = packBlockSize; } _cachedBlockStartPos = blockOffset; _cachedPackBlockSize = packBlockSize; } if (offsetInBlock + blockSize > _cachedUnpackBlockSize) return S_FALSE; if (blockSize != 0) memcpy(dest, _cachedBlock + offsetInBlock, blockSize); return S_OK; } 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(); if (numItems == 0) return S_OK; UInt64 totalSize = 0; UInt32 i; for (i = 0; i < numItems; i++) { const CItem &item = _items[allFilesMode ? i : indices[i]]; const CNode &node = _nodes[item.Node]; totalSize += node.GetSize(); } 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 < numItems; i++) { lps->InSize = totalPackSize; lps->OutSize = totalSize; RINOK(lps->SetCur()); CMyComPtr outStream; Int32 askMode = testMode ? NExtract::NAskMode::kTest : NExtract::NAskMode::kExtract; UInt32 index = allFilesMode ? i : indices[i]; const CItem &item = _items[index]; const CNode &node = _nodes[item.Node]; RINOK(extractCallback->GetStream(index, &outStream, askMode)); // const Byte *p = _data + item.Offset; if (node.IsDir()) { RINOK(extractCallback->PrepareOperation(askMode)); RINOK(extractCallback->SetOperationResult(NExtract::NOperationResult::kOK)); continue; } UInt64 unpackSize = node.GetSize(); totalSize += unpackSize; UInt64 packSize; if (GetPackSize(index, packSize, false)) 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) { COM_TRY_BEGIN const CItem &item = _items[index]; const CNode &node = _nodes[item.Node]; if (node.IsDir()) return E_FAIL; const Byte *p = _inodesData.Data + _nodesPos[item.Node]; if (node.FileSize == 0 || node.IsLink()) { CBufInStream *streamSpec = new CBufInStream; CMyComPtr streamTemp = streamSpec; if (node.IsLink()) streamSpec->Init(p + _h.GetSymLinkOffset(), (size_t)node.FileSize); else streamSpec->Init(NULL, 0); *stream = streamTemp.Detach(); return S_OK; } UInt64 packSize; if (!GetPackSize(index, packSize, true)) return S_FALSE; _nodeIndex = item.Node; size_t cacheSize = _h.BlockSize; if (_cachedBlock.Size() != cacheSize) { ClearCache(); _cachedBlock.Alloc(cacheSize); } CSquashfsInStream *streamSpec = new CSquashfsInStream; CMyComPtr streamTemp = streamSpec; streamSpec->Handler = this; unsigned cacheSizeLog = 22; if (cacheSizeLog <= _h.BlockSizeLog) cacheSizeLog = _h.BlockSizeLog + 1; if (!streamSpec->Alloc(_h.BlockSizeLog, cacheSizeLog - _h.BlockSizeLog)) return E_OUTOFMEMORY; streamSpec->Init(node.FileSize); *stream = streamTemp.Detach(); return S_OK; COM_TRY_END } static const Byte k_Signature[] = { 4, 'h', 's', 'q', 's', 4, 's', 'q', 's', 'h', 4, 's', 'h', 's', 'q' }; REGISTER_ARC_I( "SquashFS", "squashfs", 0, 0xD2, k_Signature, 0, NArcInfoFlags::kMultiSignature, NULL) }}