p7zip-rar/CPP/7zip/Archive/SquashfsHandler.cpp
2017-10-11 12:40:22 +02:00

2219 lines
51 KiB
C++

// 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<UInt32> PackPos;
CRecordVector<UInt32> 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<CItem> _items;
CRecordVector<CNode> _nodes;
CRecordVector<UInt32> _nodesPos;
CRecordVector<UInt32> _blockToNode;
CData _inodesData;
CData _dirs;
CRecordVector<CFrag> _frags;
// CByteBuffer _uids;
// CByteBuffer _gids;
CHeader _h;
bool _noPropsLZMA;
bool _needCheckLzma;
CMyComPtr<IInStream> _stream;
UInt64 _sizeCalculated;
IArchiveOpenCallback *_openCallback;
int _nodeIndex;
CRecordVector<bool> _blockCompressed;
CRecordVector<UInt64> _blockOffsets;
CByteBuffer _cachedBlock;
UInt64 _cachedBlockStartPos;
UInt32 _cachedPackBlockSize;
UInt32 _cachedUnpackBlockSize;
CLimitedSequentialInStream *_limitedInStreamSpec;
CMyComPtr<ISequentialInStream> _limitedInStream;
CBufPtrSeqOutStream *_outStreamSpec;
CMyComPtr<ISequentialOutStream> _outStream;
NCompress::NLzma::CDecoder *_lzmaDecoderSpec;
CMyComPtr<ICompressCoder> _lzmaDecoder;
NCompress::NZlib::CDecoder *_zlibDecoderSpec;
CMyComPtr<ICompressCoder> _zlibDecoder;
CXzUnpacker _xz;
CByteBuffer _inputBuffer;
CDynBufSeqOutStream *_dynOutStreamSpec;
CMyComPtr<ISequentialOutStream> _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<CTempItem> 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<ICompressCoder> copyCoder = copyCoderSpec;
CLocalProgress *lps = new CLocalProgress;
CMyComPtr<ICompressProgressInfo> progress = lps;
lps->Init(extractCallback, false);
for (i = 0; i < numItems; i++)
{
lps->InSize = totalPackSize;
lps->OutSize = totalSize;
RINOK(lps->SetCur());
CMyComPtr<ISequentialOutStream> 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<ISequentialInStream> 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<IInStream> 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<IInStream> 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)
}}