xref: /aosp_15_r20/external/lzma/CPP/7zip/Archive/Tar/TarOut.cpp (revision f6dc9357d832569d4d1f5d24eacdb3935a1ae8e6)
1 // TarOut.cpp
2 
3 #include "StdAfx.h"
4 
5 #include "../../../../C/7zCrc.h"
6 
7 #include "../../../Common/IntToString.h"
8 
9 #include "../../Common/StreamUtils.h"
10 
11 #include "TarOut.h"
12 
13 namespace NArchive {
14 namespace NTar {
15 
16 using namespace NFileHeader;
17 
18 // it's path prefix assigned by 7-Zip to show that file path was cut
19 #define K_PREFIX_PATH_CUT "@PathCut"
20 
21 static const UInt32 k_7_oct_digits_Val_Max = ((UInt32)1 << (7 * 3)) - 1;
22 
WriteOctal_8(char * s,UInt32 val)23 static void WriteOctal_8(char *s, UInt32 val)
24 {
25   const unsigned kNumDigits = 8 - 1;
26   if (val >= ((UInt32)1 << (kNumDigits * 3)))
27   {
28     val = 0;
29     // return false;
30   }
31   for (unsigned i = 0; i < kNumDigits; i++)
32   {
33     s[kNumDigits - 1 - i] = (char)('0' + (val & 7));
34     val >>= 3;
35   }
36   // return true;
37 }
38 
WriteBin_64bit(char * s,UInt64 val)39 static void WriteBin_64bit(char *s, UInt64 val)
40 {
41   for (unsigned i = 0; i < 8; i++, val <<= 8)
42     s[i] = (char)(val >> 56);
43 }
44 
WriteOctal_12(char * s,UInt64 val)45 static void WriteOctal_12(char *s, UInt64 val)
46 {
47   const unsigned kNumDigits = 12 - 1;
48   if (val >= ((UInt64)1 << (kNumDigits * 3)))
49   {
50     // GNU extension;
51     s[0] = (char)(Byte)0x80;
52     s[1] = s[2] = s[3] = 0;
53     WriteBin_64bit(s + 4, val);
54     return;
55   }
56   for (unsigned i = 0; i < kNumDigits; i++)
57   {
58     s[kNumDigits - 1 - i] = (char)('0' + (val & 7));
59     val >>= 3;
60   }
61 }
62 
WriteOctal_12_Signed(char * s,const Int64 val)63 static void WriteOctal_12_Signed(char *s, const Int64 val)
64 {
65   if (val >= 0)
66   {
67     WriteOctal_12(s, (UInt64)val);
68     return;
69   }
70   s[0] = s[1] = s[2] = s[3] = (char)(Byte)0xFF;
71   WriteBin_64bit(s + 4, (UInt64)val);
72 }
73 
CopyString(char * dest,const AString & src,const unsigned maxSize)74 static void CopyString(char *dest, const AString &src, const unsigned maxSize)
75 {
76   unsigned len = src.Len();
77   if (len == 0)
78     return;
79   // 21.07: new gnu : we don't require additional 0 character at the end
80   // if (len >= maxSize)
81   if (len > maxSize)
82   {
83     len = maxSize;
84     /*
85     // oldgnu needs 0 character at the end
86     len = maxSize - 1;
87     dest[len] = 0;
88     */
89   }
90   memcpy(dest, src.Ptr(), len);
91 }
92 
93 // #define RETURN_IF_NOT_TRUE(x) { if (!(x)) return E_INVALIDARG; }
94 #define RETURN_IF_NOT_TRUE(x) { x; }
95 
96 #define COPY_STRING_CHECK(dest, src, size) \
97     CopyString(dest, src, size);   dest += (size);
98 
99 #define WRITE_OCTAL_8_CHECK(dest, src) \
100     RETURN_IF_NOT_TRUE(WriteOctal_8(dest, src))
101 
102 
WriteHeaderReal(const CItem & item,bool isPax)103 HRESULT COutArchive::WriteHeaderReal(const CItem &item, bool isPax
104     // , bool zero_PackSize
105     // , bool zero_MTime
106     )
107 {
108   /*
109     if (isPax) { we don't use Glob_Name and Prefix }
110     if (!isPax)
111     {
112       we use Glob_Name if it's not empty
113       we use Prefix    if it's not empty
114     }
115   */
116   char record[kRecordSize];
117   memset(record, 0, kRecordSize);
118   char *cur = record;
119 
120   COPY_STRING_CHECK (cur,
121       (!isPax && !Glob_Name.IsEmpty()) ? Glob_Name : item.Name,
122       kNameSize)
123 
124   WRITE_OCTAL_8_CHECK (cur, item.Mode)  cur += 8; // & k_7_oct_digits_Val_Max
125   WRITE_OCTAL_8_CHECK (cur, item.UID)   cur += 8;
126   WRITE_OCTAL_8_CHECK (cur, item.GID)   cur += 8;
127 
128   WriteOctal_12 (cur, /* zero_PackSize ? 0 : */ item.PackSize); cur += 12;
129   WriteOctal_12_Signed (cur, /* zero_MTime ? 0 : */ item.MTime); cur += 12;
130 
131   // we will use binary init for checksum instead of memset
132   // checksum field:
133   // memset(cur, ' ', 8);
134   cur += 8;
135 
136   *cur++ = item.LinkFlag;
137 
138   COPY_STRING_CHECK (cur, item.LinkName, kNameSize)
139 
140   memcpy(cur, item.Magic, 8);
141   cur += 8;
142 
143   COPY_STRING_CHECK (cur, item.User, kUserNameSize)
144   COPY_STRING_CHECK (cur, item.Group, kGroupNameSize)
145 
146   const bool needDevice = (IsPosixMode && !isPax);
147 
148   if (item.DeviceMajor_Defined)
149     WRITE_OCTAL_8_CHECK (cur, item.DeviceMajor)
150   else if (needDevice)
151     WRITE_OCTAL_8_CHECK (cur, 0)
152   cur += 8;
153 
154   if (item.DeviceMinor_Defined)
155     WRITE_OCTAL_8_CHECK (cur, item.DeviceMinor)
156   else if (needDevice)
157     WRITE_OCTAL_8_CHECK (cur, 0)
158   cur += 8;
159 
160   if (!isPax && !Prefix.IsEmpty())
161   {
162     COPY_STRING_CHECK (cur, Prefix, kPrefixSize)
163   }
164 
165   if (item.Is_Sparse())
166   {
167     record[482] = (char)(item.SparseBlocks.Size() > 4 ? 1 : 0);
168     WriteOctal_12(record + 483, item.Size);
169     for (unsigned i = 0; i < item.SparseBlocks.Size() && i < 4; i++)
170     {
171       const CSparseBlock &sb = item.SparseBlocks[i];
172       char *p = record + 386 + 24 * i;
173       WriteOctal_12(p, sb.Offset);
174       WriteOctal_12(p + 12, sb.Size);
175     }
176   }
177 
178   {
179     UInt32 sum = (unsigned)(' ') * 8; // we use binary init
180     {
181       for (unsigned i = 0; i < kRecordSize; i++)
182         sum += (Byte)record[i];
183     }
184     /* checksum field is formatted differently from the
185        other fields: it has [6] digits, a null, then a space. */
186     // WRITE_OCTAL_8_CHECK(record + 148, sum);
187     const unsigned kNumDigits = 6;
188     for (unsigned i = 0; i < kNumDigits; i++)
189     {
190       record[148 + kNumDigits - 1 - i] = (char)('0' + (sum & 7));
191       sum >>= 3;
192     }
193     // record[148 + 6] = 0; // we need it, if we use memset(' ') init
194     record[148 + 7] = ' '; // we need it, if we use binary init
195   }
196 
197   RINOK(Write_Data(record, kRecordSize))
198 
199   if (item.Is_Sparse())
200   {
201     for (unsigned i = 4; i < item.SparseBlocks.Size();)
202     {
203       memset(record, 0, kRecordSize);
204       for (unsigned t = 0; t < 21 && i < item.SparseBlocks.Size(); t++, i++)
205       {
206         const CSparseBlock &sb = item.SparseBlocks[i];
207         char *p = record + 24 * t;
208         WriteOctal_12(p, sb.Offset);
209         WriteOctal_12(p + 12, sb.Size);
210       }
211       record[21 * 24] = (char)(i < item.SparseBlocks.Size() ? 1 : 0);
212       RINOK(Write_Data(record, kRecordSize))
213     }
214   }
215 
216   return S_OK;
217 }
218 
219 
AddPaxLine(AString & s,const char * name,const AString & val)220 static void AddPaxLine(AString &s, const char *name, const AString &val)
221 {
222   // s.Add_LF(); // for debug
223   const unsigned len = 3 + (unsigned)strlen(name) + val.Len();
224   AString n;
225   for (unsigned numDigits = 1;; numDigits++)
226   {
227     n.Empty();
228     n.Add_UInt32(numDigits + len);
229     if (numDigits == n.Len())
230       break;
231   }
232   s += n;
233   s.Add_Space();
234   s += name;
235   s.Add_Char('=');
236   s += val;
237   s.Add_LF();
238 }
239 
240 // pt is defined : (pt.NumDigits >= 0)
AddPaxTime(AString & s,const char * name,const CPaxTime & pt,const CTimeOptions & options)241 static void AddPaxTime(AString &s, const char *name, const CPaxTime &pt,
242     const CTimeOptions &options)
243 {
244   unsigned numDigits = (unsigned)pt.NumDigits;
245   if (numDigits > options.NumDigitsMax)
246     numDigits = options.NumDigitsMax;
247 
248   bool needNs = false;
249   UInt32 ns = 0;
250   if (numDigits != 0)
251   {
252     ns = pt.Ns;
253     // if (ns != 0) before reduction, we show all digits after digits reduction
254     needNs = (ns != 0 || options.RemoveZeroMode == k_PaxTimeMode_DontRemoveZero);
255     UInt32 d = 1;
256     for (unsigned k = numDigits; k < 9; k++)
257       d *= 10;
258     ns /= d;
259     ns *= d;
260   }
261 
262   AString v;
263   {
264     Int64 sec = pt.Sec;
265     if (pt.Sec < 0)
266     {
267       sec = -sec;
268       v.Add_Minus();
269       if (ns != 0)
270       {
271         ns = 1000*1000*1000 - ns;
272         sec--;
273       }
274     }
275     v.Add_UInt64((UInt64)sec);
276   }
277 
278   if (needNs)
279   {
280     AString d;
281     d.Add_UInt32(ns);
282     while (d.Len() < 9)
283       d.InsertAtFront('0');
284     // here we have precision
285     while (d.Len() > (unsigned)numDigits)
286       d.DeleteBack();
287     // GNU TAR reduces '0' digits.
288     if (options.RemoveZeroMode == k_PaxTimeMode_RemoveZero_Always)
289     while (!d.IsEmpty() && d.Back() == '0')
290       d.DeleteBack();
291 
292     if (!d.IsEmpty())
293     {
294       v.Add_Dot();
295       v += d;
296       // v += "1234567009999"; // for debug
297       // for (int y = 0; y < 1000; y++) v += '8'; // for debug
298     }
299   }
300 
301   AddPaxLine(s, name, v);
302 }
303 
304 
AddPax_UInt32_ifBig(AString & s,const char * name,const UInt32 & v)305 static void AddPax_UInt32_ifBig(AString &s, const char *name, const UInt32 &v)
306 {
307   if (v > k_7_oct_digits_Val_Max)
308   {
309     AString s2;
310     s2.Add_UInt32(v);
311     AddPaxLine(s, name, s2);
312   }
313 }
314 
315 
316 /* OLD_GNU_TAR: writes name with zero at the end
317    NEW_GNU_TAR: can write name filled with all kNameSize characters */
318 
319 static const unsigned kNameSize_Max =
320     kNameSize;     // NEW_GNU_TAR / 7-Zip 21.07
321     // kNameSize - 1; // OLD_GNU_TAR / old 7-Zip
322 
323 #define DOES_NAME_FIT_IN_FIELD(name) ((name).Len() <= kNameSize_Max)
324 
325 
WriteHeader(const CItem & item)326 HRESULT COutArchive::WriteHeader(const CItem &item)
327 {
328   Glob_Name.Empty();
329   Prefix.Empty();
330 
331   unsigned namePos = 0;
332   bool needPathCut = false;
333   bool allowPrefix = false;
334 
335   if (!DOES_NAME_FIT_IN_FIELD(item.Name))
336   {
337     const char *s = item.Name;
338     const char *p = s + item.Name.Len() - 1;
339     for (; *p == '/' && p != s; p--)
340       {}
341     for (; p != s && p[-1] != '/'; p--)
342       {}
343     namePos = (unsigned)(p - s);
344     needPathCut = true;
345   }
346 
347   if (IsPosixMode)
348   {
349     AString s;
350 
351     if (needPathCut)
352     {
353       const unsigned nameLen = item.Name.Len() - namePos;
354       if (   item.LinkFlag >= NLinkFlag::kNormal
355           && item.LinkFlag <= NLinkFlag::kDirectory
356           && namePos > 1
357           && nameLen != 0
358           // && IsPrefixAllowed
359           && item.IsMagic_Posix_ustar_00())
360       {
361         /* GNU TAR decoder supports prefix field, only if (magic)
362            signature matches 6-bytes "ustar\0".
363            so here we use prefix field only in posix mode with posix signature */
364 
365         allowPrefix = true;
366         // allowPrefix = false; // for debug
367         if (namePos <= kPrefixSize + 1 && nameLen <= kNameSize_Max)
368         {
369           needPathCut = false;
370           /* we will set Prefix and Glob_Name later, for such conditions:
371              if (!DOES_NAME_FIT_IN_FIELD(item.Name) && !needPathCut) */
372         }
373       }
374 
375       if (needPathCut)
376         AddPaxLine(s, "path", item.Name);
377     }
378 
379     // AddPaxLine(s, "testname", AString("testval")); // for debug
380 
381     if (item.LinkName.Len() > kNameSize_Max)
382       AddPaxLine(s, "linkpath", item.LinkName);
383 
384     const UInt64 kPaxSize_Limit = ((UInt64)1 << 33);
385     // const UInt64 kPaxSize_Limit = ((UInt64)1 << 1); // for debug
386     // bool zero_PackSize = false;
387     if (item.PackSize >= kPaxSize_Limit)
388     {
389       /* GNU TAR in pax mode sets PackSize = 0 in main record, if pack_size >= 8 GiB
390          But old 7-Zip doesn't detect "size" property from pax header.
391          So we write real size (>= 8 GiB) to main record in binary format,
392          and old 7-Zip can decode size correctly */
393       // zero_PackSize = true;
394       AString v;
395       v.Add_UInt64(item.PackSize);
396       AddPaxLine(s, "size", v);
397     }
398 
399     /* GNU TAR encoder can set "devmajor" / "devminor" attributes,
400        but GNU TAR decoder doesn't parse "devmajor" / "devminor" */
401     if (item.DeviceMajor_Defined)
402       AddPax_UInt32_ifBig(s, "devmajor", item.DeviceMajor);
403     if (item.DeviceMinor_Defined)
404       AddPax_UInt32_ifBig(s, "devminor", item.DeviceMinor);
405 
406     AddPax_UInt32_ifBig(s, "uid", item.UID);
407     AddPax_UInt32_ifBig(s, "gid", item.GID);
408 
409     const UInt64 kPax_MTime_Limit = ((UInt64)1 << 33);
410     const bool zero_MTime = (
411         item.MTime < 0 ||
412         item.MTime >= (Int64)kPax_MTime_Limit);
413 
414     const CPaxTime &mtime = item.PaxTimes.MTime;
415     if (mtime.IsDefined())
416     {
417       bool needPax = false;
418       if (zero_MTime)
419         needPax = true;
420       else if (TimeOptions.NumDigitsMax > 0)
421         if (mtime.Ns != 0 ||
422             (mtime.NumDigits != 0 &&
423             TimeOptions.RemoveZeroMode == k_PaxTimeMode_DontRemoveZero))
424           needPax = true;
425       if (needPax)
426         AddPaxTime(s, "mtime", mtime, TimeOptions);
427     }
428 
429     if (item.PaxTimes.ATime.IsDefined())
430       AddPaxTime(s, "atime", item.PaxTimes.ATime, TimeOptions);
431     if (item.PaxTimes.CTime.IsDefined())
432       AddPaxTime(s, "ctime", item.PaxTimes.CTime, TimeOptions);
433 
434     if (item.User.Len() > kUserNameSize)
435       AddPaxLine(s, "uname", item.User);
436     if (item.Group.Len() > kGroupNameSize)
437       AddPaxLine(s, "gname", item.Group);
438 
439     /*
440     // for debug
441     AString a ("11"); for (int y = 0; y < (1 << 24); y++) AddPaxLine(s, "temp", a);
442     */
443 
444     const unsigned paxSize = s.Len();
445     if (paxSize != 0)
446     {
447       CItem mi = item;
448       mi.LinkName.Empty();
449       // SparseBlocks will be ignored by Is_Sparse()
450       // mi.SparseBlocks.Clear();
451       //  we use "PaxHeader/*" for compatibility with previous 7-Zip decoder
452 
453       // GNU TAR writes empty for these fields;
454       mi.User.Empty();
455       mi.Group.Empty();
456       mi.UID = 0;
457       mi.GID = 0;
458 
459       mi.DeviceMajor_Defined = false;
460       mi.DeviceMinor_Defined = false;
461 
462       mi.Name = "PaxHeader/@PaxHeader";
463       mi.Mode = 0644; // octal
464       if (zero_MTime)
465         mi.MTime = 0;
466       mi.LinkFlag = NLinkFlag::kPax;
467       // mi.LinkFlag = 'Z'; // for debug
468       mi.PackSize = paxSize;
469       // for (unsigned y = 0; y < 1; y++) { // for debug
470       RINOK(WriteHeaderReal(mi, true)) // isPax
471       RINOK(Write_Data_And_Residual(s, paxSize))
472       // } // for debug
473       /*
474         we can send (zero_MTime) for compatibility with gnu tar output.
475         we can send (zero_MTime = false) for better compatibility with old 7-Zip
476       */
477       // return WriteHeaderReal(item);
478       /*
479       false, // isPax
480       false, // zero_PackSize
481       false); // zero_MTime
482       */
483     }
484   }
485   else // !PosixMode
486   if (!DOES_NAME_FIT_IN_FIELD(item.Name) ||
487       !DOES_NAME_FIT_IN_FIELD(item.LinkName))
488   {
489     // here we can get all fields from main (item) or create new empty item
490     /*
491     CItem mi;
492     mi.SetDefaultWriteFields();
493     */
494     CItem mi = item;
495     mi.LinkName.Empty();
496     // SparseBlocks will be ignored by Is_Sparse()
497     // mi.SparseBlocks.Clear();
498     mi.Name = kLongLink;
499     // mi.Name = "././@BAD_LONG_LINK_TEST"; // for debug
500     // 21.07 : we set Mode and MTime props as in GNU TAR:
501     mi.Mode = 0644; // octal
502     mi.MTime = 0;
503 
504     mi.User.Empty();
505     mi.Group.Empty();
506     /*
507       gnu tar sets "root" for such items:
508         uid_to_uname (0, &uname);
509         gid_to_gname (0, &gname);
510     */
511     /*
512     mi.User = "root";
513     mi.Group = "root";
514     */
515     mi.UID = 0;
516     mi.GID = 0;
517     mi.DeviceMajor_Defined = false;
518     mi.DeviceMinor_Defined = false;
519 
520 
521     for (unsigned i = 0; i < 2; i++)
522     {
523       const AString *name;
524       // We suppose that GNU TAR also writes item for long link before item for LongName?
525       if (i == 0)
526       {
527         mi.LinkFlag = NLinkFlag::kGnu_LongLink;
528         name = &item.LinkName;
529       }
530       else
531       {
532         mi.LinkFlag = NLinkFlag::kGnu_LongName;
533         name = &item.Name;
534       }
535       if (DOES_NAME_FIT_IN_FIELD(*name))
536         continue;
537       // GNU TAR writes null character after NAME to file. We do same here:
538       const unsigned nameStreamSize = name->Len() + 1;
539       mi.PackSize = nameStreamSize;
540       // for (unsigned y = 0; y < 3; y++) { // for debug
541       RINOK(WriteHeaderReal(mi))
542       RINOK(Write_Data_And_Residual(name->Ptr(), nameStreamSize))
543       // }
544 
545       // for debug
546       /*
547       const unsigned kSize = (1 << 29) + 16;
548       CByteBuffer buf;
549       buf.Alloc(kSize);
550       memset(buf, 0, kSize);
551       memcpy(buf, name->Ptr(), name->Len());
552       const unsigned nameStreamSize = kSize;
553       mi.PackSize = nameStreamSize;
554       // for (unsigned y = 0; y < 3; y++) { // for debug
555       RINOK(WriteHeaderReal(mi));
556       RINOK(WriteBytes(buf, nameStreamSize));
557       RINOK(FillDataResidual(nameStreamSize));
558       */
559     }
560   }
561 
562   // bool fals = false; if (fals) // for debug: for bit-to-bit output compatibility with GNU TAR
563 
564   if (!DOES_NAME_FIT_IN_FIELD(item.Name))
565   {
566     const unsigned nameLen = item.Name.Len() - namePos;
567     if (!needPathCut)
568       Prefix.SetFrom(item.Name, namePos - 1);
569     else
570     {
571       Glob_Name = K_PREFIX_PATH_CUT "/_pc_";
572 
573       if (namePos == 0)
574         Glob_Name += "root";
575       else
576       {
577         Glob_Name += "crc32/";
578         char temp[12];
579         ConvertUInt32ToHex8Digits(CrcCalc(item.Name, namePos - 1), temp);
580         Glob_Name += temp;
581       }
582 
583       if (!allowPrefix || Glob_Name.Len() + 1 + nameLen <= kNameSize_Max)
584         Glob_Name.Add_Slash();
585       else
586       {
587         Prefix = Glob_Name;
588         Glob_Name.Empty();
589       }
590     }
591     Glob_Name.AddFrom(item.Name.Ptr(namePos), nameLen);
592   }
593 
594   return WriteHeaderReal(item);
595 }
596 
597 
Write_Data(const void * data,unsigned size)598 HRESULT COutArchive::Write_Data(const void *data, unsigned size)
599 {
600   Pos += size;
601   return WriteStream(Stream, data, size);
602 }
603 
Write_AfterDataResidual(UInt64 dataSize)604 HRESULT COutArchive::Write_AfterDataResidual(UInt64 dataSize)
605 {
606   const unsigned v = ((unsigned)dataSize & (kRecordSize - 1));
607   if (v == 0)
608     return S_OK;
609   const unsigned rem = kRecordSize - v;
610   Byte buf[kRecordSize];
611   memset(buf, 0, rem);
612   return Write_Data(buf, rem);
613 }
614 
615 
Write_Data_And_Residual(const void * data,unsigned size)616 HRESULT COutArchive::Write_Data_And_Residual(const void *data, unsigned size)
617 {
618   RINOK(Write_Data(data, size))
619   return Write_AfterDataResidual(size);
620 }
621 
622 
WriteFinishHeader()623 HRESULT COutArchive::WriteFinishHeader()
624 {
625   Byte record[kRecordSize];
626   memset(record, 0, kRecordSize);
627 
628   const unsigned kNumFinishRecords = 2;
629 
630   /* GNU TAR by default uses --blocking-factor=20 (512 * 20 = 10 KiB)
631      we also can use cluster alignment:
632   const unsigned numBlocks = (unsigned)(Pos / kRecordSize) + kNumFinishRecords;
633   const unsigned kNumClusterBlocks = (1 << 3); // 8 blocks = 4 KiB
634   const unsigned numFinishRecords = kNumFinishRecords + ((kNumClusterBlocks - numBlocks) & (kNumClusterBlocks - 1));
635   */
636 
637   for (unsigned i = 0; i < kNumFinishRecords; i++)
638   {
639     RINOK(Write_Data(record, kRecordSize))
640   }
641   return S_OK;
642 }
643 
644 }}
645