1 // List.cpp
2
3 #include "StdAfx.h"
4
5 #include "../../../Common/IntToString.h"
6 #include "../../../Common/MyCom.h"
7 #include "../../../Common/StdOutStream.h"
8 #include "../../../Common/StringConvert.h"
9 #include "../../../Common/UTFConvert.h"
10
11 #include "../../../Windows/ErrorMsg.h"
12 #include "../../../Windows/FileDir.h"
13 #include "../../../Windows/PropVariant.h"
14 #include "../../../Windows/PropVariantConv.h"
15
16 #include "../Common/OpenArchive.h"
17 #include "../Common/PropIDUtils.h"
18
19 #include "ConsoleClose.h"
20 #include "List.h"
21 #include "OpenCallbackConsole.h"
22
23 using namespace NWindows;
24 using namespace NCOM;
25
26 extern CStdOutStream *g_StdStream;
27 extern CStdOutStream *g_ErrStream;
28
29 static const char * const kPropIdToName[] =
30 {
31 "0"
32 , "1"
33 , "2"
34 , "Path"
35 , "Name"
36 , "Extension"
37 , "Folder"
38 , "Size"
39 , "Packed Size"
40 , "Attributes"
41 , "Created"
42 , "Accessed"
43 , "Modified"
44 , "Solid"
45 , "Commented"
46 , "Encrypted"
47 , "Split Before"
48 , "Split After"
49 , "Dictionary Size"
50 , "CRC"
51 , "Type"
52 , "Anti"
53 , "Method"
54 , "Host OS"
55 , "File System"
56 , "User"
57 , "Group"
58 , "Block"
59 , "Comment"
60 , "Position"
61 , "Path Prefix"
62 , "Folders"
63 , "Files"
64 , "Version"
65 , "Volume"
66 , "Multivolume"
67 , "Offset"
68 , "Links"
69 , "Blocks"
70 , "Volumes"
71 , "Time Type"
72 , "64-bit"
73 , "Big-endian"
74 , "CPU"
75 , "Physical Size"
76 , "Headers Size"
77 , "Checksum"
78 , "Characteristics"
79 , "Virtual Address"
80 , "ID"
81 , "Short Name"
82 , "Creator Application"
83 , "Sector Size"
84 , "Mode"
85 , "Symbolic Link"
86 , "Error"
87 , "Total Size"
88 , "Free Space"
89 , "Cluster Size"
90 , "Label"
91 , "Local Name"
92 , "Provider"
93 , "NT Security"
94 , "Alternate Stream"
95 , "Aux"
96 , "Deleted"
97 , "Tree"
98 , "SHA-1"
99 , "SHA-256"
100 , "Error Type"
101 , "Errors"
102 , "Errors"
103 , "Warnings"
104 , "Warning"
105 , "Streams"
106 , "Alternate Streams"
107 , "Alternate Streams Size"
108 , "Virtual Size"
109 , "Unpack Size"
110 , "Total Physical Size"
111 , "Volume Index"
112 , "SubType"
113 , "Short Comment"
114 , "Code Page"
115 , "Is not archive type"
116 , "Physical Size can't be detected"
117 , "Zeros Tail Is Allowed"
118 , "Tail Size"
119 , "Embedded Stub Size"
120 , "Link"
121 , "Hard Link"
122 , "iNode"
123 , "Stream ID"
124 , "Read-only"
125 , "Out Name"
126 , "Copy Link"
127 , "ArcFileName"
128 , "IsHash"
129 , "Metadata Changed"
130 , "User ID"
131 , "Group ID"
132 , "Device Major"
133 , "Device Minor"
134 , "Dev Major"
135 , "Dev Minor"
136 };
137
138 static const char kEmptyAttribChar = '.';
139
140 static const char * const kListing = "Listing archive: ";
141
142 static const char * const kString_Files = "files";
143 static const char * const kString_Dirs = "folders";
144 static const char * const kString_AltStreams = "alternate streams";
145 static const char * const kString_Streams = "streams";
146
147 static const char * const kError = "ERROR: ";
148
GetAttribString(UInt32 wa,bool isDir,bool allAttribs,char * s)149 static void GetAttribString(UInt32 wa, bool isDir, bool allAttribs, char *s)
150 {
151 if (isDir)
152 wa |= FILE_ATTRIBUTE_DIRECTORY;
153 if (allAttribs)
154 {
155 ConvertWinAttribToString(s, wa);
156 return;
157 }
158 s[0] = ((wa & FILE_ATTRIBUTE_DIRECTORY) != 0) ? 'D': kEmptyAttribChar;
159 s[1] = ((wa & FILE_ATTRIBUTE_READONLY) != 0) ? 'R': kEmptyAttribChar;
160 s[2] = ((wa & FILE_ATTRIBUTE_HIDDEN) != 0) ? 'H': kEmptyAttribChar;
161 s[3] = ((wa & FILE_ATTRIBUTE_SYSTEM) != 0) ? 'S': kEmptyAttribChar;
162 s[4] = ((wa & FILE_ATTRIBUTE_ARCHIVE) != 0) ? 'A': kEmptyAttribChar;
163 s[5] = 0;
164 }
165
166 enum EAdjustment
167 {
168 kLeft,
169 kCenter,
170 kRight
171 };
172
173 struct CFieldInfo
174 {
175 PROPID PropID;
176 bool IsRawProp;
177 UString NameU;
178 AString NameA;
179 EAdjustment TitleAdjustment;
180 EAdjustment TextAdjustment;
181 unsigned PrefixSpacesWidth;
182 unsigned Width;
183 };
184
185 struct CFieldInfoInit
186 {
187 PROPID PropID;
188 const char *Name;
189 EAdjustment TitleAdjustment;
190 EAdjustment TextAdjustment;
191 unsigned PrefixSpacesWidth;
192 unsigned Width;
193 };
194
195 static const CFieldInfoInit kStandardFieldTable[] =
196 {
197 { kpidMTime, " Date Time", kLeft, kLeft, 0, 19 },
198 { kpidAttrib, "Attr", kRight, kCenter, 1, 5 },
199 { kpidSize, "Size", kRight, kRight, 1, 12 },
200 { kpidPackSize, "Compressed", kRight, kRight, 1, 12 },
201 { kpidPath, "Name", kLeft, kLeft, 2, 24 }
202 };
203
204 const unsigned kNumSpacesMax = 32; // it must be larger than max CFieldInfoInit.Width
205 static const char *g_Spaces =
206 " " ;
207
PrintSpaces(unsigned numSpaces)208 static void PrintSpaces(unsigned numSpaces)
209 {
210 if (numSpaces > 0 && numSpaces <= kNumSpacesMax)
211 g_StdOut << g_Spaces + (kNumSpacesMax - numSpaces);
212 }
213
PrintSpacesToString(char * dest,unsigned numSpaces)214 static void PrintSpacesToString(char *dest, unsigned numSpaces)
215 {
216 unsigned i;
217 for (i = 0; i < numSpaces; i++)
218 dest[i] = ' ';
219 dest[i] = 0;
220 }
221
222 // extern int g_CodePage;
223
PrintUString(EAdjustment adj,unsigned width,const UString & s,AString & temp)224 static void PrintUString(EAdjustment adj, unsigned width, const UString &s, AString &temp)
225 {
226 /*
227 // we don't need multibyte align.
228 int codePage = g_CodePage;
229 if (codePage == -1)
230 codePage = CP_OEMCP;
231 if (codePage == CP_UTF8)
232 ConvertUnicodeToUTF8(s, temp);
233 else
234 UnicodeStringToMultiByte2(temp, s, (UINT)codePage);
235 */
236
237 unsigned numSpaces = 0;
238
239 if (width > s.Len())
240 {
241 numSpaces = width - s.Len();
242 unsigned numLeftSpaces = 0;
243 switch ((int)adj)
244 {
245 // case kLeft: numLeftSpaces = 0; break;
246 case kCenter: numLeftSpaces = numSpaces / 2; break;
247 case kRight: numLeftSpaces = numSpaces; break;
248 // case kLeft:
249 default: break;
250 }
251 PrintSpaces(numLeftSpaces);
252 numSpaces -= numLeftSpaces;
253 }
254
255 g_StdOut.PrintUString(s, temp);
256 PrintSpaces(numSpaces);
257 }
258
PrintString(EAdjustment adj,unsigned width,const char * s)259 static void PrintString(EAdjustment adj, unsigned width, const char *s)
260 {
261 unsigned numSpaces = 0;
262 unsigned len = (unsigned)strlen(s);
263
264 if (width > len)
265 {
266 numSpaces = width - len;
267 unsigned numLeftSpaces = 0;
268 switch ((int)adj)
269 {
270 // case kLeft: numLeftSpaces = 0; break;
271 case kCenter: numLeftSpaces = numSpaces / 2; break;
272 case kRight: numLeftSpaces = numSpaces; break;
273 // case kLeft:
274 default: break;
275 }
276 PrintSpaces(numLeftSpaces);
277 numSpaces -= numLeftSpaces;
278 }
279
280 g_StdOut << s;
281 PrintSpaces(numSpaces);
282 }
283
PrintStringToString(char * dest,EAdjustment adj,unsigned width,const char * textString)284 static void PrintStringToString(char *dest, EAdjustment adj, unsigned width, const char *textString)
285 {
286 unsigned numSpaces = 0;
287 unsigned len = (unsigned)strlen(textString);
288
289 if (width > len)
290 {
291 numSpaces = width - len;
292 unsigned numLeftSpaces = 0;
293 switch ((int)adj)
294 {
295 // case kLeft: numLeftSpaces = 0; break;
296 case kCenter: numLeftSpaces = numSpaces / 2; break;
297 case kRight: numLeftSpaces = numSpaces; break;
298 // case kLeft:
299 default: break;
300 }
301 PrintSpacesToString(dest, numLeftSpaces);
302 dest += numLeftSpaces;
303 numSpaces -= numLeftSpaces;
304 }
305
306 memcpy(dest, textString, len);
307 dest += len;
308 PrintSpacesToString(dest, numSpaces);
309 }
310
311 struct CListUInt64Def
312 {
313 UInt64 Val;
314 bool Def;
315
CListUInt64DefCListUInt64Def316 CListUInt64Def(): Val(0), Def(false) {}
AddCListUInt64Def317 void Add(UInt64 v) { Val += v; Def = true; }
AddCListUInt64Def318 void Add(const CListUInt64Def &v) { if (v.Def) Add(v.Val); }
319 };
320
321
322 struct CListFileTimeDef: public CArcTime
323 {
UpdateCListFileTimeDef324 void Update(const CListFileTimeDef &t)
325 {
326 if (t.Def && (!Def || CompareWith(t) < 0))
327 (*this) = t;
328 }
329 };
330
331
332
333 struct CListStat
334 {
335 CListUInt64Def Size;
336 CListUInt64Def PackSize;
337 CListFileTimeDef MTime;
338 UInt64 NumFiles;
339
CListStatCListStat340 CListStat(): NumFiles(0) {}
UpdateCListStat341 void Update(const CListStat &st)
342 {
343 Size.Add(st.Size);
344 PackSize.Add(st.PackSize);
345 MTime.Update(st.MTime);
346 NumFiles += st.NumFiles;
347 }
SetSizeDefIfNoFilesCListStat348 void SetSizeDefIfNoFiles() { if (NumFiles == 0) Size.Def = true; }
349 };
350
351 struct CListStat2
352 {
353 CListStat MainFiles;
354 CListStat AltStreams;
355 UInt64 NumDirs;
356
CListStat2CListStat2357 CListStat2(): NumDirs(0) {}
358
UpdateCListStat2359 void Update(const CListStat2 &st)
360 {
361 MainFiles.Update(st.MainFiles);
362 AltStreams.Update(st.AltStreams);
363 NumDirs += st.NumDirs;
364 }
GetNumStreamsCListStat2365 UInt64 GetNumStreams() const { return MainFiles.NumFiles + AltStreams.NumFiles; }
GetStatCListStat2366 CListStat &GetStat(bool altStreamsMode) { return altStreamsMode ? AltStreams : MainFiles; }
367 };
368
369 class CFieldPrinter
370 {
371 CObjectVector<CFieldInfo> _fields;
372
373 void AddProp(const wchar_t *name, PROPID propID, bool isRawProp);
374 public:
375 const CArc *Arc;
376 bool TechMode;
377 UString FilePath;
378 AString TempAString;
379 UString TempWString;
380 bool IsDir;
381
382 AString LinesString;
383
Clear()384 void Clear() { _fields.Clear(); LinesString.Empty(); }
385 void Init(const CFieldInfoInit *standardFieldTable, unsigned numItems);
386
387 HRESULT AddMainProps(IInArchive *archive);
388 HRESULT AddRawProps(IArchiveGetRawProps *getRawProps);
389
390 void PrintTitle();
391 void PrintTitleLines();
392 HRESULT PrintItemInfo(UInt32 index, const CListStat &st);
393 void PrintSum(const CListStat &st, UInt64 numDirs, const char *str);
394 void PrintSum(const CListStat2 &stat2);
395 };
396
Init(const CFieldInfoInit * standardFieldTable,unsigned numItems)397 void CFieldPrinter::Init(const CFieldInfoInit *standardFieldTable, unsigned numItems)
398 {
399 Clear();
400 for (unsigned i = 0; i < numItems; i++)
401 {
402 CFieldInfo &f = _fields.AddNew();
403 const CFieldInfoInit &fii = standardFieldTable[i];
404 f.PropID = fii.PropID;
405 f.IsRawProp = false;
406 f.NameA = fii.Name;
407 f.TitleAdjustment = fii.TitleAdjustment;
408 f.TextAdjustment = fii.TextAdjustment;
409 f.PrefixSpacesWidth = fii.PrefixSpacesWidth;
410 f.Width = fii.Width;
411
412 unsigned k;
413 for (k = 0; k < fii.PrefixSpacesWidth; k++)
414 LinesString.Add_Space();
415 for (k = 0; k < fii.Width; k++)
416 LinesString.Add_Minus();
417 }
418 }
419
GetPropName(PROPID propID,const wchar_t * name,AString & nameA,UString & nameU)420 static void GetPropName(PROPID propID, const wchar_t *name, AString &nameA, UString &nameU)
421 {
422 if (propID < Z7_ARRAY_SIZE(kPropIdToName))
423 {
424 nameA = kPropIdToName[propID];
425 return;
426 }
427 if (name)
428 nameU = name;
429 else
430 {
431 nameA.Empty();
432 nameA.Add_UInt32(propID);
433 }
434 }
435
AddProp(const wchar_t * name,PROPID propID,bool isRawProp)436 void CFieldPrinter::AddProp(const wchar_t *name, PROPID propID, bool isRawProp)
437 {
438 CFieldInfo f;
439 f.PropID = propID;
440 f.IsRawProp = isRawProp;
441 GetPropName(propID, name, f.NameA, f.NameU);
442 f.NameU += " = ";
443 if (!f.NameA.IsEmpty())
444 f.NameA += " = ";
445 else
446 {
447 const UString &s = f.NameU;
448 AString sA;
449 unsigned i;
450 for (i = 0; i < s.Len(); i++)
451 {
452 const wchar_t c = s[i];
453 if (c >= 0x80)
454 break;
455 sA.Add_Char((char)c);
456 }
457 if (i == s.Len())
458 f.NameA = sA;
459 }
460 _fields.Add(f);
461 }
462
AddMainProps(IInArchive * archive)463 HRESULT CFieldPrinter::AddMainProps(IInArchive *archive)
464 {
465 UInt32 numProps;
466 RINOK(archive->GetNumberOfProperties(&numProps))
467 for (UInt32 i = 0; i < numProps; i++)
468 {
469 CMyComBSTR name;
470 PROPID propID;
471 VARTYPE vt;
472 RINOK(archive->GetPropertyInfo(i, &name, &propID, &vt))
473 AddProp(name, propID, false);
474 }
475 return S_OK;
476 }
477
AddRawProps(IArchiveGetRawProps * getRawProps)478 HRESULT CFieldPrinter::AddRawProps(IArchiveGetRawProps *getRawProps)
479 {
480 UInt32 numProps;
481 RINOK(getRawProps->GetNumRawProps(&numProps))
482 for (UInt32 i = 0; i < numProps; i++)
483 {
484 CMyComBSTR name;
485 PROPID propID;
486 RINOK(getRawProps->GetRawPropInfo(i, &name, &propID))
487 AddProp(name, propID, true);
488 }
489 return S_OK;
490 }
491
PrintTitle()492 void CFieldPrinter::PrintTitle()
493 {
494 FOR_VECTOR (i, _fields)
495 {
496 const CFieldInfo &f = _fields[i];
497 PrintSpaces(f.PrefixSpacesWidth);
498 PrintString(f.TitleAdjustment, ((f.PropID == kpidPath) ? 0: f.Width), f.NameA);
499 }
500 }
501
PrintTitleLines()502 void CFieldPrinter::PrintTitleLines()
503 {
504 g_StdOut << LinesString;
505 }
506
PrintTime(char * dest,const CListFileTimeDef & t,bool showNS)507 static void PrintTime(char *dest, const CListFileTimeDef &t, bool showNS)
508 {
509 *dest = 0;
510 if (t.IsZero())
511 return;
512 int prec = kTimestampPrintLevel_SEC;
513 unsigned flags = 0;
514 if (showNS) // techmode
515 {
516 prec = kTimestampPrintLevel_NTFS;
517 if (t.Prec != 0)
518 {
519 prec = t.GetNumDigits();
520 if (prec < kTimestampPrintLevel_DAY)
521 prec = kTimestampPrintLevel_NTFS;
522 }
523 }
524 else
525 {
526 // we want same default number of characters, so we disable 'Z' marker:
527 flags = kTimestampPrintFlags_DisableZ;
528 }
529
530 ConvertUtcFileTimeToString2(t.FT, t.Ns100, dest, prec, flags);
531 }
532
533 #ifndef Z7_SFX
534
535 #endif
536
537 #define MY_ENDL endl
538
IsPropId_for_PathString(UInt32 propId)539 inline bool IsPropId_for_PathString(UInt32 propId)
540 {
541 return (propId == kpidPath
542 // || propId == kpidName
543 || propId == kpidSymLink
544 || propId == kpidHardLink
545 || propId == kpidCopyLink);
546 }
547
PrintItemInfo(UInt32 index,const CListStat & st)548 HRESULT CFieldPrinter::PrintItemInfo(UInt32 index, const CListStat &st)
549 {
550 char temp[128];
551 size_t tempPos = 0;
552
553 bool techMode = this->TechMode;
554 /*
555 if (techMode)
556 {
557 g_StdOut << "Index = ";
558 g_StdOut << (UInt64)index;
559 g_StdOut << endl;
560 }
561 */
562 FOR_VECTOR (i, _fields)
563 {
564 const CFieldInfo &f = _fields[i];
565
566 if (!techMode)
567 {
568 PrintSpacesToString(temp + tempPos, f.PrefixSpacesWidth);
569 tempPos += f.PrefixSpacesWidth;
570 }
571
572 if (techMode)
573 {
574 if (!f.NameA.IsEmpty())
575 g_StdOut << f.NameA;
576 else
577 g_StdOut << f.NameU;
578 }
579
580 if (f.PropID == kpidPath)
581 {
582 if (!techMode)
583 g_StdOut << temp;
584 g_StdOut.NormalizePrint_UString_Path(FilePath, TempWString, TempAString);
585 if (techMode)
586 g_StdOut << MY_ENDL;
587 continue;
588 }
589
590 const unsigned width = f.Width;
591
592 if (f.IsRawProp)
593 {
594 #ifndef Z7_SFX
595
596 const void *data;
597 UInt32 dataSize;
598 UInt32 propType;
599 RINOK(Arc->GetRawProps->GetRawProp(index, f.PropID, &data, &dataSize, &propType))
600
601 if (dataSize != 0)
602 {
603 bool needPrint = true;
604
605 if (f.PropID == kpidNtSecure)
606 {
607 if (propType != NPropDataType::kRaw)
608 return E_FAIL;
609 #ifndef Z7_SFX
610 ConvertNtSecureToString((const Byte *)data, dataSize, TempAString);
611 g_StdOut << TempAString;
612 needPrint = false;
613 #endif
614 }
615 else if (f.PropID == kpidNtReparse)
616 {
617 UString s;
618 if (ConvertNtReparseToString((const Byte *)data, dataSize, s))
619 {
620 needPrint = false;
621 g_StdOut.PrintUString(s, TempAString);
622 }
623 }
624
625 if (needPrint)
626 {
627 if (propType != NPropDataType::kRaw)
628 return E_FAIL;
629
630 const UInt32 kMaxDataSize = 64;
631
632 if (dataSize > kMaxDataSize)
633 {
634 g_StdOut << "data:";
635 g_StdOut << dataSize;
636 }
637 else
638 {
639 char hexStr[kMaxDataSize * 2 + 4];
640 ConvertDataToHex_Lower(hexStr, (const Byte *)data, dataSize);
641 g_StdOut << hexStr;
642 }
643 }
644 }
645
646 #endif
647 }
648 else
649 {
650 CPropVariant prop;
651 switch (f.PropID)
652 {
653 case kpidSize: if (st.Size.Def) prop = st.Size.Val; break;
654 case kpidPackSize: if (st.PackSize.Def) prop = st.PackSize.Val; break;
655 case kpidMTime:
656 {
657 const CListFileTimeDef &mtime = st.MTime;
658 if (mtime.Def)
659 prop.SetAsTimeFrom_FT_Prec_Ns100(mtime.FT, mtime.Prec, mtime.Ns100);
660 break;
661 }
662 default:
663 RINOK(Arc->Archive->GetProperty(index, f.PropID, &prop))
664 }
665 if (f.PropID == kpidAttrib && (prop.vt == VT_EMPTY || prop.vt == VT_UI4))
666 {
667 GetAttribString((prop.vt == VT_EMPTY) ? 0 : prop.ulVal, IsDir, techMode, temp + tempPos);
668 if (techMode)
669 g_StdOut << temp + tempPos;
670 else
671 tempPos += strlen(temp + tempPos);
672 }
673 else if (prop.vt == VT_EMPTY)
674 {
675 if (!techMode)
676 {
677 PrintSpacesToString(temp + tempPos, width);
678 tempPos += width;
679 }
680 }
681 else if (prop.vt == VT_FILETIME)
682 {
683 CListFileTimeDef t;
684 t.Set_From_Prop(prop);
685 PrintTime(temp + tempPos, t, techMode);
686 if (techMode)
687 g_StdOut << temp + tempPos;
688 else
689 {
690 size_t len = strlen(temp + tempPos);
691 tempPos += len;
692 if (len < (unsigned)f.Width)
693 {
694 len = f.Width - len;
695 PrintSpacesToString(temp + tempPos, (unsigned)len);
696 tempPos += len;
697 }
698 }
699 }
700 else if (prop.vt == VT_BSTR)
701 {
702 TempWString.SetFromBstr(prop.bstrVal);
703 // do we need multi-line support here ?
704 if (IsPropId_for_PathString(f.PropID))
705 g_StdOut.Normalize_UString_Path(TempWString);
706 else
707 g_StdOut.Normalize_UString(TempWString);
708 if (techMode)
709 g_StdOut.PrintUString(TempWString, TempAString);
710 else
711 PrintUString(f.TextAdjustment, width, TempWString, TempAString);
712 }
713 else
714 {
715 char s[64];
716 ConvertPropertyToShortString2(s, prop, f.PropID);
717 if (techMode)
718 g_StdOut << s;
719 else
720 {
721 PrintStringToString(temp + tempPos, f.TextAdjustment, width, s);
722 tempPos += strlen(temp + tempPos);
723 }
724 }
725 }
726 if (techMode)
727 g_StdOut << MY_ENDL;
728 }
729 g_StdOut << MY_ENDL;
730 return S_OK;
731 }
732
PrintNumber(EAdjustment adj,unsigned width,const CListUInt64Def & value)733 static void PrintNumber(EAdjustment adj, unsigned width, const CListUInt64Def &value)
734 {
735 char s[32];
736 s[0] = 0;
737 if (value.Def)
738 ConvertUInt64ToString(value.Val, s);
739 PrintString(adj, width, s);
740 }
741
742 void Print_UInt64_and_String(AString &s, UInt64 val, const char *name);
743
PrintSum(const CListStat & st,UInt64 numDirs,const char * str)744 void CFieldPrinter::PrintSum(const CListStat &st, UInt64 numDirs, const char *str)
745 {
746 FOR_VECTOR (i, _fields)
747 {
748 const CFieldInfo &f = _fields[i];
749 PrintSpaces(f.PrefixSpacesWidth);
750 if (f.PropID == kpidSize)
751 PrintNumber(f.TextAdjustment, f.Width, st.Size);
752 else if (f.PropID == kpidPackSize)
753 PrintNumber(f.TextAdjustment, f.Width, st.PackSize);
754 else if (f.PropID == kpidMTime)
755 {
756 char s[64];
757 s[0] = 0;
758 if (st.MTime.Def)
759 PrintTime(s, st.MTime, false); // showNS
760 PrintString(f.TextAdjustment, f.Width, s);
761 }
762 else if (f.PropID == kpidPath)
763 {
764 AString s;
765 Print_UInt64_and_String(s, st.NumFiles, str);
766 if (numDirs != 0)
767 {
768 s += ", ";
769 Print_UInt64_and_String(s, numDirs, kString_Dirs);
770 }
771 PrintString(f.TextAdjustment, 0, s);
772 }
773 else
774 PrintString(f.TextAdjustment, f.Width, "");
775 }
776 g_StdOut << endl;
777 }
778
PrintSum(const CListStat2 & stat2)779 void CFieldPrinter::PrintSum(const CListStat2 &stat2)
780 {
781 PrintSum(stat2.MainFiles, stat2.NumDirs, kString_Files);
782 if (stat2.AltStreams.NumFiles != 0)
783 {
784 PrintSum(stat2.AltStreams, 0, kString_AltStreams);
785 CListStat st = stat2.MainFiles;
786 st.Update(stat2.AltStreams);
787 PrintSum(st, 0, kString_Streams);
788 }
789 }
790
GetUInt64Value(IInArchive * archive,UInt32 index,PROPID propID,CListUInt64Def & value)791 static HRESULT GetUInt64Value(IInArchive *archive, UInt32 index, PROPID propID, CListUInt64Def &value)
792 {
793 value.Val = 0;
794 value.Def = false;
795 CPropVariant prop;
796 RINOK(archive->GetProperty(index, propID, &prop))
797 value.Def = ConvertPropVariantToUInt64(prop, value.Val);
798 return S_OK;
799 }
800
GetItemMTime(IInArchive * archive,UInt32 index,CListFileTimeDef & t)801 static HRESULT GetItemMTime(IInArchive *archive, UInt32 index, CListFileTimeDef &t)
802 {
803 /* maybe we could call CArc::GetItemMTime(UInt32 index, CArcTime &ft, bool &defined) here
804 that can set default timestamp, if not defined */
805 t.Clear();
806 // t.Def = false;
807 CPropVariant prop;
808 RINOK(archive->GetProperty(index, kpidMTime, &prop))
809 if (prop.vt == VT_FILETIME)
810 t.Set_From_Prop(prop);
811 else if (prop.vt != VT_EMPTY)
812 return E_FAIL;
813 return S_OK;
814 }
815
PrintPropNameAndNumber(CStdOutStream & so,const char * name,UInt64 val)816 static void PrintPropNameAndNumber(CStdOutStream &so, const char *name, UInt64 val)
817 {
818 so << name << ": " << val << endl;
819 }
820
PrintPropName_and_Eq(CStdOutStream & so,PROPID propID)821 static void PrintPropName_and_Eq(CStdOutStream &so, PROPID propID)
822 {
823 const char *s;
824 char temp[16];
825 if (propID < Z7_ARRAY_SIZE(kPropIdToName))
826 s = kPropIdToName[propID];
827 else
828 {
829 ConvertUInt32ToString(propID, temp);
830 s = temp;
831 }
832 so << s << " = ";
833 }
834
PrintPropNameAndNumber(CStdOutStream & so,PROPID propID,UInt64 val)835 static void PrintPropNameAndNumber(CStdOutStream &so, PROPID propID, UInt64 val)
836 {
837 PrintPropName_and_Eq(so, propID);
838 so << val << endl;
839 }
840
PrintPropNameAndNumber_Signed(CStdOutStream & so,PROPID propID,Int64 val)841 static void PrintPropNameAndNumber_Signed(CStdOutStream &so, PROPID propID, Int64 val)
842 {
843 PrintPropName_and_Eq(so, propID);
844 so << val << endl;
845 }
846
847
UString_Replace_CRLF_to_LF(UString & s)848 static void UString_Replace_CRLF_to_LF(UString &s)
849 {
850 // s.Replace(L"\r\n", L"\n");
851 wchar_t *src = s.GetBuf();
852 wchar_t *dest = src;
853 for (;;)
854 {
855 wchar_t c = *src++;
856 if (c == 0)
857 break;
858 if (c == '\r' && *src == '\n')
859 {
860 src++;
861 c = '\n';
862 }
863 *dest++ = c;
864 }
865 s.ReleaseBuf_SetEnd((unsigned)(size_t)(dest - s.GetBuf()));
866 }
867
PrintPropVal_MultiLine(CStdOutStream & so,const wchar_t * val)868 static void PrintPropVal_MultiLine(CStdOutStream &so, const wchar_t *val)
869 {
870 UString s (val);
871 if (s.Find(L'\n') >= 0)
872 {
873 so << endl;
874 so << "{";
875 so << endl;
876 UString_Replace_CRLF_to_LF(s);
877 UString temp;
878 unsigned start = 0;
879 for (;;)
880 {
881 unsigned size = s.Len() - start;
882 if (size == 0)
883 break;
884 const int next = s.Find(L'\n', start);
885 if (next >= 0)
886 size = (unsigned)next - start;
887 temp.SetFrom(s.Ptr() + start, size);
888 so.NormalizePrint_UString(temp);
889 so << endl;
890 if (next < 0)
891 break;
892 start = (unsigned)next + 1;
893 }
894 so << "}";
895 }
896 else
897 {
898 so.NormalizePrint_UString(s);
899 }
900 so << endl;
901 }
902
903
PrintPropPair(CStdOutStream & so,const char * name,const wchar_t * val,bool multiLine,bool isPath=false)904 static void PrintPropPair(CStdOutStream &so, const char *name, const wchar_t *val, bool multiLine, bool isPath = false)
905 {
906 so << name << " = ";
907 if (multiLine)
908 {
909 PrintPropVal_MultiLine(so, val);
910 return;
911 }
912 UString s (val);
913 if (isPath)
914 so.Normalize_UString_Path(s);
915 else
916 so.Normalize_UString(s);
917 so << s;
918 so << endl;
919 }
920
921
PrintPropPair_Path(CStdOutStream & so,const wchar_t * path)922 static void PrintPropPair_Path(CStdOutStream &so, const wchar_t *path)
923 {
924 PrintPropPair(so, "Path", path,
925 false, // multiLine
926 true); // isPath
927 }
928
PrintPropertyPair2(CStdOutStream & so,PROPID propID,const wchar_t * name,const CPropVariant & prop)929 static void PrintPropertyPair2(CStdOutStream &so, PROPID propID, const wchar_t *name, const CPropVariant &prop)
930 {
931 UString s;
932 const int levelTopLimit = 9; // 1ns level
933 ConvertPropertyToString2(s, prop, propID, levelTopLimit);
934 if (!s.IsEmpty())
935 {
936 AString nameA;
937 UString nameU;
938 GetPropName(propID, name, nameA, nameU);
939 if (!nameA.IsEmpty())
940 so << nameA;
941 else
942 so << nameU;
943 so << " = ";
944 PrintPropVal_MultiLine(so, s);
945 }
946 }
947
PrintArcProp(CStdOutStream & so,IInArchive * archive,PROPID propID,const wchar_t * name)948 static HRESULT PrintArcProp(CStdOutStream &so, IInArchive *archive, PROPID propID, const wchar_t *name)
949 {
950 CPropVariant prop;
951 RINOK(archive->GetArchiveProperty(propID, &prop))
952 PrintPropertyPair2(so, propID, name, prop);
953 return S_OK;
954 }
955
PrintArcTypeError(CStdOutStream & so,const UString & type,bool isWarning)956 static void PrintArcTypeError(CStdOutStream &so, const UString &type, bool isWarning)
957 {
958 so << "Open " << (isWarning ? "WARNING" : "ERROR")
959 << ": Cannot open the file as ["
960 << type
961 << "] archive"
962 << endl;
963 }
964
965 int Find_FileName_InSortedVector(const UStringVector &fileName, const UString& name);
966
967 void PrintErrorFlags(CStdOutStream &so, const char *s, UInt32 errorFlags);
968
ErrorInfo_Print(CStdOutStream & so,const CArcErrorInfo & er)969 static void ErrorInfo_Print(CStdOutStream &so, const CArcErrorInfo &er)
970 {
971 PrintErrorFlags(so, "ERRORS:", er.GetErrorFlags());
972 if (!er.ErrorMessage.IsEmpty())
973 PrintPropPair(so, "ERROR", er.ErrorMessage, true);
974
975 PrintErrorFlags(so, "WARNINGS:", er.GetWarningFlags());
976 if (!er.WarningMessage.IsEmpty())
977 PrintPropPair(so, "WARNING", er.WarningMessage, true);
978 }
979
980 HRESULT Print_OpenArchive_Props(CStdOutStream &so, const CCodecs *codecs, const CArchiveLink &arcLink);
Print_OpenArchive_Props(CStdOutStream & so,const CCodecs * codecs,const CArchiveLink & arcLink)981 HRESULT Print_OpenArchive_Props(CStdOutStream &so, const CCodecs *codecs, const CArchiveLink &arcLink)
982 {
983 FOR_VECTOR (r, arcLink.Arcs)
984 {
985 const CArc &arc = arcLink.Arcs[r];
986 const CArcErrorInfo &er = arc.ErrorInfo;
987
988 so << "--\n";
989 PrintPropPair_Path(so, arc.Path);
990 if (er.ErrorFormatIndex >= 0)
991 {
992 if (er.ErrorFormatIndex == arc.FormatIndex)
993 so << "Warning: The archive is open with offset" << endl;
994 else
995 PrintArcTypeError(so, codecs->GetFormatNamePtr(er.ErrorFormatIndex), true);
996 }
997 PrintPropPair(so, "Type", codecs->GetFormatNamePtr(arc.FormatIndex), false);
998
999 ErrorInfo_Print(so, er);
1000
1001 Int64 offset = arc.GetGlobalOffset();
1002 if (offset != 0)
1003 PrintPropNameAndNumber_Signed(so, kpidOffset, offset);
1004 IInArchive *archive = arc.Archive;
1005 RINOK(PrintArcProp(so, archive, kpidPhySize, NULL))
1006 if (er.TailSize != 0)
1007 PrintPropNameAndNumber(so, kpidTailSize, er.TailSize);
1008 {
1009 UInt32 numProps;
1010 RINOK(archive->GetNumberOfArchiveProperties(&numProps))
1011
1012 for (UInt32 j = 0; j < numProps; j++)
1013 {
1014 CMyComBSTR name;
1015 PROPID propID;
1016 VARTYPE vt;
1017 RINOK(archive->GetArchivePropertyInfo(j, &name, &propID, &vt))
1018 RINOK(PrintArcProp(so, archive, propID, name))
1019 }
1020 }
1021
1022 if (r != arcLink.Arcs.Size() - 1)
1023 {
1024 UInt32 numProps;
1025 so << "----\n";
1026 if (archive->GetNumberOfProperties(&numProps) == S_OK)
1027 {
1028 UInt32 mainIndex = arcLink.Arcs[r + 1].SubfileIndex;
1029 for (UInt32 j = 0; j < numProps; j++)
1030 {
1031 CMyComBSTR name;
1032 PROPID propID;
1033 VARTYPE vt;
1034 RINOK(archive->GetPropertyInfo(j, &name, &propID, &vt))
1035 CPropVariant prop;
1036 RINOK(archive->GetProperty(mainIndex, propID, &prop))
1037 PrintPropertyPair2(so, propID, name, prop);
1038 }
1039 }
1040 }
1041 }
1042 return S_OK;
1043 }
1044
1045 HRESULT Print_OpenArchive_Error(CStdOutStream &so, const CCodecs *codecs, const CArchiveLink &arcLink);
Print_OpenArchive_Error(CStdOutStream & so,const CCodecs * codecs,const CArchiveLink & arcLink)1046 HRESULT Print_OpenArchive_Error(CStdOutStream &so, const CCodecs *codecs, const CArchiveLink &arcLink)
1047 {
1048 #ifndef Z7_NO_CRYPTO
1049 if (arcLink.PasswordWasAsked)
1050 so << "Cannot open encrypted archive. Wrong password?";
1051 else
1052 #endif
1053 {
1054 if (arcLink.NonOpen_ErrorInfo.ErrorFormatIndex >= 0)
1055 {
1056 so.NormalizePrint_UString_Path(arcLink.NonOpen_ArcPath);
1057 so << endl;
1058 PrintArcTypeError(so, codecs->Formats[(unsigned)arcLink.NonOpen_ErrorInfo.ErrorFormatIndex].Name, false);
1059 }
1060 else
1061 so << "Cannot open the file as archive";
1062 }
1063
1064 so << endl;
1065 so << endl;
1066 ErrorInfo_Print(so, arcLink.NonOpen_ErrorInfo);
1067
1068 return S_OK;
1069 }
1070
1071 bool CensorNode_CheckPath(const NWildcard::CCensorNode &node, const CReadArcItem &item);
1072
ListArchives(const CListOptions & listOptions,CCodecs * codecs,const CObjectVector<COpenType> & types,const CIntVector & excludedFormats,bool stdInMode,UStringVector & arcPaths,UStringVector & arcPathsFull,bool processAltStreams,bool showAltStreams,const NWildcard::CCensorNode & wildcardCensor,bool enableHeaders,bool techMode,bool & passwordEnabled,UString & password,const CObjectVector<CProperty> * props,UInt64 & numErrors,UInt64 & numWarnings)1073 HRESULT ListArchives(
1074 const CListOptions &listOptions,
1075 CCodecs *codecs,
1076 const CObjectVector<COpenType> &types,
1077 const CIntVector &excludedFormats,
1078 bool stdInMode,
1079 UStringVector &arcPaths, UStringVector &arcPathsFull,
1080 bool processAltStreams, bool showAltStreams,
1081 const NWildcard::CCensorNode &wildcardCensor,
1082 bool enableHeaders, bool techMode,
1083 #ifndef Z7_NO_CRYPTO
1084 bool &passwordEnabled, UString &password,
1085 #endif
1086 #ifndef Z7_SFX
1087 const CObjectVector<CProperty> *props,
1088 #endif
1089 UInt64 &numErrors,
1090 UInt64 &numWarnings)
1091 {
1092 bool allFilesAreAllowed = wildcardCensor.AreAllAllowed();
1093
1094 numErrors = 0;
1095 numWarnings = 0;
1096
1097 CFieldPrinter fp;
1098 if (!techMode)
1099 fp.Init(kStandardFieldTable, Z7_ARRAY_SIZE(kStandardFieldTable));
1100
1101 CListStat2 stat2total;
1102
1103 CBoolArr skipArcs(arcPaths.Size());
1104 unsigned arcIndex;
1105 for (arcIndex = 0; arcIndex < arcPaths.Size(); arcIndex++)
1106 skipArcs[arcIndex] = false;
1107 UInt64 numVolumes = 0;
1108 UInt64 numArcs = 0;
1109 UInt64 totalArcSizes = 0;
1110
1111 HRESULT lastError = 0;
1112
1113 for (arcIndex = 0; arcIndex < arcPaths.Size(); arcIndex++)
1114 {
1115 if (skipArcs[arcIndex])
1116 continue;
1117 const UString &arcPath = arcPaths[arcIndex];
1118 UInt64 arcPackSize = 0;
1119
1120 if (!stdInMode)
1121 {
1122 NFile::NFind::CFileInfo fi;
1123 if (!fi.Find_FollowLink(us2fs(arcPath)))
1124 {
1125 DWORD errorCode = GetLastError();
1126 if (errorCode == 0)
1127 errorCode = ERROR_FILE_NOT_FOUND;
1128 lastError = HRESULT_FROM_WIN32(errorCode);
1129 g_StdOut.Flush();
1130 if (g_ErrStream)
1131 {
1132 *g_ErrStream << endl << kError << NError::MyFormatMessage(errorCode) << endl;
1133 g_ErrStream->NormalizePrint_UString_Path(arcPath);
1134 *g_ErrStream << endl << endl;
1135 }
1136 numErrors++;
1137 continue;
1138 }
1139 if (fi.IsDir())
1140 {
1141 g_StdOut.Flush();
1142 if (g_ErrStream)
1143 {
1144 *g_ErrStream << endl << kError;
1145 g_ErrStream->NormalizePrint_UString_Path(arcPath);
1146 *g_ErrStream << " is not a file" << endl << endl;
1147 }
1148 numErrors++;
1149 continue;
1150 }
1151 arcPackSize = fi.Size;
1152 totalArcSizes += arcPackSize;
1153 }
1154
1155 CArchiveLink arcLink;
1156
1157 COpenCallbackConsole openCallback;
1158 openCallback.Init(&g_StdOut, g_ErrStream, NULL, listOptions.DisablePercents);
1159
1160 #ifndef Z7_NO_CRYPTO
1161
1162 openCallback.PasswordIsDefined = passwordEnabled;
1163 openCallback.Password = password;
1164
1165 #endif
1166
1167 /*
1168 CObjectVector<COptionalOpenProperties> optPropsVector;
1169 COptionalOpenProperties &optProps = optPropsVector.AddNew();
1170 optProps.Props = *props;
1171 */
1172
1173 COpenOptions options;
1174 #ifndef Z7_SFX
1175 options.props = props;
1176 #endif
1177 options.codecs = codecs;
1178 options.types = &types;
1179 options.excludedFormats = &excludedFormats;
1180 options.stdInMode = stdInMode;
1181 options.stream = NULL;
1182 options.filePath = arcPath;
1183
1184 if (enableHeaders)
1185 {
1186 g_StdOut << endl << kListing;
1187 g_StdOut.NormalizePrint_UString_Path(arcPath);
1188 g_StdOut << endl << endl;
1189 }
1190
1191 HRESULT result = arcLink.Open_Strict(options, &openCallback);
1192
1193 if (result != S_OK)
1194 {
1195 if (result == E_ABORT)
1196 return result;
1197 if (result != S_FALSE)
1198 lastError = result;
1199 g_StdOut.Flush();
1200 if (g_ErrStream)
1201 {
1202 *g_ErrStream << endl << kError;
1203 g_ErrStream->NormalizePrint_UString_Path(arcPath);
1204 *g_ErrStream << " : ";
1205 if (result == S_FALSE)
1206 {
1207 Print_OpenArchive_Error(*g_ErrStream, codecs, arcLink);
1208 }
1209 else
1210 {
1211 *g_ErrStream << "opening : ";
1212 if (result == E_OUTOFMEMORY)
1213 *g_ErrStream << "Can't allocate required memory";
1214 else
1215 *g_ErrStream << NError::MyFormatMessage(result);
1216 }
1217 *g_ErrStream << endl;
1218 }
1219 numErrors++;
1220 continue;
1221 }
1222
1223 {
1224 FOR_VECTOR (r, arcLink.Arcs)
1225 {
1226 const CArcErrorInfo &arc = arcLink.Arcs[r].ErrorInfo;
1227 if (!arc.WarningMessage.IsEmpty())
1228 numWarnings++;
1229 if (arc.AreThereWarnings())
1230 numWarnings++;
1231 if (arc.ErrorFormatIndex >= 0)
1232 numWarnings++;
1233 if (arc.AreThereErrors())
1234 {
1235 numErrors++;
1236 // break;
1237 }
1238 if (!arc.ErrorMessage.IsEmpty())
1239 numErrors++;
1240 }
1241 }
1242
1243 numArcs++;
1244 numVolumes++;
1245
1246 if (!stdInMode)
1247 {
1248 numVolumes += arcLink.VolumePaths.Size();
1249 totalArcSizes += arcLink.VolumesSize;
1250 FOR_VECTOR (v, arcLink.VolumePaths)
1251 {
1252 int index = Find_FileName_InSortedVector(arcPathsFull, arcLink.VolumePaths[v]);
1253 if (index >= 0 && (unsigned)index > arcIndex)
1254 skipArcs[(unsigned)index] = true;
1255 }
1256 }
1257
1258
1259 if (enableHeaders)
1260 {
1261 RINOK(Print_OpenArchive_Props(g_StdOut, codecs, arcLink))
1262
1263 g_StdOut << endl;
1264 if (techMode)
1265 g_StdOut << "----------\n";
1266 }
1267
1268 if (enableHeaders && !techMode)
1269 {
1270 fp.PrintTitle();
1271 g_StdOut << endl;
1272 fp.PrintTitleLines();
1273 g_StdOut << endl;
1274 }
1275
1276 const CArc &arc = arcLink.Arcs.Back();
1277 fp.Arc = &arc;
1278 fp.TechMode = techMode;
1279 IInArchive *archive = arc.Archive;
1280 if (techMode)
1281 {
1282 fp.Clear();
1283 RINOK(fp.AddMainProps(archive))
1284 if (arc.GetRawProps)
1285 {
1286 RINOK(fp.AddRawProps(arc.GetRawProps))
1287 }
1288 }
1289
1290 CListStat2 stat2;
1291
1292 UInt32 numItems;
1293 RINOK(archive->GetNumberOfItems(&numItems))
1294
1295 CReadArcItem item;
1296 UStringVector pathParts;
1297
1298 for (UInt32 i = 0; i < numItems; i++)
1299 {
1300 if (NConsoleClose::TestBreakSignal())
1301 return E_ABORT;
1302
1303 HRESULT res = arc.GetItem_Path2(i, fp.FilePath);
1304
1305 if (stdInMode && res == E_INVALIDARG)
1306 break;
1307 RINOK(res)
1308
1309 if (arc.Ask_Aux)
1310 {
1311 bool isAux;
1312 RINOK(Archive_IsItem_Aux(archive, i, isAux))
1313 if (isAux)
1314 continue;
1315 }
1316
1317 bool isAltStream = false;
1318 if (arc.Ask_AltStream)
1319 {
1320 RINOK(Archive_IsItem_AltStream(archive, i, isAltStream))
1321 if (isAltStream && !processAltStreams)
1322 continue;
1323 }
1324
1325 RINOK(Archive_IsItem_Dir(archive, i, fp.IsDir))
1326
1327 if (fp.IsDir ? listOptions.ExcludeDirItems : listOptions.ExcludeFileItems)
1328 continue;
1329
1330 if (!allFilesAreAllowed)
1331 {
1332 if (isAltStream)
1333 {
1334 RINOK(arc.GetItem(i, item))
1335 if (!CensorNode_CheckPath(wildcardCensor, item))
1336 continue;
1337 }
1338 else
1339 {
1340 SplitPathToParts(fp.FilePath, pathParts);
1341 bool include;
1342 if (!wildcardCensor.CheckPathVect(pathParts, !fp.IsDir, include))
1343 continue;
1344 if (!include)
1345 continue;
1346 }
1347 }
1348
1349 CListStat st;
1350
1351 RINOK(GetUInt64Value(archive, i, kpidSize, st.Size))
1352 RINOK(GetUInt64Value(archive, i, kpidPackSize, st.PackSize))
1353 RINOK(GetItemMTime(archive, i, st.MTime))
1354
1355 if (fp.IsDir)
1356 stat2.NumDirs++;
1357 else
1358 st.NumFiles = 1;
1359 stat2.GetStat(isAltStream).Update(st);
1360
1361 if (isAltStream && !showAltStreams)
1362 continue;
1363 RINOK(fp.PrintItemInfo(i, st))
1364 }
1365
1366 UInt64 numStreams = stat2.GetNumStreams();
1367 if (!stdInMode
1368 && !stat2.MainFiles.PackSize.Def
1369 && !stat2.AltStreams.PackSize.Def)
1370 {
1371 if (arcLink.VolumePaths.Size() != 0)
1372 arcPackSize += arcLink.VolumesSize;
1373 stat2.MainFiles.PackSize.Add((numStreams == 0) ? 0 : arcPackSize);
1374 }
1375
1376 stat2.MainFiles.SetSizeDefIfNoFiles();
1377 stat2.AltStreams.SetSizeDefIfNoFiles();
1378
1379 if (enableHeaders && !techMode)
1380 {
1381 fp.PrintTitleLines();
1382 g_StdOut << endl;
1383 fp.PrintSum(stat2);
1384 }
1385
1386 if (enableHeaders)
1387 {
1388 if (arcLink.NonOpen_ErrorInfo.ErrorFormatIndex >= 0)
1389 {
1390 g_StdOut << "----------\n";
1391 PrintPropPair_Path(g_StdOut, arcLink.NonOpen_ArcPath);
1392 PrintArcTypeError(g_StdOut, codecs->Formats[(unsigned)arcLink.NonOpen_ErrorInfo.ErrorFormatIndex].Name, false);
1393 }
1394 }
1395
1396 stat2total.Update(stat2);
1397
1398 g_StdOut.Flush();
1399 }
1400
1401 if (enableHeaders && !techMode && (arcPaths.Size() > 1 || numVolumes > 1))
1402 {
1403 g_StdOut << endl;
1404 fp.PrintTitleLines();
1405 g_StdOut << endl;
1406 fp.PrintSum(stat2total);
1407 g_StdOut << endl;
1408 PrintPropNameAndNumber(g_StdOut, "Archives", numArcs);
1409 PrintPropNameAndNumber(g_StdOut, "Volumes", numVolumes);
1410 PrintPropNameAndNumber(g_StdOut, "Total archives size", totalArcSizes);
1411 }
1412
1413 if (numErrors == 1 && lastError != 0)
1414 return lastError;
1415
1416 return S_OK;
1417 }
1418