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