chore: initial commit (extracted from Launchers monorepo)

Plugin: ns7zip v2.0.0
Architectures: x86-ansi, x86-unicode, amd64-unicode
License: LGPL-2.1-or-later
This commit is contained in:
Simone
2026-04-29 14:07:51 +02:00
commit d074cc7c07
3848 changed files with 1076682 additions and 0 deletions
@@ -0,0 +1,11 @@
// StdAfx.h
#ifndef ZIP7_INC_STDAFX_H
#define ZIP7_INC_STDAFX_H
#if defined(_MSC_VER) && _MSC_VER >= 1800
#pragma warning(disable : 4464) // relative include path contains '..'
#endif
#include "../../../Common/Common.h"
#endif
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,57 @@
// TarHandler.h
#ifndef ZIP7_INC_TAR_HANDLER_H
#define ZIP7_INC_TAR_HANDLER_H
#include "../../../Common/MyCom.h"
#include "../../Compress/CopyCoder.h"
#include "../Common/HandlerOut.h"
#include "TarIn.h"
namespace NArchive {
namespace NTar {
Z7_CLASS_IMP_CHandler_IInArchive_4(
IArchiveOpenSeq
, IInArchiveGetStream
, ISetProperties
, IOutArchive
)
public:
CObjectVector<CItemEx> _items;
CMyComPtr<IInStream> _stream;
CMyComPtr<ISequentialInStream> _seqStream;
private:
bool _isArc;
bool _posixMode_WasForced;
bool _posixMode;
bool _forceCodePage;
UInt32 _specifiedCodePage;
UInt32 _curCodePage;
UInt32 _openCodePage;
// CTimeOptions TimeOptions;
CHandlerTimeOptions _handlerTimeOptions;
CEncodingCharacts _encodingCharacts;
UInt32 _curIndex;
bool _latestIsRead;
CItemEx _latestItem;
CArchive _arc;
CMyComPtr2_Create<ICompressCoder, NCompress::CCopyCoder> copyCoder;
HRESULT Open2(IInStream *stream, IArchiveOpenCallback *callback);
HRESULT SkipTo(UInt32 index);
void TarStringToUnicode(const AString &s, NWindows::NCOM::CPropVariant &prop, bool toOs = false) const;
public:
void Init();
CHandler();
};
}}
#endif
@@ -0,0 +1,332 @@
// TarHandlerOut.cpp
#include "StdAfx.h"
// #include <stdio.h>
#include "../../../Common/ComTry.h"
#include "../../../Common/MyLinux.h"
#include "../../../Common/StringConvert.h"
#include "../../../Windows/TimeUtils.h"
#include "../Common/ItemNameUtils.h"
#include "TarHandler.h"
#include "TarUpdate.h"
using namespace NWindows;
namespace NArchive {
namespace NTar {
Z7_COM7F_IMF(CHandler::GetFileTimeType(UInt32 *type))
{
UInt32 t = NFileTimeType::kUnix;
const UInt32 prec = _handlerTimeOptions.Prec;
if (prec != (UInt32)(Int32)-1)
{
t = NFileTimeType::kWindows;
if (prec == k_PropVar_TimePrec_0 ||
prec == k_PropVar_TimePrec_100ns)
t = NFileTimeType::kWindows;
else if (prec == k_PropVar_TimePrec_HighPrec)
t = k_PropVar_TimePrec_1ns;
else if (prec >= k_PropVar_TimePrec_Base)
t = prec;
}
// 7-Zip before 22.00 fails, if unknown typeType.
*type = t;
return S_OK;
}
void Get_AString_From_UString(const UString &s, AString &res,
UINT codePage, unsigned utfFlags)
{
if (codePage == CP_UTF8)
ConvertUnicodeToUTF8_Flags(s, res, utfFlags);
else
UnicodeStringToMultiByte2(res, s, codePage);
}
HRESULT GetPropString(IArchiveUpdateCallback *callback, UInt32 index, PROPID propId, AString &res,
UINT codePage, unsigned utfFlags, bool convertSlash)
{
NCOM::CPropVariant prop;
RINOK(callback->GetProperty(index, propId, &prop))
if (prop.vt == VT_BSTR)
{
UString s = prop.bstrVal;
if (convertSlash)
NItemName::ReplaceSlashes_OsToUnix(s);
Get_AString_From_UString(s, res, codePage, utfFlags);
}
else if (prop.vt != VT_EMPTY)
return E_INVALIDARG;
return S_OK;
}
// sort old files with original order.
static int CompareUpdateItems(void *const *p1, void *const *p2, void *)
{
const CUpdateItem &u1 = *(*((const CUpdateItem *const *)p1));
const CUpdateItem &u2 = *(*((const CUpdateItem *const *)p2));
if (!u1.NewProps)
{
if (u2.NewProps)
return -1;
return MyCompare(u1.IndexInArc, u2.IndexInArc);
}
if (!u2.NewProps)
return 1;
return MyCompare(u1.IndexInClient, u2.IndexInClient);
}
static HRESULT GetTime(UInt32 i, UInt32 pid, IArchiveUpdateCallback *callback,
CPaxTime &pt)
{
pt.Clear();
NCOM::CPropVariant prop;
RINOK(callback->GetProperty(i, pid, &prop))
return Prop_To_PaxTime(prop, pt);
}
/*
static HRESULT GetDevice(IArchiveUpdateCallback *callback, UInt32 i,
UInt32 &majo, UInt32 &mino, bool &majo_defined, bool &mino_defined)
{
NWindows::NCOM::CPropVariant prop;
RINOK(callback->GetProperty(i, kpidDevice, &prop));
if (prop.vt == VT_EMPTY)
return S_OK;
if (prop.vt != VT_UI8)
return E_INVALIDARG;
{
const UInt64 v = prop.uhVal.QuadPart;
majo = MY_dev_major(v);
mino = MY_dev_minor(v);
majo_defined = true;
mino_defined = true;
}
return S_OK;
}
*/
static HRESULT GetDevice(IArchiveUpdateCallback *callback, UInt32 i,
UInt32 pid, UInt32 &id, bool &defined)
{
defined = false;
NWindows::NCOM::CPropVariant prop;
RINOK(callback->GetProperty(i, pid, &prop))
if (prop.vt == VT_EMPTY)
return S_OK;
if (prop.vt == VT_UI4)
{
id = prop.ulVal;
defined = true;
return S_OK;
}
return E_INVALIDARG;
}
static HRESULT GetUser(IArchiveUpdateCallback *callback, UInt32 i,
UInt32 pidName, UInt32 pidId, AString &name, UInt32 &id,
UINT codePage, unsigned utfFlags)
{
// printf("\ncallback->GetProperty(i, pidId, &prop))\n");
bool isSet = false;
{
NWindows::NCOM::CPropVariant prop;
RINOK(callback->GetProperty(i, pidId, &prop))
if (prop.vt == VT_UI4)
{
isSet = true;
id = prop.ulVal;
// printf("\ncallback->GetProperty(i, pidId, &prop)); = %d \n", (unsigned)id);
name.Empty();
}
else if (prop.vt != VT_EMPTY)
return E_INVALIDARG;
}
{
NWindows::NCOM::CPropVariant prop;
RINOK(callback->GetProperty(i, pidName, &prop))
if (prop.vt == VT_BSTR)
{
const UString s = prop.bstrVal;
Get_AString_From_UString(s, name, codePage, utfFlags);
if (!isSet)
id = 0;
}
else if (prop.vt == VT_UI4)
{
id = prop.ulVal;
name.Empty();
}
else if (prop.vt != VT_EMPTY)
return E_INVALIDARG;
}
return S_OK;
}
Z7_COM7F_IMF(CHandler::UpdateItems(ISequentialOutStream *outStream, UInt32 numItems,
IArchiveUpdateCallback *callback))
{
COM_TRY_BEGIN
if ((_stream && (_arc._error != k_ErrorType_OK || _arc._is_Warning
/* || _isSparse */
)) || _seqStream)
return E_NOTIMPL;
CObjectVector<CUpdateItem> updateItems;
const UINT codePage = (_forceCodePage ? _specifiedCodePage : _openCodePage);
const unsigned utfFlags = g_Unicode_To_UTF8_Flags;
/*
// for debug only:
unsigned utfFlags = 0;
utfFlags |= Z7_UTF_FLAG_TO_UTF8_EXTRACT_BMP_ESCAPE;
utfFlags |= Z7_UTF_FLAG_TO_UTF8_SURROGATE_ERROR;
*/
for (UInt32 i = 0; i < numItems; i++)
{
CUpdateItem ui;
Int32 newData;
Int32 newProps;
UInt32 indexInArc;
if (!callback)
return E_FAIL;
RINOK(callback->GetUpdateItemInfo(i, &newData, &newProps, &indexInArc))
ui.NewProps = IntToBool(newProps);
ui.NewData = IntToBool(newData);
ui.IndexInArc = (int)indexInArc;
ui.IndexInClient = i;
if (IntToBool(newProps))
{
{
NCOM::CPropVariant prop;
RINOK(callback->GetProperty(i, kpidIsDir, &prop))
if (prop.vt == VT_EMPTY)
ui.IsDir = false;
else if (prop.vt != VT_BOOL)
return E_INVALIDARG;
else
ui.IsDir = (prop.boolVal != VARIANT_FALSE);
}
{
NCOM::CPropVariant prop;
RINOK(callback->GetProperty(i, kpidPosixAttrib, &prop))
if (prop.vt == VT_EMPTY)
ui.Mode =
MY_LIN_S_IRWXO
| MY_LIN_S_IRWXG
| MY_LIN_S_IRWXU
| (ui.IsDir ? MY_LIN_S_IFDIR : MY_LIN_S_IFREG);
else if (prop.vt != VT_UI4)
return E_INVALIDARG;
else
ui.Mode = prop.ulVal;
// 21.07 : we clear high file type bits as GNU TAR.
// we will clear it later
// ui.Mode &= ~(UInt32)MY_LIN_S_IFMT;
}
if (_handlerTimeOptions.Write_MTime.Val)
RINOK(GetTime(i, kpidMTime, callback, ui.PaxTimes.MTime))
if (_handlerTimeOptions.Write_ATime.Val)
RINOK(GetTime(i, kpidATime, callback, ui.PaxTimes.ATime))
if (_handlerTimeOptions.Write_CTime.Val)
RINOK(GetTime(i, kpidCTime, callback, ui.PaxTimes.CTime))
RINOK(GetPropString(callback, i, kpidPath, ui.Name, codePage, utfFlags, true))
if (ui.IsDir && !ui.Name.IsEmpty() && ui.Name.Back() != '/')
ui.Name.Add_Slash();
// ui.Name.Add_Slash(); // for debug
if (_posixMode)
{
RINOK(GetDevice(callback, i, kpidDeviceMajor, ui.DeviceMajor, ui.DeviceMajor_Defined))
RINOK(GetDevice(callback, i, kpidDeviceMinor, ui.DeviceMinor, ui.DeviceMinor_Defined))
}
RINOK(GetUser(callback, i, kpidUser, kpidUserId, ui.User, ui.UID, codePage, utfFlags))
RINOK(GetUser(callback, i, kpidGroup, kpidGroupId, ui.Group, ui.GID, codePage, utfFlags))
}
if (IntToBool(newData))
{
NCOM::CPropVariant prop;
RINOK(callback->GetProperty(i, kpidSize, &prop))
if (prop.vt != VT_UI8)
return E_INVALIDARG;
ui.Size = prop.uhVal.QuadPart;
/*
// now we support GNU extension for big files
if (ui.Size >= ((UInt64)1 << 33))
return E_INVALIDARG;
*/
}
updateItems.Add(ui);
}
if (_arc._are_Pax_Items)
{
// we restore original order of files, if there are pax items
updateItems.Sort(CompareUpdateItems, NULL);
}
CUpdateOptions options;
options.CodePage = codePage;
options.UtfFlags = utfFlags;
options.PosixMode = _posixMode;
options.Write_MTime = _handlerTimeOptions.Write_MTime;
options.Write_ATime = _handlerTimeOptions.Write_ATime;
options.Write_CTime = _handlerTimeOptions.Write_CTime;
// options.TimeOptions = TimeOptions;
const UInt32 prec = _handlerTimeOptions.Prec;
if (prec != (UInt32)(Int32)-1)
{
unsigned numDigits = 0;
if (prec == 0)
numDigits = 7;
else if (prec == k_PropVar_TimePrec_HighPrec
|| prec >= k_PropVar_TimePrec_1ns)
numDigits = 9;
else if (prec >= k_PropVar_TimePrec_Base)
numDigits = prec - k_PropVar_TimePrec_Base;
options.TimeOptions.NumDigitsMax = numDigits;
// options.TimeOptions.RemoveZeroMode =
// k_PaxTimeMode_DontRemoveZero; // pure for debug
// k_PaxTimeMode_RemoveZero_if_PureSecondOnly; // optimized code
// k_PaxTimeMode_RemoveZero_Always; // original pax code
}
return UpdateArchive(_stream, outStream, _items, updateItems,
options, callback);
COM_TRY_END
}
}}
@@ -0,0 +1,99 @@
// Archive/TarHeader.cpp
#include "StdAfx.h"
#include "TarHeader.h"
namespace NArchive {
namespace NTar {
namespace NFileHeader {
const char * const kLongLink = "././@LongLink";
const char * const kLongLink2 = "@LongLink";
// The magic field is filled with this if uname and gname are valid.
namespace NMagic
{
// const char * const kUsTar = "ustar"; // 5 chars
// const char * const kGNUTar = "GNUtar "; // 7 chars and a null
// const char * const kEmpty = "\0\0\0\0\0\0\0\0";
// 7-Zip used kUsTar_00 before 21.07:
const char k_Posix_ustar_00[8] = { 'u', 's', 't', 'a', 'r', 0, '0', '0' } ;
// GNU TAR uses such header:
const char k_GNU_ustar[8] = { 'u', 's', 't', 'a', 'r', ' ', ' ', 0 } ;
}
/*
pre-POSIX.1-1988 (i.e. v7) tar header:
-----
Link indicator:
'0' or 0 : Normal file
'1' : Hard link
'2' : Symbolic link
Some pre-POSIX.1-1988 tar implementations indicated a directory by having
a trailing slash (/) in the name.
Numeric values : octal with leading zeroes.
For historical reasons, a final NUL or space character should also be used.
Thus only 11 octal digits can be stored from 12 bytes field.
2001 star : introduced a base-256 coding that is indicated by
setting the high-order bit of the leftmost byte of a numeric field.
GNU-tar and BSD-tar followed this idea.
versions of tar from before the first POSIX standard from 1988
pad the values with spaces instead of zeroes.
UStar
-----
UStar (Unix Standard TAR) : POSIX IEEE P1003.1 : 1988.
257 signature: "ustar", 0, "00"
265 32 Owner user name
297 32 Owner group name
329 8 Device major number
337 8 Device minor number
345 155 Filename prefix
POSIX.1-2001/pax
----
format is known as extended tar format or pax format
vendor-tagged vendor-specific enhancements.
tags Defined by the POSIX standard:
atime, mtime, path, linkpath, uname, gname, size, uid, gid, ...
PAX EXTENSION
-----------
Hard links
A further difference from the ustar header block is that data blocks
for files of typeflag 1 (hard link) may be included,
which means that the size field may be greater than zero.
Archives created by pax -o linkdata shall include these data
blocks with the hard links.
*
compatiblity
------------
7-Zip 16.03 supports "PaxHeader/"
7-Zip 20.01 supports "PaxHeaders.X/" with optional "./"
7-Zip 21.02 supports "@PaxHeader" with optional "./" "./"
GNU tar --format=posix uses "PaxHeaders/" in folder of file
GNU TAR format
==============
v7 - Unix V7
oldgnu - GNU tar <=1.12 : writes zero in last character in name
gnu - GNU tar 1.13 : doesn't write zero in last character in name
as 7-zip 21.07
ustar - POSIX.1-1988
posix (pax) - POSIX.1-2001
gnu tar:
if (S_ISCHR (st->stat.st_mode) || S_ISBLK (st->stat.st_mode)) {
major_t devmajor = major (st->stat.st_rdev);
minor_t devminor = minor (st->stat.st_rdev); }
*/
}}}
@@ -0,0 +1,90 @@
// Archive/TarHeader.h
#ifndef ZIP7_INC_ARCHIVE_TAR_HEADER_H
#define ZIP7_INC_ARCHIVE_TAR_HEADER_H
#include "../../../Common/MyTypes.h"
namespace NArchive {
namespace NTar {
namespace NFileHeader
{
const unsigned kRecordSize = 512;
const unsigned kNameSize = 100;
const unsigned kUserNameSize = 32;
const unsigned kGroupNameSize = 32;
const unsigned kPrefixSize = 155;
const unsigned kUstarMagic_Offset = 257;
/*
struct CHeader
{
char Name[kNameSize];
char Mode[8];
char UID[8];
char GID[8];
char Size[12];
char ModificationTime[12];
char CheckSum[8];
char LinkFlag;
char LinkName[kNameSize];
char Magic[8];
char UserName[kUserNameSize];
char GroupName[kGroupNameSize];
char DeviceMajor[8];
char DeviceMinor[8];
char Prefix[155];
};
union CRecord
{
CHeader Header;
Byte Padding[kRecordSize];
};
*/
namespace NLinkFlag
{
const char kOldNormal = 0; // Normal disk file, Unix compatible
const char kNormal = '0'; // Normal disk file
const char kHardLink = '1'; // Link to previously dumped file
const char kSymLink = '2'; // Symbolic link
const char kCharacter = '3'; // Character special file
const char kBlock = '4'; // Block special file
const char kDirectory = '5'; // Directory
const char kFIFO = '6'; // FIFO special file
const char kContiguous = '7'; // Contiguous file
const char kGnu_LongLink = 'K';
const char kGnu_LongName = 'L';
const char kSparse = 'S';
const char kLabel = 'V';
const char kPax = 'x'; // Extended header with meta data for the next file in the archive (POSIX.1-2001)
const char kPax_2 = 'X';
const char kGlobal = 'g'; // Global extended header with meta data (POSIX.1-2001)
const char kDumpDir = 'D'; /* GNUTYPE_DUMPDIR.
data: list of files created by the --incremental (-G) option
Each file name is preceded by either
- 'Y' (file should be in this archive)
- 'N' (file is a directory, or is not stored in the archive.)
Each file name is terminated by a null + an additional null after
the last file name. */
// 'A'-'Z' Vendor specific extensions (POSIX.1-1988)
}
extern const char * const kLongLink; // = "././@LongLink";
extern const char * const kLongLink2; // = "@LongLink";
namespace NMagic
{
// extern const char * const kUsTar; // = "ustar"; // 5 chars
// extern const char * const kGNUTar; // = "GNUtar "; // 7 chars and a null
// extern const char * const kEmpty; // = "\0\0\0\0\0\0\0\0"
extern const char k_Posix_ustar_00[8];
extern const char k_GNU_ustar[8];
}
}
}}
#endif
File diff suppressed because it is too large Load Diff
+151
View File
@@ -0,0 +1,151 @@
// TarIn.h
#ifndef ZIP7_INC_ARCHIVE_TAR_IN_H
#define ZIP7_INC_ARCHIVE_TAR_IN_H
#include "../IArchive.h"
#include "TarItem.h"
namespace NArchive {
namespace NTar {
enum EErrorType
{
k_ErrorType_OK,
k_ErrorType_Corrupted,
k_ErrorType_UnexpectedEnd
// , k_ErrorType_Warning
};
struct CTempBuffer
{
CByteBuffer Buffer;
size_t StringSize; // num characters before zero Byte (StringSize <= item.PackSize)
bool IsNonZeroTail;
bool StringSize_IsConfirmed;
void CopyToString(AString &s)
{
s.Empty();
if (StringSize != 0)
s.SetFrom((const char *)(const void *)(const Byte *)Buffer, (unsigned)StringSize);
}
void Init()
{
StringSize = 0;
IsNonZeroTail = false;
StringSize_IsConfirmed = false;
}
};
class CArchive
{
public:
bool _phySize_Defined;
bool _is_Warning;
bool PaxGlobal_Defined;
bool _is_PaxGlobal_Error;
bool _are_Pax_Items;
bool _are_Gnu;
bool _are_Posix;
bool _are_Pax;
bool _are_mtime;
bool _are_atime;
bool _are_ctime;
bool _are_pax_path;
bool _are_pax_link;
bool _are_LongName;
bool _are_LongLink;
bool _pathPrefix_WasUsed;
bool _are_SCHILY_fflags;
// bool _isSparse;
// temp internal vars for ReadItem():
bool filled;
private:
EErrorType error;
public:
UInt64 _phySize;
UInt64 _headersSize;
EErrorType _error;
ISequentialInStream *SeqStream;
IInStream *InStream;
IArchiveOpenCallback *OpenCallback;
UInt64 NumFiles;
UInt64 NumFiles_Prev;
UInt64 Pos_Prev;
// UInt64 NumRecords;
// UInt64 NumRecords_Prev;
CPaxExtra PaxGlobal;
void Clear()
{
SeqStream = NULL;
InStream = NULL;
OpenCallback = NULL;
NumFiles = 0;
NumFiles_Prev = 0;
Pos_Prev = 0;
// NumRecords = 0;
// NumRecords_Prev = 0;
PaxGlobal.Clear();
PaxGlobal_Defined = false;
_is_PaxGlobal_Error = false;
_are_Pax_Items = false; // if there are final paxItems
_are_Gnu = false;
_are_Posix = false;
_are_Pax = false;
_are_mtime = false;
_are_atime = false;
_are_ctime = false;
_are_pax_path = false;
_are_pax_link = false;
_are_LongName = false;
_are_LongLink = false;
_pathPrefix_WasUsed = false;
_are_SCHILY_fflags = false;
// _isSparse = false;
_is_Warning = false;
_error = k_ErrorType_OK;
_phySize_Defined = false;
_phySize = 0;
_headersSize = 0;
}
private:
CTempBuffer NameBuf;
CTempBuffer LinkBuf;
CTempBuffer PaxBuf;
CTempBuffer PaxBuf_global;
CByteBuffer Buffer;
HRESULT ReadDataToBuffer(const CItemEx &item, CTempBuffer &tb, size_t stringLimit);
HRESULT Progress(const CItemEx &item, UInt64 posOffset);
HRESULT GetNextItemReal(CItemEx &item);
HRESULT ReadItem2(CItemEx &itemInfo);
public:
CArchive()
{
// we will call Clear() in CHandler::Close().
// Clear(); // it's not required here
}
HRESULT ReadItem(CItemEx &itemInfo);
};
API_FUNC_IsArc IsArc_Tar(const Byte *p, size_t size);
}}
#endif
@@ -0,0 +1,364 @@
// TarItem.h
#ifndef ZIP7_INC_ARCHIVE_TAR_ITEM_H
#define ZIP7_INC_ARCHIVE_TAR_ITEM_H
#include "../../../Common/MyLinux.h"
#include "../../../Common/UTFConvert.h"
#include "TarHeader.h"
namespace NArchive {
namespace NTar {
struct CSparseBlock
{
UInt64 Offset;
UInt64 Size;
};
enum EPaxTimeRemoveZeroMode
{
k_PaxTimeMode_DontRemoveZero,
k_PaxTimeMode_RemoveZero_if_PureSecondOnly,
k_PaxTimeMode_RemoveZero_Always
};
struct CTimeOptions
{
EPaxTimeRemoveZeroMode RemoveZeroMode;
unsigned NumDigitsMax;
void Init()
{
RemoveZeroMode = k_PaxTimeMode_RemoveZero_if_PureSecondOnly;
NumDigitsMax = 0;
}
CTimeOptions() { Init(); }
};
struct CPaxTime
{
Int32 NumDigits; // -1 means undefined
UInt32 Ns; // it's smaller than 1G. Even if (Sec < 0), larger (Ns) value means newer files.
Int64 Sec; // can be negative
Int64 GetSec() const { return NumDigits != -1 ? Sec : 0; }
bool IsDefined() const { return NumDigits != -1; }
// bool IsDefined_And_nonZero() const { return NumDigits != -1 && (Sec != 0 || Ns != 0); }
void Clear()
{
NumDigits = -1;
Ns = 0;
Sec = 0;
}
CPaxTime() { Clear(); }
/*
void ReducePrecison(int numDigits)
{
// we don't use this->NumDigits here
if (numDigits > 0)
{
if (numDigits >= 9)
return;
UInt32 r = 1;
for (unsigned i = numDigits; i < 9; i++)
r *= 10;
Ns /= r;
Ns *= r;
return;
}
Ns = 0;
if (numDigits == 0)
return;
UInt32 r;
if (numDigits == -1) r = 60;
else if (numDigits == -2) r = 60 * 60;
else if (numDigits == -3) r = 60 * 60 * 24;
else return;
Sec /= r;
Sec *= r;
}
*/
};
struct CPaxTimes
{
CPaxTime MTime;
CPaxTime ATime;
CPaxTime CTime;
void Clear()
{
MTime.Clear();
ATime.Clear();
CTime.Clear();
}
/*
void ReducePrecison(int numDigits)
{
MTime.ReducePrecison(numDigits);
CTime.ReducePrecison(numDigits);
ATime.ReducePrecison(numDigits);
}
*/
};
struct CItem
{
UInt64 PackSize;
UInt64 Size;
Int64 MTime;
char LinkFlag;
bool DeviceMajor_Defined;
bool DeviceMinor_Defined;
UInt32 Mode;
UInt32 UID;
UInt32 GID;
UInt32 DeviceMajor;
UInt32 DeviceMinor;
AString Name;
AString LinkName;
AString User;
AString Group;
char Magic[8];
CPaxTimes PaxTimes;
CRecordVector<CSparseBlock> SparseBlocks;
void SetMagic_Posix(bool posixMode)
{
memcpy(Magic, posixMode ?
NFileHeader::NMagic::k_Posix_ustar_00 :
NFileHeader::NMagic::k_GNU_ustar,
8);
}
bool Is_SymLink() const { return LinkFlag == NFileHeader::NLinkFlag::kSymLink && (Size == 0); }
bool Is_HardLink() const { return LinkFlag == NFileHeader::NLinkFlag::kHardLink; }
bool Is_Sparse() const { return LinkFlag == NFileHeader::NLinkFlag::kSparse; }
UInt64 Get_UnpackSize() const { return Is_SymLink() ? LinkName.Len() : Size; }
bool Is_PaxExtendedHeader() const
{
switch (LinkFlag)
{
case NFileHeader::NLinkFlag::kPax:
case NFileHeader::NLinkFlag::kPax_2:
case NFileHeader::NLinkFlag::kGlobal:
return true;
default: break;
}
return false;
}
UInt32 Get_Combined_Mode() const
{
return (Mode & ~(UInt32)MY_LIN_S_IFMT) | Get_FileTypeMode_from_LinkFlag();
}
void Set_LinkFlag_for_File(UInt32 mode)
{
char lf = NFileHeader::NLinkFlag::kNormal;
if (MY_LIN_S_ISCHR(mode)) lf = NFileHeader::NLinkFlag::kCharacter;
else if (MY_LIN_S_ISBLK(mode)) lf = NFileHeader::NLinkFlag::kBlock;
else if (MY_LIN_S_ISFIFO(mode)) lf = NFileHeader::NLinkFlag::kFIFO;
// else if (MY_LIN_S_ISDIR(mode)) lf = NFileHeader::NLinkFlag::kDirectory;
// else if (MY_LIN_S_ISLNK(mode)) lf = NFileHeader::NLinkFlag::kSymLink;
LinkFlag = lf;
}
UInt32 Get_FileTypeMode_from_LinkFlag() const
{
switch (LinkFlag)
{
/*
case NFileHeader::NLinkFlag::kDirectory:
case NFileHeader::NLinkFlag::kDumpDir:
return MY_LIN_S_IFDIR;
*/
case NFileHeader::NLinkFlag::kSymLink: return MY_LIN_S_IFLNK;
case NFileHeader::NLinkFlag::kBlock: return MY_LIN_S_IFBLK;
case NFileHeader::NLinkFlag::kCharacter: return MY_LIN_S_IFCHR;
case NFileHeader::NLinkFlag::kFIFO: return MY_LIN_S_IFIFO;
// case return MY_LIN_S_IFSOCK;
default: break;
}
if (IsDir())
return MY_LIN_S_IFDIR;
return MY_LIN_S_IFREG;
}
bool IsDir() const
{
switch (LinkFlag)
{
case NFileHeader::NLinkFlag::kDirectory:
case NFileHeader::NLinkFlag::kDumpDir:
return true;
case NFileHeader::NLinkFlag::kOldNormal:
case NFileHeader::NLinkFlag::kNormal:
case NFileHeader::NLinkFlag::kSymLink:
if (Name.IsEmpty())
return false;
// GNU TAR uses last character as directory marker
// we also do it
return Name.Back() == '/';
// return NItemName::HasTailSlash(Name, CP_OEMCP);
default: break;
}
return false;
}
bool IsMagic_ustar_5chars() const
{
for (unsigned i = 0; i < 5; i++)
if (Magic[i] != NFileHeader::NMagic::k_GNU_ustar[i])
return false;
return true;
}
bool IsMagic_Posix_ustar_00() const
{
for (unsigned i = 0; i < 8; i++)
if (Magic[i] != NFileHeader::NMagic::k_Posix_ustar_00[i])
return false;
return true;
}
bool IsMagic_GNU() const
{
for (unsigned i = 0; i < 8; i++)
if (Magic[i] != NFileHeader::NMagic::k_GNU_ustar[i])
return false;
return true;
}
UInt64 Get_PackSize_Aligned() const { return (PackSize + 0x1FF) & (~((UInt64)0x1FF)); }
bool IsThereWarning() const
{
// that Header Warning is possible if (Size != 0) for dir item
return (PackSize < Size) && (LinkFlag == NFileHeader::NLinkFlag::kDirectory);
}
};
struct CEncodingCharacts
{
bool IsAscii;
// bool Oem_Checked;
// bool Oem_Ok;
// bool Utf_Checked;
CUtf8Check UtfCheck;
void Clear()
{
IsAscii = true;
// Oem_Checked = false;
// Oem_Ok = false;
// Utf_Checked = false;
UtfCheck.Clear();
}
void Update(const CEncodingCharacts &ec)
{
if (!ec.IsAscii)
IsAscii = false;
// if (ec.Utf_Checked)
{
UtfCheck.Update(ec.UtfCheck);
// Utf_Checked = true;
}
}
CEncodingCharacts() { Clear(); }
void Check(const AString &s);
AString GetCharactsString() const;
};
struct CPaxExtra
{
AString RecordPath;
AString RawLines;
void Clear()
{
RecordPath.Empty();
RawLines.Empty();
}
void Print_To_String(AString &s) const
{
if (!RecordPath.IsEmpty())
{
s += RecordPath;
s.Add_LF();
}
if (!RawLines.IsEmpty())
s += RawLines;
}
};
struct CItemEx: public CItem
{
bool HeaderError;
bool IsSignedChecksum;
bool Prefix_WasUsed;
bool Pax_Error;
bool Pax_Overflow;
bool pax_path_WasUsed;
bool pax_link_WasUsed;
bool pax_size_WasUsed;
bool MTime_IsBin;
bool PackSize_IsBin;
bool Size_IsBin;
bool LongName_WasUsed;
bool LongName_WasUsed_2;
bool LongLink_WasUsed;
bool LongLink_WasUsed_2;
// bool Name_CouldBeReduced;
// bool LinkName_CouldBeReduced;
UInt64 HeaderPos;
UInt64 HeaderSize;
UInt64 Num_Pax_Records;
CPaxExtra PaxExtra;
AString SCHILY_fflags;
CEncodingCharacts EncodingCharacts;
UInt64 Get_DataPos() const { return HeaderPos + HeaderSize; }
// UInt64 GetFullSize() const { return HeaderSize + PackSize; }
UInt64 Get_FullSize_Aligned() const { return HeaderSize + Get_PackSize_Aligned(); }
};
}}
#endif
@@ -0,0 +1,644 @@
// TarOut.cpp
#include "StdAfx.h"
#include "../../../../C/7zCrc.h"
#include "../../../Common/IntToString.h"
#include "../../Common/StreamUtils.h"
#include "TarOut.h"
namespace NArchive {
namespace NTar {
using namespace NFileHeader;
// it's path prefix assigned by 7-Zip to show that file path was cut
#define K_PREFIX_PATH_CUT "@PathCut"
static const UInt32 k_7_oct_digits_Val_Max = ((UInt32)1 << (7 * 3)) - 1;
static void WriteOctal_8(char *s, UInt32 val)
{
const unsigned kNumDigits = 8 - 1;
if (val >= ((UInt32)1 << (kNumDigits * 3)))
{
val = 0;
// return false;
}
for (unsigned i = 0; i < kNumDigits; i++)
{
s[kNumDigits - 1 - i] = (char)('0' + (val & 7));
val >>= 3;
}
// return true;
}
static void WriteBin_64bit(char *s, UInt64 val)
{
for (unsigned i = 0; i < 8; i++, val <<= 8)
s[i] = (char)(val >> 56);
}
static void WriteOctal_12(char *s, UInt64 val)
{
const unsigned kNumDigits = 12 - 1;
if (val >= ((UInt64)1 << (kNumDigits * 3)))
{
// GNU extension;
s[0] = (char)(Byte)0x80;
s[1] = s[2] = s[3] = 0;
WriteBin_64bit(s + 4, val);
return;
}
for (unsigned i = 0; i < kNumDigits; i++)
{
s[kNumDigits - 1 - i] = (char)('0' + (val & 7));
val >>= 3;
}
}
static void WriteOctal_12_Signed(char *s, const Int64 val)
{
if (val >= 0)
{
WriteOctal_12(s, (UInt64)val);
return;
}
s[0] = s[1] = s[2] = s[3] = (char)(Byte)0xFF;
WriteBin_64bit(s + 4, (UInt64)val);
}
static void CopyString(char *dest, const AString &src, const unsigned maxSize)
{
unsigned len = src.Len();
if (len == 0)
return;
// 21.07: new gnu : we don't require additional 0 character at the end
// if (len >= maxSize)
if (len > maxSize)
{
len = maxSize;
/*
// oldgnu needs 0 character at the end
len = maxSize - 1;
dest[len] = 0;
*/
}
memcpy(dest, src.Ptr(), len);
}
// #define RETURN_IF_NOT_TRUE(x) { if (!(x)) return E_INVALIDARG; }
#define RETURN_IF_NOT_TRUE(x) { x; }
#define COPY_STRING_CHECK(dest, src, size) \
CopyString(dest, src, size); dest += (size);
#define WRITE_OCTAL_8_CHECK(dest, src) \
RETURN_IF_NOT_TRUE(WriteOctal_8(dest, src))
HRESULT COutArchive::WriteHeaderReal(const CItem &item, bool isPax
// , bool zero_PackSize
// , bool zero_MTime
)
{
/*
if (isPax) { we don't use Glob_Name and Prefix }
if (!isPax)
{
we use Glob_Name if it's not empty
we use Prefix if it's not empty
}
*/
char record[kRecordSize];
memset(record, 0, kRecordSize);
char *cur = record;
COPY_STRING_CHECK (cur,
(!isPax && !Glob_Name.IsEmpty()) ? Glob_Name : item.Name,
kNameSize)
WRITE_OCTAL_8_CHECK (cur, item.Mode) cur += 8; // & k_7_oct_digits_Val_Max
WRITE_OCTAL_8_CHECK (cur, item.UID) cur += 8;
WRITE_OCTAL_8_CHECK (cur, item.GID) cur += 8;
WriteOctal_12 (cur, /* zero_PackSize ? 0 : */ item.PackSize); cur += 12;
WriteOctal_12_Signed (cur, /* zero_MTime ? 0 : */ item.MTime); cur += 12;
// we will use binary init for checksum instead of memset
// checksum field:
// memset(cur, ' ', 8);
cur += 8;
*cur++ = item.LinkFlag;
COPY_STRING_CHECK (cur, item.LinkName, kNameSize)
memcpy(cur, item.Magic, 8);
cur += 8;
COPY_STRING_CHECK (cur, item.User, kUserNameSize)
COPY_STRING_CHECK (cur, item.Group, kGroupNameSize)
const bool needDevice = (IsPosixMode && !isPax);
if (item.DeviceMajor_Defined)
WRITE_OCTAL_8_CHECK (cur, item.DeviceMajor)
else if (needDevice)
WRITE_OCTAL_8_CHECK (cur, 0)
cur += 8;
if (item.DeviceMinor_Defined)
WRITE_OCTAL_8_CHECK (cur, item.DeviceMinor)
else if (needDevice)
WRITE_OCTAL_8_CHECK (cur, 0)
cur += 8;
if (!isPax && !Prefix.IsEmpty())
{
COPY_STRING_CHECK (cur, Prefix, kPrefixSize)
}
if (item.Is_Sparse())
{
record[482] = (char)(item.SparseBlocks.Size() > 4 ? 1 : 0);
WriteOctal_12(record + 483, item.Size);
for (unsigned i = 0; i < item.SparseBlocks.Size() && i < 4; i++)
{
const CSparseBlock &sb = item.SparseBlocks[i];
char *p = record + 386 + 24 * i;
WriteOctal_12(p, sb.Offset);
WriteOctal_12(p + 12, sb.Size);
}
}
{
UInt32 sum = (unsigned)(' ') * 8; // we use binary init
{
for (unsigned i = 0; i < kRecordSize; i++)
sum += (Byte)record[i];
}
/* checksum field is formatted differently from the
other fields: it has [6] digits, a null, then a space. */
// WRITE_OCTAL_8_CHECK(record + 148, sum);
const unsigned kNumDigits = 6;
for (unsigned i = 0; i < kNumDigits; i++)
{
record[148 + kNumDigits - 1 - i] = (char)('0' + (sum & 7));
sum >>= 3;
}
// record[148 + 6] = 0; // we need it, if we use memset(' ') init
record[148 + 7] = ' '; // we need it, if we use binary init
}
RINOK(Write_Data(record, kRecordSize))
if (item.Is_Sparse())
{
for (unsigned i = 4; i < item.SparseBlocks.Size();)
{
memset(record, 0, kRecordSize);
for (unsigned t = 0; t < 21 && i < item.SparseBlocks.Size(); t++, i++)
{
const CSparseBlock &sb = item.SparseBlocks[i];
char *p = record + 24 * t;
WriteOctal_12(p, sb.Offset);
WriteOctal_12(p + 12, sb.Size);
}
record[21 * 24] = (char)(i < item.SparseBlocks.Size() ? 1 : 0);
RINOK(Write_Data(record, kRecordSize))
}
}
return S_OK;
}
static void AddPaxLine(AString &s, const char *name, const AString &val)
{
// s.Add_LF(); // for debug
const unsigned len = 3 + (unsigned)strlen(name) + val.Len();
AString n;
for (unsigned numDigits = 1;; numDigits++)
{
n.Empty();
n.Add_UInt32(numDigits + len);
if (numDigits == n.Len())
break;
}
s += n;
s.Add_Space();
s += name;
s.Add_Char('=');
s += val;
s.Add_LF();
}
// pt is defined : (pt.NumDigits >= 0)
static void AddPaxTime(AString &s, const char *name, const CPaxTime &pt,
const CTimeOptions &options)
{
unsigned numDigits = (unsigned)pt.NumDigits;
if (numDigits > options.NumDigitsMax)
numDigits = options.NumDigitsMax;
bool needNs = false;
UInt32 ns = 0;
if (numDigits != 0)
{
ns = pt.Ns;
// if (ns != 0) before reduction, we show all digits after digits reduction
needNs = (ns != 0 || options.RemoveZeroMode == k_PaxTimeMode_DontRemoveZero);
UInt32 d = 1;
for (unsigned k = numDigits; k < 9; k++)
d *= 10;
ns /= d;
ns *= d;
}
AString v;
{
Int64 sec = pt.Sec;
if (pt.Sec < 0)
{
sec = -sec;
v.Add_Minus();
if (ns != 0)
{
ns = 1000*1000*1000 - ns;
sec--;
}
}
v.Add_UInt64((UInt64)sec);
}
if (needNs)
{
AString d;
d.Add_UInt32(ns);
while (d.Len() < 9)
d.InsertAtFront('0');
// here we have precision
while (d.Len() > (unsigned)numDigits)
d.DeleteBack();
// GNU TAR reduces '0' digits.
if (options.RemoveZeroMode == k_PaxTimeMode_RemoveZero_Always)
while (!d.IsEmpty() && d.Back() == '0')
d.DeleteBack();
if (!d.IsEmpty())
{
v.Add_Dot();
v += d;
// v += "1234567009999"; // for debug
// for (int y = 0; y < 1000; y++) v += '8'; // for debug
}
}
AddPaxLine(s, name, v);
}
static void AddPax_UInt32_ifBig(AString &s, const char *name, const UInt32 &v)
{
if (v > k_7_oct_digits_Val_Max)
{
AString s2;
s2.Add_UInt32(v);
AddPaxLine(s, name, s2);
}
}
/* OLD_GNU_TAR: writes name with zero at the end
NEW_GNU_TAR: can write name filled with all kNameSize characters */
static const unsigned kNameSize_Max =
kNameSize; // NEW_GNU_TAR / 7-Zip 21.07
// kNameSize - 1; // OLD_GNU_TAR / old 7-Zip
#define DOES_NAME_FIT_IN_FIELD(name) ((name).Len() <= kNameSize_Max)
HRESULT COutArchive::WriteHeader(const CItem &item)
{
Glob_Name.Empty();
Prefix.Empty();
unsigned namePos = 0;
bool needPathCut = false;
bool allowPrefix = false;
if (!DOES_NAME_FIT_IN_FIELD(item.Name))
{
const char *s = item.Name;
const char *p = s + item.Name.Len() - 1;
for (; *p == '/' && p != s; p--)
{}
for (; p != s && p[-1] != '/'; p--)
{}
namePos = (unsigned)(p - s);
needPathCut = true;
}
if (IsPosixMode)
{
AString s;
if (needPathCut)
{
const unsigned nameLen = item.Name.Len() - namePos;
if ( item.LinkFlag >= NLinkFlag::kNormal
&& item.LinkFlag <= NLinkFlag::kDirectory
&& namePos > 1
&& nameLen != 0
// && IsPrefixAllowed
&& item.IsMagic_Posix_ustar_00())
{
/* GNU TAR decoder supports prefix field, only if (magic)
signature matches 6-bytes "ustar\0".
so here we use prefix field only in posix mode with posix signature */
allowPrefix = true;
// allowPrefix = false; // for debug
if (namePos <= kPrefixSize + 1 && nameLen <= kNameSize_Max)
{
needPathCut = false;
/* we will set Prefix and Glob_Name later, for such conditions:
if (!DOES_NAME_FIT_IN_FIELD(item.Name) && !needPathCut) */
}
}
if (needPathCut)
AddPaxLine(s, "path", item.Name);
}
// AddPaxLine(s, "testname", AString("testval")); // for debug
if (item.LinkName.Len() > kNameSize_Max)
AddPaxLine(s, "linkpath", item.LinkName);
const UInt64 kPaxSize_Limit = ((UInt64)1 << 33);
// const UInt64 kPaxSize_Limit = ((UInt64)1 << 1); // for debug
// bool zero_PackSize = false;
if (item.PackSize >= kPaxSize_Limit)
{
/* GNU TAR in pax mode sets PackSize = 0 in main record, if pack_size >= 8 GiB
But old 7-Zip doesn't detect "size" property from pax header.
So we write real size (>= 8 GiB) to main record in binary format,
and old 7-Zip can decode size correctly */
// zero_PackSize = true;
AString v;
v.Add_UInt64(item.PackSize);
AddPaxLine(s, "size", v);
}
/* GNU TAR encoder can set "devmajor" / "devminor" attributes,
but GNU TAR decoder doesn't parse "devmajor" / "devminor" */
if (item.DeviceMajor_Defined)
AddPax_UInt32_ifBig(s, "devmajor", item.DeviceMajor);
if (item.DeviceMinor_Defined)
AddPax_UInt32_ifBig(s, "devminor", item.DeviceMinor);
AddPax_UInt32_ifBig(s, "uid", item.UID);
AddPax_UInt32_ifBig(s, "gid", item.GID);
const UInt64 kPax_MTime_Limit = ((UInt64)1 << 33);
const bool zero_MTime = (
item.MTime < 0 ||
item.MTime >= (Int64)kPax_MTime_Limit);
const CPaxTime &mtime = item.PaxTimes.MTime;
if (mtime.IsDefined())
{
bool needPax = false;
if (zero_MTime)
needPax = true;
else if (TimeOptions.NumDigitsMax > 0)
if (mtime.Ns != 0 ||
(mtime.NumDigits != 0 &&
TimeOptions.RemoveZeroMode == k_PaxTimeMode_DontRemoveZero))
needPax = true;
if (needPax)
AddPaxTime(s, "mtime", mtime, TimeOptions);
}
if (item.PaxTimes.ATime.IsDefined())
AddPaxTime(s, "atime", item.PaxTimes.ATime, TimeOptions);
if (item.PaxTimes.CTime.IsDefined())
AddPaxTime(s, "ctime", item.PaxTimes.CTime, TimeOptions);
if (item.User.Len() > kUserNameSize)
AddPaxLine(s, "uname", item.User);
if (item.Group.Len() > kGroupNameSize)
AddPaxLine(s, "gname", item.Group);
/*
// for debug
AString a ("11"); for (int y = 0; y < (1 << 24); y++) AddPaxLine(s, "temp", a);
*/
const unsigned paxSize = s.Len();
if (paxSize != 0)
{
CItem mi = item;
mi.LinkName.Empty();
// SparseBlocks will be ignored by Is_Sparse()
// mi.SparseBlocks.Clear();
// we use "PaxHeader/*" for compatibility with previous 7-Zip decoder
// GNU TAR writes empty for these fields;
mi.User.Empty();
mi.Group.Empty();
mi.UID = 0;
mi.GID = 0;
mi.DeviceMajor_Defined = false;
mi.DeviceMinor_Defined = false;
mi.Name = "PaxHeader/@PaxHeader";
mi.Mode = 0644; // octal
if (zero_MTime)
mi.MTime = 0;
mi.LinkFlag = NLinkFlag::kPax;
// mi.LinkFlag = 'Z'; // for debug
mi.PackSize = paxSize;
// for (unsigned y = 0; y < 1; y++) { // for debug
RINOK(WriteHeaderReal(mi, true)) // isPax
RINOK(Write_Data_And_Residual(s, paxSize))
// } // for debug
/*
we can send (zero_MTime) for compatibility with gnu tar output.
we can send (zero_MTime = false) for better compatibility with old 7-Zip
*/
// return WriteHeaderReal(item);
/*
false, // isPax
false, // zero_PackSize
false); // zero_MTime
*/
}
}
else // !PosixMode
if (!DOES_NAME_FIT_IN_FIELD(item.Name) ||
!DOES_NAME_FIT_IN_FIELD(item.LinkName))
{
// here we can get all fields from main (item) or create new empty item
/*
CItem mi;
mi.SetDefaultWriteFields();
*/
CItem mi = item;
mi.LinkName.Empty();
// SparseBlocks will be ignored by Is_Sparse()
// mi.SparseBlocks.Clear();
mi.Name = kLongLink;
// mi.Name = "././@BAD_LONG_LINK_TEST"; // for debug
// 21.07 : we set Mode and MTime props as in GNU TAR:
mi.Mode = 0644; // octal
mi.MTime = 0;
mi.User.Empty();
mi.Group.Empty();
/*
gnu tar sets "root" for such items:
uid_to_uname (0, &uname);
gid_to_gname (0, &gname);
*/
/*
mi.User = "root";
mi.Group = "root";
*/
mi.UID = 0;
mi.GID = 0;
mi.DeviceMajor_Defined = false;
mi.DeviceMinor_Defined = false;
for (unsigned i = 0; i < 2; i++)
{
const AString *name;
// We suppose that GNU TAR also writes item for long link before item for LongName?
if (i == 0)
{
mi.LinkFlag = NLinkFlag::kGnu_LongLink;
name = &item.LinkName;
}
else
{
mi.LinkFlag = NLinkFlag::kGnu_LongName;
name = &item.Name;
}
if (DOES_NAME_FIT_IN_FIELD(*name))
continue;
// GNU TAR writes null character after NAME to file. We do same here:
const unsigned nameStreamSize = name->Len() + 1;
mi.PackSize = nameStreamSize;
// for (unsigned y = 0; y < 3; y++) { // for debug
RINOK(WriteHeaderReal(mi))
RINOK(Write_Data_And_Residual(name->Ptr(), nameStreamSize))
// }
// for debug
/*
const unsigned kSize = (1 << 29) + 16;
CByteBuffer buf;
buf.Alloc(kSize);
memset(buf, 0, kSize);
memcpy(buf, name->Ptr(), name->Len());
const unsigned nameStreamSize = kSize;
mi.PackSize = nameStreamSize;
// for (unsigned y = 0; y < 3; y++) { // for debug
RINOK(WriteHeaderReal(mi));
RINOK(WriteBytes(buf, nameStreamSize));
RINOK(FillDataResidual(nameStreamSize));
*/
}
}
// bool fals = false; if (fals) // for debug: for bit-to-bit output compatibility with GNU TAR
if (!DOES_NAME_FIT_IN_FIELD(item.Name))
{
const unsigned nameLen = item.Name.Len() - namePos;
if (!needPathCut)
Prefix.SetFrom(item.Name, namePos - 1);
else
{
Glob_Name = K_PREFIX_PATH_CUT "/_pc_";
if (namePos == 0)
Glob_Name += "root";
else
{
Glob_Name += "crc32/";
char temp[12];
ConvertUInt32ToHex8Digits(CrcCalc(item.Name, namePos - 1), temp);
Glob_Name += temp;
}
if (!allowPrefix || Glob_Name.Len() + 1 + nameLen <= kNameSize_Max)
Glob_Name.Add_Slash();
else
{
Prefix = Glob_Name;
Glob_Name.Empty();
}
}
Glob_Name.AddFrom(item.Name.Ptr(namePos), nameLen);
}
return WriteHeaderReal(item);
}
HRESULT COutArchive::Write_Data(const void *data, unsigned size)
{
Pos += size;
return WriteStream(Stream, data, size);
}
HRESULT COutArchive::Write_AfterDataResidual(UInt64 dataSize)
{
const unsigned v = ((unsigned)dataSize & (kRecordSize - 1));
if (v == 0)
return S_OK;
const unsigned rem = kRecordSize - v;
Byte buf[kRecordSize];
memset(buf, 0, rem);
return Write_Data(buf, rem);
}
HRESULT COutArchive::Write_Data_And_Residual(const void *data, unsigned size)
{
RINOK(Write_Data(data, size))
return Write_AfterDataResidual(size);
}
HRESULT COutArchive::WriteFinishHeader()
{
Byte record[kRecordSize];
memset(record, 0, kRecordSize);
const unsigned kNumFinishRecords = 2;
/* GNU TAR by default uses --blocking-factor=20 (512 * 20 = 10 KiB)
we also can use cluster alignment:
const unsigned numBlocks = (unsigned)(Pos / kRecordSize) + kNumFinishRecords;
const unsigned kNumClusterBlocks = (1 << 3); // 8 blocks = 4 KiB
const unsigned numFinishRecords = kNumFinishRecords + ((kNumClusterBlocks - numBlocks) & (kNumClusterBlocks - 1));
*/
for (unsigned i = 0; i < kNumFinishRecords; i++)
{
RINOK(Write_Data(record, kRecordSize))
}
return S_OK;
}
}}
@@ -0,0 +1,53 @@
// Archive/TarOut.h
#ifndef ZIP7_INC_ARCHIVE_TAR_OUT_H
#define ZIP7_INC_ARCHIVE_TAR_OUT_H
#include "../../../Common/MyCom.h"
#include "../../IStream.h"
#include "TarItem.h"
namespace NArchive {
namespace NTar {
class COutArchive
{
CMyComPtr<ISequentialOutStream> Stream;
AString Glob_Name;
AString Prefix;
HRESULT WriteHeaderReal(const CItem &item, bool isPax = false
// , bool zero_PackSize = false
// , bool zero_MTime = false
);
HRESULT Write_Data(const void *data, unsigned size);
HRESULT Write_Data_And_Residual(const void *data, unsigned size);
public:
UInt64 Pos;
bool IsPosixMode;
// bool IsPrefixAllowed; // it's used only if (IsPosixMode == true)
CTimeOptions TimeOptions;
void Create(ISequentialOutStream *outStream)
{
Stream = outStream;
}
HRESULT WriteHeader(const CItem &item);
HRESULT Write_AfterDataResidual(UInt64 dataSize);
HRESULT WriteFinishHeader();
COutArchive():
Pos(0),
IsPosixMode(false)
// , IsPrefixAllowed(true)
{}
};
}}
#endif
@@ -0,0 +1,31 @@
// TarRegister.cpp
#include "StdAfx.h"
#include "../../Common/RegisterArc.h"
#include "TarHandler.h"
namespace NArchive {
namespace NTar {
static const Byte k_Signature[] = { 'u', 's', 't', 'a', 'r' };
REGISTER_ARC_IO(
"tar", "tar ova", NULL, 0xEE,
k_Signature,
NFileHeader::kUstarMagic_Offset,
NArcInfoFlags::kStartOpen
| NArcInfoFlags::kSymLinks
| NArcInfoFlags::kHardLinks
| NArcInfoFlags::kMTime
| NArcInfoFlags::kMTime_Default
// | NArcInfoTimeFlags::kCTime
// | NArcInfoTimeFlags::kATime
, TIME_PREC_TO_ARC_FLAGS_MASK (NFileTimeType::kWindows)
| TIME_PREC_TO_ARC_FLAGS_MASK (NFileTimeType::kUnix)
| TIME_PREC_TO_ARC_FLAGS_MASK (NFileTimeType::k1ns)
| TIME_PREC_TO_ARC_FLAGS_TIME_DEFAULT (NFileTimeType::kUnix)
, IsArc_Tar)
}}
@@ -0,0 +1,563 @@
// TarUpdate.cpp
#include "StdAfx.h"
// #include <stdio.h>
#include "../../../Windows/TimeUtils.h"
#include "../../Common/LimitedStreams.h"
#include "../../Common/ProgressUtils.h"
#include "../../Common/StreamUtils.h"
#include "../../Compress/CopyCoder.h"
#include "TarOut.h"
#include "TarUpdate.h"
namespace NArchive {
namespace NTar {
static void FILETIME_To_PaxTime(const FILETIME &ft, CPaxTime &pt)
{
UInt32 ns;
pt.Sec = NWindows::NTime::FileTime_To_UnixTime64_and_Quantums(ft, ns);
pt.Ns = ns * 100;
pt.NumDigits = 7;
}
HRESULT Prop_To_PaxTime(const NWindows::NCOM::CPropVariant &prop, CPaxTime &pt)
{
pt.Clear();
if (prop.vt == VT_EMPTY)
{
// pt.Sec = 0;
return S_OK;
}
if (prop.vt != VT_FILETIME)
return E_INVALIDARG;
{
UInt32 ns;
pt.Sec = NWindows::NTime::FileTime_To_UnixTime64_and_Quantums(prop.filetime, ns);
ns *= 100;
pt.NumDigits = 7;
const unsigned prec = prop.wReserved1;
if (prec >= k_PropVar_TimePrec_Base)
{
pt.NumDigits = (int)(prec - k_PropVar_TimePrec_Base);
if (prop.wReserved2 < 100)
ns += prop.wReserved2;
}
pt.Ns = ns;
return S_OK;
}
}
static HRESULT GetTime(IStreamGetProp *getProp, UInt32 pid, CPaxTime &pt)
{
pt.Clear();
NWindows::NCOM::CPropVariant prop;
RINOK(getProp->GetProperty(pid, &prop))
return Prop_To_PaxTime(prop, pt);
}
static HRESULT GetUser(IStreamGetProp *getProp,
UInt32 pidName, UInt32 pidId, AString &name, UInt32 &id,
UINT codePage, unsigned utfFlags)
{
// printf("\nGetUser\n");
// we keep old values, if both GetProperty() return VT_EMPTY
// we clear old values, if any of GetProperty() returns non-VT_EMPTY;
bool isSet = false;
{
NWindows::NCOM::CPropVariant prop;
RINOK(getProp->GetProperty(pidId, &prop))
if (prop.vt == VT_UI4)
{
isSet = true;
id = prop.ulVal;
name.Empty();
}
else if (prop.vt != VT_EMPTY)
return E_INVALIDARG;
}
{
NWindows::NCOM::CPropVariant prop;
RINOK(getProp->GetProperty(pidName, &prop))
if (prop.vt == VT_BSTR)
{
const UString s = prop.bstrVal;
Get_AString_From_UString(s, name, codePage, utfFlags);
// printf("\ngetProp->GetProperty(pidName, &prop) : %s" , name.Ptr());
if (!isSet)
id = 0;
}
else if (prop.vt == VT_UI4)
{
id = prop.ulVal;
name.Empty();
}
else if (prop.vt != VT_EMPTY)
return E_INVALIDARG;
}
return S_OK;
}
/*
static HRESULT GetDevice(IStreamGetProp *getProp,
UInt32 &majo, UInt32 &mino, bool &majo_defined, bool &mino_defined)
{
NWindows::NCOM::CPropVariant prop;
RINOK(getProp->GetProperty(kpidDevice, &prop));
if (prop.vt == VT_EMPTY)
return S_OK;
if (prop.vt != VT_UI8)
return E_INVALIDARG;
{
printf("\nTarUpdate.cpp :: GetDevice()\n");
const UInt64 v = prop.uhVal.QuadPart;
majo = MY_dev_major(v);
mino = MY_dev_minor(v);
majo_defined = true;
mino_defined = true;
}
return S_OK;
}
*/
static HRESULT GetDevice(IStreamGetProp *getProp,
UInt32 pid, UInt32 &id, bool &defined)
{
defined = false;
NWindows::NCOM::CPropVariant prop;
RINOK(getProp->GetProperty(pid, &prop))
if (prop.vt == VT_EMPTY)
return S_OK;
if (prop.vt == VT_UI4)
{
id = prop.ulVal;
defined = true;
return S_OK;
}
return E_INVALIDARG;
}
HRESULT UpdateArchive(IInStream *inStream, ISequentialOutStream *outStream,
const CObjectVector<NArchive::NTar::CItemEx> &inputItems,
const CObjectVector<CUpdateItem> &updateItems,
const CUpdateOptions &options,
IArchiveUpdateCallback *updateCallback)
{
COutArchive outArchive;
outArchive.Create(outStream);
outArchive.Pos = 0;
outArchive.IsPosixMode = options.PosixMode;
outArchive.TimeOptions = options.TimeOptions;
Z7_DECL_CMyComPtr_QI_FROM(IOutStream, outSeekStream, outStream)
Z7_DECL_CMyComPtr_QI_FROM(IStreamSetRestriction, setRestriction, outStream)
Z7_DECL_CMyComPtr_QI_FROM(IArchiveUpdateCallbackFile, opCallback, outStream)
if (outSeekStream)
{
/*
// for debug
Byte buf[1 << 14];
memset (buf, 0, sizeof(buf));
RINOK(outStream->Write(buf, sizeof(buf), NULL));
*/
// we need real outArchive.Pos, if outSeekStream->SetSize() will be used.
RINOK(outSeekStream->Seek(0, STREAM_SEEK_CUR, &outArchive.Pos))
}
if (setRestriction)
RINOK(setRestriction->SetRestriction(0, 0))
UInt64 complexity = 0;
unsigned i;
for (i = 0; i < updateItems.Size(); i++)
{
const CUpdateItem &ui = updateItems[i];
if (ui.NewData)
{
if (ui.Size == (UInt64)(Int64)-1)
break;
complexity += ui.Size;
}
else
complexity += inputItems[(unsigned)ui.IndexInArc].Get_FullSize_Aligned();
}
if (i == updateItems.Size())
RINOK(updateCallback->SetTotal(complexity))
CMyComPtr2_Create<ICompressProgressInfo, CLocalProgress> lps;
lps->Init(updateCallback, true);
CMyComPtr2_Create<ICompressCoder, NCompress::CCopyCoder> copyCoder;
CMyComPtr2_Create<ISequentialInStream, CLimitedSequentialInStream> inStreamLimited;
inStreamLimited->SetStream(inStream);
complexity = 0;
// const int kNumReduceDigits = -1; // for debug
for (i = 0;; i++)
{
lps->InSize = lps->OutSize = complexity;
RINOK(lps->SetCur())
if (i == updateItems.Size())
{
if (outSeekStream && setRestriction)
RINOK(setRestriction->SetRestriction(0, 0))
return outArchive.WriteFinishHeader();
}
const CUpdateItem &ui = updateItems[i];
CItem item;
if (ui.NewProps)
{
item.SetMagic_Posix(options.PosixMode);
item.Name = ui.Name;
item.User = ui.User;
item.Group = ui.Group;
item.UID = ui.UID;
item.GID = ui.GID;
item.DeviceMajor = ui.DeviceMajor;
item.DeviceMinor = ui.DeviceMinor;
item.DeviceMajor_Defined = ui.DeviceMajor_Defined;
item.DeviceMinor_Defined = ui.DeviceMinor_Defined;
if (ui.IsDir)
{
item.LinkFlag = NFileHeader::NLinkFlag::kDirectory;
item.PackSize = 0;
}
else
{
item.PackSize = ui.Size;
item.Set_LinkFlag_for_File(ui.Mode);
}
// 22.00
item.Mode = ui.Mode & ~(UInt32)MY_LIN_S_IFMT;
item.PaxTimes = ui.PaxTimes;
// item.PaxTimes.ReducePrecison(kNumReduceDigits); // for debug
item.MTime = ui.PaxTimes.MTime.GetSec();
}
else
item = inputItems[(unsigned)ui.IndexInArc];
AString symLink;
if (ui.NewData || ui.NewProps)
{
RINOK(GetPropString(updateCallback, ui.IndexInClient, kpidSymLink, symLink,
options.CodePage, options.UtfFlags, true))
if (!symLink.IsEmpty())
{
item.LinkFlag = NFileHeader::NLinkFlag::kSymLink;
item.LinkName = symLink;
}
}
if (ui.NewData)
{
item.SparseBlocks.Clear();
item.PackSize = ui.Size;
item.Size = ui.Size;
#if 0
if (ui.Size == (UInt64)(Int64)-1)
return E_INVALIDARG;
#endif
CMyComPtr<ISequentialInStream> fileInStream;
bool needWrite = true;
if (!symLink.IsEmpty())
{
item.PackSize = 0;
item.Size = 0;
}
else
{
const HRESULT res = updateCallback->GetStream(ui.IndexInClient, &fileInStream);
if (res == S_FALSE)
needWrite = false;
else
{
RINOK(res)
if (!fileInStream)
{
item.PackSize = 0;
item.Size = 0;
}
else
{
Z7_DECL_CMyComPtr_QI_FROM(IStreamGetProp, getProp, fileInStream)
if (getProp)
{
if (options.Write_MTime.Val) RINOK(GetTime(getProp, kpidMTime, item.PaxTimes.MTime))
if (options.Write_ATime.Val) RINOK(GetTime(getProp, kpidATime, item.PaxTimes.ATime))
if (options.Write_CTime.Val) RINOK(GetTime(getProp, kpidCTime, item.PaxTimes.CTime))
if (options.PosixMode)
{
/*
RINOK(GetDevice(getProp, item.DeviceMajor, item.DeviceMinor,
item.DeviceMajor_Defined, item.DeviceMinor_Defined));
*/
bool defined = false;
UInt32 val = 0;
RINOK(GetDevice(getProp, kpidDeviceMajor, val, defined))
if (defined)
{
item.DeviceMajor = val;
item.DeviceMajor_Defined = true;
item.DeviceMinor = 0;
item.DeviceMinor_Defined = false;
RINOK(GetDevice(getProp, kpidDeviceMinor, item.DeviceMinor, item.DeviceMinor_Defined))
}
}
RINOK(GetUser(getProp, kpidUser, kpidUserId, item.User, item.UID, options.CodePage, options.UtfFlags))
RINOK(GetUser(getProp, kpidGroup, kpidGroupId, item.Group, item.GID, options.CodePage, options.UtfFlags))
{
NWindows::NCOM::CPropVariant prop;
RINOK(getProp->GetProperty(kpidPosixAttrib, &prop))
if (prop.vt == VT_EMPTY)
item.Mode =
MY_LIN_S_IRWXO
| MY_LIN_S_IRWXG
| MY_LIN_S_IRWXU
| (ui.IsDir ? MY_LIN_S_IFDIR : MY_LIN_S_IFREG);
else if (prop.vt != VT_UI4)
return E_INVALIDARG;
else
item.Mode = prop.ulVal;
// 21.07 : we clear high file type bits as GNU TAR.
item.Set_LinkFlag_for_File(item.Mode);
item.Mode &= ~(UInt32)MY_LIN_S_IFMT;
}
{
NWindows::NCOM::CPropVariant prop;
RINOK(getProp->GetProperty(kpidSize, &prop))
if (prop.vt != VT_UI8)
return E_INVALIDARG;
const UInt64 size = prop.uhVal.QuadPart;
// printf("\nTAR after GetProperty(kpidSize size = %8d\n", (unsigned)size);
item.PackSize = size;
item.Size = size;
}
/*
printf("\nNum digits = %d %d\n",
(int)item.PaxTimes.MTime.NumDigits,
(int)item.PaxTimes.MTime.Ns);
*/
}
else
{
Z7_DECL_CMyComPtr_QI_FROM(IStreamGetProps, getProps, fileInStream)
if (getProps)
{
FILETIME mTime, aTime, cTime;
UInt64 size2;
if (getProps->GetProps(&size2,
options.Write_CTime.Val ? &cTime : NULL,
options.Write_ATime.Val ? &aTime : NULL,
options.Write_MTime.Val ? &mTime : NULL,
NULL) == S_OK)
{
item.PackSize = size2;
item.Size = size2;
if (options.Write_MTime.Val) FILETIME_To_PaxTime(mTime, item.PaxTimes.MTime);
if (options.Write_ATime.Val) FILETIME_To_PaxTime(aTime, item.PaxTimes.ATime);
if (options.Write_CTime.Val) FILETIME_To_PaxTime(cTime, item.PaxTimes.CTime);
}
}
}
}
{
// we must request kpidHardLink after updateCallback->GetStream()
AString hardLink;
RINOK(GetPropString(updateCallback, ui.IndexInClient, kpidHardLink, hardLink,
options.CodePage, options.UtfFlags, true))
if (!hardLink.IsEmpty())
{
item.LinkFlag = NFileHeader::NLinkFlag::kHardLink;
item.LinkName = hardLink;
item.PackSize = 0;
item.Size = 0;
fileInStream.Release();
}
}
}
}
// item.PaxTimes.ReducePrecison(kNumReduceDigits); // for debug
if (ui.NewProps)
item.MTime = item.PaxTimes.MTime.GetSec();
if (needWrite)
{
if (fileInStream)
// if (item.PackSize == (UInt64)(Int64)-1)
if (item.Size == (UInt64)(Int64)-1)
return E_INVALIDARG;
const UInt64 headerPos = outArchive.Pos;
// item.PackSize = ((UInt64)1 << 33); // for debug
if (outSeekStream && setRestriction)
RINOK(setRestriction->SetRestriction(outArchive.Pos, (UInt64)(Int64)-1))
RINOK(outArchive.WriteHeader(item))
if (fileInStream)
{
for (unsigned numPasses = 0;; numPasses++)
{
// printf("\nTAR numPasses = %d" " old size = %8d\n", numPasses, (unsigned)item.PackSize);
/* we support 2 attempts to write header:
pass-0: main pass:
pass-1: additional pass, if size_of_file and size_of_header are changed */
if (numPasses >= 2)
{
// opRes = NArchive::NUpdate::NOperationResult::kError_FileChanged;
// break;
return E_FAIL;
}
const UInt64 dataPos = outArchive.Pos;
RINOK(copyCoder.Interface()->Code(fileInStream, outStream, NULL, NULL, lps))
outArchive.Pos += copyCoder->TotalSize;
RINOK(outArchive.Write_AfterDataResidual(copyCoder->TotalSize))
// printf("\nTAR after Code old size = %8d copyCoder->TotalSize = %8d \n", (unsigned)item.PackSize, (unsigned)copyCoder->TotalSize);
// if (numPasses >= 10) // for debug
if (copyCoder->TotalSize == item.PackSize)
break;
if (opCallback)
{
RINOK(opCallback->ReportOperation(
NEventIndexType::kOutArcIndex, (UInt32)ui.IndexInClient,
NUpdateNotifyOp::kInFileChanged))
}
if (!outSeekStream)
return E_FAIL;
const UInt64 nextPos = outArchive.Pos;
RINOK(outSeekStream->Seek(-(Int64)(nextPos - headerPos), STREAM_SEEK_CUR, NULL))
outArchive.Pos = headerPos;
item.PackSize = copyCoder->TotalSize;
RINOK(outArchive.WriteHeader(item))
// if (numPasses >= 10) // for debug
if (outArchive.Pos == dataPos)
{
const UInt64 alignedSize = nextPos - dataPos;
if (alignedSize != 0)
{
RINOK(outSeekStream->Seek((Int64)alignedSize, STREAM_SEEK_CUR, NULL))
outArchive.Pos += alignedSize;
}
break;
}
// size of header was changed.
// we remove data after header and try new attempt, if required
Z7_DECL_CMyComPtr_QI_FROM(IInStream, fileSeekStream, fileInStream)
if (!fileSeekStream)
return E_FAIL;
RINOK(InStream_SeekToBegin(fileSeekStream))
RINOK(outSeekStream->SetSize(outArchive.Pos))
if (item.PackSize == 0)
break;
}
}
}
complexity += item.PackSize;
fileInStream.Release();
RINOK(updateCallback->SetOperationResult(NArchive::NUpdate::NOperationResult::kOK))
}
else
{
// (ui.NewData == false)
if (opCallback)
{
RINOK(opCallback->ReportOperation(
NEventIndexType::kInArcIndex, (UInt32)ui.IndexInArc,
NUpdateNotifyOp::kReplicate))
}
const CItemEx &existItem = inputItems[(unsigned)ui.IndexInArc];
UInt64 size, pos;
if (ui.NewProps)
{
// memcpy(item.Magic, NFileHeader::NMagic::kEmpty, 8);
if (!symLink.IsEmpty())
{
item.PackSize = 0;
item.Size = 0;
}
else
{
if (ui.IsDir == existItem.IsDir())
item.LinkFlag = existItem.LinkFlag;
item.SparseBlocks = existItem.SparseBlocks;
item.Size = existItem.Size;
item.PackSize = existItem.PackSize;
}
item.DeviceMajor_Defined = existItem.DeviceMajor_Defined;
item.DeviceMinor_Defined = existItem.DeviceMinor_Defined;
item.DeviceMajor = existItem.DeviceMajor;
item.DeviceMinor = existItem.DeviceMinor;
item.UID = existItem.UID;
item.GID = existItem.GID;
RINOK(outArchive.WriteHeader(item))
size = existItem.Get_PackSize_Aligned();
pos = existItem.Get_DataPos();
}
else
{
size = existItem.Get_FullSize_Aligned();
pos = existItem.HeaderPos;
}
if (size != 0)
{
RINOK(InStream_SeekSet(inStream, pos))
inStreamLimited->Init(size);
if (outSeekStream && setRestriction)
RINOK(setRestriction->SetRestriction(0, 0))
// 22.00 : we copy Residual data from old archive to new archive instead of zeroing
RINOK(copyCoder.Interface()->Code(inStreamLimited, outStream, NULL, NULL, lps))
if (copyCoder->TotalSize != size)
return E_FAIL;
outArchive.Pos += size;
// RINOK(outArchive.Write_AfterDataResidual(existItem.PackSize));
complexity += size;
}
}
}
}
}}
@@ -0,0 +1,74 @@
// TarUpdate.h
#ifndef ZIP7_INC_TAR_UPDATE_H
#define ZIP7_INC_TAR_UPDATE_H
#include "../IArchive.h"
#include "TarItem.h"
namespace NArchive {
namespace NTar {
struct CUpdateItem
{
int IndexInArc;
unsigned IndexInClient;
UInt64 Size;
// Int64 MTime;
UInt32 Mode;
bool NewData;
bool NewProps;
bool IsDir;
bool DeviceMajor_Defined;
bool DeviceMinor_Defined;
UInt32 UID;
UInt32 GID;
UInt32 DeviceMajor;
UInt32 DeviceMinor;
AString Name;
AString User;
AString Group;
CPaxTimes PaxTimes;
CUpdateItem():
Size(0),
IsDir(false),
DeviceMajor_Defined(false),
DeviceMinor_Defined(false),
UID(0),
GID(0)
{}
};
struct CUpdateOptions
{
UINT CodePage;
unsigned UtfFlags;
bool PosixMode;
CBoolPair Write_MTime;
CBoolPair Write_ATime;
CBoolPair Write_CTime;
CTimeOptions TimeOptions;
};
HRESULT UpdateArchive(IInStream *inStream, ISequentialOutStream *outStream,
const CObjectVector<CItemEx> &inputItems,
const CObjectVector<CUpdateItem> &updateItems,
const CUpdateOptions &options,
IArchiveUpdateCallback *updateCallback);
HRESULT GetPropString(IArchiveUpdateCallback *callback, UInt32 index, PROPID propId, AString &res,
UINT codePage, unsigned utfFlags, bool convertSlash);
HRESULT Prop_To_PaxTime(const NWindows::NCOM::CPropVariant &prop, CPaxTime &pt);
void Get_AString_From_UString(const UString &s, AString &res,
UINT codePage, unsigned utfFlags);
}}
#endif