1 // ApfsHandler.cpp
2
3 #include "StdAfx.h"
4
5 // #define SHOW_DEBUG_INFO
6
7 #ifdef SHOW_DEBUG_INFO
8 #include <stdio.h>
9 #define PRF(x) x
10 #else
11 #define PRF(x)
12 #endif
13
14 #include "../../../C/CpuArch.h"
15 #include "../../../C/Sha256.h"
16
17 #include "../../Common/ComTry.h"
18 #include "../../Common/IntToString.h"
19 #include "../../Common/MyBuffer2.h"
20 #include "../../Common/MyLinux.h"
21 #include "../../Common/UTFConvert.h"
22
23 #include "../../Windows/PropVariantConv.h"
24 #include "../../Windows/PropVariantUtils.h"
25 #include "../../Windows/TimeUtils.h"
26
27 #include "../Common/LimitedStreams.h"
28 #include "../Common/ProgressUtils.h"
29 #include "../Common/RegisterArc.h"
30 #include "../Common/StreamObjects.h"
31 #include "../Common/StreamUtils.h"
32
33 #include "../Compress/CopyCoder.h"
34
35 #include "Common/ItemNameUtils.h"
36
37 #include "HfsHandler.h"
38
39 // if APFS_SHOW_ALT_STREAMS is defined, the handler will show attribute files.
40 #define APFS_SHOW_ALT_STREAMS
41
42 #define VI_MINUS1 ((unsigned)(int)-1)
43 #define IsViDef(x) ((int)(x) != -1)
44 #define IsViNotDef(x) ((int)(x) == -1)
45
46 #define Get16(p) GetUi16(p)
47 #define Get32(p) GetUi32(p)
48 #define Get64(p) GetUi64(p)
49
50 #define G16(_offs_, dest) dest = Get16(p + (_offs_));
51 #define G32(_offs_, dest) dest = Get32(p + (_offs_));
52 #define G64(_offs_, dest) dest = Get64(p + (_offs_));
53
54 namespace NArchive {
55 namespace NApfs {
56
57 struct CUuid
58 {
59 Byte Data[16];
60
SetHex_To_strNArchive::NApfs::CUuid61 void SetHex_To_str(char *s) const
62 {
63 ConvertDataToHex_Lower(s, Data, sizeof(Data));
64 }
65
AddHexToStringNArchive::NApfs::CUuid66 void AddHexToString(UString &dest) const
67 {
68 char temp[sizeof(Data) * 2 + 4];
69 SetHex_To_str(temp);
70 dest += temp;
71 }
72
SetFromNArchive::NApfs::CUuid73 void SetFrom(const Byte *p) { memcpy(Data, p, 16); }
74 };
75
76
77 typedef UInt64 oid_t;
78 typedef UInt64 xid_t;
79 // typedef Int64 paddr_t;
80 typedef UInt64 paddr_t_unsigned;
81
82 #define G64o G64
83 #define G64x G64
84 // #define G64a G64
85
86 /*
87 struct prange_t
88 {
89 paddr_t start_paddr;
90 UInt64 block_count;
91
92 void Parse(const Byte *p)
93 {
94 G64a (0, start_paddr);
95 G64 (8, block_count);
96 }
97 };
98 */
99
100 #define OBJECT_TYPE_NX_SUPERBLOCK 0x1
101 #define OBJECT_TYPE_BTREE 0x2
102 #define OBJECT_TYPE_BTREE_NODE 0x3
103 /*
104 #define OBJECT_TYPE_SPACEMAN 0x5
105 #define OBJECT_TYPE_SPACEMAN_CAB 0x6
106 #define OBJECT_TYPE_SPACEMAN_CIB 0x7
107 #define OBJECT_TYPE_SPACEMAN_BITMAP 0x8
108 #define OBJECT_TYPE_SPACEMAN_FREE_QUEUE 0x9
109 #define OBJECT_TYPE_EXTENT_LIST_TREE 0xa
110 */
111 #define OBJECT_TYPE_OMAP 0xb
112 /*
113 #define OBJECT_TYPE_CHECKPOINT_MAP 0xc
114 */
115 #define OBJECT_TYPE_FS 0xd
116 #define OBJECT_TYPE_FSTREE 0xe
117 /*
118 #define OBJECT_TYPE_BLOCKREFTREE 0xf
119 #define OBJECT_TYPE_SNAPMETATREE 0x10
120 #define OBJECT_TYPE_NX_REAPER 0x11
121 #define OBJECT_TYPE_NX_REAP_LIST 0x12
122 #define OBJECT_TYPE_OMAP_SNAPSHOT 0x13
123 #define OBJECT_TYPE_EFI_JUMPSTART 0x14
124 #define OBJECT_TYPE_FUSION_MIDDLE_TREE 0x15
125 #define OBJECT_TYPE_NX_FUSION_WBC 0x16
126 #define OBJECT_TYPE_NX_FUSION_WBC_LIST 0x17
127 #define OBJECT_TYPE_ER_STATE 0x18
128 #define OBJECT_TYPE_GBITMAP 0x19
129 #define OBJECT_TYPE_GBITMAP_TREE 0x1a
130 #define OBJECT_TYPE_GBITMAP_BLOCK 0x1b
131 #define OBJECT_TYPE_ER_RECOVERY_BLOCK 0x1c
132 #define OBJECT_TYPE_SNAP_META_EXT 0x1d
133 */
134 #define OBJECT_TYPE_INTEGRITY_META 0x1e
135 #define OBJECT_TYPE_FEXT_TREE 0x1f
136 /*
137 #define OBJECT_TYPE_RESERVED_20 0x20
138
139 #define OBJECT_TYPE_INVALID 0x0
140 #define OBJECT_TYPE_TEST 0xff
141 #define OBJECT_TYPE_CONTAINER_KEYBAG 'keys'
142 #define OBJECT_TYPE_VOLUME_KEYBAG 'recs'
143 #define OBJECT_TYPE_MEDIA_KEYBAG 'mkey'
144
145 #define OBJ_VIRTUAL 0x0
146 #define OBJ_EPHEMERAL 0x80000000
147 */
148 #define OBJ_PHYSICAL 0x40000000
149 /*
150 #define OBJ_NOHEADER 0x20000000
151 #define OBJ_ENCRYPTED 0x10000000
152 #define OBJ_NONPERSISTENT 0x08000000
153 */
154 #define OBJECT_TYPE_MASK 0x0000ffff
155 /*
156 #define OBJECT_TYPE_FLAGS_MASK 0xffff0000
157 #define OBJ_STORAGETYPE_MASK 0xc0000000
158 #define OBJECT_TYPE_FLAGS_DEFINED_MASK 0xf8000000
159 */
160
161 // #define MAX_CKSUM_SIZE 8
162
163 static const unsigned k_obj_phys_Size = 0x20;
164
165 // obj_phys_t
166 struct CPhys
167 {
168 // Byte cksum[MAX_CKSUM_SIZE];
169 oid_t oid;
170 xid_t xid;
171 UInt32 type;
172 UInt32 subtype;
173
GetTypeNArchive::NApfs::CPhys174 UInt32 GetType() const { return type & OBJECT_TYPE_MASK; }
175 void Parse(const Byte *p);
176 };
177
Parse(const Byte * p)178 void CPhys::Parse(const Byte *p)
179 {
180 // memcpy(cksum, p, MAX_CKSUM_SIZE);
181 G64o (8, oid)
182 G64x (0x10, xid)
183 G32 (0x18, type)
184 G32 (0x1C, subtype)
185 }
186
187 #define NX_MAX_FILE_SYSTEMS 100
188 /*
189 #define NX_EPH_INFO_COUNT 4
190 #define NX_EPH_MIN_BLOCK_COUNT 8
191 #define NX_MAX_FILE_SYSTEM_EPH_STRUCTS 4
192 #define NX_TX_MIN_CHECKPOINT_COUNT 4
193 #define NX_EPH_INFO_VERSION_1 1
194 */
195
196 /*
197 typedef enum
198 {
199 NX_CNTR_OBJ_CKSUM_SET = 0,
200 NX_CNTR_OBJ_CKSUM_FAIL = 1,
201 NX_NUM_COUNTERS = 32
202 } counter_id_t;
203 */
204
205 /* Incompatible volume feature flags */
206 #define APFS_INCOMPAT_CASE_INSENSITIVE (1 << 0)
207 /*
208 #define APFS_INCOMPAT_DATALESS_SNAPS (1 << 1)
209 #define APFS_INCOMPAT_ENC_ROLLED (1 << 2)
210 */
211 #define APFS_INCOMPAT_NORMALIZATION_INSENSITIVE (1 << 3)
212 /*
213 #define APFS_INCOMPAT_INCOMPLETE_RESTORE (1 << 4)
214 */
215 #define APFS_INCOMPAT_SEALED_VOLUME (1 << 5)
216 /*
217 #define APFS_INCOMPAT_RESERVED_40 (1 << 6)
218 */
219
220 static const char * const g_APFS_INCOMPAT_Flags[] =
221 {
222 "CASE_INSENSITIVE"
223 , "DATALESS_SNAPS"
224 , "ENC_ROLLED"
225 , "NORMALIZATION_INSENSITIVE"
226 , "INCOMPLETE_RESTORE"
227 , "SEALED_VOLUME"
228 };
229
230 /*
231 #define APFS_SUPPORTED_INCOMPAT_MASK \
232 ( APFS_INCOMPAT_CASE_INSENSITIVE \
233 | APFS_INCOMPAT_DATALESS_SNAPS \
234 | APFS_INCOMPAT_ENC_ROLLED \
235 | APFS_INCOMPAT_NORMALIZATION_INSENSITIVE \
236 | APFS_INCOMPAT_INCOMPLETE_RESTORE \
237 | APFS_INCOMPAT_SEALED_VOLUME \
238 | APFS_INCOMPAT_RESERVED_40 \
239 )
240 */
241
242 // superblock_t
243 struct CSuperBlock
244 {
245 // CPhys o;
246 // UInt32 magic;
247 UInt32 block_size;
248 unsigned block_size_Log;
249 UInt64 block_count;
250 // UInt64 features;
251 // UInt64 readonly_compatible_features;
252 // UInt64 incompatible_features;
253 CUuid uuid;
254 /*
255 oid_t next_oid;
256 xid_t next_xid;
257 UInt32 xp_desc_blocks;
258 UInt32 xp_data_blocks;
259 paddr_t xp_desc_base;
260 paddr_t xp_data_base;
261 UInt32 xp_desc_next;
262 UInt32 xp_data_next;
263 UInt32 xp_desc_index;
264 UInt32 xp_desc_len;
265 UInt32 xp_data_index;
266 UInt32 xp_data_len;
267 oid_t spaceman_oid;
268 */
269 oid_t omap_oid;
270 // oid_t reaper_oid;
271 // UInt32 test_type;
272 UInt32 max_file_systems;
273 // oid_t fs_oid[NX_MAX_FILE_SYSTEMS];
274 /*
275 UInt64 counters[NX_NUM_COUNTERS]; // counter_id_t
276 prange_t blocked_out_prange;
277 oid_t evict_mapping_tree_oid;
278 UInt64 flags;
279 paddr_t efi_jumpstart;
280 CUuid fusion_uuid;
281 prange_t keylocker;
282 UInt64 ephemeral_info[NX_EPH_INFO_COUNT];
283 oid_t test_oid;
284 oid_t fusion_mt_oid;
285 oid_t fusion_wbc_oid;
286 prange_t fusion_wbc;
287 UInt64 newest_mounted_version;
288 prange_t mkb_locker;
289 */
290
291 bool Parse(const Byte *p);
292 };
293
294 struct CSuperBlock2
295 {
296 oid_t fs_oid[NX_MAX_FILE_SYSTEMS];
ParseNArchive::NApfs::CSuperBlock2297 void Parse(const Byte *p)
298 {
299 for (unsigned i = 0; i < NX_MAX_FILE_SYSTEMS; i++)
300 {
301 G64o (0xb8 + i * 8, fs_oid[i])
302 }
303 }
304 };
305
306
307 // we include one additional byte of next field (block_size)
308 static const unsigned k_SignatureOffset = 32;
309 static const Byte k_Signature[] = { 'N', 'X', 'S', 'B', 0 };
310
311 // size must be 4 bytes aligned
Fletcher64(const Byte * data,size_t size)312 static UInt64 Fletcher64(const Byte *data, size_t size)
313 {
314 const UInt32 kMax32 = 0xffffffff;
315 const UInt64 val = 0; // startVal
316 UInt64 a = val & kMax32;
317 UInt64 b = (val >> 32) & kMax32;
318 for (size_t i = 0; i < size; i += 4)
319 {
320 a += GetUi32(data + i);
321 b += a;
322 }
323 a %= kMax32;
324 b %= kMax32;
325 b = (UInt32)(kMax32 - ((a + b) % kMax32));
326 a = (UInt32)(kMax32 - ((a + b) % kMax32));
327 return (a << 32) | b;
328 }
329
CheckFletcher64(const Byte * p,size_t size)330 static bool CheckFletcher64(const Byte *p, size_t size)
331 {
332 const UInt64 calculated_checksum = Fletcher64(p + 8, size - 8);
333 const UInt64 stored_checksum = Get64(p);
334 return (stored_checksum == calculated_checksum);
335 }
336
337
GetLogSize(UInt32 size)338 static unsigned GetLogSize(UInt32 size)
339 {
340 unsigned k;
341 for (k = 0; k < 32; k++)
342 if (((UInt32)1 << k) == size)
343 return k;
344 return k;
345 }
346
347 static const unsigned kApfsHeaderSize = 1 << 12;
348
349 // #define OID_INVALID 0
350 #define OID_NX_SUPERBLOCK 1
351 // #define OID_RESERVED_COUNT 1024
352 // This range of identifiers is reserved for physical, virtual, and ephemeral objects
353
Parse(const Byte * p)354 bool CSuperBlock::Parse(const Byte *p)
355 {
356 CPhys o;
357 o.Parse(p);
358 if (o.oid != OID_NX_SUPERBLOCK)
359 return false;
360 if (o.GetType() != OBJECT_TYPE_NX_SUPERBLOCK)
361 return false;
362 if (o.subtype != 0)
363 return false;
364 if (memcmp(p + k_SignatureOffset, k_Signature, 4) != 0)
365 return false;
366 if (!CheckFletcher64(p, kApfsHeaderSize))
367 return false;
368
369 G32 (0x24, block_size)
370 {
371 const unsigned logSize = GetLogSize(block_size);
372 // don't change it. Some code requires (block_size <= 16)
373 if (logSize < 12 || logSize > 16)
374 return false;
375 block_size_Log = logSize;
376 }
377
378 G64 (0x28, block_count)
379
380 const UInt64 kArcSize_MAX = (UInt64)1 << 62;
381 if (block_count > (kArcSize_MAX >> block_size_Log))
382 return false;
383
384 // G64 (0x30, features);
385 // G64 (0x38, readonly_compatible_features);
386 // G64 (0x40, incompatible_features);
387 uuid.SetFrom(p + 0x48);
388 /*
389 G64o (0x58, next_oid);
390 G64x (0x60, next_xid);
391 G32 (0x68, xp_desc_blocks);
392 G32 (0x6c, xp_data_blocks);
393 G64a (0x70, xp_desc_base);
394 G64a (0x78, xp_data_base);
395 G32 (0x80, xp_desc_next);
396 G32 (0x84, xp_data_next);
397 G32 (0x88, xp_desc_index);
398 G32 (0x8c, xp_desc_len);
399 G32 (0x90, xp_data_index);
400 G32 (0x94, xp_data_len);
401 G64o (0x98, spaceman_oid);
402 */
403 G64o (0xa0, omap_oid)
404 // G64o (0xa8, reaper_oid);
405 // G32 (0xb0, test_type);
406 G32 (0xb4, max_file_systems)
407 if (max_file_systems > NX_MAX_FILE_SYSTEMS)
408 return false;
409 /*
410 {
411 for (unsigned i = 0; i < NX_MAX_FILE_SYSTEMS; i++)
412 {
413 G64o (0xb8 + i * 8, fs_oid[i]);
414 }
415 }
416 */
417 /*
418 {
419 for (unsigned i = 0; i < NX_NUM_COUNTERS; i++)
420 {
421 G64 (0x3d8 + i * 8, counters[i]);
422 }
423 }
424 blocked_out_prange.Parse(p + 0x4d8);
425 G64o (0x4e8, evict_mapping_tree_oid);
426 #define NX_CRYPTO_SW 0x00000004LL
427 G64 (0x4f0, flags);
428 G64a (0x4f8, efi_jumpstart);
429 fusion_uuid.SetFrom(p + 0x500);
430 keylocker.Parse(p + 0x510);
431 {
432 for (unsigned i = 0; i < NX_EPH_INFO_COUNT; i++)
433 {
434 G64 (0x520 + i * 8, ephemeral_info[i]);
435 }
436 }
437 G64o (0x540, test_oid);
438 G64o (0x548, fusion_mt_oid);
439 G64o (0x550, fusion_wbc_oid);
440 fusion_wbc.Parse(p + 0x558);
441 G64 (0x568, newest_mounted_version); // decimal 1412141 001 000 000
442 mkb_locker.Parse(p + 0x570);
443 */
444
445 return true;
446 }
447
448
449 enum apfs_hash_type_t
450 {
451 APFS_HASH_INVALID = 0,
452 APFS_HASH_SHA256 = 1,
453 APFS_HASH_SHA512_256 = 2,
454 APFS_HASH_SHA384 = 3,
455 APFS_HASH_SHA512 = 4,
456
457 APFS_HASH_MIN = APFS_HASH_SHA256,
458 APFS_HASH_MAX = APFS_HASH_SHA512,
459
460 APFS_HASH_DEFAULT = APFS_HASH_SHA256
461 };
462
GetHashSize(unsigned hashType)463 static unsigned GetHashSize(unsigned hashType)
464 {
465 if (hashType > APFS_HASH_MAX) return 0;
466 if (hashType < APFS_HASH_MIN) return 0;
467 if (hashType == APFS_HASH_SHA256) return 32;
468 return hashType * 16;
469 }
470
471 #define APFS_HASH_MAX_SIZE 64
472
473 static const char * const g_hash_types[] =
474 {
475 NULL
476 , "SHA256"
477 , "SHA512_256"
478 , "SHA384"
479 , "SHA512"
480 };
481
482
483 struct C_integrity_meta_phys
484 {
485 // CPhys im_o;
486 // UInt32 im_version;
487 UInt32 im_flags;
488 // apfs_hash_type_t
489 UInt32 im_hash_type;
490 // UInt32 im_root_hash_offset;
491 // xid_t im_broken_xid;
492 // UInt64 im_reserved[9];
493
494 unsigned HashSize;
495 Byte Hash[APFS_HASH_MAX_SIZE];
496
Is_SHA256NArchive::NApfs::C_integrity_meta_phys497 bool Is_SHA256() const { return im_hash_type == APFS_HASH_SHA256; }
498
C_integrity_meta_physNArchive::NApfs::C_integrity_meta_phys499 C_integrity_meta_phys()
500 {
501 memset(this, 0, sizeof(*this));
502 }
503 bool Parse(const Byte *p, size_t size, oid_t oid);
504 };
505
Parse(const Byte * p,size_t size,oid_t oid)506 bool C_integrity_meta_phys::Parse(const Byte *p, size_t size, oid_t oid)
507 {
508 CPhys o;
509 if (!CheckFletcher64(p, size))
510 return false;
511 o.Parse(p);
512 if (o.GetType() != OBJECT_TYPE_INTEGRITY_META)
513 return false;
514 if (o.oid != oid)
515 return false;
516 // G32 (0x20, im_version);
517 G32 (0x24, im_flags)
518 G32 (0x28, im_hash_type)
519 UInt32 im_root_hash_offset;
520 G32 (0x2C, im_root_hash_offset)
521 // G64x (0x30, im_broken_xid);
522 const unsigned hashSize = GetHashSize(im_hash_type);
523 HashSize = hashSize;
524 if (im_root_hash_offset >= size || size - im_root_hash_offset < hashSize)
525 return false;
526 memcpy(Hash, p + im_root_hash_offset, hashSize);
527 return true;
528 }
529
530
531 struct C_omap_phys
532 {
533 // om_ prefix
534 // CPhys o;
535 /*
536 UInt32 flags;
537 UInt32 snap_count;
538 UInt32 tree_type;
539 UInt32 snapshot_tree_type;
540 */
541 oid_t tree_oid;
542 /*
543 oid_t snapshot_tree_oid;
544 xid_t most_recent_snap;
545 xid_t pending_revert_min;
546 xid_t pending_revert_max;
547 */
548 bool Parse(const Byte *p, size_t size, oid_t oid);
549 };
550
Parse(const Byte * p,size_t size,oid_t oid)551 bool C_omap_phys::Parse(const Byte *p, size_t size, oid_t oid)
552 {
553 CPhys o;
554 if (!CheckFletcher64(p, size))
555 return false;
556 o.Parse(p);
557 if (o.GetType() != OBJECT_TYPE_OMAP)
558 return false;
559 if (o.oid != oid)
560 return false;
561 /*
562 G32 (0x20, flags);
563 G32 (0x24, snap_count);
564 G32 (0x28, tree_type);
565 G32 (0x2C, snapshot_tree_type);
566 */
567 G64o (0x30, tree_oid)
568 /*
569 G64o (0x38, snapshot_tree_oid);
570 G64x (0x40, most_recent_snap);
571 G64x (0x48, pending_revert_min);
572 G64x (0x50, pending_revert_max);
573 */
574 return true;
575 }
576
577
578 // #define BTOFF_INVALID 0xffff
579 /* This value is stored in the off field of nloc_t to indicate that
580 there's no offset. For example, the last entry in a free
581 list has no entry after it, so it uses this value for its off field. */
582
583 // A location within a B-tree node
584 struct nloc
585 {
586 UInt16 off;
587 UInt16 len;
588
ParseNArchive::NApfs::nloc589 void Parse(const Byte *p)
590 {
591 G16 (0, off)
592 G16 (2, len)
593 }
GetEndNArchive::NApfs::nloc594 UInt32 GetEnd() const { return (UInt32)off + len; }
CheckOverLimitNArchive::NApfs::nloc595 bool CheckOverLimit(UInt32 limit)
596 {
597 return off < limit && len <= limit - off;
598 }
599 };
600
601
602 // The location, within a B-tree node, of a key and value
603 struct kvloc
604 {
605 nloc k;
606 nloc v;
607
ParseNArchive::NApfs::kvloc608 void Parse(const Byte *p)
609 {
610 k.Parse(p);
611 v.Parse(p + 4);
612 }
613 };
614
615
616 // The location, within a B-tree node, of a fixed-size key and value
617 struct kvoff
618 {
619 UInt16 k;
620 UInt16 v;
621
ParseNArchive::NApfs::kvoff622 void Parse(const Byte *p)
623 {
624 G16 (0, k)
625 G16 (2, v)
626 }
627 };
628
629
630 #define BTNODE_ROOT (1 << 0)
631 #define BTNODE_LEAF (1 << 1)
632 #define BTNODE_FIXED_KV_SIZE (1 << 2)
633 #define BTNODE_HASHED (1 << 3)
634 #define BTNODE_NOHEADER (1 << 4)
635 /*
636 #define BTNODE_CHECK_KOFF_INVAL (1 << 15)
637 */
638
639 static const unsigned k_Toc_offset = 0x38;
640
641 // btree_node_phys
642 struct CBTreeNodePhys
643 {
644 // btn_ prefix
645 CPhys ophys;
646 UInt16 flags;
647 UInt16 level; // the number of child levels below this node. 0 - for a leaf node, 1 for the immediate parent of a leaf node
648 UInt32 nkeys; // The number of keys stored in this node.
649 nloc table_space;
650 /*
651 nloc free_space;
652 nloc key_free_list;
653 nloc val_free_list;
654 */
655
Is_FIXED_KV_SIZENArchive::NApfs::CBTreeNodePhys656 bool Is_FIXED_KV_SIZE() const { return (flags & BTNODE_FIXED_KV_SIZE) != 0; }
Is_NOHEADERNArchive::NApfs::CBTreeNodePhys657 bool Is_NOHEADER() const { return (flags & BTNODE_NOHEADER) != 0; }
Is_HASHEDNArchive::NApfs::CBTreeNodePhys658 bool Is_HASHED() const { return (flags & BTNODE_HASHED) != 0; }
659
ParseNArchive::NApfs::CBTreeNodePhys660 bool Parse(const Byte *p, size_t size, bool noHeader = false)
661 {
662 G16 (0x20, flags)
663 G16 (0x22, level)
664 G32 (0x24, nkeys)
665 table_space.Parse(p + 0x28);
666 /*
667 free_space.Parse(p + 0x2C);
668 key_free_list.Parse(p + 0x30);
669 val_free_list.Parse(p + 0x34);
670 */
671 memset(&ophys, 0, sizeof(ophys));
672 if (noHeader)
673 {
674 for (unsigned i = 0; i < k_obj_phys_Size; i++)
675 if (p[i] != 0)
676 return false;
677 }
678 else
679 {
680 if (!CheckFletcher64(p, size))
681 return false;
682 ophys.Parse(p);
683 }
684 if (Is_NOHEADER() != noHeader)
685 return false;
686 return true;
687 }
688 };
689
690 /*
691 #define BTREE_UINT64_KEYS (1 << 0)
692 #define BTREE_SEQUENTIAL_INSERT (1 << 1)
693 #define BTREE_ALLOW_GHOSTS (1 << 2)
694 */
695 #define BTREE_EPHEMERAL (1 << 3)
696 #define BTREE_PHYSICAL (1 << 4)
697 /*
698 #define BTREE_NONPERSISTENT (1 << 5)
699 #define BTREE_KV_NONALIGNED (1 << 6)
700 #define BTREE_HASHED (1 << 7)
701 */
702 #define BTREE_NOHEADER (1 << 8)
703
704 /*
705 BTREE_EPHEMERAL: The nodes in the B-tree use ephemeral object identifiers to link to child nodes
706 BTREE_PHYSICAL : The nodes in the B-tree use physical object identifiers to link to child nodes.
707 If neither flag is set, nodes in the B-tree use virtual object
708 identifiers to link to their child nodes.
709 */
710
711 // Static information about a B-tree.
712 struct btree_info_fixed
713 {
714 UInt32 flags;
715 UInt32 node_size;
716 UInt32 key_size;
717 UInt32 val_size;
718
ParseNArchive::NApfs::btree_info_fixed719 void Parse(const Byte *p)
720 {
721 G32 (0, flags)
722 G32 (4, node_size)
723 G32 (8, key_size)
724 G32 (12, val_size)
725 }
726 };
727
728 static const unsigned k_btree_info_Size = 0x28;
729
730 struct btree_info
731 {
732 btree_info_fixed fixed;
733 UInt32 longest_key;
734 UInt32 longest_val;
735 UInt64 key_count;
736 UInt64 node_count;
737
Is_EPHEMERALNArchive::NApfs::btree_info738 bool Is_EPHEMERAL() const { return (fixed.flags & BTREE_EPHEMERAL) != 0; }
Is_PHYSICALNArchive::NApfs::btree_info739 bool Is_PHYSICAL() const { return (fixed.flags & BTREE_PHYSICAL) != 0; }
Is_NOHEADERNArchive::NApfs::btree_info740 bool Is_NOHEADER() const { return (fixed.flags & BTREE_NOHEADER) != 0; }
741
ParseNArchive::NApfs::btree_info742 void Parse(const Byte *p)
743 {
744 fixed.Parse(p);
745 G32 (0x10, longest_key)
746 G32 (0x14, longest_val)
747 G64 (0x18, key_count)
748 G64 (0x20, node_count)
749 }
750 };
751
752
753 /*
754 typedef UInt32 cp_key_class_t;
755 typedef UInt32 cp_key_os_version_t;
756 typedef UInt16 cp_key_revision_t;
757 typedef UInt32 crypto_flags_t;
758
759 struct wrapped_meta_crypto_state
760 {
761 UInt16 major_version;
762 UInt16 minor_version;
763 crypto_flags_t cpflags;
764 cp_key_class_t persistent_class;
765 cp_key_os_version_t key_os_version;
766 cp_key_revision_t key_revision;
767 // UInt16 unused;
768
769 void Parse(const Byte *p)
770 {
771 G16 (0, major_version);
772 G16 (2, minor_version);
773 G32 (4, cpflags);
774 G32 (8, persistent_class);
775 G32 (12, key_os_version);
776 G16 (16, key_revision);
777 }
778 };
779 */
780
781
782 #define APFS_MODIFIED_NAMELEN 32
783 #define sizeof_apfs_modified_by_t (APFS_MODIFIED_NAMELEN + 16)
784
785 struct apfs_modified_by_t
786 {
787 Byte id[APFS_MODIFIED_NAMELEN];
788 UInt64 timestamp;
789 xid_t last_xid;
790
ParseNArchive::NApfs::apfs_modified_by_t791 void Parse(const Byte *p)
792 {
793 memcpy(id, p, APFS_MODIFIED_NAMELEN);
794 p += APFS_MODIFIED_NAMELEN;
795 G64 (0, timestamp)
796 G64x (8, last_xid)
797 }
798 };
799
800
801 #define APFS_MAX_HIST 8
802 #define APFS_VOLNAME_LEN 256
803
804 struct CApfs
805 {
806 // apfs_
807 CPhys o;
808 // UInt32 magic;
809 UInt32 fs_index; // index of the object identifier for this volume's file system in the container's array of file systems.
810 // UInt64 features;
811 // UInt64 readonly_compatible_features;
812 UInt64 incompatible_features;
813 UInt64 unmount_time;
814 // UInt64 fs_reserve_block_count;
815 // UInt64 fs_quota_block_count;
816 UInt64 fs_alloc_count;
817 // wrapped_meta_crypto_state meta_crypto;
818 // UInt32 root_tree_type;
819 /* The type of the root file-system tree.
820 The value is typically OBJ_VIRTUAL | OBJECT_TYPE_BTREE,
821 with a subtype of OBJECT_TYPE_FSTREE */
822
823 // UInt32 extentref_tree_type;
824 // UInt32 snap_meta_tree_type;
825 oid_t omap_oid;
826 oid_t root_tree_oid;
827 /*
828 oid_t extentref_tree_oid;
829 oid_t snap_meta_tree_oid;
830 xid_t revert_to_xid;
831 oid_t revert_to_sblock_oid;
832 UInt64 next_obj_id;
833 */
834 UInt64 num_files;
835 UInt64 num_directories;
836 UInt64 num_symlinks;
837 UInt64 num_other_fsobjects;
838 UInt64 num_snapshots;
839 UInt64 total_blocks_alloced;
840 UInt64 total_blocks_freed;
841 CUuid vol_uuid;
842 UInt64 last_mod_time;
843 UInt64 fs_flags;
844 apfs_modified_by_t formatted_by;
845 apfs_modified_by_t modified_by[APFS_MAX_HIST];
846 Byte volname[APFS_VOLNAME_LEN];
847 /*
848 UInt32 next_doc_id;
849 UInt16 role; // APFS_VOL_ROLE_NONE APFS_VOL_ROLE_SYSTEM ....
850 UInt16 reserved;
851 xid_t root_to_xid;
852 oid_t er_state_oid;
853 UInt64 cloneinfo_id_epoch;
854 UInt64 cloneinfo_xid;
855 oid_t snap_meta_ext_oid;
856 CUuid volume_group_id;
857 */
858 oid_t integrity_meta_oid; // virtual object identifier of the integrity metadata object
859 oid_t fext_tree_oid; // virtual object identifier of the file extent tree.
860 UInt32 fext_tree_type; // The type of the file extent tree.
861 /*
862 UInt32 reserved_type;
863 oid_t reserved_oid;
864 */
865
GetTotalItemsNArchive::NApfs::CApfs866 UInt64 GetTotalItems() const
867 {
868 return num_files + num_directories + num_symlinks + num_other_fsobjects;
869 }
870
IsHashedNameNArchive::NApfs::CApfs871 bool IsHashedName() const
872 {
873 return
874 (incompatible_features & APFS_INCOMPAT_CASE_INSENSITIVE) != 0 ||
875 (incompatible_features & APFS_INCOMPAT_NORMALIZATION_INSENSITIVE) != 0;
876 }
877
878 bool Parse(const Byte *p, size_t size);
879 };
880
881
Parse(const Byte * p,size_t size)882 bool CApfs::Parse(const Byte *p, size_t size)
883 {
884 o.Parse(p);
885 if (Get32(p + 32) != 0x42535041) // { 'A', 'P', 'S', 'B' };
886 return false;
887 if (o.GetType() != OBJECT_TYPE_FS)
888 return false;
889 if (!CheckFletcher64(p, size))
890 return false;
891 // if (o.GetType() != OBJECT_TYPE_NX_SUPERBLOCK) return false;
892
893 G32 (0x24, fs_index)
894 // G64 (0x28, features);
895 // G64 (0x30, readonly_compatible_features);
896 G64 (0x38, incompatible_features)
897 G64 (0x40, unmount_time)
898 // G64 (0x48, fs_reserve_block_count);
899 // G64 (0x50, fs_quota_block_count);
900 G64 (0x58, fs_alloc_count)
901 // meta_crypto.Parse(p + 0x60);
902 // G32 (0x74, root_tree_type);
903 // G32 (0x78, extentref_tree_type);
904 // G32 (0x7C, snap_meta_tree_type);
905
906 G64o (0x80, omap_oid)
907 G64o (0x88, root_tree_oid)
908 /*
909 G64o (0x90, extentref_tree_oid);
910 G64o (0x98, snap_meta_tree_oid);
911 G64x (0xa0, revert_to_xid);
912 G64o (0xa8, revert_to_sblock_oid);
913 G64 (0xb0, next_obj_id);
914 */
915 G64 (0xb8, num_files)
916 G64 (0xc0, num_directories)
917 G64 (0xc8, num_symlinks)
918 G64 (0xd0, num_other_fsobjects)
919 G64 (0xd8, num_snapshots)
920 G64 (0xe0, total_blocks_alloced)
921 G64 (0xe8, total_blocks_freed)
922 vol_uuid.SetFrom(p + 0xf0);
923 G64 (0x100, last_mod_time)
924 G64 (0x108, fs_flags)
925 p += 0x110;
926 formatted_by.Parse(p);
927 p += sizeof_apfs_modified_by_t;
928 for (unsigned i = 0; i < APFS_MAX_HIST; i++)
929 {
930 modified_by[i].Parse(p);
931 p += sizeof_apfs_modified_by_t;
932 }
933 memcpy(volname, p, APFS_VOLNAME_LEN);
934 p += APFS_VOLNAME_LEN;
935 /*
936 G32 (0, next_doc_id);
937 G16 (4, role);
938 G16 (6, reserved);
939 G64x (8, root_to_xid);
940 G64o (0x10, er_state_oid);
941 G64 (0x18, cloneinfo_id_epoch);
942 G64 (0x20, cloneinfo_xid);
943 G64o (0x28, snap_meta_ext_oid);
944 volume_group_id.SetFrom(p + 0x30);
945 */
946 G64o (0x40, integrity_meta_oid)
947 G64o (0x48, fext_tree_oid)
948 G32 (0x50, fext_tree_type)
949 /*
950 G32 (0x54, reserved_type);
951 G64o (0x58, reserved_oid);
952 */
953 return true;
954 }
955
956
957 #define OBJ_ID_MASK 0x0fffffffffffffff
958 /*
959 #define OBJ_TYPE_MASK 0xf000000000000000
960 #define SYSTEM_OBJ_ID_MARK 0x0fffffff00000000
961 */
962 #define OBJ_TYPE_SHIFT 60
963
964 typedef enum
965 {
966 APFS_TYPE_ANY = 0,
967 APFS_TYPE_SNAP_METADATA = 1,
968 APFS_TYPE_EXTENT = 2,
969 APFS_TYPE_INODE = 3,
970 APFS_TYPE_XATTR = 4,
971 APFS_TYPE_SIBLING_LINK = 5,
972 APFS_TYPE_DSTREAM_ID = 6,
973 APFS_TYPE_CRYPTO_STATE = 7,
974 APFS_TYPE_FILE_EXTENT = 8,
975 APFS_TYPE_DIR_REC = 9,
976 APFS_TYPE_DIR_STATS = 10,
977 APFS_TYPE_SNAP_NAME = 11,
978 APFS_TYPE_SIBLING_MAP = 12,
979 APFS_TYPE_FILE_INFO = 13,
980 APFS_TYPE_MAX_VALID = 13,
981 APFS_TYPE_MAX = 15,
982 APFS_TYPE_INVALID = 15
983 } j_obj_types;
984
985
986 struct j_key_t
987 {
988 UInt64 obj_id_and_type;
989
ParseNArchive::NApfs::j_key_t990 void Parse(const Byte *p) { G64(0, obj_id_and_type) }
GetTypeNArchive::NApfs::j_key_t991 unsigned GetType() const { return (unsigned)(obj_id_and_type >> OBJ_TYPE_SHIFT); }
GetIDNArchive::NApfs::j_key_t992 UInt64 GetID() const { return obj_id_and_type & OBJ_ID_MASK; }
993 };
994
995
996
997 #define J_DREC_LEN_MASK 0x000003ff
998 /*
999 #define J_DREC_HASH_MASK 0xfffff400
1000 #define J_DREC_HASH_SHIFT 10
1001 */
1002
1003 static const unsigned k_SizeOf_j_drec_val = 0x12;
1004
1005 struct j_drec_val
1006 {
1007 UInt64 file_id;
1008 UInt64 date_added; /* The time that this directory entry was added to the directory.
1009 It's not updated when modifying the directory entry for example,
1010 by renaming a file without moving it to a different directory. */
1011 UInt16 flags;
1012
1013 // bool IsFlags_File() const { return flags == MY_LIN_DT_REG; }
IsFlags_UnknownNArchive::NApfs::j_drec_val1014 bool IsFlags_Unknown() const { return flags == MY_LIN_DT_UNKNOWN; }
IsFlags_DirNArchive::NApfs::j_drec_val1015 bool IsFlags_Dir() const { return flags == MY_LIN_DT_DIR; }
1016
1017 // uint8_t xfields[];
ParseNArchive::NApfs::j_drec_val1018 void Parse(const Byte *p)
1019 {
1020 G64 (0, file_id)
1021 G64 (8, date_added)
1022 G16 (0x10, flags)
1023 }
1024 };
1025
1026
1027 struct CItem
1028 {
1029 // j_key_t hdr;
1030 UInt64 ParentId;
1031 AString Name;
1032 j_drec_val Val;
1033
1034 unsigned ParentItemIndex;
1035 unsigned RefIndex;
1036 // unsigned iNode_Index;
1037
ClearNArchive::NApfs::CItem1038 void Clear()
1039 {
1040 Name.Empty();
1041 ParentItemIndex = VI_MINUS1;
1042 RefIndex = VI_MINUS1;
1043 }
1044 };
1045
1046
1047 /*
1048 #define INVALID_INO_NUM 0
1049 #define ROOT_DIR_PARENT 1 // parent for "root" and "private-dir", there's no inode on disk with this inode number.
1050 */
1051 #define ROOT_DIR_INO_NUM 2 // "root" - parent for all main files
1052 #define PRIV_DIR_INO_NUM 3 // "private-dir"
1053 /*
1054 #define SNAP_DIR_INO_NUM 6 // the directory where snapshot metadata is stored. Snapshot inodes are stored in the snapshot metedata tree.
1055 #define PURGEABLE_DIR_INO_NUM 7
1056 #define MIN_USER_INO_NUM 16
1057
1058 #define UNIFIED_ID_SPACE_MARK 0x0800000000000000
1059 */
1060
1061 //typedef enum
1062 // {
1063 /*
1064 INODE_IS_APFS_PRIVATE = 0x00000001,
1065 INODE_MAINTAIN_DIR_STATS = 0x00000002,
1066 INODE_DIR_STATS_ORIGIN = 0x00000004,
1067 INODE_PROT_CLASS_EXPLICIT = 0x00000008,
1068 INODE_WAS_CLONED = 0x00000010,
1069 INODE_FLAG_UNUSED = 0x00000020,
1070 INODE_HAS_SECURITY_EA = 0x00000040,
1071 INODE_BEING_TRUNCATED = 0x00000080,
1072 INODE_HAS_FINDER_INFO = 0x00000100,
1073 INODE_IS_SPARSE = 0x00000200,
1074 INODE_WAS_EVER_CLONED = 0x00000400,
1075 INODE_ACTIVE_FILE_TRIMMED = 0x00000800,
1076 INODE_PINNED_TO_MAIN = 0x00001000,
1077 INODE_PINNED_TO_TIER2 = 0x00002000,
1078 */
1079 // INODE_HAS_RSRC_FORK = 0x00004000,
1080 /*
1081 INODE_NO_RSRC_FORK = 0x00008000,
1082 INODE_ALLOCATION_SPILLEDOVER = 0x00010000,
1083 INODE_FAST_PROMOTE = 0x00020000,
1084 */
1085 #define INODE_HAS_UNCOMPRESSED_SIZE 0x00040000
1086 /*
1087 INODE_IS_PURGEABLE = 0x00080000,
1088 INODE_WANTS_TO_BE_PURGEABLE = 0x00100000,
1089 INODE_IS_SYNC_ROOT = 0x00200000,
1090 INODE_SNAPSHOT_COW_EXEMPTION = 0x00400000,
1091
1092
1093 INODE_INHERITED_INTERNAL_FLAGS = \
1094 ( INODE_MAINTAIN_DIR_STATS \
1095 | INODE_SNAPSHOT_COW_EXEMPTION),
1096
1097 INODE_CLONED_INTERNAL_FLAGS = \
1098 ( INODE_HAS_RSRC_FORK \
1099 | INODE_NO_RSRC_FORK \
1100 | INODE_HAS_FINDER_INFO \
1101 | INODE_SNAPSHOT_COW_EXEMPTION),
1102 */
1103 // }
1104 // j_inode_flags;
1105
1106
1107 /*
1108 #define APFS_VALID_INTERNAL_INODE_FLAGS \
1109 ( INODE_IS_APFS_PRIVATE \
1110 | INODE_MAINTAIN_DIR_STATS \
1111 | INODE_DIR_STATS_ORIGIN \
1112 | INODE_PROT_CLASS_EXPLICIT \
1113 | INODE_WAS_CLONED \
1114 | INODE_HAS_SECURITY_EA \
1115 | INODE_BEING_TRUNCATED \
1116 | INODE_HAS_FINDER_INFO \
1117 | INODE_IS_SPARSE \
1118 | INODE_WAS_EVER_CLONED \
1119 | INODE_ACTIVE_FILE_TRIMMED \
1120 | INODE_PINNED_TO_MAIN \
1121 | INODE_PINNED_TO_TIER2 \
1122 | INODE_HAS_RSRC_FORK \
1123 | INODE_NO_RSRC_FORK \
1124 | INODE_ALLOCATION_SPILLEDOVER \
1125 | INODE_FAST_PROMOTE \
1126 | INODE_HAS_UNCOMPRESSED_SIZE \
1127 | INODE_IS_PURGEABLE \
1128 | INODE_WANTS_TO_BE_PURGEABLE \
1129 | INODE_IS_SYNC_ROOT \
1130 | INODE_SNAPSHOT_COW_EXEMPTION)
1131
1132 #define APFS_INODE_PINNED_MASK (INODE_PINNED_TO_MAIN | INODE_PINNED_TO_TIER2)
1133 */
1134
1135 static const char * const g_INODE_Flags[] =
1136 {
1137 "IS_APFS_PRIVATE"
1138 , "MAINTAIN_DIR_STATS"
1139 , "DIR_STATS_ORIGIN"
1140 , "PROT_CLASS_EXPLICIT"
1141 , "WAS_CLONED"
1142 , "FLAG_UNUSED"
1143 , "HAS_SECURITY_EA"
1144 , "BEING_TRUNCATED"
1145 , "HAS_FINDER_INFO"
1146 , "IS_SPARSE"
1147 , "WAS_EVER_CLONED"
1148 , "ACTIVE_FILE_TRIMMED"
1149 , "PINNED_TO_MAIN"
1150 , "PINNED_TO_TIER2"
1151 , "HAS_RSRC_FORK"
1152 , "NO_RSRC_FORK"
1153 , "ALLOCATION_SPILLEDOVER"
1154 , "FAST_PROMOTE"
1155 , "HAS_UNCOMPRESSED_SIZE"
1156 , "IS_PURGEABLE"
1157 , "WANTS_TO_BE_PURGEABLE"
1158 , "IS_SYNC_ROOT"
1159 , "SNAPSHOT_COW_EXEMPTION"
1160 };
1161
1162
1163 // bsd stat.h
1164 /*
1165 #define MY_UF_SETTABLE 0x0000ffff // mask of owner changeable flags
1166 #define MY_UF_NODUMP 0x00000001 // do not dump file
1167 #define MY_UF_IMMUTABLE 0x00000002 // file may not be changed
1168 #define MY_UF_APPEND 0x00000004 // writes to file may only append
1169 #define MY_UF_OPAQUE 0x00000008 // directory is opaque wrt. union
1170 #define MY_UF_NOUNLINK 0x00000010 // file entry may not be removed or renamed Not implement in MacOS
1171 #define MY_UF_COMPRESSED 0x00000020 // file entry is compressed
1172 #define MY_UF_TRACKED 0x00000040 // notify about file entry changes
1173 #define MY_UF_DATAVAULT 0x00000080 // entitlement required for reading and writing
1174 #define MY_UF_HIDDEN 0x00008000 // file entry is hidden
1175
1176 #define MY_SF_SETTABLE 0xffff0000 // mask of superuser changeable flags
1177 #define MY_SF_ARCHIVED 0x00010000 // file is archived
1178 #define MY_SF_IMMUTABLE 0x00020000 // file may not be changed
1179 #define MY_SF_APPEND 0x00040000 // writes to file may only append
1180 #define MY_SF_RESTRICTED 0x00080000 // entitlement required for writing
1181 #define MY_SF_NOUNLINK 0x00100000 // file entry may not be removed, renamed or used as mount point
1182 #define MY_SF_SNAPSHOT 0x00200000 // snapshot inode
1183 Not implement in MacOS
1184 */
1185
1186 static const char * const g_INODE_BSD_Flags[] =
1187 {
1188 "UF_NODUMP"
1189 , "UF_IMMUTABLE"
1190 , "UF_APPEND"
1191 , "UF_OPAQUE"
1192 , "UF_NOUNLINK"
1193 , "UF_COMPRESSED"
1194 , "UF_TRACKED"
1195 , "UF_DATAVAULT"
1196 , NULL, NULL, NULL, NULL
1197 , NULL, NULL, NULL
1198 , "UF_HIDDEN"
1199
1200 , "SF_ARCHIVE"
1201 , "SF_IMMUTABLE"
1202 , "SF_APPEND"
1203 , "SF_RESTRICTED"
1204 , "SF_NOUNLINK"
1205 , "SF_SNAPSHOT"
1206 };
1207
1208 /*
1209 #define INO_EXT_TYPE_SNAP_XID 1
1210 #define INO_EXT_TYPE_DELTA_TREE_OID 2
1211 #define INO_EXT_TYPE_DOCUMENT_ID 3
1212 */
1213 #define INO_EXT_TYPE_NAME 4
1214 /*
1215 #define INO_EXT_TYPE_PREV_FSIZE 5
1216 #define INO_EXT_TYPE_RESERVED_6 6
1217 #define INO_EXT_TYPE_FINDER_INFO 7
1218 */
1219 #define INO_EXT_TYPE_DSTREAM 8
1220 /*
1221 #define INO_EXT_TYPE_RESERVED_9 9
1222 #define INO_EXT_TYPE_DIR_STATS_KEY 10
1223 #define INO_EXT_TYPE_FS_UUID 11
1224 #define INO_EXT_TYPE_RESERVED_12 12
1225 #define INO_EXT_TYPE_SPARSE_BYTES 13
1226 #define INO_EXT_TYPE_RDEV 14
1227 #define INO_EXT_TYPE_PURGEABLE_FLAGS 15
1228 #define INO_EXT_TYPE_ORIG_SYNC_ROOT_ID 16
1229 */
1230
1231
1232 static const unsigned k_SizeOf_j_dstream = 8 * 5;
1233
1234 struct j_dstream
1235 {
1236 UInt64 size;
1237 UInt64 alloced_size;
1238 UInt64 default_crypto_id;
1239 UInt64 total_bytes_written;
1240 UInt64 total_bytes_read;
1241
ParseNArchive::NApfs::j_dstream1242 void Parse(const Byte *p)
1243 {
1244 G64 (0, size)
1245 G64 (0x8, alloced_size)
1246 G64 (0x10, default_crypto_id)
1247 G64 (0x18, total_bytes_written)
1248 G64 (0x20, total_bytes_read)
1249 }
1250 };
1251
1252 static const unsigned k_SizeOf_j_file_extent_val = 8 * 3;
1253
1254 #define J_FILE_EXTENT_LEN_MASK 0x00ffffffffffffffU
1255 // #define J_FILE_EXTENT_FLAG_MASK 0xff00000000000000U
1256 // #define J_FILE_EXTENT_FLAG_SHIFT 56
1257
1258 #define EXTENT_GET_LEN(x) ((x) & J_FILE_EXTENT_LEN_MASK)
1259
1260 struct j_file_extent_val
1261 {
1262 UInt64 len_and_flags; // The length must be a multiple of the block size defined by the nx_block_size field of nx_superblock_t.
1263 // There are currently no flags defined
1264 UInt64 phys_block_num; // The physical block address that the extent starts at
1265 // UInt64 crypto_id; // The encryption key or the encryption tweak used in this extent.
1266
ParseNArchive::NApfs::j_file_extent_val1267 void Parse(const Byte *p)
1268 {
1269 G64 (0, len_and_flags)
1270 G64 (0x8, phys_block_num)
1271 // G64 (0x10, crypto_id);
1272 }
1273 };
1274
1275
1276 struct CExtent
1277 {
1278 UInt64 logical_offset;
1279 UInt64 len_and_flags; // The length must be a multiple of the block size defined by the nx_block_size field of nx_superblock_t.
1280 // There are currently no flags defined
1281 UInt64 phys_block_num; // The physical block address that the extent starts at
1282
GetEndOffsetNArchive::NApfs::CExtent1283 UInt64 GetEndOffset() const { return logical_offset + EXTENT_GET_LEN(len_and_flags); }
1284 };
1285
1286
1287 typedef UInt32 MY_uid_t;
1288 typedef UInt32 MY_gid_t;
1289 typedef UInt16 MY_mode_t;
1290
1291
1292 typedef enum
1293 {
1294 XATTR_DATA_STREAM = 1 << 0,
1295 XATTR_DATA_EMBEDDED = 1 << 1,
1296 XATTR_FILE_SYSTEM_OWNED = 1 << 2,
1297 XATTR_RESERVED_8 = 1 << 3
1298 } j_xattr_flags;
1299
1300
1301 struct CAttr
1302 {
1303 AString Name;
1304 UInt32 flags;
1305 bool dstream_defined;
1306 bool NeedShow;
1307 CByteBuffer Data;
1308
1309 j_dstream dstream;
1310 UInt64 Id;
1311
Is_dstream_OK_for_SymLinkNArchive::NApfs::CAttr1312 bool Is_dstream_OK_for_SymLink() const
1313 {
1314 return dstream_defined && dstream.size <= (1 << 12) && dstream.size != 0;
1315 }
1316
GetSizeNArchive::NApfs::CAttr1317 UInt64 GetSize() const
1318 {
1319 if (dstream_defined) // dstream has more priority
1320 return dstream.size;
1321 return Data.Size();
1322 }
1323
ClearNArchive::NApfs::CAttr1324 void Clear()
1325 {
1326 dstream_defined = false;
1327 NeedShow = true;
1328 Data.Free();
1329 Name.Empty();
1330 }
1331
Is_STREAMNArchive::NApfs::CAttr1332 bool Is_STREAM() const { return (flags & XATTR_DATA_STREAM) != 0; }
Is_EMBEDDEDNArchive::NApfs::CAttr1333 bool Is_EMBEDDED() const { return (flags & XATTR_DATA_EMBEDDED) != 0; }
1334 };
1335
1336
1337 // j_inode_val_t
1338 struct CNode
1339 {
1340 unsigned ItemIndex; // index to CItem. We set it only if Node is directory.
1341 unsigned NumCalcedLinks; // Num links to that node
1342 // unsigned NumItems; // Num Items in that node
1343
1344 UInt64 parent_id; // The identifier of the file system record for the parent directory.
1345 UInt64 private_id;
1346 UInt64 create_time;
1347 UInt64 mod_time;
1348 UInt64 change_time;
1349 UInt64 access_time;
1350 UInt64 internal_flags;
1351 union
1352 {
1353 UInt32 nchildren; /* The number of directory entries.
1354 is valid only if the inode is a directory */
1355 UInt32 nlink; /* The number of hard links whose target is this inode.
1356 is valid only if the inode isn't a directory.
1357 Inodes with multiple hard links (nlink > 1)
1358 - The parent_id field refers to the parent directory of the primary link.
1359 - The name field contains the name of the primary link.
1360 - The INO_EXT_TYPE_NAME extended field contains the name of this link.
1361 */
1362 };
1363 // cp_key_class_t default_protection_class;
1364 UInt32 write_generation_counter;
1365 UInt32 bsd_flags;
1366 MY_uid_t owner;
1367 MY_gid_t group;
1368 MY_mode_t mode;
1369 UInt16 pad1;
1370 UInt64 uncompressed_size;
1371
1372 j_dstream dstream;
1373 AString PrimaryName;
1374
1375 bool dstream_defined;
1376 bool refcnt_defined;
1377
1378 UInt32 refcnt; // j_dstream_id_val_t
1379 CRecordVector<CExtent> Extents;
1380 CObjectVector<CAttr> Attrs;
1381 unsigned SymLinkIndex; // index in Attrs
1382 unsigned DecmpfsIndex; // index in Attrs
1383 unsigned ResourceIndex; // index in Attrs
1384
1385 NHfs::CCompressHeader CompressHeader;
1386
CNodeNArchive::NApfs::CNode1387 CNode():
1388 ItemIndex(VI_MINUS1),
1389 NumCalcedLinks(0),
1390 // NumItems(0),
1391 dstream_defined(false),
1392 refcnt_defined(false),
1393 SymLinkIndex(VI_MINUS1),
1394 DecmpfsIndex(VI_MINUS1),
1395 ResourceIndex(VI_MINUS1)
1396 {}
1397
IsDirNArchive::NApfs::CNode1398 bool IsDir() const { return MY_LIN_S_ISDIR(mode); }
IsSymLinkNArchive::NApfs::CNode1399 bool IsSymLink() const { return MY_LIN_S_ISLNK(mode); }
1400
Has_UNCOMPRESSED_SIZENArchive::NApfs::CNode1401 bool Has_UNCOMPRESSED_SIZE() const { return (internal_flags & INODE_HAS_UNCOMPRESSED_SIZE) != 0; }
1402
Get_Type_From_modeNArchive::NApfs::CNode1403 unsigned Get_Type_From_mode() const { return mode >> 12; }
1404
GetSizeNArchive::NApfs::CNode1405 bool GetSize(unsigned attrIndex, UInt64 &size) const
1406 {
1407 if (IsViNotDef(attrIndex))
1408 {
1409 if (dstream_defined)
1410 {
1411 size = dstream.size;
1412 return true;
1413 }
1414 size = 0;
1415 if (Has_UNCOMPRESSED_SIZE())
1416 {
1417 size = uncompressed_size;
1418 return true;
1419 }
1420 if (!IsSymLink())
1421 return false;
1422 attrIndex = SymLinkIndex;
1423 if (IsViNotDef(attrIndex))
1424 return false;
1425 }
1426 const CAttr &attr = Attrs[(unsigned)attrIndex];
1427 if (attr.dstream_defined)
1428 size = attr.dstream.size;
1429 else
1430 size = attr.Data.Size();
1431 return true;
1432 }
1433
GetPackSizeNArchive::NApfs::CNode1434 bool GetPackSize(unsigned attrIndex, UInt64 &size) const
1435 {
1436 if (IsViNotDef(attrIndex))
1437 {
1438 if (dstream_defined)
1439 {
1440 size = dstream.alloced_size;
1441 return true;
1442 }
1443 size = 0;
1444
1445 if (IsSymLink())
1446 attrIndex = SymLinkIndex;
1447 else
1448 {
1449 if (!CompressHeader.IsCorrect ||
1450 !CompressHeader.IsSupported)
1451 return false;
1452 const CAttr &attr = Attrs[DecmpfsIndex];
1453 if (!CompressHeader.IsMethod_Resource())
1454 {
1455 size = attr.Data.Size() - CompressHeader.DataPos;
1456 return true;
1457 }
1458 attrIndex = ResourceIndex;
1459 }
1460 if (IsViNotDef(attrIndex))
1461 return false;
1462 }
1463 const CAttr &attr = Attrs[(unsigned)attrIndex];
1464 if (attr.dstream_defined)
1465 size = attr.dstream.alloced_size;
1466 else
1467 size = attr.Data.Size();
1468 return true;
1469 }
1470
1471 void Parse(const Byte *p);
1472 };
1473
1474
1475 // it's used for Attr streams
1476 struct CSmallNode
1477 {
1478 CRecordVector<CExtent> Extents;
1479 // UInt32 NumLinks;
1480 // CSmallNode(): NumLinks(0) {}
1481 };
1482
1483 static const unsigned k_SizeOf_j_inode_val = 0x5c;
1484
Parse(const Byte * p)1485 void CNode::Parse(const Byte *p)
1486 {
1487 G64 (0, parent_id)
1488 G64 (0x8, private_id)
1489 G64 (0x10, create_time)
1490 G64 (0x18, mod_time)
1491 G64 (0x20, change_time)
1492 G64 (0x28, access_time)
1493 G64 (0x30, internal_flags)
1494 {
1495 G32 (0x38, nchildren)
1496 // G32 (0x38, nlink);
1497 }
1498 // G32 (0x3c, default_protection_class);
1499 G32 (0x40, write_generation_counter)
1500 G32 (0x44, bsd_flags)
1501 G32 (0x48, owner)
1502 G32 (0x4c, group)
1503 G16 (0x50, mode)
1504 // G16 (0x52, pad1);
1505 G64 (0x54, uncompressed_size)
1506 }
1507
1508
1509 struct CRef
1510 {
1511 unsigned ItemIndex;
1512 unsigned NodeIndex;
1513 unsigned ParentRefIndex;
1514
1515 #ifdef APFS_SHOW_ALT_STREAMS
1516 unsigned AttrIndex;
IsAltStreamNArchive::NApfs::CRef1517 bool IsAltStream() const { return IsViDef(AttrIndex); }
GetAttrIndexNArchive::NApfs::CRef1518 unsigned GetAttrIndex() const { return AttrIndex; }
1519 #else
1520 // bool IsAltStream() const { return false; }
GetAttrIndexNArchive::NApfs::CRef1521 unsigned GetAttrIndex() const { return VI_MINUS1; }
1522 #endif
1523 };
1524
1525
1526 struct CRef2
1527 {
1528 unsigned VolIndex;
1529 unsigned RefIndex;
1530 };
1531
1532
1533 struct CHashChunk
1534 {
1535 UInt64 lba;
1536 UInt32 hashed_len; // the value is UInt16
1537 Byte hash[APFS_HASH_MAX_SIZE];
1538 };
1539
1540 typedef CRecordVector<CHashChunk> CStreamHashes;
1541
1542
1543 Z7_CLASS_IMP_NOQIB_1(
1544 COutStreamWithHash
1545 , ISequentialOutStream
1546 )
1547 bool _hashError;
1548 CAlignedBuffer1 _sha;
1549 CMyComPtr<ISequentialOutStream> _stream;
1550 const CStreamHashes *_hashes;
1551 unsigned _blockSizeLog;
1552 unsigned _chunkIndex;
1553 UInt32 _offsetInChunk;
1554 // UInt64 _size;
1555
Sha()1556 CSha256 *Sha() { return (CSha256 *)(void *)(Byte *)_sha; }
1557 public:
1558 COutStreamWithHash(): _sha(sizeof(CSha256)) {}
1559
1560 void SetStream(ISequentialOutStream *stream) { _stream = stream; }
1561 // void ReleaseStream() { _stream.Release(); }
1562 void Init(const CStreamHashes *hashes, unsigned blockSizeLog)
1563 {
1564 _hashes = hashes;
1565 _blockSizeLog = blockSizeLog;
1566 _chunkIndex = 0;
1567 _offsetInChunk = 0;
1568 _hashError = false;
1569 // _size = 0;
1570 }
1571 // UInt64 GetSize() const { return _size; }
1572 bool FinalCheck();
1573 };
1574
1575
1576 static bool Sha256_Final_and_CheckDigest(CSha256 *sha256, const Byte *digest)
1577 {
1578 MY_ALIGN (16)
1579 UInt32 temp[SHA256_NUM_DIGEST_WORDS];
1580 Sha256_Final(sha256, (Byte *)temp);
1581 return memcmp(temp, digest, SHA256_DIGEST_SIZE) == 0;
1582 }
1583
1584
1585 Z7_COM7F_IMF(COutStreamWithHash::Write(const void *data, UInt32 size, UInt32 *processedSize))
1586 {
1587 HRESULT result = S_OK;
1588 if (_stream)
1589 result = _stream->Write(data, size, &size);
1590 if (processedSize)
1591 *processedSize = size;
1592 while (size != 0)
1593 {
1594 if (_hashError)
1595 break;
1596 if (_chunkIndex >= _hashes->Size())
1597 {
1598 _hashError = true;
1599 break;
1600 }
1601 if (_offsetInChunk == 0)
1602 Sha256_Init(Sha());
1603 const CHashChunk &chunk = (*_hashes)[_chunkIndex];
1604 /* (_blockSizeLog <= 16) && chunk.hashed_len is 16-bit.
1605 so we can use 32-bit chunkSize here */
1606 const UInt32 chunkSize = ((UInt32)chunk.hashed_len << _blockSizeLog);
1607 const UInt32 rem = chunkSize - _offsetInChunk;
1608 UInt32 cur = size;
1609 if (cur > rem)
1610 cur = (UInt32)rem;
1611 Sha256_Update(Sha(), (const Byte *)data, cur);
1612 data = (const void *)((const Byte *)data + cur);
1613 size -= cur;
1614 // _size += cur;
1615 _offsetInChunk += cur;
1616 if (chunkSize == _offsetInChunk)
1617 {
1618 if (!Sha256_Final_and_CheckDigest(Sha(), chunk.hash))
1619 _hashError = true;
1620 _offsetInChunk = 0;
1621 _chunkIndex++;
1622 }
1623 }
1624 return result;
1625 }
1626
1627
1628 bool COutStreamWithHash::FinalCheck()
1629 {
1630 if (_hashError)
1631 return false;
1632
1633 if (_offsetInChunk != 0)
1634 {
1635 const CHashChunk &chunk = (*_hashes)[_chunkIndex];
1636 {
1637 const UInt32 chunkSize = ((UInt32)chunk.hashed_len << _blockSizeLog);
1638 const UInt32 rem = chunkSize - _offsetInChunk;
1639 Byte b = 0;
1640 for (UInt32 i = 0; i < rem; i++)
1641 Sha256_Update(Sha(), &b, 1);
1642 }
1643 if (!Sha256_Final_and_CheckDigest(Sha(), chunk.hash))
1644 _hashError = true;
1645 _offsetInChunk = 0;
1646 _chunkIndex++;
1647 }
1648 if (_chunkIndex != _hashes->Size())
1649 _hashError = true;
1650 return !_hashError;
1651 }
1652
1653
1654
1655 struct CVol
1656 {
1657 CObjectVector<CNode> Nodes;
1658 CRecordVector<UInt64> NodeIDs;
1659 CObjectVector<CItem> Items;
1660 CRecordVector<CRef> Refs;
1661
1662 CObjectVector<CSmallNode> SmallNodes;
1663 CRecordVector<UInt64> SmallNodeIDs;
1664
1665 CObjectVector<CSmallNode> FEXT_Nodes;
1666 CRecordVector<UInt64> FEXT_NodeIDs;
1667
1668 CObjectVector<CStreamHashes> Hash_Vectors;
1669 CRecordVector<UInt64> Hash_IDs;
1670
1671 unsigned StartRef2Index; // ref2_Index for Refs[0] item
1672 unsigned RootRef2Index; // ref2_Index of virtual root folder (Volume1)
1673 CApfs apfs;
1674 C_integrity_meta_phys integrity;
1675
1676 bool NodeNotFound;
1677 bool ThereAreUnlinkedNodes;
1678 bool WrongInodeLink;
1679 bool UnsupportedFeature;
1680 bool UnsupportedMethod;
1681
1682 unsigned NumItems_In_PrivateDir;
1683 unsigned NumAltStreams;
1684
1685 UString RootName;
1686
1687 bool ThereAreErrors() const
1688 {
1689 return NodeNotFound || ThereAreUnlinkedNodes || WrongInodeLink;
1690 }
1691
1692 void AddComment(UString &s) const;
1693
1694 HRESULT FillRefs();
1695
1696 CVol():
1697 StartRef2Index(0),
1698 RootRef2Index(VI_MINUS1),
1699 NodeNotFound(false),
1700 ThereAreUnlinkedNodes(false),
1701 WrongInodeLink(false),
1702 UnsupportedFeature(false),
1703 UnsupportedMethod(false),
1704 NumItems_In_PrivateDir(0),
1705 NumAltStreams(0)
1706 {}
1707 };
1708
1709
1710 static void ApfsTimeToFileTime(UInt64 apfsTime, FILETIME &ft, UInt32 &ns100)
1711 {
1712 const UInt64 s = apfsTime / 1000000000;
1713 const UInt32 ns = (UInt32)(apfsTime % 1000000000);
1714 ns100 = (ns % 100);
1715 const UInt64 v = NWindows::NTime::UnixTime64_To_FileTime64((Int64)s) + ns / 100;
1716 ft.dwLowDateTime = (DWORD)v;
1717 ft.dwHighDateTime = (DWORD)(v >> 32);
1718 }
1719
1720 static void AddComment_Name(UString &s, const char *name)
1721 {
1722 s += name;
1723 s += ": ";
1724 }
1725
1726 /*
1727 static void AddComment_Bool(UString &s, const char *name, bool val)
1728 {
1729 AddComment_Name(s, name);
1730 s += val ? "+" : "-";
1731 s.Add_LF();
1732 }
1733 */
1734
1735 static void AddComment_UInt64(UString &s, const char *name, UInt64 v)
1736 {
1737 AddComment_Name(s, name);
1738 s.Add_UInt64(v);
1739 s.Add_LF();
1740 }
1741
1742
1743 static void AddComment_Time(UString &s, const char *name, UInt64 v)
1744 {
1745 AddComment_Name(s, name);
1746
1747 FILETIME ft;
1748 UInt32 ns100;
1749 ApfsTimeToFileTime(v, ft, ns100);
1750 char temp[64];
1751 ConvertUtcFileTimeToString2(ft, ns100, temp
1752 // , kTimestampPrintLevel_SEC);
1753 , kTimestampPrintLevel_NS);
1754 s += temp;
1755 s.Add_LF();
1756 }
1757
1758
1759 static void AddComment_modified_by_t(UString &s, const char *name, const apfs_modified_by_t &v)
1760 {
1761 AddComment_Name(s, name);
1762 AString s2;
1763 s2.SetFrom_CalcLen((const char *)v.id, sizeof(v.id));
1764 s += s2;
1765 s.Add_LF();
1766 s += " ";
1767 AddComment_Time(s, "timestamp", v.timestamp);
1768 s += " ";
1769 AddComment_UInt64(s, "last_xid", v.last_xid);
1770 }
1771
1772
1773 static void AddVolInternalName_toString(UString &s, const CApfs &apfs)
1774 {
1775 AString temp;
1776 temp.SetFrom_CalcLen((const char *)apfs.volname, sizeof(apfs.volname));
1777 UString unicode;
1778 ConvertUTF8ToUnicode(temp, unicode);
1779 s += unicode;
1780 }
1781
1782
1783 void CVol::AddComment(UString &s) const
1784 {
1785 AddComment_UInt64(s, "fs_index", apfs.fs_index);
1786 {
1787 AddComment_Name(s, "volume_name");
1788 AddVolInternalName_toString(s, apfs);
1789 s.Add_LF();
1790 }
1791 AddComment_Name(s, "vol_uuid");
1792 apfs.vol_uuid.AddHexToString(s);
1793 s.Add_LF();
1794
1795 AddComment_Name(s, "incompatible_features");
1796 s += FlagsToString(g_APFS_INCOMPAT_Flags,
1797 Z7_ARRAY_SIZE(g_APFS_INCOMPAT_Flags),
1798 (UInt32)apfs.incompatible_features);
1799 s.Add_LF();
1800
1801
1802 if (apfs.integrity_meta_oid != 0)
1803 {
1804 /*
1805 AddComment_Name(s, "im_version");
1806 s.Add_UInt32(integrity.im_version);
1807 s.Add_LF();
1808 */
1809 AddComment_Name(s, "im_flags");
1810 s.Add_UInt32(integrity.im_flags);
1811 s.Add_LF();
1812 AddComment_Name(s, "im_hash_type");
1813 const char *p = NULL;
1814 if (integrity.im_hash_type < Z7_ARRAY_SIZE(g_hash_types))
1815 p = g_hash_types[integrity.im_hash_type];
1816 if (p)
1817 s += p;
1818 else
1819 s.Add_UInt32(integrity.im_hash_type);
1820 s.Add_LF();
1821 }
1822
1823
1824 // AddComment_UInt64(s, "reserve_block_count", apfs.fs_reserve_block_count, false);
1825 // AddComment_UInt64(s, "quota_block_count", apfs.fs_quota_block_count);
1826 AddComment_UInt64(s, "fs_alloc_count", apfs.fs_alloc_count);
1827
1828 AddComment_UInt64(s, "num_files", apfs.num_files);
1829 AddComment_UInt64(s, "num_directories", apfs.num_directories);
1830 AddComment_UInt64(s, "num_symlinks", apfs.num_symlinks);
1831 AddComment_UInt64(s, "num_other_fsobjects", apfs.num_other_fsobjects);
1832
1833 AddComment_UInt64(s, "Num_Attr_Streams", NumAltStreams);
1834
1835 AddComment_UInt64(s, "num_snapshots", apfs.num_snapshots);
1836 AddComment_UInt64(s, "total_blocks_alloced", apfs.total_blocks_alloced);
1837 AddComment_UInt64(s, "total_blocks_freed", apfs.total_blocks_freed);
1838
1839 AddComment_Time(s, "unmounted", apfs.unmount_time);
1840 AddComment_Time(s, "last_modified", apfs.last_mod_time);
1841 AddComment_modified_by_t(s, "formatted_by", apfs.formatted_by);
1842 for (unsigned i = 0; i < Z7_ARRAY_SIZE(apfs.modified_by); i++)
1843 {
1844 const apfs_modified_by_t &v = apfs.modified_by[i];
1845 if (v.last_xid == 0 && v.timestamp == 0 && v.id[0] == 0)
1846 continue;
1847 AString name ("modified_by[");
1848 name.Add_UInt32(i);
1849 name += ']';
1850 AddComment_modified_by_t(s, name.Ptr(), v);
1851 }
1852 }
1853
1854
1855
1856 struct CKeyValPair
1857 {
1858 CByteBuffer Key;
1859 CByteBuffer Val;
1860 // unsigned ValPos; // for alognment
1861 };
1862
1863
1864 struct omap_key
1865 {
1866 oid_t oid; // The object identifier
1867 xid_t xid; // The transaction identifier
1868 void Parse(const Byte *p)
1869 {
1870 G64o (0, oid)
1871 G64x (8, xid)
1872 }
1873 };
1874
1875 /*
1876 #define OMAP_VAL_DELETED (1 << 0)
1877 #define OMAP_VAL_SAVED (1 << 1)
1878 #define OMAP_VAL_ENCRYPTED (1 << 2)
1879 */
1880 #define OMAP_VAL_NOHEADER (1 << 3)
1881 /*
1882 #define OMAP_VAL_CRYPTO_GENERATION (1 << 4)
1883 */
1884
1885 struct omap_val
1886 {
1887 UInt32 flags;
1888 UInt32 size;
1889 // paddr_t paddr;
1890 paddr_t_unsigned paddr;
1891
1892 bool IsFlag_NoHeader() const { return (flags & OMAP_VAL_NOHEADER) != 0; }
1893
1894 void Parse(const Byte *p)
1895 {
1896 G32 (0, flags)
1897 G32 (4, size)
1898 G64 (8, paddr)
1899 }
1900 };
1901
1902
1903 struct CObjectMap
1904 {
1905 CRecordVector<UInt64> Keys;
1906 CRecordVector<omap_val> Vals;
1907
1908 bool Parse(const CObjectVector<CKeyValPair> &pairs);
1909 int FindKey(UInt64 id) const { return Keys.FindInSorted(id); }
1910 };
1911
1912 bool CObjectMap::Parse(const CObjectVector<CKeyValPair> &pairs)
1913 {
1914 omap_key prev;
1915 prev.oid = 0;
1916 prev.xid = 0;
1917 FOR_VECTOR (i, pairs)
1918 {
1919 const CKeyValPair &pair = pairs[i];
1920 if (pair.Key.Size() != 16 || pair.Val.Size() != 16)
1921 return false;
1922 omap_key key;
1923 key.Parse(pair.Key);
1924 omap_val val;
1925 val.Parse(pair.Val);
1926 /* Object map B-trees are sorted by object identifier and then by transaction identifier
1927 but it's possible to have identical Ids in map ?
1928 do we need to look transaction id ?
1929 and search key with largest transaction id? */
1930 if (key.oid <= prev.oid)
1931 return false;
1932 prev = key;
1933 Keys.Add(key.oid);
1934 Vals.Add(val);
1935 }
1936 return true;
1937 }
1938
1939
1940 struct CMap
1941 {
1942 CObjectVector<CKeyValPair> Pairs;
1943
1944 CObjectMap Omap;
1945 btree_info bti;
1946 UInt64 NumNodes;
1947
1948 // we use thnese options to check:
1949 UInt32 Subtype;
1950 bool IsPhysical;
1951
1952 bool CheckAtFinish() const
1953 {
1954 return NumNodes == bti.node_count && Pairs.Size() == bti.key_count;
1955 }
1956
1957 CMap():
1958 NumNodes(0),
1959 Subtype(0),
1960 IsPhysical(true)
1961 {}
1962 };
1963
1964
1965
1966 struct CDatabase
1967 {
1968 CRecordVector<CRef2> Refs2;
1969 CObjectVector<CVol> Vols;
1970
1971 bool HeadersError;
1972 bool ThereAreAltStreams;
1973 bool UnsupportedFeature;
1974 bool UnsupportedMethod;
1975
1976 CSuperBlock sb;
1977
1978 IInStream *OpenInStream;
1979 IArchiveOpenCallback *OpenCallback;
1980 UInt64 ProgressVal_Cur;
1981 UInt64 ProgressVal_Prev;
1982 UInt64 ProgressVal_NumFilesTotal;
1983 CObjectVector<CByteBuffer> Buffers;
1984
1985 UInt32 MethodsMask;
1986 UInt64 GetSize(const UInt32 index) const;
1987
1988 void Clear()
1989 {
1990 HeadersError = false;
1991 ThereAreAltStreams = false;
1992 UnsupportedFeature = false;
1993 UnsupportedMethod = false;
1994
1995 ProgressVal_Cur = 0;
1996 ProgressVal_Prev = 0;
1997 ProgressVal_NumFilesTotal = 0;
1998
1999 MethodsMask = 0;
2000
2001 Vols.Clear();
2002 Refs2.Clear();
2003 Buffers.Clear();
2004 }
2005
2006 HRESULT SeekReadBlock_FALSE(UInt64 oid, void *data);
2007 void GetItemPath(unsigned index, const CNode *inode, NWindows::NCOM::CPropVariant &path) const;
2008 HRESULT ReadMap(UInt64 oid, bool noHeader, CVol *vol, const Byte *hash,
2009 CMap &map, unsigned recurseLevel);
2010 HRESULT ReadObjectMap(UInt64 oid, CVol *vol, CObjectMap &map);
2011 HRESULT OpenVolume(const CObjectMap &omap, const oid_t fs_oid);
2012 HRESULT Open2();
2013
2014 HRESULT GetAttrStream(IInStream *apfsInStream, const CVol &vol,
2015 const CAttr &attr, ISequentialInStream **stream);
2016
2017 HRESULT GetAttrStream_dstream(IInStream *apfsInStream, const CVol &vol,
2018 const CAttr &attr, ISequentialInStream **stream);
2019
2020 HRESULT GetStream2(
2021 IInStream *apfsInStream,
2022 const CRecordVector<CExtent> *extents, UInt64 rem,
2023 ISequentialInStream **stream);
2024 };
2025
2026
2027 HRESULT CDatabase::SeekReadBlock_FALSE(UInt64 oid, void *data)
2028 {
2029 if (OpenCallback)
2030 {
2031 if (ProgressVal_Cur - ProgressVal_Prev >= (1 << 22))
2032 {
2033 RINOK(OpenCallback->SetCompleted(NULL, &ProgressVal_Cur))
2034 ProgressVal_Prev = ProgressVal_Cur;
2035 }
2036 ProgressVal_Cur += sb.block_size;
2037 }
2038 if (oid == 0 || oid >= sb.block_count)
2039 return S_FALSE;
2040 RINOK(InStream_SeekSet(OpenInStream, oid << sb.block_size_Log))
2041 return ReadStream_FALSE(OpenInStream, data, sb.block_size);
2042 }
2043
2044
2045
2046 API_FUNC_static_IsArc IsArc_APFS(const Byte *p, size_t size)
2047 {
2048 if (size < kApfsHeaderSize)
2049 return k_IsArc_Res_NEED_MORE;
2050 CSuperBlock sb;
2051 if (!sb.Parse(p))
2052 return k_IsArc_Res_NO;
2053 return k_IsArc_Res_YES;
2054 }
2055 }
2056
2057
2058 static bool CheckHash(unsigned hashAlgo, const Byte *data, size_t size, const Byte *digest)
2059 {
2060 if (hashAlgo == APFS_HASH_SHA256)
2061 {
2062 MY_ALIGN (16)
2063 CSha256 sha;
2064 Sha256_Init(&sha);
2065 Sha256_Update(&sha, data, size);
2066 return Sha256_Final_and_CheckDigest(&sha, digest);
2067 }
2068 return true;
2069 }
2070
2071
2072 HRESULT CDatabase::ReadMap(UInt64 oid, bool noHeader,
2073 CVol *vol, const Byte *hash,
2074 CMap &map, unsigned recurseLevel)
2075 {
2076 // is it allowed to use big number of levels ?
2077 if (recurseLevel > (1 << 10))
2078 return S_FALSE;
2079
2080 const UInt32 blockSize = sb.block_size;
2081 if (Buffers.Size() <= recurseLevel)
2082 {
2083 Buffers.AddNew();
2084 if (Buffers.Size() <= recurseLevel)
2085 throw 123;
2086 Buffers.Back().Alloc(blockSize);
2087 }
2088 const Byte *buf;
2089 {
2090 CByteBuffer &buf2 = Buffers[recurseLevel];
2091 RINOK(SeekReadBlock_FALSE(oid, buf2))
2092 buf = buf2;
2093 }
2094
2095 if (hash && vol)
2096 if (!CheckHash(vol->integrity.im_hash_type, buf, blockSize, hash))
2097 return S_FALSE;
2098
2099 CBTreeNodePhys bt;
2100 if (!bt.Parse(buf, blockSize, noHeader))
2101 return S_FALSE;
2102
2103 map.NumNodes++;
2104
2105 /* Specification: All values are stored in leaf nodes, which
2106 makes these B+ trees, and the values in nonleaf nodes are object
2107 identifiers of child nodes.
2108
2109 The entries in the table of contents are sorted by key. The comparison function used for sorting depends on the keys type
2110 - Object map B-trees are sorted by object identifier and then by transaction identifier.
2111 - Free queue B-trees are sorted by transaction identifier and then by physical address.
2112 - File-system records are sorted according to the rules listed in File-System Objects.
2113 */
2114
2115 if (!noHeader)
2116 if (bt.ophys.subtype != map.Subtype)
2117 return S_FALSE;
2118
2119 unsigned endLimit = blockSize;
2120
2121 if (recurseLevel == 0)
2122 {
2123 if (!noHeader)
2124 if (bt.ophys.GetType() != OBJECT_TYPE_BTREE)
2125 return S_FALSE;
2126 if ((bt.flags & BTNODE_ROOT) == 0)
2127 return S_FALSE;
2128 endLimit -= k_btree_info_Size;
2129 map.bti.Parse(buf + endLimit);
2130 btree_info &bti = map.bti;
2131 if (bti.fixed.key_size >= blockSize)
2132 return S_FALSE;
2133 if (bti.Is_EPHEMERAL() &&
2134 bti.Is_PHYSICAL())
2135 return S_FALSE;
2136 if (bti.Is_PHYSICAL() != map.IsPhysical)
2137 return S_FALSE;
2138 if (bti.Is_NOHEADER() != noHeader)
2139 return S_FALSE;
2140 // we don't allow volumes with big number of Keys
2141 const unsigned kNumItemsMax = k_VectorSizeMax;
2142 if (map.bti.node_count > kNumItemsMax)
2143 return S_FALSE;
2144 if (map.bti.key_count > kNumItemsMax)
2145 return S_FALSE;
2146 }
2147 else
2148 {
2149 if (!noHeader)
2150 if (bt.ophys.GetType() != OBJECT_TYPE_BTREE_NODE)
2151 return S_FALSE;
2152 if ((bt.flags & BTNODE_ROOT) != 0)
2153 return S_FALSE;
2154 if (map.NumNodes > map.bti.node_count
2155 || map.Pairs.Size() > map.bti.key_count)
2156 return S_FALSE;
2157 }
2158
2159 const bool isLeaf = (bt.flags & BTNODE_LEAF) != 0;
2160
2161 if (isLeaf)
2162 {
2163 if (bt.level != 0)
2164 return S_FALSE;
2165 }
2166 else
2167 {
2168 if (bt.level == 0)
2169 return S_FALSE;
2170 }
2171
2172 if (!bt.table_space.CheckOverLimit(endLimit - k_Toc_offset))
2173 return S_FALSE;
2174
2175 const unsigned tableEnd = k_Toc_offset + bt.table_space.GetEnd();
2176 const unsigned keyValRange = endLimit - tableEnd;
2177 const unsigned tocEntrySize = bt.Is_FIXED_KV_SIZE() ? 4 : 8;
2178 if (bt.table_space.len / tocEntrySize < bt.nkeys)
2179 return S_FALSE;
2180
2181 for (unsigned i = 0; i < bt.nkeys; i++)
2182 {
2183 const Byte *p = buf + k_Toc_offset + bt.table_space.off + i * tocEntrySize;
2184 if (bt.Is_FIXED_KV_SIZE())
2185 {
2186 kvoff a;
2187 a.Parse(p);
2188 if (a.k + map.bti.fixed.key_size > keyValRange
2189 || a.v > keyValRange)
2190 return S_FALSE;
2191 {
2192 CKeyValPair pair;
2193
2194 const Byte *p2 = buf + k_Toc_offset + bt.table_space.len;
2195 p2 += a.k;
2196 pair.Key.CopyFrom(p2, map.bti.fixed.key_size);
2197
2198 p2 = buf + endLimit;
2199 p2 -= a.v;
2200 if (isLeaf)
2201 {
2202 if (a.v < map.bti.fixed.val_size)
2203 return S_FALSE;
2204 pair.Val.CopyFrom(p2, map.bti.fixed.val_size);
2205 // pair.ValPos = endLimit - a.v;
2206 map.Pairs.Add(pair);
2207 continue;
2208 }
2209 {
2210 if (a.v < 8)
2211 return S_FALSE;
2212 // value is only 64-bit for non leaf.
2213 const oid_t oidNext = Get64(p2);
2214 if (map.bti.Is_PHYSICAL())
2215 {
2216 RINOK(ReadMap(oidNext, noHeader, vol,
2217 NULL, /* hash */
2218 map, recurseLevel + 1))
2219 continue;
2220 }
2221 else
2222 {
2223 // fixme
2224 return S_FALSE;
2225 }
2226 }
2227 }
2228 }
2229 else
2230 {
2231 kvloc a;
2232 a.Parse(p);
2233 if (!a.k.CheckOverLimit(keyValRange)
2234 || a.v.off > keyValRange
2235 || a.v.len > a.v.off)
2236 return S_FALSE;
2237 {
2238 CKeyValPair pair;
2239 const Byte *p2 = buf + k_Toc_offset + bt.table_space.len;
2240 p2 += a.k.off;
2241 pair.Key.CopyFrom(p2, a.k.len);
2242
2243 p2 = buf + endLimit;
2244 p2 -= a.v.off;
2245 if (isLeaf)
2246 {
2247 pair.Val.CopyFrom(p2, a.v.len);
2248 // pair.ValPos = endLimit - a.v.off;
2249 map.Pairs.Add(pair);
2250 continue;
2251 }
2252 {
2253 if (a.v.len < 8)
2254 return S_FALSE;
2255
2256 const Byte *hashNew = NULL;
2257 oid_t oidNext = Get64(p2);
2258
2259 if (bt.Is_HASHED())
2260 {
2261 if (!vol)
2262 return S_FALSE;
2263 /*
2264 struct btn_index_node_val {
2265 oid_t binv_child_oid;
2266 uint8_t binv_child_hash[BTREE_NODE_HASH_SIZE_MAX];
2267 };
2268 */
2269 /* (a.v.len == 40) is possible if Is_HASHED()
2270 so there is hash (for example, 256-bit) after 64-bit id) */
2271 if (a.v.len != 8 + vol->integrity.HashSize)
2272 return S_FALSE;
2273 hashNew = p2 + 8;
2274 /* we need to add root_tree_oid here,
2275 but where it's defined in apfs specification ? */
2276 oidNext += vol->apfs.root_tree_oid;
2277 }
2278 else
2279 {
2280 if (a.v.len != 8)
2281 return S_FALSE;
2282 }
2283
2284 // value is only 64-bit for non leaf.
2285
2286 if (map.bti.Is_PHYSICAL())
2287 {
2288 return S_FALSE;
2289 // the code was not tested:
2290 // RINOK(ReadMap(oidNext, map, recurseLevel + 1));
2291 // continue;
2292 }
2293 /*
2294 else if (map.bti.Is_EPHEMERAL())
2295 {
2296 // Ephemeral objects are stored in memory while the container is mounted and in a checkpoint when the container isn't mounted
2297 // the code was not tested:
2298 return S_FALSE;
2299 }
2300 */
2301 else
2302 {
2303 /* nodes in the B-tree use virtual object identifiers to link to their child nodes. */
2304 const int index = map.Omap.FindKey(oidNext);
2305 if (index == -1)
2306 return S_FALSE;
2307 const omap_val &ov = map.Omap.Vals[(unsigned)index];
2308 if (ov.size != blockSize) // change it : it must be multiple of
2309 return S_FALSE;
2310 RINOK(ReadMap(ov.paddr, ov.IsFlag_NoHeader(), vol, hashNew,
2311 map, recurseLevel + 1))
2312 continue;
2313 }
2314 }
2315 }
2316 }
2317 }
2318
2319 if (recurseLevel == 0)
2320 if (!map.CheckAtFinish())
2321 return S_FALSE;
2322 return S_OK;
2323 }
2324
2325
2326
2327 HRESULT CDatabase::ReadObjectMap(UInt64 oid, CVol *vol, CObjectMap &omap)
2328 {
2329 CByteBuffer buf;
2330 const size_t blockSize = sb.block_size;
2331 buf.Alloc(blockSize);
2332 RINOK(SeekReadBlock_FALSE(oid, buf))
2333 C_omap_phys op;
2334 if (!op.Parse(buf, blockSize, oid))
2335 return S_FALSE;
2336 CMap map;
2337 map.Subtype = OBJECT_TYPE_OMAP;
2338 map.IsPhysical = true;
2339 RINOK(ReadMap(op.tree_oid,
2340 false /* noHeader */,
2341 vol,
2342 NULL, /* hash */
2343 map, 0))
2344 if (!omap.Parse(map.Pairs))
2345 return S_FALSE;
2346 return S_OK;
2347 }
2348
2349
2350
2351 HRESULT CDatabase::Open2()
2352 {
2353 Clear();
2354 CSuperBlock2 sb2;
2355 {
2356 Byte buf[kApfsHeaderSize];
2357 RINOK(ReadStream_FALSE(OpenInStream, buf, kApfsHeaderSize))
2358 if (!sb.Parse(buf))
2359 return S_FALSE;
2360 sb2.Parse(buf);
2361 }
2362
2363 {
2364 CObjectMap omap;
2365 RINOK(ReadObjectMap(sb.omap_oid,
2366 NULL, /* CVol */
2367 omap))
2368 unsigned numRefs = 0;
2369 for (unsigned i = 0; i < sb.max_file_systems; i++)
2370 {
2371 const oid_t oid = sb2.fs_oid[i];
2372 if (oid == 0)
2373 continue;
2374 // for (unsigned k = 0; k < 1; k++) // for debug
2375 RINOK(OpenVolume(omap, oid))
2376 const unsigned a = Vols.Back().Refs.Size();
2377 numRefs += a;
2378 if (numRefs < a)
2379 return S_FALSE; // overflow
2380 }
2381 }
2382
2383 const bool needVolumePrefix = (Vols.Size() > 1);
2384 // const bool needVolumePrefix = true; // for debug
2385 {
2386 unsigned numRefs = 0;
2387 FOR_VECTOR (i, Vols)
2388 {
2389 const unsigned a = Vols[i].Refs.Size();
2390 numRefs += a;
2391 if (numRefs < a)
2392 return S_FALSE; // overflow
2393 }
2394 numRefs += Vols.Size();
2395 if (numRefs < Vols.Size())
2396 return S_FALSE; // overflow
2397 Refs2.Reserve(numRefs);
2398 }
2399 {
2400 FOR_VECTOR (i, Vols)
2401 {
2402 CVol &vol = Vols[i];
2403
2404 CRef2 ref2;
2405 ref2.VolIndex = i;
2406
2407 if (needVolumePrefix)
2408 {
2409 vol.RootName = "Volume";
2410 vol.RootName.Add_UInt32(1 + (UInt32)i);
2411 vol.RootRef2Index = Refs2.Size();
2412 ref2.RefIndex = VI_MINUS1;
2413 Refs2.Add(ref2);
2414 }
2415
2416 vol.StartRef2Index = Refs2.Size();
2417 const unsigned numItems = vol.Refs.Size();
2418 for (unsigned k = 0; k < numItems; k++)
2419 {
2420 ref2.RefIndex = k;
2421 Refs2.Add(ref2);
2422 }
2423 }
2424 }
2425 return S_OK;
2426 }
2427
2428
2429 HRESULT CDatabase::OpenVolume(const CObjectMap &omap, const oid_t fs_oid)
2430 {
2431 const size_t blockSize = sb.block_size;
2432 CByteBuffer buf;
2433 {
2434 const int index = omap.FindKey(fs_oid);
2435 if (index == -1)
2436 return S_FALSE;
2437 const omap_val &ov = omap.Vals[(unsigned)index];
2438 if (ov.size != blockSize) // change it : it must be multiple of
2439 return S_FALSE;
2440 buf.Alloc(blockSize);
2441 RINOK(SeekReadBlock_FALSE(ov.paddr, buf))
2442 }
2443
2444 CVol &vol = Vols.AddNew();
2445 CApfs &apfs = vol.apfs;
2446
2447 if (!apfs.Parse(buf, blockSize))
2448 return S_FALSE;
2449
2450 if (apfs.fext_tree_oid != 0)
2451 {
2452 if ((apfs.incompatible_features & APFS_INCOMPAT_SEALED_VOLUME) == 0)
2453 return S_FALSE;
2454 if ((apfs.fext_tree_type & OBJ_PHYSICAL) == 0)
2455 return S_FALSE;
2456 if ((apfs.fext_tree_type & OBJECT_TYPE_MASK) != OBJECT_TYPE_BTREE)
2457 return S_FALSE;
2458
2459 CMap map2;
2460 map2.Subtype = OBJECT_TYPE_FEXT_TREE;
2461 map2.IsPhysical = true;
2462
2463 RINOK(ReadMap(apfs.fext_tree_oid,
2464 false /* noHeader */,
2465 &vol,
2466 NULL, /* hash */
2467 map2, 0))
2468
2469 UInt64 prevId = 1;
2470
2471 FOR_VECTOR (i, map2.Pairs)
2472 {
2473 if (OpenCallback && (i & 0xffff) == 1)
2474 {
2475 const UInt64 numFiles = ProgressVal_NumFilesTotal +
2476 (vol.Items.Size() + vol.Nodes.Size()) / 2;
2477 RINOK(OpenCallback->SetCompleted(&numFiles, &ProgressVal_Cur))
2478 }
2479 // The key half of a record from a file extent tree.
2480 // struct fext_tree_key
2481 const CKeyValPair &pair = map2.Pairs[i];
2482 if (pair.Key.Size() != 16)
2483 return S_FALSE;
2484 const Byte *p = pair.Key;
2485 const UInt64 id = GetUi64(p);
2486 if (id < prevId)
2487 return S_FALSE; // IDs must be sorted
2488 prevId = id;
2489
2490 CExtent ext;
2491 ext.logical_offset = GetUi64(p + 8);
2492 {
2493 if (pair.Val.Size() != 16)
2494 return S_FALSE;
2495 const Byte *p2 = pair.Val;
2496 ext.len_and_flags = GetUi64(p2);
2497 ext.phys_block_num = GetUi64(p2 + 8);
2498 }
2499
2500 PRF(printf("\n%6d: id=%6d logical_addr = %2d len_and_flags=%5x phys_block_num = %5d",
2501 i, (unsigned)id,
2502 (unsigned)ext.logical_offset,
2503 (unsigned)ext.len_and_flags,
2504 (unsigned)ext.phys_block_num));
2505
2506 if (vol.FEXT_NodeIDs.IsEmpty() ||
2507 vol.FEXT_NodeIDs.Back() != id)
2508 {
2509 vol.FEXT_NodeIDs.Add(id);
2510 vol.FEXT_Nodes.AddNew();
2511 }
2512 CRecordVector<CExtent> &extents = vol.FEXT_Nodes.Back().Extents;
2513 if (!extents.IsEmpty())
2514 if (ext.logical_offset != extents.Back().GetEndOffset())
2515 return S_FALSE;
2516 extents.Add(ext);
2517 continue;
2518 }
2519
2520 PRF(printf("\n\n"));
2521 }
2522
2523 /* For each volume, read the root file system tree's virtual object
2524 identifier from the apfs_root_tree_oid field,
2525 and then look it up in the volume object map indicated
2526 by the omap_oid field. */
2527
2528 CMap map;
2529 ReadObjectMap(apfs.omap_oid, &vol, map.Omap);
2530
2531 const Byte *hash_for_root = NULL;
2532
2533 if (apfs.integrity_meta_oid != 0)
2534 {
2535 if ((apfs.incompatible_features & APFS_INCOMPAT_SEALED_VOLUME) == 0)
2536 return S_FALSE;
2537 const int index = map.Omap.FindKey(apfs.integrity_meta_oid);
2538 if (index == -1)
2539 return S_FALSE;
2540 const omap_val &ov = map.Omap.Vals[(unsigned)index];
2541 if (ov.size != blockSize)
2542 return S_FALSE;
2543 RINOK(SeekReadBlock_FALSE(ov.paddr, buf))
2544 if (!vol.integrity.Parse(buf, blockSize, apfs.integrity_meta_oid))
2545 return S_FALSE;
2546 if (vol.integrity.im_hash_type != 0)
2547 hash_for_root = vol.integrity.Hash;
2548 }
2549
2550 {
2551 const int index = map.Omap.FindKey(apfs.root_tree_oid);
2552 if (index == -1)
2553 return S_FALSE;
2554 const omap_val &ov = map.Omap.Vals[(unsigned)index];
2555 if (ov.size != blockSize) // change it : it must be multiple of
2556 return S_FALSE;
2557 map.Subtype = OBJECT_TYPE_FSTREE;
2558 map.IsPhysical = false;
2559 RINOK(ReadMap(ov.paddr, ov.IsFlag_NoHeader(),
2560 &vol, hash_for_root,
2561 map, 0))
2562 }
2563
2564 bool needParseAttr = false;
2565
2566 {
2567 const bool isHashed = apfs.IsHashedName();
2568 UInt64 prevId = 1;
2569
2570 {
2571 const UInt64 numApfsItems = vol.apfs.GetTotalItems()
2572 + 2; // we will have 2 additional hidden directories: root and private-dir
2573 const UInt64 numApfsItems_Reserve = numApfsItems
2574 + 16; // we reserve 16 for some possible unexpected items
2575 if (numApfsItems_Reserve < map.Pairs.Size())
2576 {
2577 vol.Items.ClearAndReserve((unsigned)numApfsItems_Reserve);
2578 vol.Nodes.ClearAndReserve((unsigned)numApfsItems_Reserve);
2579 vol.NodeIDs.ClearAndReserve((unsigned)numApfsItems_Reserve);
2580 }
2581 if (OpenCallback)
2582 {
2583 const UInt64 numFiles = ProgressVal_NumFilesTotal + numApfsItems;
2584 RINOK(OpenCallback->SetTotal(&numFiles, NULL))
2585 }
2586 }
2587
2588 CAttr attr;
2589 CItem item;
2590
2591 FOR_VECTOR (i, map.Pairs)
2592 {
2593 if (OpenCallback && (i & 0xffff) == 1)
2594 {
2595 const UInt64 numFiles = ProgressVal_NumFilesTotal +
2596 (vol.Items.Size() + vol.Nodes.Size()) / 2;
2597 RINOK(OpenCallback->SetCompleted(&numFiles, &ProgressVal_Cur))
2598 }
2599
2600 const CKeyValPair &pair = map.Pairs[i];
2601 j_key_t jkey;
2602 if (pair.Key.Size() < 8)
2603 return S_FALSE;
2604 const Byte *p = pair.Key;
2605 jkey.Parse(p);
2606 const unsigned type = jkey.GetType();
2607 const UInt64 id = jkey.GetID();
2608 if (id < prevId)
2609 return S_FALSE; // IDs must be sorted
2610 prevId = id;
2611
2612 PRF(printf("\n%6d: id=%6d type = %2d ", i, (unsigned)id, type));
2613
2614 if (type == APFS_TYPE_INODE)
2615 {
2616 PRF(printf("INODE"));
2617 if (pair.Key.Size() != 8 ||
2618 pair.Val.Size() < k_SizeOf_j_inode_val)
2619 return S_FALSE;
2620
2621 CNode inode;
2622 inode.Parse(pair.Val);
2623
2624 if (inode.private_id != id)
2625 {
2626 /* private_id : The unique identifier used by this file's data stream.
2627 This identifier appears in the owning_obj_id field of j_phys_ext_val_t
2628 records that describe the extents where the data is stored.
2629 For an inode that doesn't have data, the value of this
2630 field is the file-system object's identifier.
2631 */
2632 // APFS_TYPE_EXTENT allow to link physical address extents.
2633 // we don't support case (private_id != id)
2634 UnsupportedFeature = true;
2635 // return S_FALSE;
2636 }
2637 const UInt32 extraSize = (UInt32)pair.Val.Size() - k_SizeOf_j_inode_val;
2638 if (extraSize != 0)
2639 {
2640 if (extraSize < 4)
2641 return S_FALSE;
2642 /*
2643 struct xf_blob
2644 {
2645 uint16_t xf_num_exts;
2646 uint16_t xf_used_data;
2647 uint8_t xf_data[];
2648 };
2649 */
2650 const Byte *p2 = pair.Val + k_SizeOf_j_inode_val;
2651 const UInt32 xf_num_exts = Get16(p2);
2652 const UInt32 xf_used_data = Get16(p2 + 2);
2653 UInt32 offset = 4 + (UInt32)xf_num_exts * 4;
2654 if (offset + xf_used_data != extraSize)
2655 return S_FALSE;
2656
2657 PRF(printf(" parent_id = %d", (unsigned)inode.parent_id));
2658
2659 for (unsigned k = 0; k < xf_num_exts; k++)
2660 {
2661 // struct x_field
2662 const Byte *p3 = p2 + 4 + k * 4;
2663 const Byte x_type = p3[0];
2664 // const Byte x_flags = p3[1];
2665 const UInt32 x_size = Get16(p3 + 2);
2666 const UInt32 x_size_ceil = (x_size + 7) & ~(UInt32)7;
2667 if (offset + x_size_ceil > extraSize)
2668 return S_FALSE;
2669 const Byte *p4 = p2 + offset;
2670 if (x_type == INO_EXT_TYPE_NAME)
2671 {
2672 if (x_size < 2)
2673 return S_FALSE;
2674 inode.PrimaryName.SetFrom_CalcLen((const char *)p4, x_size);
2675 PRF(printf(" PrimaryName = %s", inode.PrimaryName.Ptr()));
2676 if (inode.PrimaryName.Len() != x_size - 1)
2677 HeadersError = true;
2678 // return S_FALSE;
2679 }
2680 else if (x_type == INO_EXT_TYPE_DSTREAM)
2681 {
2682 if (x_size != k_SizeOf_j_dstream)
2683 return S_FALSE;
2684 if (inode.dstream_defined)
2685 return S_FALSE;
2686 inode.dstream.Parse(p4);
2687 inode.dstream_defined = true;
2688 }
2689 else
2690 {
2691 // UnsupportedFeature = true;
2692 // return S_FALSE;
2693 }
2694 offset += x_size_ceil;
2695 }
2696 if (offset != extraSize)
2697 return S_FALSE;
2698 }
2699
2700 if (!vol.NodeIDs.IsEmpty())
2701 if (id <= vol.NodeIDs.Back())
2702 return S_FALSE;
2703 vol.Nodes.Add(inode);
2704 vol.NodeIDs.Add(id);
2705 continue;
2706 }
2707
2708 if (type == APFS_TYPE_XATTR)
2709 {
2710 PRF(printf(" XATTR"));
2711
2712 /*
2713 struct j_xattr_key
2714 {
2715 j_key_t hdr;
2716 uint16_t name_len;
2717 uint8_t name[0];
2718 }
2719 */
2720
2721 UInt32 len;
2722 unsigned nameOffset;
2723 {
2724 nameOffset = 8 + 2;
2725 if (pair.Key.Size() < nameOffset + 1)
2726 return S_FALSE;
2727 len = Get16(p + 8);
2728 }
2729 if (nameOffset + len != pair.Key.Size())
2730 return S_FALSE;
2731
2732 attr.Clear();
2733 attr.Name.SetFrom_CalcLen((const char *)p + nameOffset, len);
2734 if (attr.Name.Len() != len - 1)
2735 return S_FALSE;
2736
2737 PRF(printf(" name=%s", attr.Name.Ptr()));
2738
2739 const unsigned k_SizeOf_j_xattr_val = 4;
2740 if (pair.Val.Size() < k_SizeOf_j_xattr_val)
2741 return S_FALSE;
2742 /*
2743 struct j_xattr_val
2744 {
2745 uint16_t flags;
2746 uint16_t xdata_len;
2747 uint8_t xdata[0];
2748 }
2749 */
2750 attr.flags = Get16(pair.Val);
2751 const UInt32 xdata_len = Get16(pair.Val + 2);
2752
2753 PRF(printf(" flags=%x xdata_len = %d",
2754 (unsigned)attr.flags,
2755 (unsigned)xdata_len));
2756
2757 const Byte *p4 = pair.Val + 4;
2758
2759 if (k_SizeOf_j_xattr_val + xdata_len != pair.Val.Size())
2760 return S_FALSE;
2761 if (attr.Is_EMBEDDED())
2762 attr.Data.CopyFrom(p4, xdata_len);
2763 else if (attr.Is_STREAM())
2764 {
2765 // why (attr.flags == 0x11) here? (0x11 is undocummented flag)
2766 if (k_SizeOf_j_xattr_val + 8 + k_SizeOf_j_dstream != pair.Val.Size())
2767 return S_FALSE;
2768 attr.Id = Get64(p4);
2769 attr.dstream.Parse(p4 + 8);
2770 attr.dstream_defined = true;
2771 PRF(printf(" streamID=%d streamSize=%d", (unsigned)attr.Id, (unsigned)attr.dstream.size));
2772 }
2773 else
2774 {
2775 // unknown attribute
2776 // UnsupportedFeature = true;
2777 // return S_FALSE;
2778 }
2779
2780 if (vol.NodeIDs.IsEmpty() ||
2781 vol.NodeIDs.Back() != id)
2782 {
2783 return S_FALSE;
2784 // UnsupportedFeature = true;
2785 // continue;
2786 }
2787
2788 CNode &inode = vol.Nodes.Back();
2789
2790 if (attr.Name.IsEqualTo("com.apple.fs.symlink"))
2791 {
2792 inode.SymLinkIndex = inode.Attrs.Size();
2793 if (attr.Is_dstream_OK_for_SymLink())
2794 needParseAttr = true;
2795 }
2796 else if (attr.Name.IsEqualTo("com.apple.decmpfs"))
2797 {
2798 inode.DecmpfsIndex = inode.Attrs.Size();
2799 // if (attr.dstream_defined)
2800 needParseAttr = true;
2801 }
2802 else if (attr.Name.IsEqualTo("com.apple.ResourceFork"))
2803 {
2804 inode.ResourceIndex = inode.Attrs.Size();
2805 }
2806 inode.Attrs.Add(attr);
2807 continue;
2808 }
2809
2810 if (type == APFS_TYPE_DSTREAM_ID)
2811 {
2812 PRF(printf(" DSTREAM_ID"));
2813 if (pair.Key.Size() != 8)
2814 return S_FALSE;
2815 // j_dstream_id_val_t
2816 if (pair.Val.Size() != 4)
2817 return S_FALSE;
2818 const UInt32 refcnt = Get32(pair.Val);
2819
2820 // The data stream record can be deleted when its reference count reaches zero.
2821 PRF(printf(" refcnt = %d", (unsigned)refcnt));
2822
2823 if (vol.NodeIDs.IsEmpty())
2824 return S_FALSE;
2825
2826 if (vol.NodeIDs.Back() != id)
2827 {
2828 // is it possible ?
2829 // continue;
2830 return S_FALSE;
2831 }
2832
2833 CNode &inode = vol.Nodes.Back();
2834
2835 if (inode.refcnt_defined)
2836 return S_FALSE;
2837
2838 inode.refcnt = refcnt;
2839 inode.refcnt_defined = true;
2840 if (inode.refcnt != (UInt32)inode.nlink)
2841 {
2842 PRF(printf(" refcnt != nlink"));
2843 // is it possible ?
2844 // return S_FALSE;
2845 }
2846 continue;
2847 }
2848
2849 if (type == APFS_TYPE_FILE_EXTENT)
2850 {
2851 PRF(printf(" FILE_EXTENT"));
2852 /*
2853 struct j_file_extent_key
2854 {
2855 j_key_t hdr;
2856 uint64_t logical_addr;
2857 }
2858 */
2859 if (pair.Key.Size() != 16)
2860 return S_FALSE;
2861 // The offset within the file's data, in bytes, for the data stored in this extent
2862 const UInt64 logical_addr = Get64(p + 8);
2863
2864 j_file_extent_val eval;
2865 if (pair.Val.Size() != k_SizeOf_j_file_extent_val)
2866 return S_FALSE;
2867 eval.Parse(pair.Val);
2868
2869 if (logical_addr != 0)
2870 {
2871 PRF(printf(" logical_addr = %d", (unsigned)logical_addr));
2872 }
2873 PRF(printf(" len = %8d pos = %8d",
2874 (unsigned)eval.len_and_flags,
2875 (unsigned)eval.phys_block_num
2876 ));
2877
2878 CExtent ext;
2879 ext.logical_offset = logical_addr;
2880 ext.len_and_flags = eval.len_and_flags;
2881 ext.phys_block_num = eval.phys_block_num;
2882
2883 if (vol.NodeIDs.IsEmpty())
2884 return S_FALSE;
2885 if (vol.NodeIDs.Back() != id)
2886 {
2887 // extents for Attributs;
2888 if (vol.SmallNodeIDs.IsEmpty() ||
2889 vol.SmallNodeIDs.Back() != id)
2890 {
2891 vol.SmallNodeIDs.Add(id);
2892 vol.SmallNodes.AddNew();
2893 }
2894 vol.SmallNodes.Back().Extents.Add(ext);
2895 continue;
2896 // return S_FALSE;
2897 }
2898
2899 CNode &inode = vol.Nodes.Back();
2900 inode.Extents.Add(ext);
2901 continue;
2902 }
2903
2904 if (type == APFS_TYPE_DIR_REC)
2905 {
2906 UInt32 len;
2907 unsigned nameOffset;
2908
2909 if (isHashed)
2910 {
2911 /*
2912 struct j_drec_hashed_key
2913 {
2914 j_key_t hdr;
2915 UInt32 name_len_and_hash;
2916 uint8_t name[0];
2917 }
2918 */
2919 nameOffset = 8 + 4;
2920 if (pair.Key.Size() < nameOffset + 1)
2921 return S_FALSE;
2922 const UInt32 name_len_and_hash = Get32(p + 8);
2923 len = name_len_and_hash & J_DREC_LEN_MASK;
2924 }
2925 else
2926 {
2927 /*
2928 struct j_drec_key
2929 {
2930 j_key_t hdr;
2931 UInt16 name_len; // The length of the name, including the final null character
2932 uint8_t name[0]; // The name, represented as a null-terminated UTF-8 string
2933 }
2934 */
2935 nameOffset = 8 + 2;
2936 if (pair.Key.Size() < nameOffset + 1)
2937 return S_FALSE;
2938 len = Get16(p + 8);
2939 }
2940 if (nameOffset + len != pair.Key.Size())
2941 return S_FALSE;
2942
2943 // CItem item;
2944 item.Clear();
2945
2946 item.ParentId = id;
2947 item.Name.SetFrom_CalcLen((const char *)p + nameOffset, len);
2948 if (item.Name.Len() != len - 1)
2949 return S_FALSE;
2950
2951 if (pair.Val.Size() < k_SizeOf_j_drec_val)
2952 return S_FALSE;
2953
2954 item.Val.Parse(pair.Val);
2955
2956 if (pair.Val.Size() > k_SizeOf_j_drec_val)
2957 {
2958 // fixme: parse extra fields;
2959 // UnsupportedFeature = true;
2960 // return S_FALSE;
2961 }
2962
2963 vol.Items.Add(item);
2964
2965 /*
2966 if (!vol.NodeIDs.IsEmpty() && vol.NodeIDs.Back() == id)
2967 vol.Nodes.Back().NumItems++;
2968 */
2969 if (id == PRIV_DIR_INO_NUM)
2970 vol.NumItems_In_PrivateDir++;
2971
2972 PRF(printf(" DIR_REC next=%6d flags=%2x %s",
2973 (unsigned)item.Val.file_id,
2974 (unsigned)item.Val.flags,
2975 item.Name.Ptr()));
2976 continue;
2977 }
2978
2979 if (type == APFS_TYPE_FILE_INFO)
2980 {
2981 if (pair.Key.Size() != 16)
2982 return S_FALSE;
2983 // j_file_info_key
2984 const UInt64 info_and_lba = Get64(p + 8);
2985
2986 #define J_FILE_INFO_LBA_MASK 0x00ffffffffffffffUL
2987 // #define J_FILE_INFO_TYPE_MASK 0xff00000000000000UL
2988 #define J_FILE_INFO_TYPE_SHIFT 56
2989 #define APFS_FILE_INFO_DATA_HASH 1
2990
2991 const unsigned infoType = (unsigned)(info_and_lba >> J_FILE_INFO_TYPE_SHIFT);
2992 // address is a paddr_t
2993 const UInt64 lba = info_and_lba & J_FILE_INFO_LBA_MASK;
2994 // j_file_data_hash_val_t
2995 /* Use this field of the union if the type stored in the info_and_lba field of j_file_info_val_t is
2996 APFS_FILE_INFO_DATA_HASH */
2997 if (infoType != APFS_FILE_INFO_DATA_HASH)
2998 return S_FALSE;
2999 if (pair.Val.Size() != 3 + vol.integrity.HashSize)
3000 return S_FALSE;
3001 /*
3002 struct j_file_data_hash_val
3003 {
3004 UInt16 hashed_len; // The length, in blocks, of the data segment that was hashed.
3005 UInt8 hash_size; // must be consistent with integrity_meta_phys_t::im_hash_type
3006 UInt8 hash[0];
3007 }
3008 */
3009
3010 const unsigned hash_size = pair.Val[2];
3011 if (hash_size != vol.integrity.HashSize)
3012 return S_FALSE;
3013
3014 CHashChunk hr;
3015 hr.hashed_len = GetUi16(pair.Val);
3016 if (hr.hashed_len == 0)
3017 return S_FALSE;
3018 memcpy(hr.hash, (const Byte *)pair.Val + 3, vol.integrity.HashSize);
3019 // (hashed_len <= 4) : we have seen
3020 hr.lba = lba;
3021
3022 PRF(printf(" FILE_INFO lba = %6x, hashed_len=%6d",
3023 (unsigned)lba,
3024 (unsigned)hr.hashed_len));
3025
3026 if (vol.Hash_IDs.IsEmpty() || vol.Hash_IDs.Back() != id)
3027 {
3028 vol.Hash_Vectors.AddNew();
3029 vol.Hash_IDs.Add(id);
3030 }
3031 CStreamHashes &hashes = vol.Hash_Vectors.Back();
3032 if (hashes.Size() != 0)
3033 {
3034 const CHashChunk &hr_Back = hashes.Back();
3035 if (lba != hr_Back.lba + ((UInt64)hr_Back.hashed_len << sb.block_size_Log))
3036 return S_FALSE;
3037 }
3038 hashes.Add(hr);
3039 continue;
3040 }
3041
3042 if (type == APFS_TYPE_SNAP_METADATA)
3043 {
3044 if (pair.Key.Size() != 8)
3045 return S_FALSE;
3046 PRF(printf(" SNAP_METADATA"));
3047 // continue;
3048 }
3049
3050 /* SIBLING items are used, if there are more than one hard link to some inode
3051 key : value
3052 parent_id_1 DIR_REC : inode_id, name_1
3053 parent_id_2 DIR_REC : inode_id, name_2
3054 inode_id INODE : parent_id_1, name_1
3055 inode_id SIBLING_LINK sibling_id_1 : parent_id_1, name_1
3056 inode_id SIBLING_LINK sibling_id_2 : parent_id_2, name_2
3057 sibling_id_1 SIBLING_MAP : inode_id
3058 sibling_id_2 SIBLING_MAP : inode_id
3059 */
3060
3061 if (type == APFS_TYPE_SIBLING_LINK)
3062 {
3063 if (pair.Key.Size() != 16)
3064 return S_FALSE;
3065 if (pair.Val.Size() < 10 + 1)
3066 return S_FALSE;
3067 /*
3068 // struct j_sibling_key
3069 // The sibling's unique identifier.
3070 // This value matches the object identifier for the sibling map record
3071 const UInt64 sibling_id = Get64(p + 8);
3072 // struct j_sibling_val
3073 const Byte *v = pair.Val;
3074 const UInt64 parent_id = Get64(v); // The object identifier for the inode that's the parent directory.
3075 const unsigned name_len = Get16(v + 8); // len including the final null character
3076 if (10 + name_len != pair.Val.Size())
3077 return S_FALSE;
3078 if (v[10 + name_len - 1] != 0)
3079 return S_FALSE;
3080 AString name ((const char *)(v + 10));
3081 if (name.Len() != name_len - 1)
3082 return S_FALSE;
3083 PRF(printf(" SIBLING_LINK sibling_id = %6d : parent_id=%6d %s",
3084 (unsigned)sibling_id, (unsigned)parent_id, name.Ptr()));
3085 */
3086 continue;
3087 }
3088
3089 if (type == APFS_TYPE_SIBLING_MAP)
3090 {
3091 // struct j_sibling_map_key
3092 // The object identifier in the header is the sibling's unique identifier
3093 if (pair.Key.Size() != 8 || pair.Val.Size() != 8)
3094 return S_FALSE;
3095 /*
3096 // j_sibling_map_val
3097 // The inode number of the underlying file
3098 const UInt64 file_id = Get64(pair.Val);
3099 PRF(printf(" SIBLING_MAP : file_id = %d", (unsigned)file_id));
3100 */
3101 continue;
3102 }
3103
3104 UnsupportedFeature = true;
3105 // return S_FALSE;
3106 }
3107 ProgressVal_NumFilesTotal += vol.Items.Size();
3108 }
3109
3110
3111 if (needParseAttr)
3112 {
3113 /* we read external streams for attributes
3114 So we can get SymLink for GetProperty(kpidSymLink) later */
3115 FOR_VECTOR (i, vol.Nodes)
3116 {
3117 CNode &node = vol.Nodes[i];
3118
3119 FOR_VECTOR (a, node.Attrs)
3120 {
3121 CAttr &attr = node.Attrs[a];
3122 if (attr.Data.Size() != 0 || !attr.dstream_defined)
3123 continue;
3124 if (a == node.SymLinkIndex)
3125 {
3126 if (!attr.Is_dstream_OK_for_SymLink())
3127 continue;
3128 }
3129 else
3130 {
3131 if (a != node.DecmpfsIndex
3132 // && a != node.ResourceIndex
3133 )
3134 continue;
3135 }
3136 // we don't expect big streams here
3137 // largest dstream for Decmpfs attribute is (2Kib+17)
3138 if (attr.dstream.size > ((UInt32)1 << 16))
3139 continue;
3140 CMyComPtr<ISequentialInStream> inStream;
3141 const HRESULT res = GetAttrStream_dstream(OpenInStream, vol, attr, &inStream);
3142 if (res == S_OK && inStream)
3143 {
3144 CByteBuffer buf2;
3145 const size_t size = (size_t)attr.dstream.size;
3146 buf2.Alloc(size);
3147 if (ReadStream_FAIL(inStream, buf2, size) == S_OK)
3148 attr.Data = buf2;
3149
3150 ProgressVal_Cur += size;
3151 if (OpenCallback)
3152 if (ProgressVal_Cur - ProgressVal_Prev >= (1 << 22))
3153 {
3154
3155 RINOK(OpenCallback->SetCompleted(
3156 &ProgressVal_NumFilesTotal,
3157 &ProgressVal_Cur))
3158 ProgressVal_Prev = ProgressVal_Cur;
3159 }
3160 }
3161 }
3162
3163 if (node.Has_UNCOMPRESSED_SIZE())
3164 if (IsViDef(node.DecmpfsIndex))
3165 {
3166 CAttr &attr = node.Attrs[node.DecmpfsIndex];
3167 node.CompressHeader.Parse(attr.Data, attr.Data.Size());
3168
3169 if (node.CompressHeader.IsCorrect)
3170 if (node.CompressHeader.Method < sizeof(MethodsMask) * 8)
3171 MethodsMask |= ((UInt32)1 << node.CompressHeader.Method);
3172
3173 if (node.CompressHeader.IsCorrect
3174 && node.CompressHeader.IsSupported
3175 && node.CompressHeader.UnpackSize == node.uncompressed_size)
3176 {
3177 attr.NeedShow = false;
3178 if (node.CompressHeader.IsMethod_Resource()
3179 && IsViDef(node.ResourceIndex))
3180 node.Attrs[node.ResourceIndex].NeedShow = false;
3181 }
3182 else
3183 {
3184 vol.UnsupportedMethod = true;
3185 }
3186 }
3187 }
3188 }
3189
3190 const HRESULT res = vol.FillRefs();
3191
3192 if (vol.ThereAreErrors())
3193 HeadersError = true;
3194 if (vol.UnsupportedFeature)
3195 UnsupportedFeature = true;
3196 if (vol.UnsupportedMethod)
3197 UnsupportedMethod = true;
3198 if (vol.NumAltStreams != 0)
3199 ThereAreAltStreams = true;
3200
3201 return res;
3202 }
3203
3204
3205
3206 HRESULT CVol::FillRefs()
3207 {
3208 {
3209 Refs.Reserve(Items.Size());
3210 // we fill Refs[*]
3211 // we
3212 // and set Nodes[*].ItemIndex for Nodes that are directories;
3213 FOR_VECTOR (i, Items)
3214 {
3215 CItem &item = Items[i];
3216 const UInt64 id = item.Val.file_id;
3217 // if (item.Id == ROOT_DIR_PARENT) continue;
3218 /* for two root folders items
3219 we don't set Node.ItemIndex; */
3220 // so nodes
3221 if (id == ROOT_DIR_INO_NUM)
3222 continue;
3223 if (id == PRIV_DIR_INO_NUM)
3224 if (NumItems_In_PrivateDir == 0) // if (inode.NumItems == 0)
3225 continue;
3226
3227 CRef ref;
3228 ref.ItemIndex = i;
3229 // ref.NodeIndex = VI_MINUS1;
3230 ref.ParentRefIndex = VI_MINUS1;
3231 #ifdef APFS_SHOW_ALT_STREAMS
3232 ref.AttrIndex = VI_MINUS1;
3233 #endif
3234 const int index = NodeIDs.FindInSorted(id);
3235 // const int index = -1; // for debug
3236 ref.NodeIndex = (unsigned)index;
3237 item.RefIndex = Refs.Size();
3238 Refs.Add(ref);
3239
3240 if (index == -1)
3241 {
3242 NodeNotFound = true;
3243 continue;
3244 // return S_FALSE;
3245 }
3246
3247 // item.iNode_Index = index;
3248 CNode &inode = Nodes[(unsigned)index];
3249 if (!item.Val.IsFlags_Unknown()
3250 && inode.Get_Type_From_mode() != item.Val.flags)
3251 {
3252 Refs.Back().NodeIndex = VI_MINUS1;
3253 WrongInodeLink = true;
3254 continue;
3255 // return S_FALSE;
3256 }
3257
3258 const bool isDir = inode.IsDir();
3259 if (isDir)
3260 {
3261 if (IsViDef(inode.ItemIndex))
3262 {
3263 // hard links to dirs are not supported
3264 Refs.Back().NodeIndex = VI_MINUS1;
3265 WrongInodeLink = true;
3266 continue;
3267 }
3268 inode.ItemIndex = i;
3269 }
3270 inode.NumCalcedLinks++;
3271
3272 #ifdef APFS_SHOW_ALT_STREAMS
3273 if (!isDir)
3274 {
3275 // we use alt streams only for files
3276 const unsigned numAttrs = inode.Attrs.Size();
3277 if (numAttrs != 0)
3278 {
3279 ref.ParentRefIndex = item.RefIndex;
3280 for (unsigned k = 0; k < numAttrs; k++)
3281 {
3282 // comment it for debug
3283 const CAttr &attr = inode.Attrs[k];
3284 if (!attr.NeedShow)
3285 continue;
3286
3287 if (k == inode.SymLinkIndex)
3288 continue;
3289 ref.AttrIndex = k;
3290 NumAltStreams++;
3291 Refs.Add(ref);
3292 /*
3293 if (attr.dstream_defined)
3294 {
3295 const int idIndex = SmallNodeIDs.FindInSorted(attr.Id);
3296 if (idIndex != -1)
3297 SmallNodes[(unsigned)idIndex].NumLinks++; // for debug
3298 }
3299 */
3300 }
3301 }
3302 }
3303 #endif
3304 }
3305 }
3306
3307
3308 {
3309 // fill ghost nodes
3310 CRef ref;
3311 ref.ItemIndex = VI_MINUS1;
3312 ref.ParentRefIndex = VI_MINUS1;
3313 #ifdef APFS_SHOW_ALT_STREAMS
3314 ref.AttrIndex = VI_MINUS1;
3315 #endif
3316 FOR_VECTOR (i, Nodes)
3317 {
3318 if (Nodes[i].NumCalcedLinks != 0)
3319 continue;
3320 const UInt64 id = NodeIDs[i];
3321 if (id == ROOT_DIR_INO_NUM ||
3322 id == PRIV_DIR_INO_NUM)
3323 continue;
3324 ThereAreUnlinkedNodes = true;
3325 ref.NodeIndex = i;
3326 Refs.Add(ref);
3327 }
3328 }
3329
3330 /* if want to create Refs for ghost data streams,
3331 we need additional CRef::SmallNodeIndex field */
3332
3333 {
3334 /* all Nodes[*].ItemIndex were already filled for directory Nodes,
3335 except of "root" and "private-dir" Nodes. */
3336
3337 // now we fill Items[*].ParentItemIndex and Refs[*].ParentRefIndex
3338
3339 UInt64 prev_ID = (UInt64)(Int64)-1;
3340 unsigned prev_ParentItemIndex = VI_MINUS1;
3341
3342 FOR_VECTOR (i, Items)
3343 {
3344 CItem &item = Items[i];
3345 const UInt64 id = item.ParentId; // it's id of parent NODE
3346 if (id != prev_ID)
3347 {
3348 prev_ID = id;
3349 prev_ParentItemIndex = VI_MINUS1;
3350 const int index = NodeIDs.FindInSorted(id);
3351 if (index == -1)
3352 continue;
3353 prev_ParentItemIndex = Nodes[(unsigned)index].ItemIndex;
3354 }
3355
3356 if (IsViNotDef(prev_ParentItemIndex))
3357 continue;
3358 item.ParentItemIndex = prev_ParentItemIndex;
3359 if (IsViNotDef(item.RefIndex))
3360 {
3361 // RefIndex is not set for 2 Items (root folders)
3362 // but there is no node for them usually
3363 continue;
3364 }
3365 CRef &ref = Refs[item.RefIndex];
3366
3367 /*
3368 // it's optional check that parent_id is set correclty
3369 if (IsViDef(ref.NodeIndex))
3370 {
3371 const CNode &node = Nodes[ref.NodeIndex];
3372 if (node.IsDir() && node.parent_id != id)
3373 return S_FALSE;
3374 }
3375 */
3376
3377 /*
3378 if (id == ROOT_DIR_INO_NUM)
3379 {
3380 // ItemIndex in Node for ROOT_DIR_INO_NUM was not set bofere
3381 // probably unused now.
3382 ref.ParentRefIndex = VI_MINUS1;
3383 }
3384 else
3385 */
3386 ref.ParentRefIndex = Items[prev_ParentItemIndex].RefIndex;
3387 }
3388 }
3389
3390 {
3391 // check for loops
3392 const unsigned numItems = Items.Size();
3393 if (numItems + 1 == 0)
3394 return S_FALSE;
3395 CUIntArr arr;
3396 arr.Alloc(numItems);
3397 {
3398 for (unsigned i = 0; i < numItems; i++)
3399 arr[i] = 0;
3400 }
3401 for (unsigned i = 0; i < numItems;)
3402 {
3403 unsigned k = i++;
3404 for (;;)
3405 {
3406 const unsigned a = arr[k];
3407 if (a != 0)
3408 {
3409 if (a == i)
3410 return S_FALSE;
3411 break;
3412 }
3413 arr[k] = i;
3414 k = Items[k].ParentItemIndex;
3415 if (IsViNotDef(k))
3416 break;
3417 }
3418 }
3419 }
3420
3421 return S_OK;
3422 }
3423
3424
3425
3426 Z7_class_CHandler_final:
3427 public IInArchive,
3428 public IArchiveGetRawProps,
3429 public IInArchiveGetStream,
3430 public CMyUnknownImp,
3431 public CDatabase
3432 {
3433 Z7_IFACES_IMP_UNK_3(
3434 IInArchive,
3435 IArchiveGetRawProps,
3436 IInArchiveGetStream)
3437
3438 CMyComPtr<IInStream> _stream;
3439 int FindHashIndex_for_Item(UInt32 index);
3440 };
3441
3442
3443 Z7_COM7F_IMF(CHandler::Open(IInStream *inStream,
3444 const UInt64 * /* maxCheckStartPosition */,
3445 IArchiveOpenCallback *callback))
3446 {
3447 COM_TRY_BEGIN
3448 Close();
3449 OpenInStream = inStream;
3450 OpenCallback = callback;
3451 RINOK(Open2())
3452 _stream = inStream;
3453 return S_OK;
3454 COM_TRY_END
3455 }
3456
3457
3458 Z7_COM7F_IMF(CHandler::Close())
3459 {
3460 _stream.Release();
3461 Clear();
3462 return S_OK;
3463 }
3464
3465
3466 enum
3467 {
3468 kpidBytesWritten = kpidUserDefined,
3469 kpidBytesRead,
3470 kpidPrimeName,
3471 kpidParentINode,
3472 kpidAddTime,
3473 kpidGeneration,
3474 kpidBsdFlags
3475 // kpidUncompressedSize
3476 };
3477
3478 static const CStatProp kProps[] =
3479 {
3480 { NULL, kpidPath, VT_BSTR },
3481 { NULL, kpidSize, VT_UI8 },
3482 { NULL, kpidPackSize, VT_UI8 },
3483 { NULL, kpidPosixAttrib, VT_UI4 },
3484 { NULL, kpidMTime, VT_FILETIME },
3485 { NULL, kpidCTime, VT_FILETIME },
3486 { NULL, kpidATime, VT_FILETIME },
3487 { NULL, kpidChangeTime, VT_FILETIME },
3488 { "Added Time", kpidAddTime, VT_FILETIME },
3489 { NULL, kpidMethod, VT_BSTR },
3490 { NULL, kpidINode, VT_UI8 },
3491 { NULL, kpidLinks, VT_UI4 },
3492 { NULL, kpidSymLink, VT_BSTR },
3493 { NULL, kpidUserId, VT_UI4 },
3494 { NULL, kpidGroupId, VT_UI4 },
3495 { NULL, kpidCharacts, VT_BSTR },
3496 #ifdef APFS_SHOW_ALT_STREAMS
3497 { NULL, kpidIsAltStream, VT_BOOL },
3498 #endif
3499 { "Parent iNode", kpidParentINode, VT_UI8 },
3500 { "Primary Name", kpidPrimeName, VT_BSTR },
3501 { "Generation", kpidGeneration, VT_UI4 },
3502 { "Written Size", kpidBytesWritten, VT_UI8 },
3503 { "Read Size", kpidBytesRead, VT_UI8 },
3504 { "BSD Flags", kpidBsdFlags, VT_UI4 }
3505 // , { "Uncompressed Size", kpidUncompressedSize, VT_UI8 }
3506 };
3507
3508
3509 static const Byte kArcProps[] =
3510 {
3511 kpidName,
3512 kpidCharacts,
3513 kpidId,
3514 kpidClusterSize,
3515 kpidCTime,
3516 kpidMTime,
3517 kpidComment
3518 };
3519
3520 IMP_IInArchive_Props_WITH_NAME
3521 IMP_IInArchive_ArcProps
3522
3523
3524 static void ApfsTimeToProp(UInt64 hfsTime, NWindows::NCOM::CPropVariant &prop)
3525 {
3526 if (hfsTime == 0)
3527 return;
3528 FILETIME ft;
3529 UInt32 ns100;
3530 ApfsTimeToFileTime(hfsTime, ft, ns100);
3531 prop.SetAsTimeFrom_FT_Prec_Ns100(ft, k_PropVar_TimePrec_1ns, ns100);
3532 }
3533
3534
3535 Z7_COM7F_IMF(CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value))
3536 {
3537 COM_TRY_BEGIN
3538 NWindows::NCOM::CPropVariant prop;
3539 const CApfs *apfs = NULL;
3540 if (Vols.Size() == 1)
3541 apfs = &Vols[0].apfs;
3542 switch (propID)
3543 {
3544 case kpidPhySize:
3545 prop = (UInt64)sb.block_count << sb.block_size_Log;
3546 break;
3547 case kpidClusterSize: prop = (UInt32)(sb.block_size); break;
3548 case kpidCharacts: NHfs::MethodsMaskToProp(MethodsMask, prop); break;
3549 case kpidMTime:
3550 if (apfs)
3551 ApfsTimeToProp(apfs->modified_by[0].timestamp, prop);
3552 break;
3553 case kpidCTime:
3554 if (apfs)
3555 ApfsTimeToProp(apfs->formatted_by.timestamp, prop);
3556 break;
3557 case kpidIsTree: prop = true; break;
3558 case kpidErrorFlags:
3559 {
3560 UInt32 flags = 0;
3561 if (HeadersError) flags |= kpv_ErrorFlags_HeadersError;
3562 if (flags != 0)
3563 prop = flags;
3564 break;
3565 }
3566 case kpidWarningFlags:
3567 {
3568 UInt32 flags = 0;
3569 if (UnsupportedFeature) flags |= kpv_ErrorFlags_UnsupportedFeature;
3570 if (UnsupportedMethod) flags |= kpv_ErrorFlags_UnsupportedMethod;
3571 if (flags != 0)
3572 prop = flags;
3573 break;
3574 }
3575
3576 case kpidName:
3577 {
3578 if (apfs)
3579 {
3580 UString s;
3581 AddVolInternalName_toString(s, *apfs);
3582 s += ".apfs";
3583 prop = s;
3584 }
3585 break;
3586 }
3587
3588 case kpidId:
3589 {
3590 char s[32 + 4];
3591 sb.uuid.SetHex_To_str(s);
3592 prop = s;
3593 break;
3594 }
3595
3596 case kpidComment:
3597 {
3598 UString s;
3599 {
3600 AddComment_UInt64(s, "block_size", sb.block_size);
3601
3602 FOR_VECTOR (i, Vols)
3603 {
3604 if (Vols.Size() > 1)
3605 {
3606 if (i != 0)
3607 {
3608 s += "----";
3609 s.Add_LF();
3610 }
3611 AddComment_UInt64(s, "Volume", i + 1);
3612 }
3613 Vols[i].AddComment(s);
3614 }
3615 }
3616 prop = s;
3617 break;
3618 }
3619
3620 #ifdef APFS_SHOW_ALT_STREAMS
3621 case kpidIsAltStream:
3622 prop = ThereAreAltStreams;
3623 // prop = false; // for debug
3624 break;
3625 #endif
3626 }
3627 prop.Detach(value);
3628 return S_OK;
3629 COM_TRY_END
3630 }
3631
3632
3633 Z7_COM7F_IMF(CHandler::GetNumRawProps(UInt32 *numProps))
3634 {
3635 *numProps = 0;
3636 return S_OK;
3637 }
3638
3639
3640 Z7_COM7F_IMF(CHandler::GetRawPropInfo(UInt32 /* index */, BSTR *name, PROPID *propID))
3641 {
3642 *name = NULL;
3643 *propID = 0;
3644 return S_OK;
3645 }
3646
3647
3648 Z7_COM7F_IMF(CHandler::GetParent(UInt32 index, UInt32 *parent, UInt32 *parentType))
3649 {
3650 *parentType = NParentType::kDir;
3651
3652 const CRef2 &ref2 = Refs2[index];
3653 const CVol &vol = Vols[ref2.VolIndex];
3654 UInt32 parentIndex = (UInt32)(Int32)-1;
3655
3656 if (IsViDef(ref2.RefIndex))
3657 {
3658 const CRef &ref = vol.Refs[ref2.RefIndex];
3659 #ifdef APFS_SHOW_ALT_STREAMS
3660 if (ref.IsAltStream())
3661 *parentType = NParentType::kAltStream;
3662 #endif
3663 if (IsViDef(ref.ParentRefIndex))
3664 parentIndex = (UInt32)(ref.ParentRefIndex + vol.StartRef2Index);
3665 else if (index != vol.RootRef2Index && IsViDef(vol.RootRef2Index))
3666 parentIndex = (UInt32)vol.RootRef2Index;
3667 }
3668
3669 *parent = parentIndex;
3670 return S_OK;
3671 }
3672
3673
3674 Z7_COM7F_IMF(CHandler::GetRawProp(UInt32 index, PROPID propID, const void **data, UInt32 *dataSize, UInt32 *propType))
3675 {
3676 *data = NULL;
3677 *dataSize = 0;
3678 *propType = 0;
3679 UNUSED_VAR(index)
3680 UNUSED_VAR(propID)
3681 return S_OK;
3682 }
3683
3684
3685 static void Utf8Name_to_InterName(const AString &src, UString &dest)
3686 {
3687 ConvertUTF8ToUnicode(src, dest);
3688 NItemName::NormalizeSlashes_in_FileName_for_OsPath(dest);
3689 }
3690
3691
3692 static void AddNodeName(UString &s, const CNode &inode, UInt64 id)
3693 {
3694 s += "node";
3695 s.Add_UInt64(id);
3696 if (!inode.PrimaryName.IsEmpty())
3697 {
3698 s.Add_Dot();
3699 UString s2;
3700 Utf8Name_to_InterName(inode.PrimaryName, s2);
3701 s += s2;
3702 }
3703 }
3704
3705
3706 void CDatabase::GetItemPath(unsigned index, const CNode *inode, NWindows::NCOM::CPropVariant &path) const
3707 {
3708 const unsigned kNumLevelsMax = (1 << 10);
3709 const unsigned kLenMax = (1 << 12);
3710 UString s;
3711 const CRef2 &ref2 = Refs2[index];
3712 const CVol &vol = Vols[ref2.VolIndex];
3713
3714 if (IsViDef(ref2.RefIndex))
3715 {
3716 const CRef &ref = vol.Refs[ref2.RefIndex];
3717 unsigned cur = ref.ItemIndex;
3718 UString s2;
3719 if (IsViNotDef(cur))
3720 {
3721 if (inode)
3722 AddNodeName(s, *inode, vol.NodeIDs[ref.NodeIndex]);
3723 }
3724 else
3725 {
3726 for (unsigned i = 0;; i++)
3727 {
3728 if (i >= kNumLevelsMax || s.Len() > kLenMax)
3729 {
3730 s.Insert(0, UString("[LONG_PATH]"));
3731 break;
3732 }
3733 const CItem &item = vol.Items[(unsigned)cur];
3734 Utf8Name_to_InterName(item.Name, s2);
3735 // s2 += "a\\b"; // for debug
3736 s.Insert(0, s2);
3737 cur = item.ParentItemIndex;
3738 if (IsViNotDef(cur))
3739 break;
3740 // ParentItemIndex was not set for such items
3741 // if (item.ParentId == ROOT_DIR_INO_NUM) break;
3742 s.InsertAtFront(WCHAR_PATH_SEPARATOR);
3743 }
3744 }
3745
3746 #ifdef APFS_SHOW_ALT_STREAMS
3747 if (IsViDef(ref.AttrIndex) && inode)
3748 {
3749 s.Add_Colon();
3750 Utf8Name_to_InterName(inode->Attrs[(unsigned)ref.AttrIndex].Name, s2);
3751 // s2 += "a\\b"; // for debug
3752 s += s2;
3753 }
3754 #endif
3755 }
3756
3757 if (!vol.RootName.IsEmpty())
3758 {
3759 if (IsViDef(ref2.RefIndex))
3760 s.InsertAtFront(WCHAR_PATH_SEPARATOR);
3761 s.Insert(0, vol.RootName);
3762 }
3763
3764 path = s;
3765 }
3766
3767
3768
3769 Z7_COM7F_IMF(CHandler::GetProperty(UInt32 index, PROPID propID, PROPVARIANT *value))
3770 {
3771 COM_TRY_BEGIN
3772 NWindows::NCOM::CPropVariant prop;
3773
3774 const CRef2 &ref2 = Refs2[index];
3775 const CVol &vol = Vols[ref2.VolIndex];
3776
3777 if (IsViNotDef(ref2.RefIndex))
3778 {
3779 switch (propID)
3780 {
3781 case kpidName:
3782 case kpidPath:
3783 GetItemPath(index, NULL, prop);
3784 break;
3785 case kpidIsDir:
3786 prop = true;
3787 break;
3788 }
3789 prop.Detach(value);
3790 return S_OK;
3791 }
3792
3793 const CRef &ref = vol.Refs[ref2.RefIndex];
3794
3795 const CItem *item = NULL;
3796 if (IsViDef(ref.ItemIndex))
3797 item = &vol.Items[ref.ItemIndex];
3798
3799 const CNode *inode = NULL;
3800 if (IsViDef(ref.NodeIndex))
3801 inode = &vol.Nodes[ref.NodeIndex];
3802
3803 switch (propID)
3804 {
3805 case kpidPath:
3806 GetItemPath(index, inode, prop);
3807 break;
3808 case kpidPrimeName:
3809 {
3810 #ifdef APFS_SHOW_ALT_STREAMS
3811 if (!ref.IsAltStream())
3812 #endif
3813 if (inode && !inode->PrimaryName.IsEmpty())
3814 {
3815 UString s;
3816 ConvertUTF8ToUnicode(inode->PrimaryName, s);
3817 /*
3818 // for debug:
3819 if (inode.PrimaryName != item.Name) throw 123456;
3820 */
3821 prop = s;
3822 }
3823 break;
3824 }
3825
3826 case kpidName:
3827 {
3828 UString s;
3829 #ifdef APFS_SHOW_ALT_STREAMS
3830 if (ref.IsAltStream())
3831 {
3832 // if (inode)
3833 {
3834 const CAttr &attr = inode->Attrs[(unsigned)ref.AttrIndex];
3835 ConvertUTF8ToUnicode(attr.Name, s);
3836 }
3837 }
3838 else
3839 #endif
3840 {
3841 if (item)
3842 ConvertUTF8ToUnicode(item->Name, s);
3843 else if (inode)
3844 AddNodeName(s, *inode, vol.NodeIDs[ref.NodeIndex]);
3845 else
3846 break;
3847 }
3848 // s += "s/1bs\\2"; // for debug:
3849 prop = s;
3850 break;
3851 }
3852
3853 case kpidSymLink:
3854 #ifdef APFS_SHOW_ALT_STREAMS
3855 if (!ref.IsAltStream())
3856 #endif
3857 if (inode)
3858 {
3859 if (inode->IsSymLink() && IsViDef(inode->SymLinkIndex))
3860 {
3861 const CByteBuffer &buf = inode->Attrs[(unsigned)inode->SymLinkIndex].Data;
3862 if (buf.Size() != 0)
3863 {
3864 AString s;
3865 s.SetFrom_CalcLen((const char *)(const Byte *)buf, (unsigned)buf.Size());
3866 if (s.Len() == buf.Size() - 1)
3867 {
3868 UString u;
3869 ConvertUTF8ToUnicode(s, u);
3870 prop = u;
3871 }
3872 }
3873 }
3874 }
3875 break;
3876
3877 case kpidSize:
3878 if (inode)
3879 {
3880 UInt64 size = 0;
3881 if (inode->GetSize(ref.GetAttrIndex(), size) ||
3882 !inode->IsDir())
3883 prop = size;
3884 }
3885 break;
3886
3887 case kpidPackSize:
3888 if (inode)
3889 {
3890 UInt64 size;
3891 if (inode->GetPackSize(ref.GetAttrIndex(), size) ||
3892 !inode->IsDir())
3893 prop = size;
3894 }
3895 break;
3896
3897 case kpidMethod:
3898 #ifdef APFS_SHOW_ALT_STREAMS
3899 if (!ref.IsAltStream())
3900 #endif
3901 if (inode)
3902 {
3903 if (inode->CompressHeader.IsCorrect)
3904 inode->CompressHeader.MethodToProp(prop);
3905 else if (IsViDef(inode->DecmpfsIndex))
3906 prop = "decmpfs";
3907 else if (!inode->IsDir() && !inode->dstream_defined)
3908 {
3909 if (inode->IsSymLink())
3910 {
3911 if (IsViDef(inode->SymLinkIndex))
3912 prop = "symlink";
3913 }
3914 // else prop = "no_dstream";
3915 }
3916 }
3917 break;
3918
3919 /*
3920 case kpidUncompressedSize:
3921 if (inode && inode->Has_UNCOMPRESSED_SIZE())
3922 prop = inode->uncompressed_size;
3923 break;
3924 */
3925
3926 case kpidIsDir:
3927 {
3928 bool isDir = false;
3929 #ifdef APFS_SHOW_ALT_STREAMS
3930 if (!ref.IsAltStream())
3931 #endif
3932 {
3933 if (inode)
3934 isDir = inode->IsDir();
3935 else if (item)
3936 isDir = item->Val.IsFlags_Dir();
3937 }
3938 prop = isDir;
3939 break;
3940 }
3941
3942 case kpidPosixAttrib:
3943 {
3944 if (inode)
3945 {
3946 UInt32 mode = inode->mode;
3947 #ifdef APFS_SHOW_ALT_STREAMS
3948 if (ref.IsAltStream())
3949 {
3950 mode &= 0666; // we disable execution
3951 mode |= MY_LIN_S_IFREG;
3952 }
3953 #endif
3954 prop = (UInt32)mode;
3955 }
3956 else if (item && !item->Val.IsFlags_Unknown())
3957 prop = (UInt32)(item->Val.flags << 12);
3958 break;
3959 }
3960
3961 case kpidCTime: if (inode) ApfsTimeToProp(inode->create_time, prop); break;
3962 case kpidMTime: if (inode) ApfsTimeToProp(inode->mod_time, prop); break;
3963 case kpidATime: if (inode) ApfsTimeToProp(inode->access_time, prop); break;
3964 case kpidChangeTime: if (inode) ApfsTimeToProp(inode->change_time, prop); break;
3965 case kpidAddTime: if (item) ApfsTimeToProp(item->Val.date_added, prop); break;
3966
3967 case kpidBytesWritten:
3968 #ifdef APFS_SHOW_ALT_STREAMS
3969 if (!ref.IsAltStream())
3970 #endif
3971 if (inode && inode->dstream_defined)
3972 prop = inode->dstream.total_bytes_written;
3973 break;
3974 case kpidBytesRead:
3975 #ifdef APFS_SHOW_ALT_STREAMS
3976 if (!ref.IsAltStream())
3977 #endif
3978 if (inode && inode->dstream_defined)
3979 prop = inode->dstream.total_bytes_read;
3980 break;
3981
3982 #ifdef APFS_SHOW_ALT_STREAMS
3983 case kpidIsAltStream:
3984 prop = ref.IsAltStream();
3985 break;
3986 #endif
3987
3988 case kpidCharacts:
3989 #ifdef APFS_SHOW_ALT_STREAMS
3990 if (!ref.IsAltStream())
3991 #endif
3992 if (inode)
3993 {
3994 FLAGS_TO_PROP(g_INODE_Flags, (UInt32)inode->internal_flags, prop);
3995 }
3996 break;
3997
3998 case kpidBsdFlags:
3999 #ifdef APFS_SHOW_ALT_STREAMS
4000 if (!ref.IsAltStream())
4001 #endif
4002 if (inode)
4003 {
4004 FLAGS_TO_PROP(g_INODE_BSD_Flags, inode->bsd_flags, prop);
4005 }
4006 break;
4007
4008 case kpidGeneration:
4009 #ifdef APFS_SHOW_ALT_STREAMS
4010 // if (!ref.IsAltStream())
4011 #endif
4012 if (inode)
4013 prop = inode->write_generation_counter;
4014 break;
4015
4016 case kpidUserId:
4017 if (inode)
4018 prop = (UInt32)inode->owner;
4019 break;
4020
4021 case kpidGroupId:
4022 if (inode)
4023 prop = (UInt32)inode->group;
4024 break;
4025
4026 case kpidLinks:
4027 #ifdef APFS_SHOW_ALT_STREAMS
4028 if (!ref.IsAltStream())
4029 #endif
4030 if (inode && !inode->IsDir())
4031 prop = (UInt32)inode->nlink;
4032 break;
4033
4034 case kpidINode:
4035 #ifdef APFS_SHOW_ALT_STREAMS
4036 // here we can disable iNode for alt stream.
4037 if (!ref.IsAltStream())
4038 #endif
4039 if (IsViDef(ref.NodeIndex))
4040 prop = (UInt32)vol.NodeIDs[ref.NodeIndex];
4041 break;
4042
4043 case kpidParentINode:
4044 #ifdef APFS_SHOW_ALT_STREAMS
4045 if (!ref.IsAltStream())
4046 #endif
4047 if (inode)
4048 prop = (UInt32)inode->parent_id;
4049 break;
4050 }
4051 prop.Detach(value);
4052 return S_OK;
4053 COM_TRY_END
4054 }
4055
4056
4057 UInt64 CDatabase::GetSize(const UInt32 index) const
4058 {
4059 const CRef2 &ref2 = Refs2[index];
4060 const CVol &vol = Vols[ref2.VolIndex];
4061 if (IsViNotDef(ref2.RefIndex))
4062 return 0;
4063 const CRef &ref = vol.Refs[ref2.RefIndex];
4064 if (IsViNotDef(ref.NodeIndex))
4065 return 0;
4066 const CNode &inode = vol.Nodes[ref.NodeIndex];
4067 UInt64 size;
4068 if (inode.GetSize(ref.GetAttrIndex(), size))
4069 return size;
4070 return 0;
4071 }
4072
4073
4074 Z7_COM7F_IMF(CHandler::Extract(const UInt32 *indices, UInt32 numItems,
4075 Int32 testMode, IArchiveExtractCallback *extractCallback))
4076 {
4077 COM_TRY_BEGIN
4078 const bool allFilesMode = (numItems == (UInt32)(Int32)-1);
4079 if (allFilesMode)
4080 numItems = Refs2.Size();
4081 if (numItems == 0)
4082 return S_OK;
4083 UInt32 i;
4084 {
4085 UInt64 totalSize = 0;
4086 for (i = 0; i < numItems; i++)
4087 {
4088 const UInt32 index = allFilesMode ? i : indices[i];
4089 totalSize += GetSize(index);
4090 }
4091 RINOK(extractCallback->SetTotal(totalSize))
4092 }
4093
4094 UInt64 currentTotalSize = 0, currentItemSize = 0;
4095
4096 CMyComPtr2_Create<ICompressProgressInfo, CLocalProgress> lps;
4097 lps->Init(extractCallback, false);
4098
4099 CMyComPtr2_Create<ICompressCoder, NCompress::CCopyCoder> copyCoder;
4100
4101 // We don't know if zlib without Adler is allowed in APFS.
4102 // But zlib without Adler is allowed in HFS.
4103 // So here we allow apfs/zlib without Adler:
4104 NHfs::CDecoder decoder(true); // IsAdlerOptional
4105
4106 for (i = 0;; i++, currentTotalSize += currentItemSize)
4107 {
4108 lps->InSize = currentTotalSize;
4109 lps->OutSize = currentTotalSize;
4110 RINOK(lps->SetCur())
4111
4112 if (i >= numItems)
4113 break;
4114
4115 const UInt32 index = allFilesMode ? i : indices[i];
4116 const CRef2 &ref2 = Refs2[index];
4117 const CVol &vol = Vols[ref2.VolIndex];
4118
4119 currentItemSize = GetSize(index);
4120
4121 int opRes;
4122 {
4123 CMyComPtr<ISequentialOutStream> realOutStream;
4124 const Int32 askMode = testMode ?
4125 NExtract::NAskMode::kTest :
4126 NExtract::NAskMode::kExtract;
4127 RINOK(extractCallback->GetStream(index, &realOutStream, askMode))
4128
4129 if (IsViNotDef(ref2.RefIndex))
4130 {
4131 RINOK(extractCallback->PrepareOperation(askMode))
4132 RINOK(extractCallback->SetOperationResult(NExtract::NOperationResult::kOK))
4133 continue;
4134 }
4135
4136 const CRef &ref = vol.Refs[ref2.RefIndex];
4137 bool isDir = false;
4138 if (IsViDef(ref.NodeIndex))
4139 isDir = vol.Nodes[ref.NodeIndex].IsDir();
4140 else if (IsViDef(ref.ItemIndex))
4141 isDir =
4142 #ifdef APFS_SHOW_ALT_STREAMS
4143 !ref.IsAltStream() &&
4144 #endif
4145 vol.Items[ref.ItemIndex].Val.IsFlags_Dir();
4146
4147 if (isDir)
4148 {
4149 RINOK(extractCallback->PrepareOperation(askMode))
4150 RINOK(extractCallback->SetOperationResult(NExtract::NOperationResult::kOK))
4151 continue;
4152 }
4153 if (!testMode && !realOutStream)
4154 continue;
4155 RINOK(extractCallback->PrepareOperation(askMode))
4156 opRes = NExtract::NOperationResult::kDataError;
4157
4158 if (IsViDef(ref.NodeIndex))
4159 {
4160 const CNode &inode = vol.Nodes[ref.NodeIndex];
4161 if (
4162 #ifdef APFS_SHOW_ALT_STREAMS
4163 !ref.IsAltStream() &&
4164 #endif
4165 !inode.dstream_defined
4166 && inode.Extents.IsEmpty()
4167 && inode.Has_UNCOMPRESSED_SIZE()
4168 && inode.uncompressed_size == inode.CompressHeader.UnpackSize)
4169 {
4170 if (inode.CompressHeader.IsSupported)
4171 {
4172 CMyComPtr<ISequentialInStream> inStreamFork;
4173 UInt64 forkSize = 0;
4174 const CByteBuffer *decmpfs_Data = NULL;
4175
4176 if (inode.CompressHeader.IsMethod_Resource())
4177 {
4178 if (IsViDef(inode.ResourceIndex))
4179 {
4180 const CAttr &attr = inode.Attrs[inode.ResourceIndex];
4181 forkSize = attr.GetSize();
4182 GetAttrStream(_stream, vol, attr, &inStreamFork);
4183 }
4184 }
4185 else
4186 {
4187 const CAttr &attr = inode.Attrs[inode.DecmpfsIndex];
4188 decmpfs_Data = &attr.Data;
4189 }
4190
4191 if (inStreamFork || decmpfs_Data)
4192 {
4193 const HRESULT hres = decoder.Extract(
4194 inStreamFork, realOutStream,
4195 forkSize,
4196 inode.CompressHeader,
4197 decmpfs_Data,
4198 currentTotalSize, extractCallback,
4199 opRes);
4200 if (hres != S_FALSE && hres != S_OK)
4201 return hres;
4202 }
4203 }
4204 else
4205 opRes = NExtract::NOperationResult::kUnsupportedMethod;
4206 }
4207 else
4208 {
4209 CMyComPtr<ISequentialInStream> inStream;
4210 if (GetStream(index, &inStream) == S_OK && inStream)
4211 {
4212 CMyComPtr2<ISequentialOutStream, COutStreamWithHash> hashStream;
4213
4214 if (vol.integrity.Is_SHA256())
4215 {
4216 const int hashIndex = FindHashIndex_for_Item(index);
4217 if (hashIndex != -1)
4218 {
4219 hashStream.Create_if_Empty();
4220 hashStream->SetStream(realOutStream);
4221 hashStream->Init(&(vol.Hash_Vectors[(unsigned)hashIndex]), sb.block_size_Log);
4222 }
4223 }
4224
4225 RINOK(copyCoder.Interface()->Code(inStream,
4226 hashStream.IsDefined() ?
4227 hashStream.Interface() :
4228 realOutStream.Interface(),
4229 NULL, NULL, lps))
4230 opRes = NExtract::NOperationResult::kDataError;
4231 if (copyCoder->TotalSize == currentItemSize)
4232 {
4233 opRes = NExtract::NOperationResult::kOK;
4234 if (hashStream.IsDefined())
4235 if (!hashStream->FinalCheck())
4236 opRes = NExtract::NOperationResult::kCRCError;
4237 }
4238 else if (copyCoder->TotalSize < currentItemSize)
4239 opRes = NExtract::NOperationResult::kUnexpectedEnd;
4240 }
4241 }
4242 }
4243 }
4244 RINOK(extractCallback->SetOperationResult(opRes))
4245 }
4246 return S_OK;
4247 COM_TRY_END
4248 }
4249
4250
4251 Z7_COM7F_IMF(CHandler::GetNumberOfItems(UInt32 *numItems))
4252 {
4253 *numItems = Refs2.Size();
4254 return S_OK;
4255 }
4256
4257
4258 int CHandler::FindHashIndex_for_Item(UInt32 index)
4259 {
4260 const CRef2 &ref2 = Refs2[index];
4261 const CVol &vol = Vols[ref2.VolIndex];
4262 if (IsViNotDef(ref2.RefIndex))
4263 return -1;
4264
4265 const CRef &ref = vol.Refs[ref2.RefIndex];
4266 if (IsViNotDef(ref.NodeIndex))
4267 return -1;
4268 const CNode &inode = vol.Nodes[ref.NodeIndex];
4269
4270 unsigned attrIndex = ref.GetAttrIndex();
4271
4272 if (IsViNotDef(attrIndex)
4273 && !inode.dstream_defined
4274 && inode.IsSymLink())
4275 {
4276 attrIndex = inode.SymLinkIndex;
4277 if (IsViNotDef(attrIndex))
4278 return -1;
4279 }
4280
4281 if (IsViDef(attrIndex))
4282 {
4283 /* we have seen examples, where hash available for "com.apple.ResourceFork" stream.
4284 these hashes for "com.apple.ResourceFork" stream are for unpacked data.
4285 but the caller here needs packed data of stream. So we don't use hashes */
4286 return -1;
4287 }
4288 else
4289 {
4290 if (!inode.dstream_defined)
4291 return -1;
4292 const UInt64 id = vol.NodeIDs[ref.NodeIndex];
4293 return vol.Hash_IDs.FindInSorted(id);
4294 }
4295 }
4296
4297
4298 Z7_COM7F_IMF(CHandler::GetStream(UInt32 index, ISequentialInStream **stream))
4299 {
4300 *stream = NULL;
4301
4302 const CRef2 &ref2 = Refs2[index];
4303 const CVol &vol = Vols[ref2.VolIndex];
4304 if (IsViNotDef(ref2.RefIndex))
4305 return S_FALSE;
4306
4307 const CRef &ref = vol.Refs[ref2.RefIndex];
4308 if (IsViNotDef(ref.NodeIndex))
4309 return S_FALSE;
4310 const CNode &inode = vol.Nodes[ref.NodeIndex];
4311
4312 const CRecordVector<CExtent> *extents;
4313 UInt64 rem = 0;
4314
4315 unsigned attrIndex = ref.GetAttrIndex();
4316
4317 if (IsViNotDef(attrIndex)
4318 && !inode.dstream_defined
4319 && inode.IsSymLink())
4320 {
4321 attrIndex = inode.SymLinkIndex;
4322 if (IsViNotDef(attrIndex))
4323 return S_FALSE;
4324 }
4325
4326 if (IsViDef(attrIndex))
4327 {
4328 const CAttr &attr = inode.Attrs[(unsigned)attrIndex];
4329 if (!attr.dstream_defined)
4330 {
4331 CMyComPtr2<ISequentialInStream, CBufInStream> streamTemp;
4332 streamTemp.Create_if_Empty();
4333 streamTemp->Init(attr.Data, attr.Data.Size(), (IInArchive *)this);
4334 *stream = streamTemp.Detach();
4335 return S_OK;
4336 }
4337 const int idIndex = vol.SmallNodeIDs.FindInSorted(attr.Id);
4338 if (idIndex != -1)
4339 extents = &vol.SmallNodes[(unsigned)idIndex].Extents;
4340 else
4341 {
4342 const int fext_Index = vol.FEXT_NodeIDs.FindInSorted(attr.Id);
4343 if (fext_Index == -1)
4344 return S_FALSE;
4345 extents = &vol.FEXT_Nodes[(unsigned)fext_Index].Extents;
4346 }
4347 rem = attr.dstream.size;
4348 }
4349 else
4350 {
4351 if (IsViDef(ref.ItemIndex))
4352 if (vol.Items[ref.ItemIndex].Val.IsFlags_Dir())
4353 return S_FALSE;
4354 if (inode.IsDir())
4355 return S_FALSE;
4356 extents = &inode.Extents;
4357 if (inode.dstream_defined)
4358 {
4359 rem = inode.dstream.size;
4360 if (inode.Extents.Size() == 0)
4361 {
4362 const int fext_Index = vol.FEXT_NodeIDs.FindInSorted(vol.NodeIDs[ref.NodeIndex]);
4363 if (fext_Index != -1)
4364 extents = &vol.FEXT_Nodes[(unsigned)fext_Index].Extents;
4365 }
4366 }
4367 else
4368 {
4369 // return S_FALSE; // check it !!! How zero size files are stored with dstream_defined?
4370 }
4371 }
4372 return GetStream2(_stream, extents, rem, stream);
4373 }
4374
4375
4376
4377 HRESULT CDatabase::GetAttrStream(IInStream *apfsInStream, const CVol &vol,
4378 const CAttr &attr, ISequentialInStream **stream)
4379 {
4380 *stream = NULL;
4381 if (!attr.dstream_defined)
4382 {
4383 CMyComPtr2<ISequentialInStream, CBufInStream> streamTemp;
4384 streamTemp.Create_if_Empty();
4385 streamTemp->Init(attr.Data, attr.Data.Size(), (IInArchive *)this);
4386 *stream = streamTemp.Detach();
4387 return S_OK;
4388 }
4389 return GetAttrStream_dstream(apfsInStream, vol, attr, stream);
4390 }
4391
4392
4393 HRESULT CDatabase::GetAttrStream_dstream( IInStream *apfsInStream, const CVol &vol,
4394 const CAttr &attr, ISequentialInStream **stream)
4395 {
4396 const CRecordVector<CExtent> *extents;
4397 {
4398 const int idIndex = vol.SmallNodeIDs.FindInSorted(attr.Id);
4399 if (idIndex != -1)
4400 extents = &vol.SmallNodes[(unsigned)idIndex].Extents;
4401 else
4402 {
4403 const int fext_Index = vol.FEXT_NodeIDs.FindInSorted(attr.Id);
4404 if (fext_Index == -1)
4405 return S_FALSE;
4406 extents = &vol.FEXT_Nodes[(unsigned)fext_Index].Extents;
4407 }
4408 }
4409 return GetStream2(apfsInStream, extents, attr.dstream.size, stream);
4410 }
4411
4412
4413 HRESULT CDatabase::GetStream2(
4414 IInStream *apfsInStream,
4415 const CRecordVector<CExtent> *extents, UInt64 rem,
4416 ISequentialInStream **stream)
4417 {
4418 CMyComPtr2<ISequentialInStream, CExtentsStream> extentStream;
4419 extentStream.Create_if_Empty();
4420
4421 UInt64 virt = 0;
4422 FOR_VECTOR (i, *extents)
4423 {
4424 const CExtent &e = (*extents)[i];
4425 if (virt != e.logical_offset)
4426 return S_FALSE;
4427 const UInt64 len = EXTENT_GET_LEN(e.len_and_flags);
4428 if (len == 0)
4429 {
4430 return S_FALSE;
4431 // continue;
4432 }
4433 if (rem == 0)
4434 return S_FALSE;
4435 UInt64 cur = len;
4436 if (cur > rem)
4437 cur = rem;
4438 CSeekExtent se;
4439 se.Phy = (UInt64)e.phys_block_num << sb.block_size_Log;
4440 se.Virt = virt;
4441 virt += cur;
4442 rem -= cur;
4443 extentStream->Extents.Add(se);
4444 if (rem == 0)
4445 if (i != extents->Size() - 1)
4446 return S_FALSE;
4447 }
4448
4449 if (rem != 0)
4450 return S_FALSE;
4451
4452 CSeekExtent se;
4453 se.Phy = 0;
4454 se.Virt = virt;
4455 extentStream->Extents.Add(se);
4456 extentStream->Stream = apfsInStream;
4457 extentStream->Init();
4458 *stream = extentStream.Detach();
4459 return S_OK;
4460 }
4461
4462
4463 REGISTER_ARC_I(
4464 "APFS", "apfs img", NULL, 0xc3,
4465 k_Signature,
4466 k_SignatureOffset,
4467 0,
4468 IsArc_APFS)
4469
4470 }}
4471