// CpioHandler.cpp #include "StdAfx.h" #include "../../../C/CpuArch.h" #include "../../Common/ComTry.h" #include "../../Common/MyLinux.h" #include "../../Common/StringConvert.h" #include "../../Common/StringToInt.h" #include "../../Common/UTFConvert.h" #include "../../Windows/PropVariant.h" #include "../../Windows/TimeUtils.h" #include "../Common/LimitedStreams.h" #include "../Common/ProgressUtils.h" #include "../Common/RegisterArc.h" #include "../Common/StreamUtils.h" #include "../Compress/CopyCoder.h" #include "Common/ItemNameUtils.h" using namespace NWindows; namespace NArchive { namespace NCpio { static const Byte kMagicBin0 = 0xC7; static const Byte kMagicBin1 = 0x71; // #define MAGIC_ASCII { '0', '7', '0', '7', '0' } static const Byte kMagicHex = '1'; // New ASCII Format static const Byte kMagicHexCrc = '2'; // New CRC Format static const Byte kMagicOct = '7'; // Portable ASCII Format static const char *kName_TRAILER = "TRAILER!!!"; static const unsigned k_BinRecord_Size = 2 + 8 * 2 + 2 * 4; static const unsigned k_OctRecord_Size = 6 + 8 * 6 + 2 * 11; static const unsigned k_HexRecord_Size = 6 + 13 * 8; static const unsigned k_RecordSize_Max = k_HexRecord_Size; /* struct CBinRecord { unsigned short c_magic; short c_dev; unsigned short c_ino; unsigned short c_mode; unsigned short c_uid; unsigned short c_gid; unsigned short c_nlink; short c_rdev; unsigned short c_mtimes[2]; unsigned short c_namesize; unsigned short c_filesizes[2]; }; struct CHexRecord { char Magic[6]; char inode[8]; char Mode[8]; char UID[8]; char GID[8]; char nlink[8]; char mtime[8]; char Size[8]; // must be 0 for FIFOs and directories char DevMajor[8]; char DevMinor[8]; char RDevMajor[8]; //only valid for chr and blk special files char RDevMinor[8]; //only valid for chr and blk special files char NameSize[8]; // count includes terminating NUL in pathname char ChkSum[8]; // 0 for "new" portable format; for CRC format the sum of all the bytes in the file }; */ enum EType { k_Type_BinLe, k_Type_BinBe, k_Type_Oct, k_Type_Hex, k_Type_HexCrc }; static const char * const k_Types[] = { "Binary LE" , "Binary BE" , "Portable ASCII" , "New ASCII" , "New CRC" }; struct CItem { AString Name; UInt32 inode; UInt32 Mode; UInt32 UID; UInt32 GID; UInt64 Size; UInt32 MTime; UInt32 NumLinks; UInt32 DevMajor; UInt32 DevMinor; UInt32 RDevMajor; UInt32 RDevMinor; UInt32 ChkSum; UInt32 Align; EType Type; UInt32 HeaderSize; UInt64 HeaderPos; bool IsBin() const { return Type == k_Type_BinLe || Type == k_Type_BinBe; } bool IsCrcFormat() const { return Type == k_Type_HexCrc; } bool IsDir() const { return MY_LIN_S_ISDIR(Mode); } bool IsTrailer() const { return strcmp(Name, kName_TRAILER) == 0; } UInt64 GetDataPosition() const { return HeaderPos + HeaderSize; } }; enum EErrorType { k_ErrorType_OK, k_ErrorType_Corrupted, k_ErrorType_UnexpectedEnd, }; struct CInArchive { ISequentialInStream *Stream; UInt64 Processed; HRESULT Read(void *data, size_t *size); HRESULT GetNextItem(CItem &item, EErrorType &errorType); }; HRESULT CInArchive::Read(void *data, size_t *size) { HRESULT res = ReadStream(Stream, data, size); Processed += *size; return res; } static bool ReadHex(const Byte *p, UInt32 &resVal) { char sz[16]; memcpy(sz, p, 8); sz[8] = 0; const char *end; resVal = ConvertHexStringToUInt32(sz, &end); return (unsigned)(end - sz) == 8; } static bool ReadOct6(const Byte *p, UInt32 &resVal) { char sz[16]; memcpy(sz, p, 6); sz[6] = 0; const char *end; resVal = ConvertOctStringToUInt32(sz, &end); return (unsigned)(end - sz) == 6; } static bool ReadOct11(const Byte *p, UInt64 &resVal) { char sz[16]; memcpy(sz, p, 11); sz[11] = 0; const char *end; resVal = ConvertOctStringToUInt64(sz, &end); return (unsigned)(end - sz) == 11; } #define READ_HEX(y) { if (!ReadHex(p2, y)) return S_OK; p2 += 8; } #define READ_OCT_6(y) { if (!ReadOct6(p2, y)) return S_OK; p2 += 6; } #define READ_OCT_11(y) { if (!ReadOct11(p2, y)) return S_OK; p2 += 11; } static UInt32 GetAlignedSize(UInt32 size, UInt32 align) { while ((size & (align - 1)) != 0) size++; return size; } static UInt16 Get16(const Byte *p, bool be) { if (be) return GetBe16(p); return GetUi16(p); } static UInt32 Get32(const Byte *p, bool be) { return ((UInt32)Get16(p, be) << 16) + Get16(p + 2, be); } #define G16(offs, v) v = Get16(p + (offs), be) #define G32(offs, v) v = Get32(p + (offs), be) static const unsigned kNameSizeMax = 1 << 12; API_FUNC_static_IsArc IsArc_Cpio(const Byte *p, size_t size) { if (size < k_BinRecord_Size) return k_IsArc_Res_NEED_MORE; UInt32 nameSize; UInt32 numLinks; if (p[0] == '0') { if (p[1] != '7' || p[2] != '0' || p[3] != '7' || p[4] != '0') return k_IsArc_Res_NO; if (p[5] == '7') { if (size < k_OctRecord_Size) return k_IsArc_Res_NEED_MORE; for (int i = 6; i < k_OctRecord_Size; i++) { char c = p[i]; if (c < '0' || c > '7') return k_IsArc_Res_NO; } ReadOct6(p + 6 * 6, numLinks); ReadOct6(p + 8 * 6 + 11, nameSize); } else if (p[5] == '1' || p[5] == '2') { if (size < k_HexRecord_Size) return k_IsArc_Res_NEED_MORE; for (int i = 6; i < k_HexRecord_Size; i++) { char c = p[i]; if ((c < '0' || c > '9') && (c < 'A' || c > 'F') && (c < 'a' || c > 'f')) return k_IsArc_Res_NO; } ReadHex(p + 6 + 4 * 8, numLinks); ReadHex(p + 6 + 11 * 8, nameSize); } else return k_IsArc_Res_NO; } else { UInt32 rDevMinor; if (p[0] == kMagicBin0 && p[1] == kMagicBin1) { numLinks = GetUi16(p + 12); rDevMinor = GetUi16(p + 14); nameSize = GetUi16(p + 20); } else if (p[0] == kMagicBin1 && p[1] == kMagicBin0) { numLinks = GetBe16(p + 12); rDevMinor = GetBe16(p + 14); nameSize = GetBe16(p + 20); } else return k_IsArc_Res_NO; if (rDevMinor != 0) return k_IsArc_Res_NO; if (nameSize > (1 << 8)) return k_IsArc_Res_NO; } if (numLinks == 0 || numLinks >= (1 << 10)) return k_IsArc_Res_NO; if (nameSize == 0 || nameSize > kNameSizeMax) return k_IsArc_Res_NO; return k_IsArc_Res_YES; } } #define READ_STREAM(_dest_, _size_) \ { size_t processed = (_size_); RINOK(Read(_dest_, &processed)); \ if (processed != (_size_)) { errorType = k_ErrorType_UnexpectedEnd; return S_OK; } } HRESULT CInArchive::GetNextItem(CItem &item, EErrorType &errorType) { errorType = k_ErrorType_Corrupted; Byte p[k_RecordSize_Max]; READ_STREAM(p, k_BinRecord_Size) UInt32 nameSize; if (p[0] != '0') { bool be; if (p[0] == kMagicBin0 && p[1] == kMagicBin1) { be = false; item.Type = k_Type_BinLe; } else if (p[0] == kMagicBin1 && p[1] == kMagicBin0) { be = true; item.Type = k_Type_BinBe; } else return S_FALSE; item.Align = 2; item.DevMajor = 0; item.RDevMajor =0; item.ChkSum = 0; G16(2, item.DevMinor); G16(4, item.inode); G16(6, item.Mode); G16(8, item.UID); G16(10, item.GID); G16(12, item.NumLinks); G16(14, item.RDevMinor); G32(16, item.MTime); G16(20, nameSize); G32(22, item.Size); /* if (item.RDevMinor != 0) return S_FALSE; */ item.HeaderSize = GetAlignedSize(nameSize + k_BinRecord_Size, item.Align); nameSize = item.HeaderSize - k_BinRecord_Size; } else { if (p[1] != '7' || p[2] != '0' || p[3] != '7' || p[4] != '0') return S_FALSE; if (p[5] == kMagicOct) { item.Type = k_Type_Oct; READ_STREAM(p + k_BinRecord_Size, k_OctRecord_Size - k_BinRecord_Size) item.Align = 1; item.DevMajor = 0; item.RDevMajor = 0; const Byte *p2 = p + 6; READ_OCT_6(item.DevMinor); READ_OCT_6(item.inode); READ_OCT_6(item.Mode); READ_OCT_6(item.UID); READ_OCT_6(item.GID); READ_OCT_6(item.NumLinks); READ_OCT_6(item.RDevMinor); { UInt64 mTime64; READ_OCT_11(mTime64); item.MTime = 0; if (mTime64 < (UInt32)(Int32)-1) item.MTime = (UInt32)mTime64; } READ_OCT_6(nameSize); READ_OCT_11(item.Size); // ????? item.HeaderSize = GetAlignedSize(nameSize + k_OctRecord_Size, item.Align); nameSize = item.HeaderSize - k_OctRecord_Size; } else { if (p[5] == kMagicHex) item.Type = k_Type_Hex; else if (p[5] == kMagicHexCrc) item.Type = k_Type_HexCrc; else return S_FALSE; READ_STREAM(p + k_BinRecord_Size, k_HexRecord_Size - k_BinRecord_Size) item.Align = 4; const Byte *p2 = p + 6; READ_HEX(item.inode); READ_HEX(item.Mode); READ_HEX(item.UID); READ_HEX(item.GID); READ_HEX(item.NumLinks); READ_HEX(item.MTime); { UInt32 size32; READ_HEX(size32); item.Size = size32; } READ_HEX(item.DevMajor); READ_HEX(item.DevMinor); READ_HEX(item.RDevMajor); READ_HEX(item.RDevMinor); READ_HEX(nameSize); READ_HEX(item.ChkSum); if (nameSize >= kNameSizeMax) return S_OK; item.HeaderSize = GetAlignedSize(nameSize + k_HexRecord_Size, item.Align); nameSize = item.HeaderSize - k_HexRecord_Size; } } if (nameSize > kNameSizeMax) return S_FALSE; if (nameSize == 0 || nameSize >= kNameSizeMax) return S_OK; char *s = item.Name.GetBuf(nameSize); size_t processedSize = nameSize; RINOK(Read(s, &processedSize)); item.Name.ReleaseBuf_CalcLen(nameSize); if (processedSize != nameSize) { errorType = k_ErrorType_UnexpectedEnd; return S_OK; } errorType = k_ErrorType_OK; return S_OK; } class CHandler: public IInArchive, public IInArchiveGetStream, public CMyUnknownImp { CObjectVector _items; CMyComPtr _stream; UInt64 _phySize; EType _Type; EErrorType _error; bool _isArc; public: MY_UNKNOWN_IMP2(IInArchive, IInArchiveGetStream) INTERFACE_IInArchive(;) STDMETHOD(GetStream)(UInt32 index, ISequentialInStream **stream); }; static const Byte kArcProps[] = { kpidSubType }; static const Byte kProps[] = { kpidPath, kpidIsDir, kpidSize, kpidMTime, kpidPosixAttrib, kpidLinks }; IMP_IInArchive_Props IMP_IInArchive_ArcProps STDMETHODIMP CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value) { COM_TRY_BEGIN NCOM::CPropVariant prop; switch (propID) { case kpidSubType: prop = k_Types[(unsigned)_Type]; break; case kpidPhySize: prop = _phySize; break; case kpidErrorFlags: { UInt32 v = 0; if (!_isArc) v |= kpv_ErrorFlags_IsNotArc; switch (_error) { case k_ErrorType_UnexpectedEnd: v |= kpv_ErrorFlags_UnexpectedEnd; break; case k_ErrorType_Corrupted: v |= kpv_ErrorFlags_HeadersError; break; } prop = v; break; } } prop.Detach(value); return S_OK; COM_TRY_END } STDMETHODIMP CHandler::Open(IInStream *stream, const UInt64 *, IArchiveOpenCallback *callback) { COM_TRY_BEGIN { Close(); UInt64 endPos = 0; RINOK(stream->Seek(0, STREAM_SEEK_END, &endPos)); RINOK(stream->Seek(0, STREAM_SEEK_SET, NULL)); if (callback) { RINOK(callback->SetTotal(NULL, &endPos)); } _items.Clear(); CInArchive arc; arc.Stream = stream; arc.Processed = 0; for (;;) { CItem item; item.HeaderPos = arc.Processed; HRESULT result = arc.GetNextItem(item, _error); if (result == S_FALSE) return S_FALSE; if (result != S_OK) return S_FALSE; if (_error != k_ErrorType_OK) { if (_error == k_ErrorType_Corrupted) arc.Processed = item.HeaderPos; break; } if (_items.IsEmpty()) _Type = item.Type; else if (_items.Back().Type != item.Type) { _error = k_ErrorType_Corrupted; arc.Processed = item.HeaderPos; break; } if (item.IsTrailer()) break; _items.Add(item); { // archive.SkipDataRecords(item.Size, item.Align); UInt64 dataSize = item.Size; UInt32 align = item.Align; while ((dataSize & (align - 1)) != 0) dataSize++; // _error = k_ErrorType_UnexpectedEnd; break; arc.Processed += dataSize; if (arc.Processed > endPos) { _error = k_ErrorType_UnexpectedEnd; break; } UInt64 newPostion; RINOK(stream->Seek(dataSize, STREAM_SEEK_CUR, &newPostion)); if (arc.Processed != newPostion) return E_FAIL; } if (callback && (_items.Size() & 0xFF) == 0) { UInt64 numFiles = _items.Size(); RINOK(callback->SetCompleted(&numFiles, &item.HeaderPos)); } } _phySize = arc.Processed; if (_error != k_ErrorType_OK) { if (_items.Size() == 0) return S_FALSE; if (_items.Size() == 1 && _items[0].IsBin()) { // probably it's false detected archive. So we return error return S_FALSE; } } else { // Read tailing zeros. // Most of cpio files use 512-bytes aligned zeros UInt64 pos = arc.Processed; const UInt32 kTailSize_MAX = 1 << 9; Byte buf[kTailSize_MAX]; UInt32 rem = (kTailSize_MAX - (UInt32)pos) & (kTailSize_MAX - 1); if (rem != 0) { rem++; // we need to see that it's end of file size_t processed = rem; RINOK(ReadStream(stream, buf, &processed)); if (processed < rem) { unsigned i; for (i = 0; i < processed && buf[i] == 0; i++); if (i == processed) _phySize += processed; } } } _isArc = true; _stream = stream; } return S_OK; COM_TRY_END } STDMETHODIMP CHandler::Close() { _items.Clear(); _stream.Release(); _phySize = 0; _Type = k_Type_BinLe; _isArc = false; _error = k_ErrorType_OK; return S_OK; } STDMETHODIMP CHandler::GetNumberOfItems(UInt32 *numItems) { *numItems = _items.Size(); return S_OK; } STDMETHODIMP CHandler::GetProperty(UInt32 index, PROPID propID, PROPVARIANT *value) { COM_TRY_BEGIN NCOM::CPropVariant prop; const CItem &item = _items[index]; switch (propID) { case kpidPath: { UString res; bool needConvert = true; #ifdef _WIN32 if (ConvertUTF8ToUnicode(item.Name, res)) needConvert = false; #endif if (needConvert) res = MultiByteToUnicodeString(item.Name, CP_OEMCP); prop = NItemName::GetOSName(res); break; } case kpidIsDir: prop = item.IsDir(); break; case kpidSize: case kpidPackSize: prop = (UInt64)item.Size; break; case kpidMTime: { if (item.MTime != 0) { FILETIME utc; NTime::UnixTimeToFileTime(item.MTime, utc); prop = utc; } break; } case kpidPosixAttrib: prop = item.Mode; break; case kpidLinks: prop = item.NumLinks; break; /* case kpidinode: prop = item.inode; break; case kpidiChkSum: prop = item.ChkSum; break; */ } prop.Detach(value); return S_OK; COM_TRY_END } class COutStreamWithSum: public ISequentialOutStream, public CMyUnknownImp { CMyComPtr _stream; UInt64 _size; UInt32 _crc; bool _calculate; public: MY_UNKNOWN_IMP STDMETHOD(Write)(const void *data, UInt32 size, UInt32 *processedSize); void SetStream(ISequentialOutStream *stream) { _stream = stream; } void ReleaseStream() { _stream.Release(); } void Init(bool calculate = true) { _size = 0; _calculate = calculate; _crc = 0; } void EnableCalc(bool calculate) { _calculate = calculate; } void InitCRC() { _crc = 0; } UInt64 GetSize() const { return _size; } UInt32 GetCRC() const { return _crc; } }; STDMETHODIMP COutStreamWithSum::Write(const void *data, UInt32 size, UInt32 *processedSize) { HRESULT result = S_OK; if (_stream) result = _stream->Write(data, size, &size); if (_calculate) { UInt32 crc = 0; for (UInt32 i = 0; i < size; i++) crc += (UInt32)(((const Byte *)data)[i]); _crc += crc; } if (processedSize) *processedSize = size; return result; } 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++) totalSize += _items[allFilesMode ? i : indices[i]].Size; extractCallback->SetTotal(totalSize); UInt64 currentTotalSize = 0; NCompress::CCopyCoder *copyCoderSpec = new NCompress::CCopyCoder(); CMyComPtr copyCoder = copyCoderSpec; CLocalProgress *lps = new CLocalProgress; CMyComPtr progress = lps; lps->Init(extractCallback, false); CLimitedSequentialInStream *streamSpec = new CLimitedSequentialInStream; CMyComPtr inStream(streamSpec); streamSpec->SetStream(_stream); COutStreamWithSum *outStreamSumSpec = new COutStreamWithSum; CMyComPtr outStreamSum(outStreamSumSpec); for (i = 0; i < numItems; i++) { lps->InSize = lps->OutSize = currentTotalSize; RINOK(lps->SetCur()); CMyComPtr outStream; Int32 askMode = testMode ? NExtract::NAskMode::kTest : NExtract::NAskMode::kExtract; Int32 index = allFilesMode ? i : indices[i]; const CItem &item = _items[index]; RINOK(extractCallback->GetStream(index, &outStream, askMode)); currentTotalSize += item.Size; if (item.IsDir()) { RINOK(extractCallback->PrepareOperation(askMode)); RINOK(extractCallback->SetOperationResult(NExtract::NOperationResult::kOK)); continue; } if (!testMode && !outStream) continue; outStreamSumSpec->Init(item.IsCrcFormat()); outStreamSumSpec->SetStream(outStream); outStream.Release(); RINOK(extractCallback->PrepareOperation(askMode)); RINOK(_stream->Seek(item.GetDataPosition(), STREAM_SEEK_SET, NULL)); streamSpec->Init(item.Size); RINOK(copyCoder->Code(inStream, outStreamSum, NULL, NULL, progress)); outStreamSumSpec->ReleaseStream(); Int32 res = NExtract::NOperationResult::kDataError; if (copyCoderSpec->TotalSize == item.Size) { res = NExtract::NOperationResult::kOK; if (item.IsCrcFormat() && item.ChkSum != outStreamSumSpec->GetCRC()) res = NExtract::NOperationResult::kCRCError; } 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]; return CreateLimitedInStream(_stream, item.GetDataPosition(), item.Size, stream); COM_TRY_END } static const Byte k_Signature[] = { 5, '0', '7', '0', '7', '0', 2, kMagicBin0, kMagicBin1, 2, kMagicBin1, kMagicBin0 }; REGISTER_ARC_I( "Cpio", "cpio", 0, 0xED, k_Signature, 0, NArcInfoFlags::kMultiSignature, IsArc_Cpio) }}