Coverage Report

Created: 2026-02-14 06:27

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/sleuthkit/tsk/fs/apfs_compat.cpp
Line
Count
Source
1
/*
2
 * The Sleuth Kit
3
 *
4
 * Brian Carrier [carrier <at> sleuthkit [dot] org]
5
 * Copyright (c) 2019-2020 Brian Carrier.  All Rights reserved
6
 * Copyright (c) 2018-2019 BlackBag Technologies.  All Rights reserved
7
 *
8
 * This software is distributed under the Common Public License 1.0
9
 */
10
#include "tsk/libtsk.h"
11
12
#include "decmpfs.h"
13
#include "tsk_fs_i.h"
14
15
#include "tsk/pool/apfs_pool_compat.hpp"
16
#include "tsk/img/pool.hpp"
17
#include "apfs_compat.hpp"
18
19
#include <cstring>
20
21
// Forward declarations
22
void error_detected(uint32_t errnum, const char* errstr, ...);
23
void error_returned(const char* errstr, ...);
24
25
static inline const APFSPoolCompat& to_pool(
26
3
    const TSK_POOL_INFO* pool_info) noexcept {
27
28
3
    const auto pool = static_cast<APFSPoolCompat*>(pool_info->impl);
29
3
    return *pool;
30
3
}
31
32
static inline const APFSPoolCompat& fs_info_to_pool(
33
0
    const TSK_FS_INFO* fs_info) noexcept {
34
35
0
    IMG_POOL_INFO *pool_img = (IMG_POOL_INFO*)fs_info->img_info;
36
0
    return to_pool(pool_img->pool_info);
37
0
}
38
39
static inline TSK_DADDR_T to_pool_vol_block(
40
0
    const TSK_FS_INFO* fs_info) noexcept {
41
42
0
    if (fs_info->img_info->itype != TSK_IMG_TYPE_POOL) {
43
0
        return 0;
44
0
    }
45
46
0
    IMG_POOL_INFO *pool_img = (IMG_POOL_INFO*)fs_info->img_info;
47
0
    return pool_img->pvol_block;
48
0
}
49
50
2
static inline APFSFSCompat& to_fs(const TSK_FS_INFO* fs_info) noexcept {
51
2
  const auto fs = static_cast<APFSFSCompat*>(fs_info->impl);
52
2
  return *fs;
53
2
}
54
55
0
static uint8_t unsupported_function(const char* func) noexcept {
56
0
  tsk_error_reset();
57
0
  tsk_error_set_errno(TSK_ERR_FS_UNSUPFUNC);
58
0
  tsk_error_set_errstr("%s not implemented for APFS yet", func);
59
0
  return 1;
60
0
}
61
62
0
static TSK_FS_ATTR_TYPE_ENUM xattribute_type(const std::string& name) noexcept {
63
0
  if (name == APFS_XATTR_NAME_DECOMPFS) {
64
0
    return TSK_FS_ATTR_TYPE_APFS_COMP_REC;
65
0
  }
66
67
0
  if (name == APFS_XATTR_NAME_RESOURCEFORK) {
68
0
    return TSK_FS_ATTR_TYPE_APFS_RSRC;
69
0
  }
70
71
  // Default to XATTR
72
0
  return TSK_FS_ATTR_TYPE_APFS_EXT_ATTR;
73
0
}
74
75
0
static TSK_FS_NAME_TYPE_ENUM to_name_type(APFS_ITEM_TYPE type) noexcept {
76
0
  switch (type) {
77
0
    case APFS_ITEM_TYPE_FIFO:
78
0
      return TSK_FS_NAME_TYPE_FIFO;
79
0
    case APFS_ITEM_TYPE_CHAR_DEVICE:
80
0
      return TSK_FS_NAME_TYPE_CHR;
81
0
    case APFS_ITEM_TYPE_DIRECTORY:
82
0
      return TSK_FS_NAME_TYPE_DIR;
83
0
    case APFS_ITEM_TYPE_BLOCK_DEVICE:
84
0
      return TSK_FS_NAME_TYPE_BLK;
85
0
    case APFS_ITEM_TYPE_REGULAR:
86
0
      return TSK_FS_NAME_TYPE_REG;
87
0
    case APFS_ITEM_TYPE_SYMBOLIC_LINK:
88
0
      return TSK_FS_NAME_TYPE_LNK;
89
0
    case APFS_ITEM_TYPE_SOCKET:
90
0
      return TSK_FS_NAME_TYPE_SOCK;
91
0
    case APFS_ITEM_TYPE_WHITEOUT:
92
0
      return TSK_FS_NAME_TYPE_WHT;
93
0
    default:
94
0
      return TSK_FS_NAME_TYPE_UNDEF;
95
0
  }
96
0
}
97
98
0
static TSK_FS_META_TYPE_ENUM to_meta_type(APFS_ITEM_TYPE type) noexcept {
99
0
  switch (type) {
100
0
    case APFS_ITEM_TYPE_FIFO:
101
0
      return TSK_FS_META_TYPE_FIFO;
102
0
    case APFS_ITEM_TYPE_CHAR_DEVICE:
103
0
      return TSK_FS_META_TYPE_CHR;
104
0
    case APFS_ITEM_TYPE_DIRECTORY:
105
0
      return TSK_FS_META_TYPE_DIR;
106
0
    case APFS_ITEM_TYPE_BLOCK_DEVICE:
107
0
      return TSK_FS_META_TYPE_BLK;
108
0
    case APFS_ITEM_TYPE_REGULAR:
109
0
      return TSK_FS_META_TYPE_REG;
110
0
    case APFS_ITEM_TYPE_SYMBOLIC_LINK:
111
0
      return TSK_FS_META_TYPE_LNK;
112
0
    case APFS_ITEM_TYPE_SOCKET:
113
0
      return TSK_FS_META_TYPE_SOCK;
114
0
    case APFS_ITEM_TYPE_WHITEOUT:
115
0
      return TSK_FS_META_TYPE_WHT;
116
0
    default:
117
0
      return TSK_FS_META_TYPE_UNDEF;
118
0
  }
119
0
}
120
121
0
static const char* to_string(TSK_FS_META_TYPE_ENUM type) noexcept {
122
0
  switch (type) {
123
0
    case TSK_FS_META_TYPE_FIFO:
124
0
      return "Named Pipe (FIFO)";
125
126
0
    case TSK_FS_META_TYPE_CHR:
127
0
      return "Character Device";
128
129
0
    case TSK_FS_META_TYPE_DIR:
130
0
      return "Directory";
131
132
0
    case TSK_FS_META_TYPE_BLK:
133
0
      return "Block Device";
134
135
0
    case TSK_FS_META_TYPE_REG:
136
0
      return "Regular File";
137
138
0
    case TSK_FS_META_TYPE_LNK:
139
0
      return "Link";
140
141
0
    case TSK_FS_META_TYPE_SOCK:
142
0
      return "Socket";
143
144
0
    case TSK_FS_META_TYPE_WHT:
145
0
      return "Whiteout";
146
0
    default:
147
0
      return "Unknown";
148
0
  }
149
0
}
150
151
0
static const char* attr_type_name(uint32_t typeNum) noexcept {
152
0
  switch (typeNum) {
153
0
    case TSK_FS_ATTR_TYPE_DEFAULT:
154
0
      return "DFLT";
155
0
    case TSK_FS_ATTR_TYPE_APFS_DATA:
156
0
      return "DATA";
157
0
    case TSK_FS_ATTR_TYPE_APFS_EXT_ATTR:
158
0
      return "ExATTR";
159
0
    case TSK_FS_ATTR_TYPE_APFS_COMP_REC:
160
0
      return "CMPF";
161
0
    case TSK_FS_ATTR_TYPE_APFS_RSRC:
162
0
      return "RSRC";
163
0
    default:
164
0
      return "UNKN";
165
0
  }
166
0
}
167
168
APFSFSCompat::APFSFSCompat(TSK_IMG_INFO* img_info, const TSK_POOL_INFO* pool_info,
169
                           apfs_block_num vol_block, const char* pass)
