xref: /aosp_15_r20/external/lzma/CPP/7zip/UI/Common/PropIDUtils.cpp (revision f6dc9357d832569d4d1f5d24eacdb3935a1ae8e6)
1 // PropIDUtils.cpp
2 
3 #include "StdAfx.h"
4 
5 #include "../../../../C/CpuArch.h"
6 
7 #include "../../../Common/IntToString.h"
8 #include "../../../Common/StringConvert.h"
9 
10 #include "../../../Windows/FileIO.h"
11 #include "../../../Windows/PropVariantConv.h"
12 
13 #include "../../PropID.h"
14 
15 #include "PropIDUtils.h"
16 
17 #ifndef Z7_SFX
18 #define Get16(x) GetUi16(x)
19 #define Get32(x) GetUi32(x)
20 #endif
21 
22 using namespace NWindows;
23 
24 static const unsigned kNumWinAtrribFlags = 30;
25 static const char g_WinAttribChars[kNumWinAtrribFlags + 1] = "RHS8DAdNTsLCOIEVvX.PU.M......B";
26 
27 /*
28 FILE_ATTRIBUTE_
29 
30 0 READONLY
31 1 HIDDEN
32 2 SYSTEM
33 3 (Volume label - obsolete)
34 4 DIRECTORY
35 5 ARCHIVE
36 6 DEVICE
37 7 NORMAL
38 8 TEMPORARY
39 9 SPARSE_FILE
40 10 REPARSE_POINT
41 11 COMPRESSED
42 12 OFFLINE
43 13 NOT_CONTENT_INDEXED (I - Win10 attrib/Explorer)
44 14 ENCRYPTED
45 15 INTEGRITY_STREAM (V - ReFS Win8/Win2012)
46 16 VIRTUAL (reserved)
47 17 NO_SCRUB_DATA (X - ReFS Win8/Win2012 attrib)
48 18 RECALL_ON_OPEN or EA
49 19 PINNED
50 20 UNPINNED
51 21 STRICTLY_SEQUENTIAL  (10.0.16267)
52 22 RECALL_ON_DATA_ACCESS
53 29 STRICTLY_SEQUENTIAL  (10.0.17134+) (SMR Blob)
54 */
55 
56 
57 static const char kPosixTypes[16] = { '0', 'p', 'c', '3', 'd', '5', 'b', '7', '-', '9', 'l', 'B', 's', 'D', 'E', 'F' };
58 #define MY_ATTR_CHAR(a, n, c) (((a) & (1 << (n))) ? c : '-')
59 
ConvertPosixAttribToString(char * s,UInt32 a)60 static void ConvertPosixAttribToString(char *s, UInt32 a) throw()
61 {
62   s[0] = kPosixTypes[(a >> 12) & 0xF];
63   for (int i = 6; i >= 0; i -= 3)
64   {
65     s[7 - i] = MY_ATTR_CHAR(a, i + 2, 'r');
66     s[8 - i] = MY_ATTR_CHAR(a, i + 1, 'w');
67     s[9 - i] = MY_ATTR_CHAR(a, i + 0, 'x');
68   }
69   if ((a & 0x800) != 0) s[3] = ((a & (1 << 6)) ? 's' : 'S'); // S_ISUID
70   if ((a & 0x400) != 0) s[6] = ((a & (1 << 3)) ? 's' : 'S'); // S_ISGID
71   if ((a & 0x200) != 0) s[9] = ((a & (1 << 0)) ? 't' : 'T'); // S_ISVTX
72   s[10] = 0;
73 
74   a &= ~(UInt32)0xFFFF;
75   if (a != 0)
76   {
77     s[10] = ' ';
78     ConvertUInt32ToHex8Digits(a, s + 11);
79   }
80 }
81 
82 
ConvertWinAttribToString(char * s,UInt32 wa)83 void ConvertWinAttribToString(char *s, UInt32 wa) throw()
84 {
85   /*
86   some programs store posix attributes in high 16 bits.
87     p7zip - stores additional 0x8000 flag marker.
88     macos - stores additional 0x4000 flag marker.
89     info-zip - no additional marker.
90   But this code works with Attrib from internal 7zip code.
91   So we expect that 0x8000 marker is set, if there are posix attributes.
92   (DT_UNKNOWN == 0) type in high bits is possible in some case for linux files.
93   0x8000 flag is possible also in ReFS (Windows)?
94   */
95 
96   const bool isPosix = (
97       (wa & 0x8000) != 0 // FILE_ATTRIBUTE_UNIX_EXTENSION;
98       // && (wa & 0xFFFF0000u) != 0
99       );
100 
101   UInt32 posix = 0;
102   if (isPosix)
103   {
104     posix = wa >> 16;
105     if ((wa & 0xF0000000u) != 0)
106       wa &= (UInt32)0x3FFF;
107   }
108 
109   for (unsigned i = 0; i < kNumWinAtrribFlags; i++)
110   {
111     const UInt32 flag = (UInt32)1 << i;
112     if (wa & flag)
113     {
114       const char c = g_WinAttribChars[i];
115       if (c != '.')
116       {
117         wa &= ~flag;
118         // if (i != 7) // we can disable N (NORMAL) printing
119         *s++ = c;
120       }
121     }
122   }
123 
124   if (wa != 0)
125   {
126     *s++ = ' ';
127     ConvertUInt32ToHex8Digits(wa, s);
128     s += strlen(s);
129   }
130 
131   *s = 0;
132 
133   if (isPosix)
134   {
135     *s++ = ' ';
136     ConvertPosixAttribToString(s, posix);
137   }
138 }
139 
140 
ConvertPropertyToShortString2(char * dest,const PROPVARIANT & prop,PROPID propID,int level)141 void ConvertPropertyToShortString2(char *dest, const PROPVARIANT &prop, PROPID propID, int level) throw()
142 {
143   *dest = 0;
144 
145   if (prop.vt == VT_FILETIME)
146   {
147     const FILETIME &ft = prop.filetime;
148     unsigned ns100 = 0;
149     int numDigits = kTimestampPrintLevel_NTFS;
150     const unsigned prec = prop.wReserved1;
151     const unsigned ns100_Temp = prop.wReserved2;
152     if (prec != 0
153         && prec <= k_PropVar_TimePrec_1ns
154         && ns100_Temp < 100
155         && prop.wReserved3 == 0)
156     {
157       ns100 = ns100_Temp;
158       if (prec == k_PropVar_TimePrec_Unix ||
159           prec == k_PropVar_TimePrec_DOS)
160         numDigits = 0;
161       else if (prec == k_PropVar_TimePrec_HighPrec)
162         numDigits = 9;
163       else
164       {
165         numDigits = (int)prec - (int)k_PropVar_TimePrec_Base;
166         if (
167             // numDigits < kTimestampPrintLevel_DAY // for debuf
168             numDigits < kTimestampPrintLevel_SEC
169             )
170 
171           numDigits = kTimestampPrintLevel_NTFS;
172       }
173     }
174     if (ft.dwHighDateTime == 0 && ft.dwLowDateTime == 0 && ns100 == 0)
175       return;
176     if (level > numDigits)
177       level = numDigits;
178     ConvertUtcFileTimeToString2(ft, ns100, dest, level);
179     return;
180   }
181 
182   switch (propID)
183   {
184     case kpidCRC:
185     {
186       if (prop.vt != VT_UI4)
187         break;
188       ConvertUInt32ToHex8Digits(prop.ulVal, dest);
189       return;
190     }
191     case kpidAttrib:
192     {
193       if (prop.vt != VT_UI4)
194         break;
195       const UInt32 a = prop.ulVal;
196 
197       /*
198       if ((a & 0x8000) && (a & 0x7FFF) == 0)
199         ConvertPosixAttribToString(dest, a >> 16);
200       else
201       */
202       ConvertWinAttribToString(dest, a);
203       return;
204     }
205     case kpidPosixAttrib:
206     {
207       if (prop.vt != VT_UI4)
208         break;
209       ConvertPosixAttribToString(dest, prop.ulVal);
210       return;
211     }
212     case kpidINode:
213     {
214       if (prop.vt != VT_UI8)
215         break;
216       ConvertUInt32ToString((UInt32)(prop.uhVal.QuadPart >> 48), dest);
217       dest += strlen(dest);
218       *dest++ = '-';
219       const UInt64 low = prop.uhVal.QuadPart & (((UInt64)1 << 48) - 1);
220       ConvertUInt64ToString(low, dest);
221       return;
222     }
223     case kpidVa:
224     {
225       UInt64 v = 0;
226       if (prop.vt == VT_UI4)
227         v = prop.ulVal;
228       else if (prop.vt == VT_UI8)
229         v = (UInt64)prop.uhVal.QuadPart;
230       else
231         break;
232       dest[0] = '0';
233       dest[1] = 'x';
234       ConvertUInt64ToHex(v, dest + 2);
235       return;
236     }
237 
238     /*
239     case kpidDevice:
240     {
241       UInt64 v = 0;
242       if (prop.vt == VT_UI4)
243         v = prop.ulVal;
244       else if (prop.vt == VT_UI8)
245         v = (UInt64)prop.uhVal.QuadPart;
246       else
247         break;
248       ConvertUInt32ToString(MY_dev_major(v), dest);
249       dest += strlen(dest);
250       *dest++ = ',';
251       ConvertUInt32ToString(MY_dev_minor(v), dest);
252       return;
253     }
254     */
255     default: break;
256   }
257 
258   ConvertPropVariantToShortString(prop, dest);
259 }
260 
ConvertPropertyToString2(UString & dest,const PROPVARIANT & prop,PROPID propID,int level)261 void ConvertPropertyToString2(UString &dest, const PROPVARIANT &prop, PROPID propID, int level)
262 {
263   if (prop.vt == VT_BSTR)
264   {
265     dest.SetFromBstr(prop.bstrVal);
266     return;
267   }
268   char temp[64];
269   ConvertPropertyToShortString2(temp, prop, propID, level);
270   dest = temp;
271 }
272 
273 #ifndef Z7_SFX
274 
AddHexToString(AString & res,unsigned v)275 static inline void AddHexToString(AString &res, unsigned v)
276 {
277   res.Add_Char((char)GET_HEX_CHAR_UPPER(v >> 4));
278   res.Add_Char((char)GET_HEX_CHAR_UPPER(v & 15));
279 }
280 
281 /*
282 static AString Data_To_Hex(const Byte *data, size_t size)
283 {
284   AString s;
285   for (size_t i = 0; i < size; i++)
286     AddHexToString(s, data[i]);
287   return s;
288 }
289 */
290 
291 static const char * const sidNames[] =
292 {
293     "0"
294   , "Dialup"
295   , "Network"
296   , "Batch"
297   , "Interactive"
298   , "Logon"  // S-1-5-5-X-Y
299   , "Service"
300   , "Anonymous"
301   , "Proxy"
302   , "EnterpriseDC"
303   , "Self"
304   , "AuthenticatedUsers"
305   , "RestrictedCode"
306   , "TerminalServer"
307   , "RemoteInteractiveLogon"
308   , "ThisOrganization"
309   , "16"
310   , "IUserIIS"
311   , "LocalSystem"
312   , "LocalService"
313   , "NetworkService"
314   , "Domains"
315 };
316 
317 struct CSecID2Name
318 {
319   UInt32 n;
320   const char *sz;
321 };
322 
FindPairIndex(const CSecID2Name * pairs,unsigned num,UInt32 id)323 static int FindPairIndex(const CSecID2Name * pairs, unsigned num, UInt32 id)
324 {
325   for (unsigned i = 0; i < num; i++)
326     if (pairs[i].n == id)
327       return (int)i;
328   return -1;
329 }
330 
331 static const CSecID2Name sid_32_Names[] =
332 {
333   { 544, "Administrators" },
334   { 545, "Users" },
335   { 546, "Guests" },
336   { 547, "PowerUsers" },
337   { 548, "AccountOperators" },
338   { 549, "ServerOperators" },
339   { 550, "PrintOperators" },
340   { 551, "BackupOperators" },
341   { 552, "Replicators" },
342   { 553, "Backup Operators" },
343   { 554, "PreWindows2000CompatibleAccess" },
344   { 555, "RemoteDesktopUsers" },
345   { 556, "NetworkConfigurationOperators" },
346   { 557, "IncomingForestTrustBuilders" },
347   { 558, "PerformanceMonitorUsers" },
348   { 559, "PerformanceLogUsers" },
349   { 560, "WindowsAuthorizationAccessGroup" },
350   { 561, "TerminalServerLicenseServers" },
351   { 562, "DistributedCOMUsers" },
352   { 569, "CryptographicOperators" },
353   { 573, "EventLogReaders" },
354   { 574, "CertificateServiceDCOMAccess" }
355 };
356 
357 static const CSecID2Name sid_21_Names[] =
358 {
359   { 500, "Administrator" },
360   { 501, "Guest" },
361   { 502, "KRBTGT" },
362   { 512, "DomainAdmins" },
363   { 513, "DomainUsers" },
364   { 515, "DomainComputers" },
365   { 516, "DomainControllers" },
366   { 517, "CertPublishers" },
367   { 518, "SchemaAdmins" },
368   { 519, "EnterpriseAdmins" },
369   { 520, "GroupPolicyCreatorOwners" },
370   { 553, "RASandIASServers" },
371   { 553, "RASandIASServers" },
372   { 571, "AllowedRODCPasswordReplicationGroup" },
373   { 572, "DeniedRODCPasswordReplicationGroup" }
374 };
375 
376 struct CServicesToName
377 {
378   UInt32 n[5];
379   const char *sz;
380 };
381 
382 static const CServicesToName services_to_name[] =
383 {
384   { { 0x38FB89B5, 0xCBC28419, 0x6D236C5C, 0x6E770057, 0x876402C0 } , "TrustedInstaller" }
385 };
386 
ParseSid(AString & s,const Byte * p,size_t lim)387 static void ParseSid(AString &s, const Byte *p, size_t lim /* , unsigned &sidSize */)
388 {
389   // sidSize = 0;
390   if (lim < 8)
391   {
392     s += "ERROR";
393     return;
394   }
395   if (p[0] != 1) // rev
396   {
397     s += "UNSUPPORTED";
398     return;
399   }
400   const unsigned num = p[1];
401   const unsigned sidSize_Loc = 8 + num * 4;
402   if (sidSize_Loc > lim)
403   {
404     s += "ERROR";
405     return;
406   }
407   // sidSize = sidSize_Loc;
408   const UInt32 authority = GetBe32(p + 4);
409 
410   if (p[2] == 0 && p[3] == 0 && authority == 5 && num >= 1)
411   {
412     const UInt32 v0 = Get32(p + 8);
413     if (v0 < Z7_ARRAY_SIZE(sidNames))
414     {
415       s += sidNames[v0];
416       return;
417     }
418     if (v0 == 32 && num == 2)
419     {
420       const UInt32 v1 = Get32(p + 12);
421       const int index = FindPairIndex(sid_32_Names, Z7_ARRAY_SIZE(sid_32_Names), v1);
422       if (index >= 0)
423       {
424         s += sid_32_Names[(unsigned)index].sz;
425         return;
426       }
427     }
428     if (v0 == 21 && num == 5)
429     {
430       UInt32 v4 = Get32(p + 8 + 4 * 4);
431       const int index = FindPairIndex(sid_21_Names, Z7_ARRAY_SIZE(sid_21_Names), v4);
432       if (index >= 0)
433       {
434         s += sid_21_Names[(unsigned)index].sz;
435         return;
436       }
437     }
438     if (v0 == 80 && num == 6)
439     {
440       for (unsigned i = 0; i < Z7_ARRAY_SIZE(services_to_name); i++)
441       {
442         const CServicesToName &sn = services_to_name[i];
443         int j;
444         for (j = 0; j < 5 && sn.n[j] == Get32(p + 8 + 4 + j * 4); j++);
445         if (j == 5)
446         {
447           s += sn.sz;
448           return;
449         }
450       }
451     }
452   }
453 
454   s += "S-1-";
455   if (p[2] == 0 && p[3] == 0)
456     s.Add_UInt32(authority);
457   else
458   {
459     s += "0x";
460     for (int i = 2; i < 8; i++)
461       AddHexToString(s, p[i]);
462   }
463   for (UInt32 i = 0; i < num; i++)
464   {
465     s.Add_Minus();
466     s.Add_UInt32(Get32(p + 8 + i * 4));
467   }
468 }
469 
ParseOwner(AString & s,const Byte * p,size_t size,UInt32 pos)470 static void ParseOwner(AString &s, const Byte *p, size_t size, UInt32 pos)
471 {
472   if (pos > size)
473   {
474     s += "ERROR";
475     return;
476   }
477   // unsigned sidSize = 0;
478   ParseSid(s, p + pos, size - pos /* , sidSize */);
479 }
480 
ParseAcl(AString & s,const Byte * p,size_t size,const char * strName,UInt32 flags,UInt32 offset)481 static void ParseAcl(AString &s, const Byte *p, size_t size, const char *strName, UInt32 flags, UInt32 offset)
482 {
483   const unsigned control = Get16(p + 2);
484   if ((flags & control) == 0)
485     return;
486   const UInt32 pos = Get32(p + offset);
487   s.Add_Space();
488   s += strName;
489   if (pos >= size)
490     return;
491   p += pos;
492   size -= (size_t)pos;
493   if (size < 8)
494     return;
495   if (Get16(p) != 2) // revision
496     return;
497   const UInt32 num = Get32(p + 4);
498   s.Add_UInt32(num);
499 
500   /*
501   UInt32 aclSize = Get16(p + 2);
502   if (num >= (1 << 16))
503     return;
504   if (aclSize > size)
505     return;
506   size = aclSize;
507   size -= 8;
508   p += 8;
509   for (UInt32 i = 0 ; i < num; i++)
510   {
511     if (size <= 8)
512       return;
513     // Byte type = p[0];
514     // Byte flags = p[1];
515     // UInt32 aceSize = Get16(p + 2);
516     // UInt32 mask = Get32(p + 4);
517     p += 8;
518     size -= 8;
519 
520     UInt32 sidSize = 0;
521     s.Add_Space();
522     ParseSid(s, p, size, sidSize);
523     if (sidSize == 0)
524       return;
525     p += sidSize;
526     size -= sidSize;
527   }
528 
529   // the tail can contain zeros. So (size != 0) is not ERROR
530   // if (size != 0) s += " ERROR";
531   */
532 }
533 
534 /*
535 #define MY_SE_OWNER_DEFAULTED       (0x0001)
536 #define MY_SE_GROUP_DEFAULTED       (0x0002)
537 */
538 #define MY_SE_DACL_PRESENT          (0x0004)
539 /*
540 #define MY_SE_DACL_DEFAULTED        (0x0008)
541 */
542 #define MY_SE_SACL_PRESENT          (0x0010)
543 /*
544 #define MY_SE_SACL_DEFAULTED        (0x0020)
545 #define MY_SE_DACL_AUTO_INHERIT_REQ (0x0100)
546 #define MY_SE_SACL_AUTO_INHERIT_REQ (0x0200)
547 #define MY_SE_DACL_AUTO_INHERITED   (0x0400)
548 #define MY_SE_SACL_AUTO_INHERITED   (0x0800)
549 #define MY_SE_DACL_PROTECTED        (0x1000)
550 #define MY_SE_SACL_PROTECTED        (0x2000)
551 #define MY_SE_RM_CONTROL_VALID      (0x4000)
552 #define MY_SE_SELF_RELATIVE         (0x8000)
553 */
554 
ConvertNtSecureToString(const Byte * data,size_t size,AString & s)555 void ConvertNtSecureToString(const Byte *data, size_t size, AString &s)
556 {
557   s.Empty();
558   if (size < 20 || size > (1 << 18))
559   {
560     s += "ERROR";
561     return;
562   }
563   if (Get16(data) != 1) // revision
564   {
565     s += "UNSUPPORTED";
566     return;
567   }
568   ParseOwner(s, data, size, Get32(data + 4));
569   s.Add_Space();
570   ParseOwner(s, data, size, Get32(data + 8));
571   ParseAcl(s, data, size, "s:", MY_SE_SACL_PRESENT, 12);
572   ParseAcl(s, data, size, "d:", MY_SE_DACL_PRESENT, 16);
573   s.Add_Space();
574   s.Add_UInt32((UInt32)size);
575   // s.Add_LF();
576   // s += Data_To_Hex(data, size);
577 }
578 
579 #ifdef _WIN32
580 
CheckSid(const Byte * data,size_t size,UInt32 pos)581 static bool CheckSid(const Byte *data, size_t size, UInt32 pos) throw()
582 {
583   if (pos >= size)
584     return false;
585   size -= pos;
586   if (size < 8)
587     return false;
588   if (data[pos] != 1) // rev
589     return false;
590   const unsigned num = data[pos + 1];
591   return (8 + num * 4 <= size);
592 }
593 
CheckAcl(const Byte * p,size_t size,UInt32 flags,size_t offset)594 static bool CheckAcl(const Byte *p, size_t size, UInt32 flags, size_t offset) throw()
595 {
596   const unsigned control = Get16(p + 2);
597   if ((flags & control) == 0)
598     return true;
599   const UInt32 pos = Get32(p + offset);
600   if (pos >= size)
601     return false;
602   p += pos;
603   size -= pos;
604   if (size < 8)
605     return false;
606   const unsigned aclSize = Get16(p + 2);
607   return (aclSize <= size);
608 }
609 
CheckNtSecure(const Byte * data,size_t size)610 bool CheckNtSecure(const Byte *data, size_t size) throw()
611 {
612   if (size < 20)
613     return false;
614   if (Get16(data) != 1) // revision
615     return true; // windows function can handle such error, so we allow it
616   if (size > (1 << 18))
617     return false;
618   if (!CheckSid(data, size, Get32(data + 4))) return false;
619   if (!CheckSid(data, size, Get32(data + 8))) return false;
620   if (!CheckAcl(data, size, MY_SE_SACL_PRESENT, 12)) return false;
621   if (!CheckAcl(data, size, MY_SE_DACL_PRESENT, 16)) return false;
622   return true;
623 }
624 
625 #endif
626 
627 
628 
629 // IO_REPARSE_TAG_*
630 
631 static const CSecID2Name k_ReparseTags[] =
632 {
633   { 0xA0000003, "MOUNT_POINT" },
634   { 0xC0000004, "HSM" },
635   { 0x80000005, "DRIVE_EXTENDER" },
636   { 0x80000006, "HSM2" },
637   { 0x80000007, "SIS" },
638   { 0x80000008, "WIM" },
639   { 0x80000009, "CSV" },
640   { 0x8000000A, "DFS" },
641   { 0x8000000B, "FILTER_MANAGER" },
642   { 0xA000000C, "SYMLINK" },
643   { 0xA0000010, "IIS_CACHE" },
644   { 0x80000012, "DFSR" },
645   { 0x80000013, "DEDUP" },
646   { 0xC0000014, "APPXSTRM" },
647   { 0x80000014, "NFS" },
648   { 0x80000015, "FILE_PLACEHOLDER" },
649   { 0x80000016, "DFM" },
650   { 0x80000017, "WOF" },
651   { 0x80000018, "WCI" },
652   { 0x8000001B, "APPEXECLINK" },
653   { 0xA000001D, "LX_SYMLINK" },
654   { 0x80000023, "AF_UNIX" },
655   { 0x80000024, "LX_FIFO" },
656   { 0x80000025, "LX_CHR" },
657   { 0x80000026, "LX_BLK" }
658 };
659 
ConvertNtReparseToString(const Byte * data,size_t size,UString & s)660 bool ConvertNtReparseToString(const Byte *data, size_t size, UString &s)
661 {
662   s.Empty();
663   NFile::CReparseAttr attr;
664 
665   if (attr.Parse(data, size))
666   {
667     if (attr.IsSymLink_WSL())
668     {
669       s += "WSL: ";
670       s += attr.GetPath();
671     }
672     else
673     {
674       if (!attr.IsSymLink_Win())
675         s += "Junction: ";
676       s += attr.GetPath();
677       if (s.IsEmpty())
678         s += "Link: ";
679       if (!attr.IsOkNamePair())
680       {
681         s += " : ";
682         s += attr.PrintName;
683       }
684     }
685     if (attr.MinorError)
686       s += " : MINOR_ERROR";
687     return true;
688     // s.Add_Space(); // for debug
689   }
690 
691   if (size < 8)
692     return false;
693   const UInt32 tag = Get32(data);
694   const UInt32 len = Get16(data + 4);
695   if (len + 8 > size)
696     return false;
697   if (Get16(data + 6) != 0) // padding
698     return false;
699 
700   /*
701   #define my_IO_REPARSE_TAG_DEDUP        (0x80000013L)
702   if (tag == my_IO_REPARSE_TAG_DEDUP)
703   {
704   }
705   */
706 
707   {
708     const int index = FindPairIndex(k_ReparseTags, Z7_ARRAY_SIZE(k_ReparseTags), tag);
709     if (index >= 0)
710       s += k_ReparseTags[(unsigned)index].sz;
711     else
712     {
713       s += "REPARSE:";
714       char hex[16];
715       ConvertUInt32ToHex8Digits(tag, hex);
716       s += hex;
717     }
718   }
719 
720   s.Add_Colon();
721   s.Add_UInt32(len);
722 
723   if (len != 0)
724   {
725     s.Add_Space();
726 
727     data += 8;
728 
729     for (UInt32 i = 0; i < len; i++)
730     {
731       if (i >= 16)
732       {
733         s += "...";
734         break;
735       }
736       const unsigned b = data[i];
737       s.Add_Char((char)GET_HEX_CHAR_UPPER(b >> 4));
738       s.Add_Char((char)GET_HEX_CHAR_UPPER(b & 15));
739     }
740   }
741 
742   return true;
743 }
744 
745 #endif
746