From 6738d166727e00ab224dc38b44a1a6cc287c9585 Mon Sep 17 00:00:00 2001 From: isciurus <isciurus@gmail.com> Date: Tue, 26 Mar 2019 14:55:20 -0700 Subject: [PATCH] XFS_DINODE_FMT_BTREE directories - Finished XFS_DINODE_FMT_BTREE directory blocks parsing - A bit of refactoring --- tsk/fs/tsk_xfs.h | 75 ++++-- tsk/fs/xfs.cpp | 626 ++++++++++++++++++++++++++++++++--------------- 2 files changed, 481 insertions(+), 220 deletions(-) diff --git a/tsk/fs/tsk_xfs.h b/tsk/fs/tsk_xfs.h index 058f615e9..da88c2dba 100644 --- a/tsk/fs/tsk_xfs.h +++ b/tsk/fs/tsk_xfs.h @@ -289,6 +289,27 @@ typedef struct xfs_agi { XFS_DIFLAG_PROJINHERIT | XFS_DIFLAG_NOSYMLINKS | XFS_DIFLAG_EXTSIZE | \ XFS_DIFLAG_EXTSZINHERIT | XFS_DIFLAG_NODEFRAG | XFS_DIFLAG_FILESTREAM) + +// from https://github.com/torvalds/linux/blob/master/fs/xfs/libxfs/xfs_types.h + +typedef uint64_t xfs_fsblock_t; /* blockno in filesystem (agno|agbno) */ +typedef uint64_t xfs_rfsblock_t; /* blockno in filesystem (raw) */ +typedef uint64_t xfs_rtblock_t; /* extent (block) in realtime area */ +typedef uint64_t xfs_fileoff_t; /* block number in a file */ +typedef uint64_t xfs_filblks_t; /* number of blocks in a file */ + +typedef enum { + XFS_EXT_NORM, XFS_EXT_UNWRITTEN, +} xfs_exntst_t; + +typedef struct xfs_bmbt_irec +{ + xfs_fileoff_t br_startoff; /* starting file offset */ + xfs_fsblock_t br_startblock; /* starting block number */ + xfs_filblks_t br_blockcount; /* number of blocks */ + xfs_exntst_t br_state; /* extent state */ +} xfs_bmbt_irec_t; + /* * Bmap root header, on-disk form only. */ @@ -298,6 +319,21 @@ typedef struct xfs_bmdr_block { } xfs_bmdr_block_t; +typedef struct xfs_btree_lblock xfs_bmbt_block_t; +typedef struct xfs_btree_lblock { + uint32_t bb_magic; + uint16_t bb_level; + uint16_t bb_numrecs; + uint64_t bb_leftsib; + uint64_t bb_rightsib; +} xfs_btree_lblock_t; + + +typedef struct xfs_bmbt_key { + xfs_dfiloff_t br_startoff; +} xfs_bmbt_key_t, xfs_bmdr_key_t; + + // From linux/v2.6.28/source/fs/xfs/xfs_bmap_btree.h typedef struct xfs_bmbt_rec_32 { @@ -324,26 +360,10 @@ typedef uint16_t xfs_dir2_sf_off_t; +typedef xfs_fsblock_t xfs_bmbt_ptr_t, xfs_bmdr_ptr_t; -// from https://github.com/torvalds/linux/blob/master/fs/xfs/libxfs/xfs_types.h -typedef uint64_t xfs_fsblock_t; /* blockno in filesystem (agno|agbno) */ -typedef uint64_t xfs_rfsblock_t; /* blockno in filesystem (raw) */ -typedef uint64_t xfs_rtblock_t; /* extent (block) in realtime area */ -typedef uint64_t xfs_fileoff_t; /* block number in a file */ -typedef uint64_t xfs_filblks_t; /* number of blocks in a file */ -typedef enum { - XFS_EXT_NORM, XFS_EXT_UNWRITTEN, -} xfs_exntst_t; - -typedef struct xfs_bmbt_irec -{ - xfs_fileoff_t br_startoff; /* starting file offset */ - xfs_fsblock_t br_startblock; /* starting block number */ - xfs_filblks_t br_blockcount; /* number of blocks */ - xfs_exntst_t br_state; /* extent state */ -} xfs_bmbt_irec_t; @@ -528,8 +548,29 @@ static inline uint xfs_dinode_size(int version) #define XFS_ATTR_FORK 1 #define XFS_COW_FORK 2 +/* + * Inode size for given fs. + */ +#define XFS_LITINO(mp, version) \ + ((int)(((mp)->fs->sb_inodesize) - xfs_dinode_size(version))) + +#define XFS_DFORK_Q(dip) ((dip)->di_forkoff != 0) #define XFS_DFORK_BOFF(dip) ((int)((dip)->di_forkoff << 3)) +#define XFS_DFORK_DSIZE(dip,mp) \ + (XFS_DFORK_Q(dip) ? \ + XFS_DFORK_BOFF(dip) : \ + XFS_LITINO(mp, (dip)->di_version)) +#define XFS_DFORK_ASIZE(dip,mp) \ + (XFS_DFORK_Q(dip) ? \ + XFS_LITINO(mp, (dip)->di_version) - XFS_DFORK_BOFF(dip) : \ + 0) + +#define XFS_DFORK_SIZE(dip,mp,w) \ + ((w) == XFS_DATA_FORK ? \ + XFS_DFORK_DSIZE(dip, mp) : \ + XFS_DFORK_ASIZE(dip, mp)) + /* * Return pointers to the data or attribute forks. */ diff --git a/tsk/fs/xfs.cpp b/tsk/fs/xfs.cpp index a9b65eadd..90495e3b6 100644 --- a/tsk/fs/xfs.cpp +++ b/tsk/fs/xfs.cpp @@ -475,8 +475,8 @@ xfs_dinode_copy(XFSFS_INFO * xfsfs, TSK_FS_META * fs_meta, } } - if (tsk_verbose) { tsk_fprintf(stderr, "inode %" PRId64, inum); } - + if (tsk_verbose) { tsk_fprintf(stderr, "inode %" PRId64 " ", inum); } + if (dino_buf->di_core.di_format == XFS_DINODE_FMT_LOCAL) { if (tsk_verbose) { tsk_fprintf(stderr, "dino_buf->di_format == XFS_DINODE_FMT_LOCAL \n"); } @@ -547,16 +547,21 @@ xfs_dinode_copy(XFSFS_INFO * xfsfs, TSK_FS_META * fs_meta, fs_meta->content_type = TSK_FS_META_CONTENT_TYPE_XFS_FMT_BTREE; - /* - Walk the b+tree - */ + fs_meta->content_len = sizeof(TSK_OFF_T); + + // data fork pointer offset -> data fork raw file offset + di_core_ptr = (xfs_dinode_core_t*) &dino_buf->di_core; + char *dfork_ptr = XFS_DFORK_PTR(di_core_ptr, XFS_DATA_FORK); + TSK_OFF_T dfork_off = dfork_ptr - (char*) di_core_ptr; + TSK_OFF_T bmap_root_offset = (TSK_OFF_T) inum * (TSK_OFF_T) xfsfs->inode_size + dfork_off; + + memcpy(fs_meta->content_ptr, &bmap_root_offset, sizeof(TSK_OFF_T)); } else if (dino_buf->di_core.di_format == XFS_DINODE_FMT_UUID) { // not used - if (tsk_verbose) { tsk_fprintf(stderr, "dino_buf->di_format == XFS_DINODE_FMT_UUID, which is not used \n"); } - + // a stub fs_meta->content_type = TSK_FS_META_CONTENT_TYPE_DEFAULT; } @@ -570,7 +575,7 @@ xfs_dinode_copy(XFSFS_INFO * xfsfs, TSK_FS_META * fs_meta, { // shouldn't reach this state if (tsk_verbose) { tsk_fprintf(stderr, "dino_buf->di_format == %d, which is an unexpected value \n"); } - + // a stub fs_meta->content_type = TSK_FS_META_CONTENT_TYPE_DEFAULT; } @@ -796,7 +801,7 @@ xfs_block_getflags(TSK_FS_INFO * a_fs, TSK_DADDR_T a_addr) XFSFS_INFO *xfsfs = (XFSFS_INFO *) a_fs; TSK_OFF_T ag_start_off = 0; TSK_OFF_T offset = 0; - unsigned int len = 0; + uint32_t len = 0; xfs_agf *agf = NULL; xfs_agblock_t *agfl = NULL; unsigned int agfl_cur_len = 0; @@ -842,14 +847,14 @@ xfs_block_getflags(TSK_FS_INFO * a_fs, TSK_DADDR_T a_addr) if ((agf = (xfs_agf *) tsk_malloc(len)) == NULL) return (TSK_FS_BLOCK_FLAG_ENUM) NULL; - if (tsk_verbose) { tsk_fprintf(stderr, "reading xfs AG Free Space Block, ag_start_off = %" PRId64 ", sect_size = %u, len = %u \n", ag_start_off, xfsfs->fs->sb_sectsize, len); } + if (tsk_verbose) { tsk_fprintf(stderr, "reading xfs AG Free Space Block, ag_start_off = %" PRId64 ", sect_size = %" PRId64 ", len = %" PRId64 " \n", ag_start_off, xfsfs->fs->sb_sectsize, len); } cnt = tsk_fs_read(&xfsfs->fs_info, ag_start_off + (TSK_OFF_T) xfsfs->fs->sb_sectsize, (char *) agf, len); if (cnt != len) { if (cnt >= 0) { tsk_error_reset(); tsk_error_set_errno(TSK_ERR_FS_READ); } - tsk_error_set_errstr2("xfs_block_getflags: xfs_agf, cnt = %u, len = %u", cnt, len); + tsk_error_set_errstr2("xfs_block_getflags: xfs_agf, cnt = %" PRId64 ", len = %" PRId64 "", cnt, len); free(agf); tsk_fs_free((TSK_FS_INFO *)xfsfs); return (TSK_FS_BLOCK_FLAG_ENUM) NULL; @@ -872,9 +877,9 @@ xfs_block_getflags(TSK_FS_INFO * a_fs, TSK_DADDR_T a_addr) agf->agf_btreeblks = tsk_getu32(TSK_BIG_ENDIAN, &agf->agf_btreeblks); if (tsk_verbose) { tsk_fprintf(stderr, "agf->agf_magicnum = %.4s \n", &agf->agf_magicnum); } - if (tsk_verbose) { tsk_fprintf(stderr, "agf->agf_length = %u \n", agf->agf_length); } - if (tsk_verbose) { tsk_fprintf(stderr, "agf->agf_flfirst = %u \n", agf->agf_flfirst); } - if (tsk_verbose) { tsk_fprintf(stderr, "agf->agf_fllast = %u \n", agf->agf_fllast); } + if (tsk_verbose) { tsk_fprintf(stderr, "agf->agf_length = %" PRId64 " \n", agf->agf_length); } + if (tsk_verbose) { tsk_fprintf(stderr, "agf->agf_flfirst = %" PRId64 " \n", agf->agf_flfirst); } + if (tsk_verbose) { tsk_fprintf(stderr, "agf->agf_fllast = %" PRId64 " \n", agf->agf_fllast); } // agfl is one sector and 4 blocks len = (xfsfs->fs->sb_blocksize * 4 + xfsfs->fs->sb_sectsize) * sizeof(xfs_agblock_t); @@ -888,7 +893,7 @@ xfs_block_getflags(TSK_FS_INFO * a_fs, TSK_DADDR_T a_addr) if(xfsfs->fs->sb_sectsize < sizeof(xfs_agfl)) { // free other structures - tsk_error_set_errstr2("xfs_block_getflags: sb_sectsize = %u < sizeof(xfs_agfl) = %u", xfsfs->fs->sb_sectsize, sizeof(xfs_agfl)); + tsk_error_set_errstr2("xfs_block_getflags: sb_sectsize = %" PRId64 " < sizeof(xfs_agfl) = %" PRId64 "", xfsfs->fs->sb_sectsize, sizeof(xfs_agfl)); tsk_fs_free((TSK_FS_INFO *)xfsfs); return (TSK_FS_BLOCK_FLAG_ENUM) NULL; } @@ -904,7 +909,7 @@ xfs_block_getflags(TSK_FS_INFO * a_fs, TSK_DADDR_T a_addr) tsk_error_reset(); tsk_error_set_errno(TSK_ERR_FS_READ); } - tsk_error_set_errstr2("xfs_block_getflags: xfs_agf, cnt = %u, len = %u", cnt, len); + tsk_error_set_errstr2("xfs_block_getflags: xfs_agf, cnt = %" PRId64 ", len = %" PRId64 "", cnt, len); free(agf); tsk_fs_free((TSK_FS_INFO *)xfsfs); return (TSK_FS_BLOCK_FLAG_ENUM) NULL; @@ -922,7 +927,7 @@ xfs_block_getflags(TSK_FS_INFO * a_fs, TSK_DADDR_T a_addr) tsk_error_reset(); tsk_error_set_errno(TSK_ERR_FS_READ); } - tsk_error_set_errstr2("xfs_block_getflags: xfs_agf, cnt = %u, len = %u", cnt, len); + tsk_error_set_errstr2("xfs_block_getflags: xfs_agf, cnt = %" PRId64 ", len = %" PRId64 "", cnt, len); free(agf); tsk_fs_free((TSK_FS_INFO *)xfsfs); return (TSK_FS_BLOCK_FLAG_ENUM) NULL; @@ -971,7 +976,7 @@ xfs_block_getflags(TSK_FS_INFO * a_fs, TSK_DADDR_T a_addr) // take b+tree sorted by block offset cur_sblock_num = agf->agf_roots[0]; - if (tsk_verbose) { tsk_fprintf(stderr, "cur_sblock_num = %u \n", cur_sblock_num); } + if (tsk_verbose) { tsk_fprintf(stderr, "cur_sblock_num = %" PRId64 " \n", cur_sblock_num); } len = sizeof(xfs_btree_sblock_t); cnt = tsk_fs_read(&xfsfs->fs_info, (TSK_OFF_T) xfsfs->fs->sb_blocksize * cur_sblock_num, (char *) cur_btree_sblock, len); if (cnt != len) { @@ -979,7 +984,7 @@ xfs_block_getflags(TSK_FS_INFO * a_fs, TSK_DADDR_T a_addr) tsk_error_reset(); tsk_error_set_errno(TSK_ERR_FS_READ); } - tsk_error_set_errstr2("xfs_block_getflags: xfs_agf, cnt = %u, len = %u", cnt, len); + tsk_error_set_errstr2("xfs_block_getflags: xfs_agf, cnt = %" PRId64 ", len = %" PRId64 "", cnt, len); free(agf); tsk_fs_free((TSK_FS_INFO *)xfsfs); return (TSK_FS_BLOCK_FLAG_ENUM) NULL; @@ -1003,7 +1008,7 @@ xfs_block_getflags(TSK_FS_INFO * a_fs, TSK_DADDR_T a_addr) tsk_error_reset(); tsk_error_set_errno(TSK_ERR_FS_READ); } - tsk_error_set_errstr2("xfs_block_getflags: xfs_agf, cnt = %u, len = %u", cnt, len); + tsk_error_set_errstr2("xfs_block_getflags: xfs_agf, cnt = %" PRId64 ", len = %" PRId64 "", cnt, len); free(agf); tsk_fs_free((TSK_FS_INFO *)xfsfs); return (TSK_FS_BLOCK_FLAG_ENUM) NULL; @@ -1018,7 +1023,7 @@ xfs_block_getflags(TSK_FS_INFO * a_fs, TSK_DADDR_T a_addr) tsk_error_reset(); tsk_error_set_errno(TSK_ERR_FS_READ); } - tsk_error_set_errstr2("xfs_block_getflags: xfs_agf, cnt = %u, len = %u", cnt, len); + tsk_error_set_errstr2("xfs_block_getflags: xfs_agf, cnt = %" PRId64 ", len = %" PRId64 "", cnt, len); free(agf); tsk_fs_free((TSK_FS_INFO *)xfsfs); return (TSK_FS_BLOCK_FLAG_ENUM) NULL; @@ -1035,9 +1040,9 @@ xfs_block_getflags(TSK_FS_INFO * a_fs, TSK_DADDR_T a_addr) { // go one level down in b+tree found = true; - cur_sblock_num = tsk_getu32(TSK_BIG_ENDIAN, ptrs[cur_key]); + cur_sblock_num = tsk_getu32(TSK_BIG_ENDIAN, &ptrs[cur_key]); - if (tsk_verbose) { tsk_fprintf(stderr, "go one level down in b+tree, cur_sblock_num = %u \n", cur_sblock_num); } + if (tsk_verbose) { tsk_fprintf(stderr, "go one level down in b+tree, cur_sblock_num = %" PRId64 " \n", cur_sblock_num); } cnt = tsk_fs_read(&xfsfs->fs_info, (TSK_OFF_T) xfsfs->fs->sb_blocksize * cur_sblock_num, (char *) cur_btree_sblock, len); if (cnt != len) { @@ -1045,7 +1050,7 @@ xfs_block_getflags(TSK_FS_INFO * a_fs, TSK_DADDR_T a_addr) tsk_error_reset(); tsk_error_set_errno(TSK_ERR_FS_READ); } - tsk_error_set_errstr2("xfs_block_getflags: xfs_agf, cnt = %u, len = %u", cnt, len); + tsk_error_set_errstr2("xfs_block_getflags: xfs_agf, cnt = %" PRId64 ", len = %" PRId64 "", cnt, len); free(agf); tsk_fs_free((TSK_FS_INFO *)xfsfs); return (TSK_FS_BLOCK_FLAG_ENUM) NULL; @@ -1061,7 +1066,7 @@ xfs_block_getflags(TSK_FS_INFO * a_fs, TSK_DADDR_T a_addr) if(!found) { // The block is not in a free list, means it's allocated - if (tsk_verbose) { tsk_fprintf(stderr, "didn't find a_addr at level cur_btree_sblock->bb_level = %u \n", cur_btree_sblock->bb_level); } + if (tsk_verbose) { tsk_fprintf(stderr, "didn't find a_addr at level cur_btree_sblock->bb_level = %" PRId64 " \n", cur_btree_sblock->bb_level); } free(agf); free(cur_btree_sblock); return (TSK_FS_BLOCK_FLAG_ENUM) (TSK_FS_BLOCK_FLAG_CONT | TSK_FS_BLOCK_FLAG_ALLOC); @@ -1079,12 +1084,12 @@ xfs_block_getflags(TSK_FS_INFO * a_fs, TSK_DADDR_T a_addr) recs[cur_key].ar_startblock = tsk_getu32(TSK_BIG_ENDIAN, &recs[cur_key].ar_startblock); recs[cur_key].ar_blockcount = tsk_getu32(TSK_BIG_ENDIAN, &recs[cur_key].ar_blockcount); - if (tsk_verbose) { tsk_fprintf(stderr, "checking cur_key = %u, recs[cur_key].ar_startblock = %u, recs[cur_key].ar_blockcount = %u \n", + if (tsk_verbose) { tsk_fprintf(stderr, "checking cur_key = %" PRId64 ", recs[cur_key].ar_startblock = %" PRId64 ", recs[cur_key].ar_blockcount = %" PRId64 " \n", cur_key, recs[cur_key].ar_startblock, recs[cur_key].ar_blockcount); } if(rel_blk >= recs[cur_key].ar_startblock && rel_blk - recs[cur_key].ar_startblock < recs[cur_key].ar_blockcount) { - if (tsk_verbose) { tsk_fprintf(stderr, "found at cur_btree_sblock->bb_level = %u, cur_key = %u, recs[cur_key].ar_startblock = %u, recs[cur_key].ar_blockcount = %u \n", + if (tsk_verbose) { tsk_fprintf(stderr, "found at cur_btree_sblock->bb_level = %" PRId64 ", cur_key = %" PRId64 ", recs[cur_key].ar_startblock = %" PRId64 ", recs[cur_key].ar_blockcount = %" PRId64 " \n", cur_btree_sblock->bb_level, cur_key, recs[cur_key].ar_startblock, recs[cur_key].ar_blockcount); } free(agf); free(cur_btree_sblock); @@ -1367,7 +1372,7 @@ static uint8_t tsk_fprintf(hFile, "Total Range in Image: %" PRIuDADDR " - %" PRIuDADDR "\n", fs->first_block, fs->last_block_act); - tsk_fprintf(hFile, "Block Size: %u\n", fs->block_size); + tsk_fprintf(hFile, "Block Size: %" PRId64 "\n", fs->block_size); tsk_fprintf(hFile, "Free Blocks: %" PRIu64 "\n", sb->sb_fdblocks); tsk_fprintf(hFile, "Sector Size: %" PRIu16 "\n", sb->sb_sectsize); @@ -1639,6 +1644,366 @@ xfs_istat(TSK_FS_INFO * fs, TSK_FS_ISTAT_FLAG_ENUM istat_flags, FILE * hFile, TS /* Directories */ +/* + * Calculate number of records in a bmap btree inode root. + */ +uint32_t +xfs_bmdr_maxrecs( + uint32_t blocklen, + bool leaf) +{ + blocklen -= sizeof(xfs_bmdr_block_t); + + if (leaf) + return blocklen / sizeof(xfs_bmdr_rec_t); + return blocklen / (sizeof(xfs_bmdr_key_t) + sizeof(xfs_bmdr_ptr_t)); +} + +static TSK_RETVAL_ENUM +parse_dir_block(TSK_FS_INFO *a_fs, TSK_FS_DIR *fs_dir, TSK_FS_META *fs_meta, xfs_bmbt_irec_t *irec, TSK_FS_NAME *fs_name) +{ + TSK_OFF_T size = 0; + char *dirbuf = NULL; + XFSFS_INFO *xfs = (XFSFS_INFO *) a_fs; + uint8_t ftype_size = xfs->fs->sb_features2 & XFS_SB_VERSION2_FTYPE ? sizeof(uint8_t) : 0; + + // skip ft if that's not a data block + if (irec->br_startoff >= XFS_DIR2_LEAF_OFFSET / a_fs->block_size || irec->br_startoff >= XFS_DIR2_FREE_OFFSET / a_fs->block_size) + { + return TSK_COR; + } + + if (tsk_verbose) { + tsk_fprintf(stderr, "adding irec->br_startoff = %" PRId64 " br_startblock = %" PRId64 " / br_blockcount = %" PRId64 ", XFS_DIR2_LEAF_OFFSET = %" PRId64 ", XFS_DIR2_FREE_OFFSET = %" PRId64 "\n", irec->br_startoff, irec->br_startblock, irec->br_blockcount, XFS_DIR2_LEAF_OFFSET, XFS_DIR2_FREE_OFFSET); + } + + size = (TSK_OFF_T) irec->br_blockcount * (TSK_OFF_T) a_fs->block_size; + + if ((dirbuf = (char*) tsk_malloc(size)) == NULL) { + return TSK_ERR; + } + + xfs_agnumber_t ag_num = (TSK_OFF_T) irec->br_startblock >> xfs->fs->sb_agblklog; + uint64_t rel_blk_neg = 1 << (xfs->fs->sb_agblklog); + rel_blk_neg -= 1; + uint64_t rel_blk = (TSK_OFF_T) irec->br_startblock & rel_blk_neg; + TSK_OFF_T offset = ((TSK_OFF_T) ag_num * (TSK_OFF_T) xfs->fs->sb_agblocks + rel_blk) * (TSK_OFF_T) a_fs->block_size; + + // read xfs_dir2_data_hdr (on a v5 filesystem this is xfs_dir3_data_hdr_t) + + // let's read the whole extent, but parse it block-by-block + ssize_t len = size; + ssize_t cnt = tsk_fs_read(a_fs, offset, dirbuf, len); + if (cnt != len) { + tsk_error_reset(); + tsk_error_set_errno(TSK_ERR_FS_FWALK); + tsk_error_set_errstr + ("xfs_dir_open_meta: Error reading directory contents: %" + PRIuINUM "\n", fs_meta->addr); + free(dirbuf); + return TSK_COR; + } + + for (uint16_t block_num = 0; block_num < irec->br_blockcount; block_num++) + { + TSK_OFF_T offset_in_block = (TSK_OFF_T) block_num * (TSK_OFF_T) a_fs->block_size; + TSK_OFF_T limit = (TSK_OFF_T) (block_num + 1) * (TSK_OFF_T) a_fs->block_size; + + xfs_dir2_data_hdr data_hdr; + memcpy(&data_hdr, dirbuf + offset_in_block, sizeof(data_hdr)); + offset_in_block += sizeof(data_hdr); + + data_hdr.bestfree[0].offset = tsk_getu16(TSK_BIG_ENDIAN, &data_hdr.bestfree[0].offset); + data_hdr.bestfree[0].length = tsk_getu16(TSK_BIG_ENDIAN, &data_hdr.bestfree[0].length); + data_hdr.bestfree[1].offset = tsk_getu16(TSK_BIG_ENDIAN, &data_hdr.bestfree[1].offset); + data_hdr.bestfree[1].length = tsk_getu16(TSK_BIG_ENDIAN, &data_hdr.bestfree[1].length); + data_hdr.bestfree[2].offset = tsk_getu16(TSK_BIG_ENDIAN, &data_hdr.bestfree[2].offset); + data_hdr.bestfree[2].length = tsk_getu16(TSK_BIG_ENDIAN, &data_hdr.bestfree[2].length); + + xfs_dir2_data_entry_t data_entry; + + while (offset_in_block < limit) + { + if (tsk_verbose) { tsk_fprintf(stderr, "offset_in_block = %d \n", offset_in_block); } + + uint16_t *xfs_dir2_data_unused_freetag = (uint16_t*) (dirbuf + offset_in_block); + + if (*xfs_dir2_data_unused_freetag == 0xffff) + { + xfs_dir2_data_unused *data_unused = (xfs_dir2_data_unused *) (dirbuf + offset_in_block); + + if (tsk_verbose) { tsk_fprintf(stderr, "offset_in_block = % is a free space, shifting forward by tsk_getu32(TSK_BIG_ENDIAN, &data_unused->length)) = %d \n", offset_in_block, tsk_getu32(TSK_BIG_ENDIAN, &data_unused->length)); } + offset_in_block += tsk_getu32(TSK_BIG_ENDIAN, &data_unused->length); + } + else + { + if (offset_in_block + sizeof(uint64_t) + sizeof(uint8_t) >= limit) + { + tsk_error_set_errno(TSK_ERR_FS_FWALK); + tsk_error_set_errstr + ("xfs_dir_open_meta: Error reading directory contents: %" + PRIuINUM "\n", fs_meta->addr); + return TSK_COR; + } + + memcpy(&data_entry, dirbuf + offset_in_block, sizeof(uint64_t) + sizeof(uint8_t)); + offset_in_block += sizeof(uint64_t) + sizeof(uint8_t); + + data_entry.inumber = tsk_getu64(TSK_BIG_ENDIAN, &data_entry.inumber); + fs_name->meta_addr = data_entry.inumber; + + + if (offset_in_block + data_entry.namelen + ftype_size >= limit) + { + tsk_error_set_errno(TSK_ERR_FS_FWALK); + tsk_error_set_errstr + ("xfs_dir_open_meta: Error reading directory contents: %" + PRIuINUM "\n", fs_meta->addr); + return TSK_COR; + } + + char *name = (char *) dirbuf + offset_in_block; + memcpy(fs_name->name, name, data_entry.namelen); + offset_in_block += data_entry.namelen; + fs_name->name[data_entry.namelen] = '\0'; + + uint8_t ftype = 0; + if (ftype_size > 0) + { + ftype = * (uint8_t *) (name + data_entry.namelen); + } + else + { + xfs_dinode_t *dino_buf = NULL; + ssize_t dinodesize = + xfs->fs->sb_inodesize > + sizeof(xfs_dinode) ? xfs->fs->sb_inodesize : sizeof(xfs_dinode); + if ((dino_buf = (xfs_dinode_t *) tsk_malloc(dinodesize)) == NULL) { + return TSK_ERR; + } + + if (xfs_dinode_load(xfs, fs_name->meta_addr, dino_buf)) { + free(dino_buf); + return TSK_ERR; + } + + ftype = dino_buf->di_core.di_mode & XFS_IN_FMT; + } + + uint32_t ftype32 = (uint32_t) ftype << 12; + switch (ftype32) { + case XFS_IN_REG: + fs_meta->type = TSK_FS_META_TYPE_REG; + break; + case XFS_IN_DIR: + fs_meta->type = TSK_FS_META_TYPE_DIR; + break; + case XFS_IN_SOCK: + fs_meta->type = TSK_FS_META_TYPE_SOCK; + break; + case XFS_IN_LNK: + fs_meta->type = TSK_FS_META_TYPE_LNK; + break; + case XFS_IN_BLK: + fs_meta->type = TSK_FS_META_TYPE_BLK; + break; + case XFS_IN_CHR: + fs_meta->type = TSK_FS_META_TYPE_CHR; + break; + case XFS_IN_FIFO: + fs_meta->type = TSK_FS_META_TYPE_FIFO; + break; + default: + fs_meta->type = TSK_FS_META_TYPE_UNDEF; + break; + } + + // we iterate over allocated directories + fs_name->flags = TSK_FS_NAME_FLAG_ALLOC; + + if (tsk_verbose) { tsk_fprintf(stderr, "namelen = %d, fs_name->name = %s, fs_meta->type = %d, fs_name->meta_addr = %" PRId64 " fs_name->flags = \n", data_entry.namelen, fs_name->name, fs_meta->type, fs_name->meta_addr, fs_name->flags); } + + if (tsk_fs_dir_add(fs_dir, fs_name)) { + tsk_fs_name_free(fs_name); + return TSK_ERR; + } + + // skipping xfs_dir2_data_off_t tag (and ftype, if present) + offset_in_block += sizeof(xfs_dir2_data_off_t) + ftype_size; + + // x64 alignment + offset_in_block = roundup(offset_in_block, sizeof(uint64_t)); + } + } + } +} + +/* visit the btree node (or leaf) */ +static TSK_RETVAL_ENUM +visit_btree_node(TSK_FS_INFO *a_fs, TSK_FS_DIR *fs_dir, TSK_FS_META *fs_meta, xfs_off_t cur_node_offset, xfs_dinode_t *dino_buf, TSK_FS_NAME *fs_name, bool is_root) +{ + XFSFS_INFO *xfs = (XFSFS_INFO *) a_fs; + // xfs_bmdr_block and xfs_bmbt_block_t share those two fields + uint16_t bb_numrecs = 0; + uint16_t bb_level = 0; + uint16_t header_offset = 0; + ssize_t len = 0; + ssize_t cnt = 0; + + if(is_root) + { + xfs_bmdr_block *cur_bmdr_block = NULL; + + if ((cur_bmdr_block = (xfs_bmdr_block *) tsk_malloc(sizeof(xfs_bmdr_block))) == NULL) + { + return TSK_ERR; + } + + len = header_offset = sizeof(xfs_bmdr_block); + cnt = tsk_fs_read(&xfs->fs_info, cur_node_offset, (char *) cur_bmdr_block, len); + if (cnt != len) { + if (cnt >= 0) { + tsk_error_reset(); + tsk_error_set_errno(TSK_ERR_FS_READ); + } + tsk_error_set_errstr2("xfs_block_getflags: xfs_agf, cnt = %" PRId64 ", len = %" PRId64 "", cnt, len); + return TSK_ERR; + } + + bb_level = tsk_getu16(TSK_BIG_ENDIAN, &cur_bmdr_block->bb_level); + bb_numrecs = tsk_getu16(TSK_BIG_ENDIAN, &cur_bmdr_block->bb_numrecs); + + free(cur_bmdr_block); + } + else + { + xfs_bmbt_block_t *cur_bmbt_block; + + if ((cur_bmbt_block = (xfs_bmbt_block_t *) tsk_malloc(sizeof(xfs_bmbt_block_t))) == NULL) + { + return TSK_ERR; + } + + len = header_offset = sizeof(xfs_bmbt_block_t); + cnt = tsk_fs_read(&xfs->fs_info, cur_node_offset, (char *) cur_bmbt_block, len); + if (cnt != len) { + if (cnt >= 0) { + tsk_error_reset(); + tsk_error_set_errno(TSK_ERR_FS_READ); + } + tsk_error_set_errstr2("xfs_block_getflags: xfs_agf, cnt = %" PRId64 ", len = %" PRId64 "", cnt, len); + return TSK_ERR; + } + + bb_level = tsk_getu16(TSK_BIG_ENDIAN, &cur_bmbt_block->bb_level); + bb_numrecs = tsk_getu16(TSK_BIG_ENDIAN, &cur_bmbt_block->bb_numrecs); + + free(cur_bmbt_block); + } + + uint32_t dblocksize = XFS_DFORK_SIZE(&dino_buf->di_core, xfs, XFS_DATA_FORK); + + // if not a leaf node + if(bb_level > 0) + { + uint32_t maxrecs = xfs_bmdr_maxrecs(dblocksize, 0 /* not leaf */); + + xfs_bmbt_rec_t *node_recs = NULL; + size_t len = (size_t) bb_numrecs * sizeof(xfs_bmbt_rec_t); + + if ((node_recs = (xfs_bmbt_rec_t *) tsk_malloc(len)) == NULL) + { + return TSK_ERR; + } + + // read all the keys + cnt = tsk_fs_read(&xfs->fs_info, cur_node_offset + (TSK_OFF_T) header_offset, (char *) node_recs, len); + if (cnt != len) { + if (cnt >= 0) { + tsk_error_reset(); + tsk_error_set_errno(TSK_ERR_FS_READ); + } + tsk_error_set_errstr2("xfs_block_getflags: xfs_agf, cnt = %" PRId64 ", len = %" PRId64 "", cnt, len); + return TSK_ERR; + } + + xfs_bmbt_ptr_t *node_ptrs = NULL; + len = bb_numrecs * sizeof(xfs_bmbt_ptr_t); + if ((node_ptrs = (xfs_bmbt_ptr_t *) tsk_malloc(len)) == NULL) + { + return TSK_ERR; + } + + // read all the node pointers + cnt = tsk_fs_read(&xfs->fs_info, cur_node_offset + (TSK_OFF_T) header_offset + (TSK_OFF_T) maxrecs * (TSK_OFF_T) sizeof(xfs_bmbt_key), + (char *) node_ptrs, len); + if (cnt != len) { + if (cnt >= 0) { + tsk_error_reset(); + tsk_error_set_errno(TSK_ERR_FS_READ); + } + tsk_error_set_errstr2("xfs_block_getflags: xfs_agf, cnt = %" PRId64 ", len = %" PRId64 "", cnt, len); + return TSK_ERR; + } + + // traverse all the pointers + for(uint32_t cur_key = 0; cur_key < bb_numrecs; cur_key++) + { + xfs_fsblock_t next_node_block = tsk_getu64(TSK_BIG_ENDIAN, &node_ptrs[cur_key]); + + // block -> offset + xfs_agnumber_t ag_num = next_node_block >> xfs->fs->sb_agblklog; + uint64_t rel_blk_neg = 1 << (xfs->fs->sb_agblklog); + rel_blk_neg -= 1; + uint64_t rel_blk = (uint64_t) next_node_block & rel_blk_neg; + TSK_OFF_T next_node_offset = ((TSK_OFF_T) ag_num * (TSK_OFF_T) xfs->fs->sb_agblocks + rel_blk) * (TSK_OFF_T) xfs->fs_info.block_size; + + if (tsk_verbose) { tsk_fprintf(stderr, "visiting next_node (block %" PRId64", offset %"PRId64" \n", next_node_block, next_node_offset); } + + visit_btree_node(a_fs, fs_dir, fs_meta, next_node_offset, dino_buf, fs_name, 0); + } + + free(node_recs); + free(node_ptrs); + + return TSK_OK; + } + else + { + // at the leaf node now + xfs_bmbt_rec_t *node_recs = NULL; + + size_t len = bb_numrecs * sizeof(xfs_bmbt_rec_t); + + if ((node_recs = (xfs_bmbt_rec_t *) tsk_malloc(len)) == NULL) + { + return TSK_ERR; + } + + // read all the records + cnt = tsk_fs_read(&xfs->fs_info, cur_node_offset + (TSK_OFF_T) header_offset, (char *) node_recs, len); + + // iterate over the keys + for(uint32_t cur_key = 0; cur_key < bb_numrecs; cur_key++) + { + // unpack extent + xfs_bmbt_irec_t irec; + memset(&irec, 0, sizeof(irec)); + xfs_bmbt_disk_get_all(&node_recs[cur_key], &irec); + + if (tsk_verbose) { tsk_fprintf(stderr, "now at cur_key = %" PRId64 ", &irec = %" PRIx64" \n", + cur_key, &irec); } + + parse_dir_block(a_fs, fs_dir, fs_meta, &irec, fs_name); + // parse the directory entry in this extent + } + + free(node_recs); + + return TSK_OK; + } +} + /** \internal * Process a directory and load up FS_DIR with the entries. If a pointer to * an already allocated FS_DIR structure is given, it will be cleared. If no existing @@ -1839,7 +2204,7 @@ xfs_dir_open_meta(TSK_FS_INFO * a_fs, TSK_FS_DIR ** a_fs_dir, xfs_bmbt_rec_t *extent_data_offset = (xfs_bmbt_rec_t *) fs_meta->content_ptr; uint32_t nextents = fs_meta->content_len / sizeof(xfs_bmbt_rec_t); - tsk_fprintf(stderr, "nextents == %" PRId64 ", fs_meta->size = %" PRId64 " \n", nextents, fs_meta->size); + if (tsk_verbose) { tsk_fprintf(stderr, "nextents == %" PRId64 ", fs_meta->size = %" PRId64 " \n", nextents, fs_meta->size); } if (fs_meta->size <= xfs->fs_info.block_size /* nextents can be used too */) { @@ -2038,182 +2403,37 @@ xfs_dir_open_meta(TSK_FS_INFO * a_fs, TSK_FS_DIR ** a_fs_dir, memset(&irec, 0, sizeof(irec)); xfs_bmbt_disk_get_all(&extent_data_offset[extent_num], &irec); - // skip it if that's not a data block - if (irec.br_startoff >= XFS_DIR2_LEAF_OFFSET / a_fs->block_size || irec.br_startoff >= XFS_DIR2_FREE_OFFSET / a_fs->block_size) - { - continue; - } - - if (tsk_verbose) { - tsk_fprintf(stderr, "extent_num = %d, adding irec.br_startoff = %" PRId64 " br_startblock = %" PRId64 " / br_blockcount = %" PRId64 ", XFS_DIR2_LEAF_OFFSET = %" PRId64 ", XFS_DIR2_FREE_OFFSET = %" PRId64 "\n", extent_num, irec.br_startoff, irec.br_startblock, irec.br_blockcount, XFS_DIR2_LEAF_OFFSET, XFS_DIR2_FREE_OFFSET); - } - - size = (TSK_OFF_T) irec.br_blockcount * (TSK_OFF_T) a_fs->block_size; - - if ((dirbuf = (char*) tsk_malloc(size)) == NULL) { - return TSK_ERR; - } - - xfs_agnumber_t ag_num = (TSK_OFF_T) irec.br_startblock >> xfs->fs->sb_agblklog; - uint64_t rel_blk_neg = 1 << (xfs->fs->sb_agblklog); - rel_blk_neg -= 1; - uint64_t rel_blk = (TSK_OFF_T) irec.br_startblock & rel_blk_neg; - TSK_OFF_T offset = ((TSK_OFF_T) ag_num * (TSK_OFF_T) xfs->fs->sb_agblocks + rel_blk) * (TSK_OFF_T) a_fs->block_size; - - // read xfs_dir2_data_hdr (on a v5 filesystem this is xfs_dir3_data_hdr_t) - - // let's read the whole extent, but parse it block-by-block - ssize_t len = size; - ssize_t cnt = tsk_fs_read(a_fs, offset, dirbuf, len); - if (cnt != len) { - tsk_error_reset(); - tsk_error_set_errno(TSK_ERR_FS_FWALK); - tsk_error_set_errstr - ("xfs_dir_open_meta: Error reading directory contents: %" - PRIuINUM "\n", a_addr); - free(dirbuf); - return TSK_COR; - } - - for (uint16_t block_num = 0; block_num < irec.br_blockcount; block_num++) - { - TSK_OFF_T offset_in_block = (TSK_OFF_T) block_num * (TSK_OFF_T) a_fs->block_size; - TSK_OFF_T limit = (TSK_OFF_T) (block_num + 1) * (TSK_OFF_T) a_fs->block_size; - - xfs_dir2_data_hdr data_hdr; - memcpy(&data_hdr, dirbuf + offset_in_block, sizeof(data_hdr)); - offset_in_block += sizeof(data_hdr); + parse_dir_block(a_fs, fs_dir, fs_meta, &irec, fs_name); + } + } - data_hdr.bestfree[0].offset = tsk_getu16(TSK_BIG_ENDIAN, &data_hdr.bestfree[0].offset); - data_hdr.bestfree[0].length = tsk_getu16(TSK_BIG_ENDIAN, &data_hdr.bestfree[0].length); - data_hdr.bestfree[1].offset = tsk_getu16(TSK_BIG_ENDIAN, &data_hdr.bestfree[1].offset); - data_hdr.bestfree[1].length = tsk_getu16(TSK_BIG_ENDIAN, &data_hdr.bestfree[1].length); - data_hdr.bestfree[2].offset = tsk_getu16(TSK_BIG_ENDIAN, &data_hdr.bestfree[2].offset); - data_hdr.bestfree[2].length = tsk_getu16(TSK_BIG_ENDIAN, &data_hdr.bestfree[2].length); + free(dirbuf); + } + else if (fs_meta->content_type == TSK_FS_META_CONTENT_TYPE_XFS_FMT_BTREE) + { + TSK_OFF_T *cur_node_offset = (TSK_OFF_T*) fs_meta->content_ptr; - xfs_dir2_data_entry_t data_entry; + if (tsk_verbose) { tsk_fprintf(stderr, "starting TSK_FS_META_CONTENT_TYPE_XFS_FMT_BTREE btree traversal, cur_node_offset = %" PRId64 " \n", *cur_node_offset); } - while (offset_in_block < limit) - { - if (tsk_verbose) { tsk_fprintf(stderr, "offset_in_block = %d \n", offset_in_block); } + // have to load the dinode again for proper data fork size calculation + xfs_dinode_t *dino_buf = NULL; + ssize_t dinode_size = + xfs->fs->sb_inodesize > + sizeof(xfs_dinode) ? xfs->fs->sb_inodesize : sizeof(xfs_dinode); + if ((dino_buf = (xfs_dinode_t *) tsk_malloc(dinode_size)) == NULL) { + return TSK_ERR; + } - uint16_t *xfs_dir2_data_unused_freetag = (uint16_t*) (dirbuf + offset_in_block); + if (xfs_dinode_load(xfs, a_addr, dino_buf)) { + free(dino_buf); + return TSK_ERR; + } - if (*xfs_dir2_data_unused_freetag == 0xffff) - { - xfs_dir2_data_unused *data_unused = (xfs_dir2_data_unused *) (dirbuf + offset_in_block); + retval = visit_btree_node(a_fs, fs_dir, fs_meta, *cur_node_offset, dino_buf, fs_name, 1 /* root node */); - if (tsk_verbose) { tsk_fprintf(stderr, "offset_in_block = % is a free space, shifting forward by tsk_getu32(TSK_BIG_ENDIAN, &data_unused->length)) = %d \n", offset_in_block, tsk_getu32(TSK_BIG_ENDIAN, &data_unused->length)); } - offset_in_block += tsk_getu32(TSK_BIG_ENDIAN, &data_unused->length); - } - else - { - if (offset_in_block + sizeof(uint64_t) + sizeof(uint8_t) >= limit) - { - tsk_error_set_errno(TSK_ERR_FS_FWALK); - tsk_error_set_errstr - ("xfs_dir_open_meta: Error reading directory contents: %" - PRIuINUM "\n", a_addr); - return TSK_COR; - } - - memcpy(&data_entry, dirbuf + offset_in_block, sizeof(uint64_t) + sizeof(uint8_t)); - offset_in_block += sizeof(uint64_t) + sizeof(uint8_t); - - data_entry.inumber = tsk_getu64(TSK_BIG_ENDIAN, &data_entry.inumber); - fs_name->meta_addr = data_entry.inumber; - - - if (offset_in_block + data_entry.namelen + ftype_size >= limit) - { - tsk_error_set_errno(TSK_ERR_FS_FWALK); - tsk_error_set_errstr - ("xfs_dir_open_meta: Error reading directory contents: %" - PRIuINUM "\n", a_addr); - return TSK_COR; - } - - char *name = (char *) dirbuf + offset_in_block; - memcpy(fs_name->name, name, data_entry.namelen); - offset_in_block += data_entry.namelen; - fs_name->name[data_entry.namelen] = '\0'; - - uint8_t ftype = 0; - if (ftype_size > 0) - { - ftype = * (uint8_t *) (name + data_entry.namelen); - } - else - { - xfs_dinode_t *dino_buf = NULL; - ssize_t dinodesize = - xfs->fs->sb_inodesize > - sizeof(xfs_dinode) ? xfs->fs->sb_inodesize : sizeof(xfs_dinode); - if ((dino_buf = (xfs_dinode_t *) tsk_malloc(dinodesize)) == NULL) { - return TSK_ERR; - } - - if (xfs_dinode_load(xfs, fs_name->meta_addr, dino_buf)) { - free(dino_buf); - return TSK_ERR; - } - - ftype = dino_buf->di_core.di_mode & XFS_IN_FMT; - } - - uint32_t ftype32 = (uint32_t) ftype << 12; - switch (ftype32) { - case XFS_IN_REG: - fs_meta->type = TSK_FS_META_TYPE_REG; - break; - case XFS_IN_DIR: - fs_meta->type = TSK_FS_META_TYPE_DIR; - break; - case XFS_IN_SOCK: - fs_meta->type = TSK_FS_META_TYPE_SOCK; - break; - case XFS_IN_LNK: - fs_meta->type = TSK_FS_META_TYPE_LNK; - break; - case XFS_IN_BLK: - fs_meta->type = TSK_FS_META_TYPE_BLK; - break; - case XFS_IN_CHR: - fs_meta->type = TSK_FS_META_TYPE_CHR; - break; - case XFS_IN_FIFO: - fs_meta->type = TSK_FS_META_TYPE_FIFO; - break; - default: - fs_meta->type = TSK_FS_META_TYPE_UNDEF; - break; - } - - // we iterate over allocated directories - fs_name->flags = TSK_FS_NAME_FLAG_ALLOC; - - if (tsk_verbose) { tsk_fprintf(stderr, "namelen = %d, fs_name->name = %s, fs_meta->type = %d, fs_name->meta_addr = %" PRId64 " fs_name->flags = \n", data_entry.namelen, fs_name->name, fs_meta->type, fs_name->meta_addr, fs_name->flags); } - - if (tsk_fs_dir_add(fs_dir, fs_name)) { - tsk_fs_name_free(fs_name); - return TSK_ERR; - } - - // skipping xfs_dir2_data_off_t tag (and ftype, if present) - offset_in_block += sizeof(xfs_dir2_data_off_t) + ftype_size; - - // x64 alignment - offset_in_block = roundup(offset_in_block, sizeof(uint64_t)); - } - } - } - } - } + free(dino_buf); - free(dirbuf); - } - else if (fs_meta->content_type == TSK_FS_META_CONTENT_TYPE_XFS_FMT_BTREE) { - tsk_fprintf(stderr, "fs_meta->content_type == XFS_DINODE_FMT_BTREE is not supported yet \n"); + if (tsk_verbose) { tsk_fprintf(stderr, "finished TSK_FS_META_CONTENT_TYPE_XFS_FMT_BTREE btree traversal \n"); } } return retval; @@ -2299,9 +2519,9 @@ TSK_FS_INFO * tsk_fs_free((TSK_FS_INFO *)xfsfs); return NULL; } - if (tsk_verbose) { tsk_fprintf(stderr, "reading xfs superblock, len = %u \n", len); } + if (tsk_verbose) { tsk_fprintf(stderr, "reading xfs superblock, len = %" PRId64 " \n", len); } cnt = tsk_fs_read(fs, (TSK_OFF_T) 0, (char *) xfsfs->fs, len); - if (tsk_verbose) { tsk_fprintf(stderr, "read the xfs superblock, cnt =%u \n", cnt); } + if (tsk_verbose) { tsk_fprintf(stderr, "read the xfs superblock, cnt =%" PRId64 " \n", cnt); } if (cnt != len) { if (cnt >= 0) { tsk_error_reset(); @@ -2381,7 +2601,7 @@ TSK_FS_INFO * tsk_error_reset(); tsk_error_set_errno(TSK_ERR_FS_READ); } - tsk_error_set_errstr2("xfs_block_getflags: xfs_agf, cnt = %u, len = %u", cnt, len); + tsk_error_set_errstr2("xfs_block_getflags: xfs_agf, cnt = %" PRId64 ", len = %" PRId64 "", cnt, len); free(agi); tsk_fs_free((TSK_FS_INFO *)xfsfs); return NULL; @@ -2399,8 +2619,8 @@ TSK_FS_INFO * agi[current_ag].agi_dirino = tsk_getu32(TSK_BIG_ENDIAN, &agi[current_ag].agi_dirino); if (tsk_verbose) { tsk_fprintf(stderr, "agi->agi_magicnum = %.4s \n", &agi[current_ag].agi_magicnum); } - if (tsk_verbose) { tsk_fprintf(stderr, "agi->agi_length = %u \n", agi[current_ag].agi_length); } - if (tsk_verbose) { tsk_fprintf(stderr, "agi->agi_count = %u \n", agi[current_ag].agi_count); } + if (tsk_verbose) { tsk_fprintf(stderr, "agi->agi_length = %" PRId64 " \n", agi[current_ag].agi_length); } + if (tsk_verbose) { tsk_fprintf(stderr, "agi->agi_count = %" PRId64 " \n", agi[current_ag].agi_count); } } xfsfs->agi = agi; -- GitLab