170
2
    : APFSJObjTree(APFSFileSystem{to_pool(pool_info), vol_block, pass}) {
171
2
  const auto& pool = to_pool(pool_info);
172
173
2
  const APFSFileSystem vol{pool, vol_block};
174
175
2
  _fsinfo.tag = TSK_FS_INFO_TAG;
176
2
  _fsinfo.root_inum = APFS_ROOT_INODE_NUM;
177
2
  _fsinfo.ftype = TSK_FS_TYPE_APFS;
178
2
  _fsinfo.duname = "Block";
179
2
  _fsinfo.flags = TSK_FS_INFO_FLAG_HAVE_NANOSEC;
180
181
2
  if (vol.encrypted()) {
182
0
    _fsinfo.flags |= TSK_FS_INFO_FLAG_ENCRYPTED;
183
0
  }
184
185
2
  _fsinfo.img_info = img_info;
186
2
  _fsinfo.offset = pool.first_img_offset();
187
2
  _fsinfo.block_count = vol.alloc_blocks();
188
2
  _fsinfo.block_size = pool.block_size();
189
2
  _fsinfo.dev_bsize = pool.dev_block_size();
190
2
  _fsinfo.first_block = 0;
191
2
  _fsinfo.last_block = pool.num_blocks() - 1;
192
2
  _fsinfo.last_block_act = pool.num_blocks() - 1;
193
2
  _fsinfo.first_inum = APFS_ROOT_INODE_NUM;
194
2
  _fsinfo.last_inum = vol.last_inum();
195
196
  // Locks
197
2
  tsk_init_lock(&_fsinfo.list_inum_named_lock);
198
2
  tsk_init_lock(&_fsinfo.orphan_dir_lock);
199
200
  // Callbacks
201
2
  _fsinfo.block_walk = [](TSK_FS_INFO * fs, TSK_DADDR_T start, TSK_DADDR_T end,
202
2
                          TSK_FS_BLOCK_WALK_FLAG_ENUM flags, TSK_FS_BLOCK_WALK_CB cb,
203
2
                          void *ptr) {
204
0
      return to_fs(fs).block_walk(fs, start, end, flags, cb, ptr);
205
0
  };
206
207
2
  _fsinfo.block_getflags = [](TSK_FS_INFO* a_fs, TSK_DADDR_T a_addr) {
208
0
      return to_fs(a_fs).block_getflags(a_fs, a_addr);
209
0
  };
210
211
2
  _fsinfo.inode_walk = [](TSK_FS_INFO* fs, TSK_INUM_T start_inum, TSK_INUM_T end_inum,
212
2
                          TSK_FS_META_FLAG_ENUM flags, TSK_FS_META_WALK_CB action,
213
2
                          void* ptr) {
214
0
      return to_fs(fs).inode_walk(fs, start_inum, end_inum, flags, action, ptr);
215
0
  };
216
217
2
  _fsinfo.file_add_meta = [](TSK_FS_INFO* fs, TSK_FS_FILE* fs_file,
218
2
                             TSK_INUM_T addr) {
219
1
    return to_fs(fs).file_add_meta(fs_file, addr);
220
1
  };
221
222
2
  _fsinfo.istat = [](TSK_FS_INFO* fs, TSK_FS_ISTAT_FLAG_ENUM flags, FILE* hFile,
223
2
                     TSK_INUM_T inode_num, TSK_DADDR_T numblock,
224
2
                     int32_t sec_skew) {
225
0
    return to_fs(fs).istat(flags, hFile, inode_num, numblock, sec_skew);
226
0
  };
227
228
2
  _fsinfo.dir_open_meta = [](TSK_FS_INFO* fs, TSK_FS_DIR** a_fs_dir,
229
2
                             TSK_INUM_T inode, int recursion_depth) {
230
1
    return to_fs(fs).dir_open_meta(a_fs_dir, inode, recursion_depth);
231
1
  };
232
233
2
  _fsinfo.fscheck = [](TSK_FS_INFO*, FILE*) {
234
0
    return unsupported_function("fscheck");
235
0
  };
236
237
2
  _fsinfo.fsstat = [](TSK_FS_INFO* fs, FILE* hFile) {
238
0
    return to_fs(fs).fsstat(hFile);
239
0
  };
240
241
2
  _fsinfo.close = [](TSK_FS_INFO* fs) {
242
1
    delete static_cast<APFSFSCompat*>(fs->impl);
243
1
  };
244
245
2
  _fsinfo.decrypt_block = [](TSK_FS_INFO* fs, TSK_DADDR_T block_num,
246
2
                             void* data) {
247
0
    return to_fs(fs).decrypt_block(block_num, data);
248
0
  };
249
250
2
  _fsinfo.get_default_attr_type = [](const TSK_FS_FILE*) {
251
0
    return TSK_FS_ATTR_TYPE_APFS_DATA;
252
0
  };
253
254
2
  _fsinfo.load_attrs = [](TSK_FS_FILE* file) {
255
0
    return to_fs(file->fs_info).load_attrs(file);
256
0
  };
257
258
2
  _fsinfo.name_cmp = [](TSK_FS_INFO* fs, const char* s1, const char* s2) {
259
0
    return to_fs(fs).name_cmp(s1, s2);
260
0
  };
261
262
2
  _fsinfo.impl = this;
263
2
}
264
265
0
uint8_t APFSFSCompat::fsstat(FILE* hFile) const noexcept try {
266
0
  const auto& pool = fs_info_to_pool(&_fsinfo);
267
#ifdef HAVE_LIBCRYPTO
268
  APFSFileSystem vol{pool, to_pool_vol_block(&_fsinfo), _crypto.password};
269
#else
270
0
  APFSFileSystem vol{ pool, to_pool_vol_block(&_fsinfo) };
271
0
#endif
272
273
0
  tsk_fprintf(hFile, "FILE SYSTEM INFORMATION\n");
274
0
  tsk_fprintf(hFile, "--------------------------------------------\n");
275
0
  tsk_fprintf(hFile, "File System Type: APFS\n");
276
277
0
  tsk_fprintf(hFile, "Volume UUID %s\n", vol.uuid().str().c_str());
278
279
0
  const auto role = [&] {
280
0
    switch (vol.role()) {
281
0
      case APFS_VOLUME_ROLE_NONE:
282
0
        return "No specific role";
283
0
      case APFS_VOLUME_ROLE_SYSTEM:
284
0
        return "System";
285
0
      case APFS_VOLUME_ROLE_USER:
286
0
        return "User";
287
0
      case APFS_VOLUME_ROLE_RECOVERY:
288
0
        return "Recovery";
289
0
      case APFS_VOLUME_ROLE_VM:
290
0
        return "VM";
291
0
      case APFS_VOLUME_ROLE_PREBOOT:
292
0
        return "Preboot";
293
0
    }
294
295
0
    return "Unknown";
296
0
  }();
297
0
  tsk_fprintf(hFile, "APSB Block Number: %llu\n", vol.block_num());
298
0
  tsk_fprintf(hFile, "APSB oid: %llu\n", vol.oid());
299
0
  tsk_fprintf(hFile, "APSB xid: %llu\n", vol.xid());
300
0
  tsk_fprintf(hFile, "Name (Role): %s (%s)\n", vol.name().c_str(), role);
301
302
0
  tsk_fprintf(hFile, "Capacity Consumed: %lld B\n", vol.used());
303
304
0
  tsk_fprintf(hFile, "Capacity Reserved: ");
305
0
  if (vol.reserved() != 0) {
306
0
    tsk_fprintf(hFile, "%lld B\n", vol.reserved());
307
0
  } else {
308
0
    tsk_fprintf(hFile, "None\n");
309
0
  }
310
311
0
  tsk_fprintf(hFile, "Capacity Quota: ");
312
0
  if (vol.quota() != 0) {
313
0
    tsk_fprintf(hFile, "%lld B\n", vol.quota());
314
0
  } else {
315
0
    tsk_fprintf(hFile, "None\n");
316
0
  }
317
0
  tsk_fprintf(hFile, "Case Sensitive: %s\n",
318
0
              vol.case_sensitive() ? "Yes" : "No");
319
0
  tsk_fprintf(hFile, "Encrypted: %s%s\n", vol.encrypted() ? "Yes" : "No",
320
0
              (vol.encrypted() && pool.hardware_crypto())
321
0
                  ? " (hardware assisted)"
322
0
                  : "");
323
0
  tsk_fprintf(hFile, "Formatted by: %s\n", vol.formatted_by().c_str());
324
0
  tsk_fprintf(hFile, "\n");
325
326
0
  char time_buf[1024];
327
0
  tsk_fprintf(hFile, "Created: %s\n",
328
0
              tsk_fs_time_to_str_subsecs(vol.created() / 1000000000,
329
0
                                         vol.created() % 1000000000, time_buf));
330
0
  tsk_fprintf(hFile, "Changed: %s\n",
331
0
              tsk_fs_time_to_str_subsecs(vol.changed() / 1000000000,
332
0
                                         vol.changed() % 1000000000, time_buf));
333
334
0
  if (vol.encrypted() && !pool.hardware_crypto()) {
335
0
    tsk_fprintf(hFile, "\n");
336
0
    tsk_fprintf(hFile, "Encryption Info\n");
337
0
    tsk_fprintf(hFile, "---------------\n");
338
339
0
    const auto crypto = vol.crypto_info();
340
341
0
    if (crypto.unlocked) {
342
0
      tsk_fprintf(hFile, "Password: %s\n", crypto.password.c_str());
343
0
    }
344
0
    tsk_fprintf(hFile, "Password Hint: %s\n", crypto.password_hint.c_str());
345
346
0
    for (const auto& kek : crypto.wrapped_keks) {
347
0
      tsk_fprintf(hFile, "KEK (%s):", kek.uuid.str().c_str());
348
0
      for (auto i = 0U; i < sizeof(kek.data); i++) {
349
0
        if (i % 8 == 0) {
350
0
          tsk_fprintf(hFile, "\n   ");
351
0
        }
352
0
        tsk_fprintf(hFile, " %2.2X", kek.data[i]);
353
0
      }
354
0
      tsk_fprintf(hFile, "\n\n");
355
356
0
      tsk_fprintf(hFile, "    Salt:");
357
0
      for (auto i = 0U; i < sizeof(kek.salt); i++) {
358
0
        tsk_fprintf(hFile, " %2.2X", kek.salt[i]);
359
0
      }
360
0
      tsk_fprintf(hFile, "\n\n");
361
362
0
      tsk_fprintf(hFile, "    Iterations: %lld\n\n", kek.iterations);
363
0
    }
364
365
0
    tsk_fprintf(hFile, "Wrapped VEK:");
366
0
    for (auto i = 0U; i < sizeof(crypto.wrapped_vek); i++) {
367
0
      if (i % 8 == 0 && i != 0) {
368
0
        tsk_fprintf(hFile, "\n            ");
369
0
      }
370
0
      tsk_fprintf(hFile, " %2.2X", crypto.wrapped_vek[i]);
371
0
    }
372
0
    tsk_fprintf(hFile, "\n\n");
373
374
0
    if (crypto.unlocked) {
375
0
      tsk_fprintf(hFile, "VEK (AES-XTS-128):");
376
0
      for (auto i = 0U; i < sizeof(crypto.vek); i++) {
377
0
        if (i % 16 == 0 && i != 0) {
378
0
          tsk_fprintf(hFile, "\n                  ");
379
0
        }
380
0
        tsk_fprintf(hFile, " %2.2X", crypto.vek[i]);
381
0
      }
382
0
      tsk_fprintf(hFile, "\n\n");
383
0
    }
384
0
  }
385
386
0
  const auto snapshots = vol.snapshots();
387
0
  if (!snapshots.empty()) {
388
0
    tsk_fprintf(hFile, "\n");
389
0
    tsk_fprintf(hFile, "Snapshots\n");
390
0
    tsk_fprintf(hFile, "---------\n");
391
0
    for (const auto& snapshot : snapshots) {
392
0
      tsk_fprintf(
393
0
          hFile, "[%lld] %s %s %s\n", snapshot.snap_xid,
394
0
          tsk_fs_time_to_str_subsecs(snapshot.timestamp / 1000000000,
395
0
                                     snapshot.timestamp % 1000000000, time_buf),
396
0
          snapshot.name.c_str(), (snapshot.dataless) ? "(dataless)" : "");
397
0
    }
398
0
  }
399
400
0
  const auto unmount_log = vol.unmount_log();
401
0
  if (!unmount_log.empty()) {
402
0
    tsk_fprintf(hFile, "\n");
403
0
    tsk_fprintf(hFile, "Unmount Logs\n");
404
0
    tsk_fprintf(hFile, "------------\n");
405
0
    tsk_fprintf(hFile, "Timestamp                            Log String\n");
406
0
    for (const auto& log : unmount_log) {
407
0
      tsk_fprintf(
408
0
          hFile, "%s  %s\n",
409
0
          tsk_fs_time_to_str_subsecs(log.timestamp / 1000000000,
410
0
                                     log.timestamp % 1000000000, time_buf),
411
0
          log.logstr.c_str());
412
0
    }
413
0
  }
414
415
0
  return 0;
416
0
} catch (const std::exception& e) {
417
0
  tsk_error_reset();
418
0
  tsk_error_set_errno(TSK_ERR_FS_GENFS);
419
0
  tsk_error_set_errstr("%s", e.what());
420
0
  return 1;
421
0
}
422
423
0
uint8_t tsk_apfs_fsstat(TSK_FS_INFO* fs_info, apfs_fsstat_info* info) try {
424
0
  if (fs_info == nullptr) {
425
0
    tsk_error_reset();
426
0
    tsk_error_set_errno(TSK_ERR_FS_ARG);
427
0
    tsk_error_set_errstr("tsk_apfs_fsstat: Null fs_info");
428
0
    return 1;
429
0
  }
430
431
0
  if (info == nullptr) {
432
0
    tsk_error_reset();
433
0
    tsk_error_set_errno(TSK_ERR_FS_ARG);
434
0
    tsk_error_set_errstr("tsk_apfs_fsstat: Null info");
435
0
    return 1;
436
0
  }
437
438
0
  const APFSFileSystem vol{fs_info_to_pool(fs_info), to_pool_vol_block(fs_info)};
439
440
0
  memset(info, 0, sizeof(*info));
441
442
0
  strncpy(info->name, vol.name().c_str(), sizeof(info->name) - 1);
443
0
  memcpy(info->uuid, vol.uuid().bytes().data(), 16);
444
0
  strncpy(info->password_hint, vol.password_hint().c_str(),
445
0
          sizeof(info->password_hint) - 1);
446
0
  strncpy(info->formatted_by, vol.formatted_by().c_str(),
447
0
          sizeof(info->formatted_by) - 1);
448
449
0
  info->apsb_block_num = vol.block_num();
450
0
  info->apsb_oid = vol.oid();
451
0
  info->apsb_xid = vol.xid();
452
0
  info->capacity_consumed = vol.used();
453
0
  info->capacity_reserved = vol.reserved();
454
0
  info->capacity_quota = vol.quota();
455
0
  info->created = vol.created();
456
0
  info->changed = vol.changed();
457
458
0
  const auto unmount_log = vol.unmount_log();
459
0
  auto i = 0;
460
0
  for (const auto& log : unmount_log) {
461
0
    auto& l = info->unmount_logs[i++];
462
0
    strncpy(l.kext_ver_str, log.logstr.c_str(), sizeof(l.kext_ver_str));
463
0
    l.timestamp = log.timestamp;
464
0
    l.last_xid = log.last_xid;
465
0
  }
466
467
0
  info->role = vol.role();
468
0
  info->case_sensitive = vol.case_sensitive();
469
0
  info->encrypted = vol.encrypted();
470
471
0
  return 0;
472
473
0
} catch (const std::exception& e) {
474
0
  tsk_error_reset();
475
0
  tsk_error_set_errno(TSK_ERR_FS_GENFS);
476
0
  tsk_error_set_errstr("%s", e.what());
477
0
  return 1;
478
0
}
479
480
TSK_RETVAL_ENUM APFSFSCompat::dir_open_meta(
481
  TSK_FS_DIR** a_fs_dir,
482
  TSK_INUM_T inode_num,
483
  [[maybe_unused]] int recursion_depth
484
1
) const noexcept try {
485
  // Sanity checks
486
1
  if (a_fs_dir == NULL) {
487
0
    tsk_error_reset();
488
0
    tsk_error_set_errno(TSK_ERR_FS_ARG);
489
0
    tsk_error_set_errstr("APFS dir_open_meta: NULL fs_attr argument given");
490
0
    return TSK_ERR;
491
0
  }
492
493
1
  if (tsk_verbose) {
494
0
    tsk_fprintf(stderr,
495
0
                "APFS dir_open_meta: Processing directory %" PRIuINUM "\n",
496
0
                inode_num);
497
0
  }
498
499
1
  auto fs_dir = *a_fs_dir;
500
1
  if (fs_dir != nullptr) {
501
0
    tsk_fs_dir_reset(fs_dir);
502
0
    fs_dir->addr = inode_num;
503
1
  } else {
504
1
    *a_fs_dir = fs_dir = tsk_fs_dir_alloc(&_fsinfo, inode_num, 128);
505
1
  }
506
507
1
  if (fs_dir == nullptr) {
508
0
    return TSK_ERR;
509
0
  }
510
511
1
  fs_dir->fs_file = tsk_fs_file_open_meta(&_fsinfo, nullptr, inode_num);
512
1
  if (fs_dir->fs_file == nullptr) {
513
1
    tsk_error_reset();
514
1
    tsk_error_set_errno(TSK_ERR_FS_INODE_NUM);
515
1
    tsk_error_set_errstr(
516
1
        "APFS dir_open_meta: %" PRIuINUM " is not a valid inode", inode_num);
517
1
    return TSK_COR;
518
1
  }
519
520
0
  const auto inode_ptr =
521
0
      static_cast<APFSJObject*>(fs_dir->fs_file->meta->content_ptr);
522
0
  if (!inode_ptr->valid()) {
523
0
    tsk_error_reset();
524
0
    tsk_error_set_errno(TSK_ERR_FS_INODE_NUM);
525
0
    tsk_error_set_errstr("APFS dir_open_meta: inode_num is not valid %" PRIuINUM
526
0
                         "\n",
527
0
                         inode_num);
528
0
    return TSK_COR;
529
0
  }
530
531
0
  for (const auto& child : inode_ptr->children()) {
532
0
    auto fs_name = tsk_fs_name_alloc(child.name.length(), 0);
533
0
    if (fs_name == nullptr) {
534
0
      return TSK_ERR;
535
0
    }
536
537
0
    const auto type =
538
0
        bitfield_value(child.rec.type_and_flags, APFS_DIR_RECORD_TYPE_BITS,
539
0
                       APFS_DIR_RECORD_TYPE_SHIFT);
540
541
0
    strncpy(fs_name->name, child.name.c_str(), fs_name->name_size);
542
0
    fs_name->meta_addr = child.rec.file_id;
543
0
    fs_name->type = to_name_type(APFS_ITEM_TYPE(type));
544
0
    fs_name->flags = TSK_FS_NAME_FLAG_ALLOC;
545
0
    fs_name->date_added = child.rec.date_added;
546
547
0
    if (tsk_fs_dir_add(fs_dir, fs_name)) {
548
0
      tsk_fs_name_free(fs_name);
549
0
      return TSK_ERR;
550
0
    }
551
552
0
    tsk_fs_name_free(fs_name);
553
0
  }
554
555
0
  return TSK_OK;
556
0
} catch (const std::exception& e) {
557
0
  tsk_error_reset();
558
0
  tsk_error_set_errno(TSK_ERR_FS_GENFS);
559
0
  tsk_error_set_errstr("%s", e.what());
560
0
  return TSK_ERR;
561
0
}
562
563
uint8_t APFSFSCompat::inode_walk(TSK_FS_INFO* fs, TSK_INUM_T start_inum, TSK_INUM_T end_inum,
564
0
    TSK_FS_META_FLAG_ENUM flags, TSK_FS_META_WALK_CB action, void* ptr) {
565
566
0
    TSK_INUM_T inum;
567
568
0
    if (end_inum < start_inum) {
569
0
        tsk_error_reset();
570
0
        tsk_error_set_errno(TSK_ERR_FS_WALK_RNG);
571
0
        tsk_error_set_errstr("inode_walk: end object id must be >= start object id: "
572
0
            "%" PRIuINUM " must be >= %" PRIuINUM "",
573
0
            end_inum, start_inum);
574
0
        return 1;
575
0
    }
576
577
0
    if (flags & TSK_FS_META_FLAG_ORPHAN) {
578
0
        if (tsk_verbose) {
579
0
            tsk_fprintf(stderr, "inode_walk: ORPHAN flag unsupported by AFPS");
580
0
        }
581
0
    }
582
583
0
    if (((flags & TSK_FS_META_FLAG_ALLOC) == 0) &&
584
0
        ((flags & TSK_FS_META_FLAG_UNALLOC) == 0)) {
585
0
        flags = (TSK_FS_META_FLAG_ENUM)(flags | TSK_FS_META_FLAG_ALLOC | TSK_FS_META_FLAG_UNALLOC);
586
0
    }
587
588
    /* If neither of the USED or UNUSED flags are set, then set them both
589
    */
590
0
    if (((flags & TSK_FS_META_FLAG_USED) == 0) &&
591
0
        ((flags & TSK_FS_META_FLAG_UNUSED) == 0)) {
592
0
        flags = (TSK_FS_META_FLAG_ENUM)(flags | TSK_FS_META_FLAG_USED | TSK_FS_META_FLAG_UNUSED);
593
0
    }
594
595
0
    std::unique_ptr<TSK_FS_FILE, decltype(&tsk_fs_file_close)> fs_file{
596
0
        tsk_fs_file_alloc(fs),
597
0
        tsk_fs_file_close
598
0
    };
599
600
0
    if (!fs_file)
601
0
        return 1;
602
0
    if ((fs_file->meta =
603
0
        tsk_fs_meta_alloc(sizeof(APFSJObject))) == NULL)
604
0
        return 1;
605
606
0
    for (inum = start_inum; inum < end_inum; inum++) {
607
0
        int result = fs->file_add_meta(fs, fs_file.get(), inum);
608
0
        if (result == TSK_OK) {
609
0
            if ((fs_file->meta->flags & flags) == fs_file->meta->flags) {
610
0
                int retval = action(fs_file.get(), ptr);
611
0
                if (retval == TSK_WALK_STOP) {
612
0
                    return 0;
613
0
                }
614
0
                else if (retval == TSK_WALK_ERROR) {
615
0
                    return 1;
616
0
                }
617
0
            }
618
0
        }
619
0
    }
620
621
0
    return TSK_OK;
622
0
}
623
624
uint8_t APFSFSCompat::file_add_meta(TSK_FS_FILE* fs_file, TSK_INUM_T addr) const
625
1
    noexcept try {
626
1
  if (fs_file == nullptr) {
627
0
    tsk_error_reset();
628
0
    tsk_error_set_errno(TSK_ERR_FS_ARG);
629
0
    tsk_error_set_errstr("APFS file_add_meta: NULL fs_file given");
630
0
    return 1;
631
0
  }
632
633
  /* Allocate or reset the TSK_FS_META struct. */
634
1
  if (fs_file->meta == nullptr) {
635
1
    if ((fs_file->meta = tsk_fs_meta_alloc(sizeof(APFSJObject))) == nullptr) {
636
0
      return 1;
637
0
    }
638
1
  } else {
639
0
    tsk_fs_meta_reset(fs_file->meta);
640
0
  }
641
642
1
  fs_file->meta->attr_state = TSK_FS_META_ATTR_EMPTY;
643
644
1
  fs_file->meta->reset_content = [](void* content_ptr) {
645
    // Destruct the APFSJObject
646
1
    static_cast<APFSJObject*>(content_ptr)->~APFSJObject();
647
1
  };
648
649
1
  auto inode_ptr = static_cast<APFSJObject*>(fs_file->meta->content_ptr);
650
651
1
  new (inode_ptr) APFSJObject(obj(addr));
652
1
  if (!inode_ptr->valid()) {
653
1
    tsk_error_reset();
654
1
    tsk_error_set_errno(TSK_ERR_FS_INODE_NUM);
655
1
    tsk_error_set_errstr(
656
1
        "APFS file_add_meta: inode_num is not valid %" PRIuINUM "\n", addr);
657
1
    return 1;
658
1
  }
659
660
0
  const auto inode_meta = inode_ptr->inode();
661
662
0
  const auto mode = bitfield_value(inode_meta.mode_and_type,
663
0
                                   APFS_INODE_MODE_BITS, APFS_INODE_MODE_SHIFT);
664
0
  const auto type = bitfield_value(inode_meta.mode_and_type,
665
0
                                   APFS_INODE_TYPE_BITS, APFS_INODE_TYPE_SHIFT);
666
667
0
  fs_file->meta->flags = TSK_FS_META_FLAG_ALLOC;
668
0
  fs_file->meta->addr = addr;
669
0
  fs_file->meta->type = to_meta_type(APFS_ITEM_TYPE(type));
670
0
  fs_file->meta->mode = TSK_FS_META_MODE_ENUM(mode);
671
0
  fs_file->meta->nlink = inode_meta.nlink;
672
0
  fs_file->meta->size = inode_ptr->size();
673
0
  fs_file->meta->uid = inode_meta.owner;
674
0
  fs_file->meta->gid = inode_meta.group;
675
676
0
  fs_file->meta->mtime = inode_meta.modified_time / 1000000000;
677
0
  fs_file->meta->mtime_nano = inode_meta.modified_time % 1000000000;
678
0
  fs_file->meta->atime = inode_meta.accessed_time / 1000000000;
679
0
  fs_file->meta->atime_nano = inode_meta.accessed_time % 1000000000;
680
0
  fs_file->meta->ctime = inode_meta.changed_time / 1000000000;
681
0
  fs_file->meta->ctime_nano = inode_meta.changed_time % 1000000000;
682
0
  fs_file->meta->crtime = inode_meta.create_time / 1000000000;
683
0
  fs_file->meta->crtime_nano = inode_meta.create_time % 1000000000;
684
685
  // For symlinks we need to read a special exattr to get the link
686
0
  if (fs_file->meta->type == TSK_FS_META_TYPE_LNK) {
687
0
    const auto num_attrs = tsk_fs_file_attr_getsize(fs_file);
688
0
    for (int i = 0; i < num_attrs; i++) {
689
0
      const auto attr = tsk_fs_file_attr_get_idx(fs_file, i);
690
0
      if (attr->type == TSK_FS_ATTR_TYPE_APFS_EXT_ATTR &&
691
0
          attr->name != NULL &&
692
0
          strcmp(attr->name, APFS_XATTR_NAME_SYMLINK) == 0) {
693
        // We've found our symlink attribute
694
0
        fs_file->meta->link = (char*)tsk_malloc(attr->size + 1);
695
0
        tsk_fs_attr_read(attr, (TSK_OFF_T)0, fs_file->meta->link, attr->size,
696
0
                         TSK_FS_FILE_READ_FLAG_NONE);
697
0
        if (fs_file->meta->link != NULL) {
698
0
            fs_file->meta->link[attr->size] = 0;
699
0
        }
700
0
        break;
701
0
      }
702
0
    }
703
0
  }
704
705
0
  return 0;
706
1
} catch (const std::exception& e) {
707
0
  tsk_error_reset();
708
0
  tsk_error_set_errno(TSK_ERR_FS_GENFS);
709
0
  tsk_error_set_errstr("%s", e.what());
710
0
  return 1;
711
0
}
712
713
0
uint8_t APFSFSCompat::load_attrs(TSK_FS_FILE* file) const noexcept try {
714
0
  auto fs_meta = file->meta;
715
716
  /* Check for an already populated attribute list, since a lazy strategy
717
   * is used to fill in attributes. If the attribute list is not yet
718
   * allocated, do so now. */
719
0
  if ((fs_meta->attr != nullptr) &&
720
0
      (fs_meta->attr_state == TSK_FS_META_ATTR_STUDIED)) {
721
0
    return 0;
722
0
  } else if (fs_meta->attr_state == TSK_FS_META_ATTR_ERROR) {
723
0
    return 1;
724
0
  }
725
726
0
  if (fs_meta->attr != nullptr) {
727
0
    tsk_fs_attrlist_markunused(fs_meta->attr);
728
0
  } else {
729
0
    fs_meta->attr = tsk_fs_attrlist_alloc();
730
0
  }
731
732
  // Load non-resident extents
733
0
  auto jobj = static_cast<APFSJObject*>(file->meta->content_ptr);
734
735
  // Default Attribute
736
0
  if (!jobj->extents().empty()) {
737
0
    auto fs_attr = tsk_fs_attrlist_getnew(fs_meta->attr, TSK_FS_ATTR_NONRES);
738
0
    if (fs_attr == nullptr) {
739
0
      fs_meta->attr_state = TSK_FS_META_ATTR_ERROR;
740
0
      return 1;
741
0
    }
742
743
0
    TSK_FS_ATTR_RUN* data_run_head = nullptr;
744
0
    TSK_FS_ATTR_RUN* data_run_last = nullptr;
745
746
    // Create the runs
747
0
    for (const auto& extent : jobj->extents()) {
748
0
      auto data_run = tsk_fs_attr_run_alloc();
749
0
      if (data_run == nullptr) {
750
0
        fs_meta->attr_state = TSK_FS_META_ATTR_ERROR;
751
0
        tsk_fs_attr_run_free(data_run_head);
752
0
        return 1;
753
0
      }
754
755
0
      data_run->addr = extent.phys;
756
0
      data_run->offset = extent.offset / _fsinfo.block_size;
757
0
      data_run->len = extent.len / _fsinfo.block_size;
758
0
      data_run->crypto_id = extent.crypto_id;
759
0
      data_run->flags = TSK_FS_ATTR_RUN_FLAG_NONE;
760
0
      data_run->next = nullptr;
761
762
0
      if (extent.phys == 0) {
763
0
        data_run->flags |= TSK_FS_ATTR_RUN_FLAG_SPARSE;
764
0
      }
765
766
0
      if (extent.crypto_id != 0) {
767
0
        data_run->flags |= TSK_FS_ATTR_RUN_FLAG_ENCRYPTED;
768
0
      }
769
770
0
      if (data_run_head == nullptr) {
771
0
        data_run_head = data_run;
772
0
      } else {
773
0
        data_run_last->next = data_run;
774
0
      }
775
776
0
      data_run_last = data_run;
777
0
    }
778
779
    // initialize the data run
780
0
    if (tsk_fs_attr_set_run(file, fs_attr, data_run_head, "",
781
0
                            TSK_FS_ATTR_TYPE_APFS_DATA, TSK_FS_ATTR_ID_DEFAULT,
782
0
                            fs_meta->size, fs_meta->size, jobj->size_on_disk(),
783
0
                            TSK_FS_ATTR_NONRES, 0)) {
784
0
      fs_meta->attr_state = TSK_FS_META_ATTR_ERROR;
785
0
      tsk_fs_attr_run_free(data_run_head);
786
0
      return 1;
787
0
    }
788
0
  } else if (jobj->is_clone()) {
789
0
    const auto clone = obj(jobj->inode().private_id);
790
791
    // We've got to add the cloned extents
792
0
    if (!clone.extents().empty()) {
793
0
      auto fs_attr = tsk_fs_attrlist_getnew(fs_meta->attr, TSK_FS_ATTR_NONRES);
794
0
      if (fs_attr == nullptr) {
795
0
        fs_meta->attr_state = TSK_FS_META_ATTR_ERROR;
796
0
        return 1;
797
0
      }
798
799
0
      TSK_FS_ATTR_RUN* data_run_head = nullptr;
800
0
      TSK_FS_ATTR_RUN* data_run_last = nullptr;
801
802
      // Create the runs
803
0
      for (const auto& extent : clone.extents()) {
804
0
        auto data_run = tsk_fs_attr_run_alloc();
805
0
        if (data_run == nullptr) {
806
0
          fs_meta->attr_state = TSK_FS_META_ATTR_ERROR;
807
0
          tsk_fs_attr_run_free(data_run_head);
808
0
          return 1;
809
0
        }
810
811
0
        data_run->addr = extent.phys;
812
0
        data_run->offset = extent.offset / _fsinfo.block_size;
813
0
        data_run->len = extent.len / _fsinfo.block_size;
814
0
        data_run->crypto_id = extent.crypto_id;
815
0
        data_run->flags = TSK_FS_ATTR_RUN_FLAG_NONE;
816
0
        data_run->next = nullptr;
817
818
0
        if (extent.phys == 0) {
819
0
          data_run->flags |= TSK_FS_ATTR_RUN_FLAG_SPARSE;
820
0
        }
821
822
0
        if (extent.crypto_id != 0) {
823
0
          data_run->flags |= TSK_FS_ATTR_RUN_FLAG_ENCRYPTED;
824
0
        }
825
826
0
        if (data_run_head == nullptr) {
827
0
          data_run_head = data_run;
828
0
        } else {
829
0
          data_run_last->next = data_run;
830
0
        }
831
832
0
        data_run_last = data_run;
833
0
      }
834
835
      // initialize the data run
836
0
      if (tsk_fs_attr_set_run(
837
0
              file, fs_attr, data_run_head, "", TSK_FS_ATTR_TYPE_APFS_DATA,
838
0
              TSK_FS_ATTR_ID_DEFAULT, fs_meta->size, fs_meta->size,
839
0
              jobj->size_on_disk(), TSK_FS_ATTR_NONRES, 0)) {
840
0
        fs_meta->attr_state = TSK_FS_META_ATTR_ERROR;
841
0
        tsk_fs_attr_run_free(data_run_head);
842
0
        return 1;
843
0
      }
844
0
    }
845
0
  }
846
847
0
  uint16_t attribute_counter = TSK_FS_ATTR_ID_DEFAULT + 1;
848
0
  const TSK_FS_ATTR* decmpfs_attr = nullptr;
849
0
  TSK_FS_ATTR_RUN* rsrc_runs = nullptr;
850
851
  // Inline extended attributes
852
0
  for (const auto& xattr : jobj->inline_xattrs()) {
853
0
    auto fs_attr = tsk_fs_attrlist_getnew(fs_meta->attr, TSK_FS_ATTR_RES);
854
0
    if (fs_attr == nullptr) {
855
0
      fs_meta->attr_state = TSK_FS_META_ATTR_ERROR;
856
0
      return 1;
857
0
    }
858
859
0
    const auto type = xattribute_type(xattr.name);
860
861
0
    if (type == TSK_FS_ATTR_TYPE_APFS_COMP_REC) {
862
0
      fs_meta->flags |= TSK_FS_META_FLAG_COMP;
863
0
      decmpfs_attr = fs_attr;
864
0
    }
865
866
    // set the details in the fs_attr structure
867
0
    if (tsk_fs_attr_set_str(file, fs_attr, xattr.name.c_str(), type,
868
0
                            attribute_counter++, (void*)xattr.data.c_str(),
869
0
                            xattr.data.length())) {
870
0
      fs_meta->attr_state = TSK_FS_META_ATTR_ERROR;
871
0
      return 1;
872
0
    }
873
0
  }
874
875
  // Non-Resident extended attributes
876
0
  for (const auto& xattr : jobj->nonres_xattrs()) {
877
0
    const auto xobj = obj(xattr.oid);
878
879
0
    if (!xobj.valid()) {
880
0
      fs_meta->attr_state = TSK_FS_META_ATTR_ERROR;
881
0
      if (tsk_verbose) {
882
0
        tsk_fprintf(
883
0
            stderr,
884
0
            "Error loading non-resident attribute %s with oid %" PRIuINUM "\n",
885
0
            xattr.name.c_str(), xattr.oid);
886
0
      }
887
0
      continue;
888
0
    }
889
890
0
    auto fs_attr = tsk_fs_attrlist_getnew(fs_meta->attr, TSK_FS_ATTR_NONRES);
891
0
    if (fs_attr == nullptr) {
892
0
      fs_meta->attr_state = TSK_FS_META_ATTR_ERROR;
893
0
      return 1;
894
0
    }
895
896
0
    TSK_FS_ATTR_RUN* data_run_head = nullptr;
897
0
    TSK_FS_ATTR_RUN* data_run_last = nullptr;
898
899
    // Create the runs
900
0
    for (const auto& extent : xobj.extents()) {
901
0
      auto data_run = tsk_fs_attr_run_alloc();
902
0
      if (data_run == nullptr) {
903
0
        fs_meta->attr_state = TSK_FS_META_ATTR_ERROR;
904
0
        tsk_fs_attr_run_free(data_run_head);
905
0
        return 1;
906
0
      }
907
908
0
      data_run->addr = extent.phys;
909
0
      data_run->offset = extent.offset / _fsinfo.block_size;
910
0
      data_run->len = extent.len / _fsinfo.block_size;
911
0
      data_run->crypto_id = extent.crypto_id;
912
0
      data_run->flags = TSK_FS_ATTR_RUN_FLAG_NONE;
913
0
      data_run->next = nullptr;
914
915
0
      if (extent.phys == 0) {
916
0
        data_run->flags |= TSK_FS_ATTR_RUN_FLAG_SPARSE;
917
0
      }
918
919
0
      if (extent.crypto_id != 0) {
920
0
        data_run->flags |= TSK_FS_ATTR_RUN_FLAG_ENCRYPTED;
921
0
      }
922
923
0
      if (data_run_head == nullptr) {
924
0
        data_run_head = data_run;
925
0
      } else {
926
0
        data_run_last->next = data_run;
927
0
      }
928
929
0
      data_run_last = data_run;
930
0
    }
931
932
0
    const auto type = xattribute_type(xattr.name);
933
934
0
    if (type == TSK_FS_ATTR_TYPE_APFS_COMP_REC) {
935
0
      decmpfs_attr = fs_attr;
936
0
      fs_meta->flags |= TSK_FS_META_FLAG_COMP;
937
0
    } else if (type == TSK_FS_ATTR_TYPE_APFS_RSRC) {
938
0
      rsrc_runs = data_run_head;
939
0
    }
940
941
    // initialize the data run
942
0
    if (tsk_fs_attr_set_run(file, fs_attr, data_run_head, xattr.name.c_str(),
943
0
                            type, attribute_counter++, xattr.size, xattr.size,
944
0
                            xattr.allocated_size, TSK_FS_ATTR_NONRES, 0)) {
945
0
      fs_meta->attr_state = TSK_FS_META_ATTR_ERROR;
946
0
      tsk_fs_attr_run_free(data_run_head);
947
0
      return 1;
948
0
    }
949
0
  }
950
951
  // Compression Stuff
952
0
  if (decmpfs_attr != nullptr) {
953
    // Read the decmpfs data
954
955
0
    if ((size_t)decmpfs_attr->size < sizeof(DECMPFS_DISK_HEADER)) {
956
0
      error_returned("APFS load_attrs: decmpfs attr is too small");
957
0
      return 1;
958
0
    }
959
960
0
    auto buffer = std::make_unique<char[]>(decmpfs_attr->size);
961
962
0
    const auto ret = tsk_fs_attr_read(decmpfs_attr, (TSK_OFF_T)0, buffer.get(),
963
0
                                      (size_t)decmpfs_attr->size,
964
0
                                      TSK_FS_FILE_READ_FLAG_NONE);
965
966
0
    if (ret == -1) {
967
0
      error_returned("APFS load_attrs: reading the compression attribute");
968
0
      return 1;
969
0
    }
970
971
0
    if (ret < decmpfs_attr->size) {
972
0
      error_detected(
973
0
          TSK_ERR_FS_READ,
974
0
          "APFS load_attrs: could not read the whole compression attribute");
975
0
      return 1;
976
0
    }
977
978
0
    const auto decmpfs_header =
979
0
        reinterpret_cast<DECMPFS_DISK_HEADER*>(buffer.get());
980
0
    const auto ct =
981
0
        tsk_getu32(TSK_LIT_ENDIAN, decmpfs_header->compression_type);
982
0
    const auto uncompressed_size =
983
0
        tsk_getu64(TSK_LIT_ENDIAN, decmpfs_header->uncompressed_size);
984
985
0
    switch (ct) {
986
        // Data is inline. We will load the uncompressed
987
        // data as a resident attribute.
988
0
      case DECMPFS_TYPE_ZLIB_ATTR:
989
0
        if (!decmpfs_file_read_zlib_attr(file, buffer.get(), decmpfs_attr->size,
990
0
                                         uncompressed_size)) {
991
0
          return 1;
992
0
        }
993
0
        break;
994
995
0
      case DECMPFS_TYPE_LZVN_ATTR:
996
0
        if (!decmpfs_file_read_lzvn_attr(file, buffer.get(), decmpfs_attr->size,
997
0
                                         uncompressed_size)) {
998
0
          return 1;
999
0
        }
1000
0
        break;
1001
0
      case DECMPFS_TYPE_ZLIB_RSRC:  // fallthrough
1002
0
      case DECMPFS_TYPE_LZVN_RSRC: {
1003
0
        if (rsrc_runs == nullptr) {
1004
0
          error_returned("No resource runs for resource-compressed data");
1005
0
          return 1;
1006
0
        }
1007
1008
0
        auto fs_attr =
1009
0
            tsk_fs_attrlist_getnew(fs_meta->attr, TSK_FS_ATTR_NONRES);
1010
0
        if (fs_attr == nullptr) {
1011
0
          fs_meta->attr_state = TSK_FS_META_ATTR_ERROR;
1012
0
          return 1;
1013
0
        }
1014
1015
0
        if (ct == DECMPFS_TYPE_ZLIB_RSRC) {
1016
0
#ifdef HAVE_LIBZ
1017
0
          fs_attr->w = decmpfs_attr_walk_zlib_rsrc;
1018
0
          fs_attr->r = decmpfs_file_read_zlib_rsrc;
1019
#else
1020
          // We don't have zlib, so the uncompressed data is not
1021
          // available to us; however, we must have a default DATA
1022
          // attribute, or icat will misbehave.
1023
          if (tsk_verbose)
1024
            tsk_fprintf(stderr,
1025
                        "APFS load_attrs: No zlib compression library, so "
1026
                        "setting a zero-length default DATA attribute.\n");
1027
1028
          if (tsk_fs_attr_set_run(file, fs_attr, NULL, "DECOMP",
1029
                                  TSK_FS_ATTR_TYPE_HFS_DATA,
1030
                                  TSK_FS_ATTR_ID_DEFAULT, 0, 0, 0, TSK_FS_ATTR_FLAG_NONE, 0)) {
1031
            error_returned(" - APFS load_attrs (non-file)");
1032
            return 1;
1033
          }
1034
#endif
1035
0
        } else if (ct == DECMPFS_TYPE_LZVN_RSRC) {
1036
0
          fs_attr->w = decmpfs_attr_walk_lzvn_rsrc;
1037
0
          fs_attr->r = decmpfs_file_read_lzvn_rsrc;
1038
0
        }
1039
1040
0
        TSK_FS_ATTR_RUN* data_run_head = nullptr;
1041
0
        TSK_FS_ATTR_RUN* data_run_last = nullptr;
1042
1043
0
        while (rsrc_runs != nullptr) {
1044
0
          auto data_run = tsk_fs_attr_run_alloc();
1045
0
          if (data_run == nullptr) {
1046
0
            fs_meta->attr_state = TSK_FS_META_ATTR_ERROR;
1047
0
            tsk_fs_attr_run_free(data_run_head);
1048
0
            return 1;
1049
0
          }
1050
1051
          // Copy the resource run
1052
0
          *data_run = *rsrc_runs;
1053
1054
0
          if (data_run_head == nullptr) {
1055
0
            data_run_head = data_run;
1056
0
          } else {
1057
0
            data_run_last->next = data_run;
1058
0
          }
1059
1060
0
          data_run_last = data_run;
1061
0
          rsrc_runs = rsrc_runs->next;
1062
0
        }
1063
1064
0
        if (tsk_fs_attr_set_run(
1065
0
                file, fs_attr, data_run_head, "DECOMP",
1066
0
                TSK_FS_ATTR_TYPE_APFS_DATA, TSK_FS_ATTR_ID_DEFAULT,
1067
0
                uncompressed_size, uncompressed_size, uncompressed_size,
1068
0
                TSK_FS_ATTR_FLAG_ENUM(TSK_FS_ATTR_COMP | TSK_FS_ATTR_NONRES),
1069
0
                0)) {
1070
0
          fs_meta->attr_state = TSK_FS_META_ATTR_ERROR;
1071
0
          tsk_fs_attr_run_free(data_run_head);
1072
0
          return 1;
1073
0
        }
1074
1075
0
        break;
1076
0
      }
1077
0
    }
1078
0
  }
1079
1080
  // Mark loaded
1081
0
  fs_meta->attr_state = TSK_FS_META_ATTR_STUDIED;
1082
1083
0
  return 0;
1084
0
} catch (const std::exception& e) {
1085
0
  tsk_error_reset();
1086
0
  tsk_error_set_errno(TSK_ERR_FS_GENFS);
1087
0
  tsk_error_set_errstr("%s", e.what());
1088
0
  return 1;
1089
0
}
1090
1091
0
#define APFS_PRINT_WIDTH   8
1092
typedef struct {
1093
    FILE *hFile;
1094
    int idx;
1095
} APFS_PRINT_ADDR;
1096
1097
static TSK_WALK_RET_ENUM
1098
print_addr_act(
1099
  [[maybe_unused]] TSK_FS_FILE * fs_file,
1100
  [[maybe_unused]] TSK_OFF_T a_off,
1101
  TSK_DADDR_T addr,
1102
  [[maybe_unused]] char *buf,
1103
  [[maybe_unused]] size_t size,
1104
  [[maybe_unused]] TSK_FS_BLOCK_FLAG_ENUM flags,
1105
  [[maybe_unused]] void *ptr)
