Files
easy7zip/CPP/7zip/Archive/Tar/TarOut.cpp
Igor Pavlov a36c48cece 23.01
2023-12-22 17:17:05 +00:00

645 lines
17 KiB
C++
Executable File

// 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 += '=';
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;
}
}}