1106
0
{
1107
0
    APFS_PRINT_ADDR *print = (APFS_PRINT_ADDR *)ptr;
1108
0
    tsk_fprintf(print->hFile, "%" PRIuDADDR " ", addr);
1109
0
    if (++(print->idx) == APFS_PRINT_WIDTH) {
1110
0
        tsk_fprintf(print->hFile, "\n");
1111
0
        print->idx = 0;
1112
0
    }
1113
1114
0
    return TSK_WALK_CONT;
1115
0
}
1116
1117
uint8_t APFSFSCompat::istat(TSK_FS_ISTAT_FLAG_ENUM istat_flags, FILE* hFile,
1118
                            TSK_INUM_T inode_num, TSK_DADDR_T numblock,
1119
0
                            int32_t sec_skew) const noexcept try {
1120
0
  tsk_error_reset();
1121
1122
0
  auto fs = &_fsinfo;
1123
0
  char buffer[128];
1124
1125
0
  if (tsk_verbose)
1126
0
    tsk_fprintf(stderr,
1127
0
                "APFS istat: inode_num: %" PRIuINUM " numblock: %" PRIu32 "\n",
1128
0
                inode_num, numblock);
1129
1130
0
  std::unique_ptr<TSK_FS_FILE, decltype(&tsk_fs_file_close)> fs_file{
1131
0
    tsk_fs_file_open_meta(fs, nullptr, inode_num),
1132
0
    tsk_fs_file_close
1133
0
  };
1134
1135
0
  if (!fs_file) {
1136
0
    error_returned("APFS istat: getting metadata for the file");
1137
0
    return 1;
1138
0
  }
1139
1140
0
  const auto jobj = static_cast<APFSJObject*>(fs_file->meta->content_ptr);
1141
1142
0
  tsk_fprintf(hFile, "INode Number: %" PRIuINUM, inode_num);
1143
0
  if (jobj->is_clone()) {
1144
0
    tsk_fprintf(hFile, " (clone of INode %" PRIuINUM ")",
1145
0
                jobj->inode().private_id);
1146
0
  }
1147
0
  tsk_fprintf(hFile, "\n%sAllocated\n\n",
1148
0
              (fs_file->meta->flags & TSK_FS_META_FLAG_UNALLOC) ? "Not " : "");
1149
1150
0
  tsk_fprintf(hFile, "Type:\t%s\n", to_string(fs_file->meta->type));
1151
1152
0
  tsk_fs_meta_make_ls(fs_file->meta, buffer, sizeof(buffer));
1153
0
  tsk_fprintf(hFile, "Mode:\t%s\n", buffer);
1154
1155
0
  tsk_fprintf(hFile, "Size:\t%" PRIdOFF "\n", fs_file->meta->size);
1156
1157
0
  if (fs_file->meta->link) {
1158
0
    tsk_fprintf(hFile, "Symbolic link to:\t%s\n", fs_file->meta->link);
1159
0
  }
1160
1161
0
  tsk_fprintf(hFile, "owner / group: %" PRIuUID " / %" PRIuGID "\n",
1162
0
              fs_file->meta->uid, fs_file->meta->gid);
1163
1164
0
  tsk_fprintf(hFile, "%s: %d\n",
1165
0
              (fs_file->meta->type == TSK_FS_META_TYPE_DIR)
1166
0
                  ? "Number of Children"
1167
0
                  : "Number of Links",
1168
0
              fs_file->meta->nlink);
1169
1170
0
  tsk_fprintf(hFile, "\n");
1171
1172
0
  tsk_fprintf(hFile, "Filename:\t%s\n", jobj->name().c_str());
1173
1174
0
  const auto bsdflags = jobj->inode().bsdflags;
1175
1176
0
  tsk_fprintf(hFile, "BSD flags:\t0x%8.8x\n", bsdflags);
1177
1178
0
  if (bsdflags & 0xFFFF0000) {
1179
0
    tsk_fprintf(hFile, "Admin flags:\t");
1180
0
    if (bsdflags & APFS_BSD_FLAG_SF_ARCHIVED) {
1181
0
      tsk_fprintf(hFile, "archived ");
1182
0
    }
1183
0
    if (bsdflags & APFS_BSD_FLAG_SF_IMMUTABLE) {
1184
0
      tsk_fprintf(hFile, "immutable ");
1185
0
    }
1186
0
    if (bsdflags & APFS_BSD_FLAG_SF_APPEND) {
1187
0
      tsk_fprintf(hFile, "append-only ");
1188
0
    }
1189
0
    if (bsdflags & APFS_BSD_FLAG_SF_RESTRICTED) {
1190
0
      tsk_fprintf(hFile, "restricted ");
1191
0
    }
1192
0
    if (bsdflags & APFS_BSD_FLAG_SF_NOUNLINK) {
1193
0
      tsk_fprintf(hFile, "no-unlink ");
1194
0
    }
1195
0
    tsk_fprintf(hFile, "\n");
1196
0
  }
1197
1198
0
  if (bsdflags & 0x0000FFFF) {
1199
0
    tsk_fprintf(hFile, "Owner flags:\t");
1200
0
    if (bsdflags & APFS_BSD_FLAG_UF_NODUMP) {
1201
0
      tsk_fprintf(hFile, "no-dump ");
1202
0
    }
1203
0
    if (bsdflags & APFS_BSD_FLAG_UF_IMMUTABLE) {
1204
0
      tsk_fprintf(hFile, "immutable ");
1205
0
    }
1206
0
    if (bsdflags & APFS_BSD_FLAG_UF_APPEND) {
1207
0
      tsk_fprintf(hFile, "append-only ");
1208
0
    }
1209
0
    if (bsdflags & APFS_BSD_FLAG_UF_OPAQUE) {
1210
0
      tsk_fprintf(hFile, "opaque ");
1211
0
    }
1212
0
    if (bsdflags & APFS_BSD_FLAG_UF_COMPRESSED) {
1213
0
      tsk_fprintf(hFile, "compressed ");
1214
0
    }
1215
0
    if (bsdflags & APFS_BSD_FLAG_UF_TRACKED) {
1216
0
      tsk_fprintf(hFile, "tracked ");
1217
0
    }
1218
0
    if (bsdflags & APFS_BSD_FLAG_UF_DATAVAULT) {
1219
0
      tsk_fprintf(hFile, "data-vault ");
1220
0
    }
1221
0
    if (bsdflags & APFS_BSD_FLAG_UF_HIDDEN) {
1222
0
      tsk_fprintf(hFile, "hidden ");
1223
0
    }
1224
0
    tsk_fprintf(hFile, "\n");
1225
0
  }
1226
1227
0
  auto date_added =
1228
0
      this->date_added(jobj->inode().parent_id, fs_file->meta->addr);
1229
1230
0
  if (sec_skew != 0) {
1231
0
    tsk_fprintf(hFile, "\nAdjusted times:\n");
1232
0
    if (fs_file->meta->mtime) fs_file->meta->mtime -= sec_skew;
1233
0
    if (fs_file->meta->atime) fs_file->meta->atime -= sec_skew;
1234
0
    if (fs_file->meta->ctime) fs_file->meta->ctime -= sec_skew;
1235
0
    if (fs_file->meta->crtime) fs_file->meta->crtime -= sec_skew;
1236
0
    if (date_added) date_added -= sec_skew * 1000000000;
1237
1238
0
    tsk_fprintf(hFile, "Created:\t\t%s\n",
1239
0
                tsk_fs_time_to_str_subsecs(fs_file->meta->crtime,
1240
0
                                           fs_file->meta->crtime_nano, buffer));
1241
0
    tsk_fprintf(hFile, "Content Modified:\t%s\n",
1242
0
                tsk_fs_time_to_str_subsecs(fs_file->meta->mtime,
1243
0
                                           fs_file->meta->mtime_nano, buffer));
1244
0
    tsk_fprintf(hFile, "Attributes Modified:\t%s\n",
1245
0
                tsk_fs_time_to_str_subsecs(fs_file->meta->ctime,
1246
0
                                           fs_file->meta->ctime_nano, buffer));
1247
0
    tsk_fprintf(hFile, "Accessed:\t\t%s\n",
1248
0
                tsk_fs_time_to_str_subsecs(fs_file->meta->atime,
1249
0
                                           fs_file->meta->atime_nano, buffer));
1250
1251
0
    if (date_added) {
1252
0
      tsk_fprintf(hFile, "Date Added:\t\t%s\n",
1253
0
                  tsk_fs_time_to_str_subsecs(date_added / 1000000000,
1254
0
                                             date_added % 1000000000, buffer));
1255
0
    }
1256
1257
0
    if (fs_file->meta->mtime) fs_file->meta->mtime += sec_skew;
1258
0
    if (fs_file->meta->atime) fs_file->meta->atime += sec_skew;
1259
0
    if (fs_file->meta->ctime) fs_file->meta->ctime += sec_skew;
1260
0
    if (fs_file->meta->crtime) fs_file->meta->crtime += sec_skew;
1261
0
    if (date_added) date_added += sec_skew * 1000000000;
1262
1263
0
    tsk_fprintf(hFile, "\nOriginal times:\n");
1264
0
  } else {
1265
0
    tsk_fprintf(hFile, "\nTimes:\n");
1266
0
  }
1267
1268
0
  tsk_fprintf(hFile, "Created:\t\t%s\n",
1269
0
              tsk_fs_time_to_str_subsecs(fs_file->meta->crtime,
1270
0
                                         fs_file->meta->crtime_nano, buffer));
1271
0
  tsk_fprintf(hFile, "Content Modified:\t%s\n",
1272
0
              tsk_fs_time_to_str_subsecs(fs_file->meta->mtime,
1273
0
                                         fs_file->meta->mtime_nano, buffer));
1274
0
  tsk_fprintf(hFile, "Attributes Modified:\t%s\n",
1275
0
              tsk_fs_time_to_str_subsecs(fs_file->meta->ctime,
1276
0
                                         fs_file->meta->ctime_nano, buffer));
1277
0
  tsk_fprintf(hFile, "Accessed:\t\t%s\n",
1278
0
              tsk_fs_time_to_str_subsecs(fs_file->meta->atime,
1279
0
                                         fs_file->meta->atime_nano, buffer));
1280
1281
0
  if (date_added) {
1282
0
    tsk_fprintf(hFile, "Date Added:\t\t%s\n",
1283
0
                tsk_fs_time_to_str_subsecs(date_added / 1000000000,
1284
0
                                           date_added % 1000000000, buffer));
1285
0
  }
1286
1287
  // Force the loading of all attributes.
1288
0
  (void)tsk_fs_file_attr_get(fs_file.get());
1289
1290
0
  const TSK_FS_ATTR* compressionAttr = nullptr;
1291
1292
  /* Print all of the attributes */
1293
0
  tsk_fprintf(hFile, "\nAttributes: \n");
1294
0
  if (fs_file->meta->attr != nullptr) {
1295
    // cycle through the attributes
1296
0
    const auto cnt = tsk_fs_file_attr_getsize(fs_file.get());
1297
0
    for (auto i = 0; i < cnt; ++i) {
1298
0
      const auto fs_attr = tsk_fs_file_attr_get_idx(fs_file.get(), i);
1299
1300
0
      if (fs_attr == nullptr) {
1301
0
        continue;
1302
0
      }
1303
1304
0
      const auto type = attr_type_name((uint32_t)fs_attr->type);
1305
1306
      // print the layout if it is non-resident
1307
0
      if (fs_attr->flags & TSK_FS_ATTR_NONRES) {
1308
        // NTFS_PRINT_ADDR print_addr;
1309
1310
0
        tsk_fprintf(hFile,
1311
0
                    "Type: %s (%" PRIu32 "-%" PRIu16
1312
0
                    ")   Name: %s   Non-Resident%s%s%s   size: %" PRIdOFF
1313
0
                    "  init_size: %" PRIdOFF "\n",
1314
0
                    type, fs_attr->type, fs_attr->id,
1315
0
                    (fs_attr->name) ? fs_attr->name : "N/A",
1316
0
                    (fs_attr->flags & TSK_FS_ATTR_ENC) ? ", Encrypted" : "",
1317
0
                    (fs_attr->flags & TSK_FS_ATTR_COMP) ? ", Compressed" : "",
1318
0
                    (fs_attr->flags & TSK_FS_ATTR_SPARSE) ? ", Sparse" : "",
1319
0
                    fs_attr->size, fs_attr->nrd.initsize);
1320
0
        if (istat_flags & TSK_FS_ISTAT_RUNLIST) {
1321
0
          if (tsk_fs_attr_print(fs_attr, hFile)) {
1322
0
            tsk_fprintf(hFile, "\nError creating run lists\n");
1323
0
            tsk_error_print(hFile);
1324
0
            tsk_error_reset();
1325
0
          }
1326
0
        }
1327
0
        else {
1328
0
            APFS_PRINT_ADDR print_addr;
1329
0
            print_addr.idx = 0;
1330
0
            print_addr.hFile = hFile;
1331
0
            if (tsk_fs_file_walk_type(fs_file.get(), fs_attr->type,
1332
0
                fs_attr->id,
1333
0
                TSK_FS_FILE_WALK_FLAG_ENUM((TSK_FS_FILE_WALK_FLAG_AONLY |
1334
0
                    TSK_FS_FILE_WALK_FLAG_SLACK)),
1335
0
                print_addr_act, (void *)&print_addr)) {
1336
0
                tsk_fprintf(hFile, "\nError walking file\n");
1337
0
                tsk_error_print(hFile);
1338
0
                tsk_error_reset();
1339
0
            }
1340
0
            if (print_addr.idx != 0)
1341
0
                tsk_fprintf(hFile, "\n");
1342
0
        }
1343
0
      } else {
1344
        // Resident attributes
1345
0
        tsk_fprintf(hFile,
1346
0
                    "Type: %s (%" PRIu32 "-%" PRIu16
1347
0
                    ")   Name: %s   Resident%s%s%s   size: %" PRIdOFF "\n",
1348
0
                    type, fs_attr->type, fs_attr->id,
1349
0
                    (fs_attr->name) ? fs_attr->name : "N/A",
1350
0
                    (fs_attr->flags & TSK_FS_ATTR_ENC) ? ", Encrypted" : "",
1351
0
                    (fs_attr->flags & TSK_FS_ATTR_COMP) ? ", Compressed" : "",
1352
0
                    (fs_attr->flags & TSK_FS_ATTR_SPARSE) ? ", Sparse" : "",
1353
0
                    fs_attr->size);
1354
0
      }
1355
1356
0
      if (fs_attr->type == TSK_FS_ATTR_TYPE_APFS_COMP_REC) {
1357
0
        if (compressionAttr == nullptr) {
1358
0
          compressionAttr = fs_attr;
1359
0
        } else {
1360
          // Problem:  there is more than one compression attribute
1361
0
          error_detected(TSK_ERR_FS_CORRUPT,
1362
0
                         "APFS istat: more than one compression attribute");
1363
0
          return 1;
1364
0
        }
1365
0
      }
1366
0
    }
1367
0
  }
1368
1369
0
  if ((bsdflags & APFS_BSD_FLAG_UF_COMPRESSED) &&
1370
0
      (compressionAttr == nullptr)) {
1371
0
    tsk_fprintf(hFile,
1372
0
                "WARNING: Compression Flag is set, but there"
1373
0
                " is no compression record for this file.\n");
1374
0
  }
1375
1376
0
  if (((bsdflags & APFS_BSD_FLAG_UF_COMPRESSED) == 0) &&
1377
0
      (compressionAttr != nullptr)) {
1378
0
    tsk_fprintf(hFile,
1379
0
                "WARNING: Compression Flag is NOT set, but there"
1380
0
                " is a compression record for this file.\n");
1381
0
  }
1382
1383
  // TODO(JTS): compression stuff
1384
1385
0
  return 0;
1386
0
} catch (const std::exception& e) {
1387
0
  tsk_error_reset();
1388
0
  tsk_error_set_errno(TSK_ERR_FS_GENFS);
1389
0
  tsk_error_set_errstr("%s", e.what());
1390
0
  return 1;
1391
0
}
1392
1393
0
uint8_t tsk_apfs_istat(TSK_FS_FILE* fs_file, apfs_istat_info* info) try {
1394
0
  if (fs_file == nullptr) {
1395
0
    tsk_error_reset();
1396
0
    tsk_error_set_errno(TSK_ERR_FS_ARG);
1397
0
    tsk_error_set_errstr("tsk_apfs_istat: Null fs_file");
1398
0
    return 1;
1399
0
  }
1400
1401
0
  if (info == nullptr) {
1402
0
    tsk_error_reset();
1403
0
    tsk_error_set_errno(TSK_ERR_FS_ARG);
1404
0
    tsk_error_set_errstr("tsk_apfs_istat: Null info");
1405
0
    return 1;
1406
0
  }
1407
1408
0
  memset(info, 0, sizeof(*info));
1409
1410
0
  const auto jobj = static_cast<APFSJObject*>(fs_file->meta->content_ptr);
1411
1412
0
  if (jobj->is_clone()) {
1413
0
    info->cloned_inum = jobj->inode().private_id;
1414
0
  }
1415
1416
0
  info->bsdflags = jobj->inode().bsdflags;
1417
1418
0
  const auto& fs = to_fs(fs_file->fs_info);
1419
1420
0
  info->date_added =
1421
0
      fs.date_added(jobj->inode().parent_id, fs_file->meta->addr);
1422
1423
0
  return 0;
1424
1425
0
} catch (const std::exception& e) {
1426
0
  tsk_error_reset();
1427
0
  tsk_error_set_errno(TSK_ERR_FS_GENFS);
1428
0
  tsk_error_set_errstr("%s", e.what());
1429
0
  return 1;
1430
0
}
1431
1432
/* Returns TSK_FS_BLOCK_FLAG_UNALLOC if the addr corresponds to an address
1433
 * stored in the unallocated ranges for the pool and TSK_FS_BLOCK_FLAG_ALLOC
1434
 * otherwise. Note that TSK_FS_BLOCK_FLAG_ALLOC does not mean the block belongs
1435
 * to the current file system, just that one of the volumes in the pool or the pool
1436
 * itself is using it.
1437
 */
1438
0
TSK_FS_BLOCK_FLAG_ENUM APFSFSCompat::block_getflags(TSK_FS_INFO* fs, TSK_DADDR_T addr) {
1439
1440
0
    if (fs->img_info->itype != TSK_IMG_TYPE_POOL) {
1441
        // No way to return an error
1442
0
        return TSK_FS_BLOCK_FLAG_UNALLOC;
1443
0
    }
1444
1445
0
    IMG_POOL_INFO *pool_img = (IMG_POOL_INFO*)fs->img_info;
1446
0
    const APFSPoolCompat* pool = static_cast<APFSPoolCompat*>(pool_img->pool_info->impl);
1447
1448
    // Check if the given addr is contained in an unallocated range
1449
0
    for (const TSKPool::range &range : pool->nx()->unallocated_ranges()) {
1450
0
        if (range.start_block < addr
1451
0
            && (range.start_block + range.num_blocks > addr)) {
1452
0
            return TSK_FS_BLOCK_FLAG_UNALLOC;
1453
0
        }
1454
0
    }
1455
0
    return TSK_FS_BLOCK_FLAG_ALLOC;
1456
0
}
1457
1458
uint8_t APFSFSCompat::block_walk(TSK_FS_INFO * fs, TSK_DADDR_T start, TSK_DADDR_T end,
1459
    TSK_FS_BLOCK_WALK_FLAG_ENUM flags, TSK_FS_BLOCK_WALK_CB cb,
1460
0
    void *ptr) {
1461
1462
0
    TSK_FS_BLOCK *fs_block;
1463
0
    TSK_DADDR_T addr;
1464
1465
    // clean up any error messages that are lying around
1466
0
    tsk_error_reset();
1467
1468
    /*
1469
    * Sanity checks.
1470
    */
1471
0
    if (start < fs->first_block || start > fs->last_block) {
1472
0
        tsk_error_reset();
1473
0
        tsk_error_set_errno(TSK_ERR_FS_WALK_RNG);
1474
0
        tsk_error_set_errstr("APFSFSCompat::block_walk: start block: %" PRIuDADDR,
1475
0
            start);
1476
0
        return 1;
1477
0
    }
1478
0
    if (end < fs->first_block || end > fs->last_block
1479
0
        || end < start) {
1480
0
        tsk_error_reset();
1481
0
        tsk_error_set_errno(TSK_ERR_FS_WALK_RNG);
1482
0
        tsk_error_set_errstr("APFSFSCompat::block_walk: end block: %" PRIuDADDR,
1483
0
            end);
1484
0
        return 1;
1485
0
    }
1486
1487
    /* Sanity check on a_flags -- make sure at least one ALLOC is set */
1488
0
    if (((flags & TSK_FS_BLOCK_WALK_FLAG_ALLOC) == 0) &&
1489
0
        ((flags & TSK_FS_BLOCK_WALK_FLAG_UNALLOC) == 0)) {
1490
0
        flags = (TSK_FS_BLOCK_WALK_FLAG_ENUM)
1491
0
            (flags | TSK_FS_BLOCK_WALK_FLAG_ALLOC |
1492
0
                TSK_FS_BLOCK_WALK_FLAG_UNALLOC);
1493
0
    }
1494
0
    if (((flags & TSK_FS_BLOCK_WALK_FLAG_META) == 0) &&
1495
0
        ((flags & TSK_FS_BLOCK_WALK_FLAG_CONT) == 0)) {
1496
0
        flags = (TSK_FS_BLOCK_WALK_FLAG_ENUM)
1497
0
            (flags | TSK_FS_BLOCK_WALK_FLAG_CONT | TSK_FS_BLOCK_WALK_FLAG_META);
1498
0
    }
1499
1500
    /* Allocate memory for a block */
1501
0
    if ((fs_block = tsk_fs_block_alloc(fs)) == NULL) {
1502
0
        return 1;
1503
0
    }
1504
1505
0
    for (addr = start; addr <= end; addr++) {
1506
0
        int retval;
1507
1508
        /* If we're getting both alloc and unalloc, no need to load and
1509
         * check the flags here */
1510
0
        if (((flags & TSK_FS_BLOCK_WALK_FLAG_ALLOC) == 0) ||
1511
0
            ((flags & TSK_FS_BLOCK_WALK_FLAG_UNALLOC) == 0)) {
1512
1513
0
            int myflags = fs->block_getflags(fs, addr);
1514
1515
            // Test if we should call the callback with this one
1516
0
            if ((myflags & TSK_FS_BLOCK_FLAG_ALLOC)
1517
0
                && (!(flags & TSK_FS_BLOCK_WALK_FLAG_ALLOC)))
1518
0
                continue;
1519
0
            else if ((myflags & TSK_FS_BLOCK_FLAG_UNALLOC)
1520
0
                && (!(flags & TSK_FS_BLOCK_WALK_FLAG_UNALLOC)))
1521
0
                continue;
1522
0
        }
1523
1524
        /* Get the block */
1525
0
        if (tsk_fs_block_get(fs, fs_block, addr) == NULL) {
1526
0
            tsk_error_set_errstr2("APFSFSCompat::block_walk: block %" PRIuDADDR,
1527
0
                addr);
1528
0
            tsk_fs_block_free(fs_block);
1529
0
            return 1;
1530
0
        }
1531
1532
        /* Run the callback on the block */
1533
0
        retval = cb(fs_block, ptr);
1534
0
        if (retval == TSK_WALK_STOP) {
1535
0
            break;
1536
0
        }
1537
0
        else if (retval == TSK_WALK_ERROR) {
1538
0
            tsk_fs_block_free(fs_block);
1539
0
            return 1;
1540
0
        }
1541
0
    }
1542
    /*
1543
    * Cleanup.
1544
    */
1545
0
    tsk_fs_block_free(fs_block);
1546
1547
0
    return TSK_OK;
1548
0
}
1549
1550
0
uint8_t APFSFSCompat::decrypt_block(TSK_DADDR_T block_num, void* data) noexcept {
1551
#ifdef HAVE_LIBCRYPTO
1552
    try {
1553
        if (_crypto.decryptor) {
1554
            _crypto.decryptor->decrypt_buffer(data, APFS_BLOCK_SIZE,
1555
                block_num * APFS_BLOCK_SIZE);
1556
1557
            return 0;
1558
        }
1559
1560
        return 1;
1561
    }
1562
    catch (const std::exception& e) {
1563
        tsk_error_reset();
1564
        tsk_error_set_errno(TSK_ERR_FS_GENFS);
1565
        tsk_error_set_errstr("%s", e.what());
1566
        return 1;
1567
    }
1568
#else
1569
0
    tsk_error_reset();
1570
0
    tsk_error_set_errno(TSK_ERR_FS_GENFS);
1571
0
    tsk_error_set_errstr("decrypt_block: crypto library not loaded");
1572
0
    return 1;
1573
0
#endif
1574
0
}
1575
1576
0
int APFSFSCompat::name_cmp(const char* s1, const char* s2) const noexcept try {
1577
#ifdef HAVE_LIBCRYPTO
1578
    const APFSFileSystem vol{ fs_info_to_pool(&_fsinfo), to_pool_vol_block(&_fsinfo),
1579
                           _crypto.password};
1580
#else
1581
0
    const APFSFileSystem vol{ fs_info_to_pool(&_fsinfo), to_pool_vol_block(&_fsinfo)};
1582
0
#endif
1583
1584
0
  if (vol.case_sensitive()) {
1585
0
    return strcmp(s1, s2);
1586
0
  }
1587
1588
0
  return strcasecmp(s1, s2);
1589
0
} catch (const std::exception& e) {
1590
0
  tsk_error_reset();
1591
0
  tsk_error_set_errno(TSK_ERR_FS_GENFS);
1592
0
  tsk_error_set_errstr("%s", e.what());
1593
0
  return 1;
1594
0
}
1595
1596
uint8_t tsk_apfs_list_snapshots(TSK_FS_INFO* fs_info,
1597
0
                                apfs_snapshot_list** list) try {
1598
0
  if (fs_info == nullptr) {
1599
0
    tsk_error_reset();
1600
0
    tsk_error_set_errno(TSK_ERR_FS_ARG);
1601
0
    tsk_error_set_errstr("tsk_apfs_list_snapshots: Null fs_info");
1602
0
    return 1;
1603
0
  }
1604
1605
0
  if (list == nullptr) {
1606
0
    tsk_error_reset();
1607
0
    tsk_error_set_errno(TSK_ERR_FS_ARG);
1608
0
    tsk_error_set_errstr("tsk_apfs_list_snapshots: Null list");
1609
0
    return 1;
1610
0
  }
1611
1612
0
  const auto snapshots =
1613
0
      APFSFileSystem{ fs_info_to_pool(fs_info), to_pool_vol_block(fs_info)}
1614
0
          .snapshots();
1615
1616
0
  *list = (apfs_snapshot_list*)tsk_malloc(
1617
0
      sizeof(apfs_snapshot_list) + sizeof(apfs_snapshot) * snapshots.size());
1618
1619
0
  (*list)->num_snapshots = snapshots.size();
1620
1621
0
  for (size_t i = 0; i < snapshots.size(); i++) {
1622
0
    const auto& snapshot = snapshots[i];
1623
0
    auto& dest = (*list)->snapshots[i];
1624
0
    dest.snap_xid = snapshot.snap_xid;
1625
0
    dest.timestamp = snapshot.timestamp;
1626
0
    dest.name = new char[snapshot.name.length() + 1];
1627
0
    snapshot.name.copy(dest.name, snapshot.name.length());
1628
0
    dest.name[snapshot.name.length()] = 0;
1629
0
    dest.dataless = snapshot.dataless ? 1 : 0;
1630
0
  }
1631
1632
0
  return 0;
1633
0
} catch (const std::exception& e) {
1634
0
  tsk_error_reset();
1635
0
  tsk_error_set_errno(TSK_ERR_FS_GENFS);
1636
0
  tsk_error_set_errstr("%s", e.what());
1637
0
  return 1;
1638
0
}
1639
1640
0
uint8_t tsk_apfs_free_snapshot_list(apfs_snapshot_list* list) try {
1641
0
  if (list == nullptr) {
1642
0
    tsk_error_reset();
1643
0
    tsk_error_set_errno(TSK_ERR_FS_ARG);
1644
0
    tsk_error_set_errstr("tsk_apfs_free_snapshot_list: Null list");
1645
0
    return 1;
1646
0
  }
1647
1648
0
  for (size_t i = 0; i < list->num_snapshots; i++) {
1649
0
    auto& snapshot = list->snapshots[i];
1650
0
    delete[] snapshot.name;
1651
0
  }
1652
1653
0
  free(list);
1654
1655
0
  return 0;
1656
0
} catch (const std::exception& e) {
1657
0
  tsk_error_reset();
1658
0
  tsk_error_set_errno(TSK_ERR_FS_GENFS);
1659
0
  tsk_error_set_errstr("%s", e.what());
1660
0
  return 1;
1661
0
}
1662
1663
0
uint8_t tsk_apfs_set_snapshot(TSK_FS_INFO* fs_info, uint64_t snap_xid) try {
1664
0
  if (fs_info == nullptr) {
1665
0
    tsk_error_reset();
1666
0
    tsk_error_set_errno(TSK_ERR_FS_ARG);
1667
0
    tsk_error_set_errstr("tsk_apfs_set_snapshot: Null fs_info");
1668
0
    return 1;
1669
0
  }
1670
1671
0
  to_fs(fs_info).set_snapshot(snap_xid);
1672
1673
0
  return 0;
1674
0
} catch (const std::exception& e) {
1675
0
  tsk_error_reset();
1676
0
  tsk_error_set_errno(TSK_ERR_FS_GENFS);
1677
0
  tsk_error_set_errstr("%s", e.what());
1678
0
  return 1;
1679
0
}
1680
0
void APFSFSCompat::date_added_cache::populate(uint64_t pid) noexcept {
1681
0
  _cache.clear();
1682
0
  _last_parent = pid;
1683
1684
0
  tsk_fs_dir_walk(
1685
0
      _fs, pid, TSK_FS_DIR_WALK_FLAG_NONE,
1686
0
      [](TSK_FS_FILE* file, const char*, void* a) -> TSK_WALK_RET_ENUM {
1687
0
        auto& cache = *static_cast<std::unordered_map<uint64_t, uint64_t>*>(a);
1688
0
        cache[file->name->meta_addr] = file->name->date_added;
1689
0
        return TSK_WALK_CONT;
1690
0
      },
1691
0
      &_cache);
1692
0
}
1693
1694
uint64_t APFSFSCompat::date_added_cache::lookup(uint64_t parent_id,
1695
0
                                                uint64_t inode_num) noexcept {
1696
0
  if (parent_id < APFS_ROOT_INODE_NUM) {
1697
0
    return 0;
1698
0
  }
1699
1700
0
  if (_last_parent != parent_id) {
1701
0
    populate(parent_id);
1702
0
  }
1703
1704
0
  try {
1705
0
    return _cache[inode_num];
1706
0
  } catch (...) {
1707
    // Something went wrong
1708
0
    return 0;
1709
0
  }
1710
0
}