diff --git a/Makefile.am b/Makefile.am
index 7da8eb4da5a9ba2ad0f8f6ecff74245f36228f51..a41f7f0a8766fc1998b33ecac7ba0f0b33d956e2 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -56,7 +56,7 @@ nobase_include_HEADERS = tsk/libtsk.h tsk/tsk_incs.h \
     tsk/fs/tsk_fs.h tsk/fs/tsk_ffs.h tsk/fs/tsk_ext2fs.h tsk/fs/tsk_fatfs.h \
     tsk/fs/tsk_ntfs.h tsk/fs/tsk_iso9660.h tsk/fs/tsk_hfs.h tsk/fs/tsk_yaffs.h tsk/fs/tsk_logical_fs.h \
     tsk/fs/tsk_apfs.h tsk/fs/tsk_apfs.hpp tsk/fs/apfs_fs.h tsk/fs/apfs_fs.hpp tsk/fs/apfs_compat.hpp \
-    tsk/fs/decmpfs.h tsk/fs/tsk_exfatfs.h tsk/fs/tsk_fatxxfs.h \
+    tsk/fs/decmpfs.h tsk/fs/tsk_exfatfs.h tsk/fs/tsk_fatxxfs.h tsk/fs/tsk_xfs.h \
     tsk/hashdb/tsk_hashdb.h tsk/auto/tsk_auto.h \
     tsk/auto/tsk_is_image_supported.h tsk/auto/guid.h \
     tsk/pool/tsk_pool.h tsk/pool/tsk_pool.hpp tsk/pool/tsk_apfs.h tsk/pool/tsk_apfs.hpp \
diff --git a/tsk/fs/Makefile.am b/tsk/fs/Makefile.am
index 5cda2c37eaf89d32ed18cd17670709c510686178..dae20e3cc7f741b2ce26aa278b66651693f54d31 100644
--- a/tsk/fs/Makefile.am
+++ b/tsk/fs/Makefile.am
@@ -18,7 +18,7 @@ libtskfs_la_SOURCES  = tsk_fs_i.h fs_inode.c fs_io.c fs_block.c fs_open.c \
     dcalc_lib.c dcat_lib.c dls_lib.c dstat_lib.c ffind_lib.c \
     fls_lib.c icat_lib.c ifind_lib.c ils_lib.c usn_journal.c usnjls_lib.c \
     walk_cpp.cpp yaffs.cpp logical_fs.cpp \
-    apfs.cpp apfs_compat.cpp apfs_fs.cpp apfs_open.cpp
+    apfs.cpp apfs_compat.cpp apfs_fs.cpp apfs_open.cpp xfs.cpp
 
 indent:
 	indent *.c *.h
diff --git a/tsk/fs/fs_open.c b/tsk/fs/fs_open.c
index f25523719122786036409c2448cdab26e20c1d0f..2772c7732f142330d7a48c3d20deb086f64a7d4e 100644
--- a/tsk/fs/fs_open.c
+++ b/tsk/fs/fs_open.c
@@ -145,6 +145,7 @@ tsk_fs_open_img_decrypt(TSK_IMG_INFO * a_img_info, TSK_OFF_T a_offset,
         { "HFS",      hfs_open,     TSK_FS_TYPE_HFS_DETECT     },
 #endif
         { "ISO9660",  iso9660_open, TSK_FS_TYPE_ISO9660_DETECT },
+        { "XFS",      xfs_open,     TSK_FS_TYPE_XFS_DETECT     },
         { "APFS",     apfs_open_auto_detect,    TSK_FS_TYPE_APFS_DETECT }
     };
     if (a_img_info == NULL) {
@@ -281,6 +282,9 @@ tsk_fs_open_img_decrypt(TSK_IMG_INFO * a_img_info, TSK_OFF_T a_offset,
     else if (TSK_FS_TYPE_ISAPFS(a_ftype)) {
         return apfs_open(a_img_info, a_offset, a_ftype, a_pass);
     }
+    else if (TSK_FS_TYPE_ISXFS(a_ftype)) {
+      return xfs_open(a_img_info, a_offset, a_ftype, 0);
+    }
     tsk_error_reset();
     tsk_error_set_errno(TSK_ERR_FS_UNSUPTYPE);
     tsk_error_set_errstr("%X", (int) a_ftype);
@@ -298,12 +302,12 @@ tsk_fs_close(TSK_FS_INFO * a_fs)
     if ((a_fs == NULL) || (a_fs->tag != TSK_FS_INFO_TAG))
         return;
 
-    // each file system is supposed to call tsk_fs_free() 
+    // each file system is supposed to call tsk_fs_free()
 
     a_fs->close(a_fs);
 }
 
-/* tsk_fs_malloc - init lock after tsk_malloc 
+/* tsk_fs_malloc - init lock after tsk_malloc
  * This is for fs module and all it's inheritances
  */
 TSK_FS_INFO *
@@ -320,7 +324,7 @@ tsk_fs_malloc(size_t a_len)
     return fs_info;
 }
 
-/* tsk_fs_free - deinit lock before free memory 
+/* tsk_fs_free - deinit lock before free memory
  * This is for fs module and all it's inheritances
  */
 void
@@ -333,7 +337,7 @@ tsk_fs_free(TSK_FS_INFO * a_fs_info)
 
     /* we should probably get the lock, but we're 
      * about to kill the entire object so there are
-     * bigger problems if another thread is still 
+     * bigger problems if another thread is still
      * using the fs */
     if (a_fs_info->orphan_dir) {
         tsk_fs_dir_close(a_fs_info->orphan_dir);
diff --git a/tsk/fs/fs_types.c b/tsk/fs/fs_types.c
index 23981203a17456dee2b95a9dc9920acfd50b3e9b..4f10149db65bf86bc400f4c9a7073c5a1953e1ba 100644
--- a/tsk/fs/fs_types.c
+++ b/tsk/fs/fs_types.c
@@ -6,7 +6,7 @@
 **
 ** Brian Carrier [carrier <at> sleuthkit [dot] org]
 ** Copyright (c) 2006-2011 Brian Carrier, Basis Technology.  All Rights reserved
-** Copyright (c) 2003-2005 Brian Carrier.  All rights reserved 
+** Copyright (c) 2003-2005 Brian Carrier.  All rights reserved
 **
 ** TASK
 ** Copyright (c) 2002 Brian Carrier, @stake Inc.  All rights reserved
@@ -47,6 +47,7 @@ static FS_TYPES fs_type_table[] = {
     {"hfs", TSK_FS_TYPE_HFS_DETECT, "HFS+ (Auto Detection)"},
 #endif
     {"yaffs2", TSK_FS_TYPE_YAFFS2, "YAFFS2"},
+    {"xfs", TSK_FS_TYPE_XFS, "XFS"},
     {"apfs", TSK_FS_TYPE_APFS, "APFS"},
 	{"logical", TSK_FS_TYPE_LOGICAL, "Logical Directory"},
     {"ufs", TSK_FS_TYPE_FFS_DETECT, "UFS (Auto Detection)"},
@@ -167,7 +168,7 @@ tsk_fs_type_toname(TSK_FS_TYPE_ENUM ftype)
 
 /**
  * \ingroup fslib
- * Return the supported file system types. 
+ * Return the supported file system types.
  * @returns The bit in the return value is 1 if the type is supported.
  */
 TSK_FS_TYPE_ENUM
diff --git a/tsk/fs/tsk_fs.h b/tsk/fs/tsk_fs.h
index 3cfdd914e0f0c3ea8c6b4b3464bc966fc44e13c1..9f12bb8ebc9ed82a1830ba0e3ebc7d902c227430 100644
--- a/tsk/fs/tsk_fs.h
+++ b/tsk/fs/tsk_fs.h
@@ -437,8 +437,11 @@ extern "C" {
 
     typedef enum TSK_FS_META_CONTENT_TYPE_ENUM {
         TSK_FS_META_CONTENT_TYPE_DEFAULT = 0x0,
-        TSK_FS_META_CONTENT_TYPE_EXT4_EXTENTS = 0x1,     ///< Ext4 with extents instead of individual pointers
-        TSK_FS_META_CONTENT_TYPE_EXT4_INLINE = 0x02      ///< Ext4 with inline data
+        TSK_FS_META_CONTENT_TYPE_EXT4_EXTENTS = 0x1,  ///< Ext4 with extents instead of individual pointers
+        TSK_FS_META_CONTENT_TYPE_EXT4_INLINE = 0x2,   ///< Ext4 with inline data
+        TSK_FS_META_CONTENT_TYPE_XFS_EXTENTS = 0x3,   ///< XFS with extents instead of individual pointers
+        TSK_FS_META_CONTENT_TYPE_XFS_LOCAL = 0x4,     ///< XFS with all info stored locally instead of individual pointers
+        TSK_FS_META_CONTENT_TYPE_XFS_FMT_BTREE = 0x5  ///< XFS with all info stored in B+tree
     } TSK_FS_META_CONTENT_TYPE_ENUM;
 
 
@@ -821,7 +824,9 @@ extern "C" {
         TSK_FS_TYPE_HFS_LEGACY= 0x00008000,   ///< HFS file system
         TSK_FS_TYPE_APFS = 0x00010000, ///< APFS file system
         TSK_FS_TYPE_APFS_DETECT = 0x00010000, ///< APFS auto detection
-		TSK_FS_TYPE_LOGICAL = 0x00020000, ///< Logical directory (aut detection not supported)
+        TSK_FS_TYPE_LOGICAL = 0x00020000, ///< Logical directory (aut detection not supported)
+        TSK_FS_TYPE_XFS = 0x00040000,        ///< XFS file system
+        TSK_FS_TYPE_XFS_DETECT = 0x000800000, ///< XFS auto detection
         TSK_FS_TYPE_UNSUPP = 0xffffffff,        ///< Unsupported file system
     };
     /* NOTE: Update bindings/java/src/org/sleuthkit/datamodel/TskData.java
@@ -887,10 +892,17 @@ extern "C" {
     /**
     * \ingroup fslib
     * Macro that takes a file system type and returns 1 if the type
-    * is for a YAFFS2 file system. */
+    * is for an APFS file system. */
 #define TSK_FS_TYPE_ISAPFS(ftype) \
     (((ftype) & TSK_FS_TYPE_APFS_DETECT)?1:0)
 
+   /**
+    * \ingroup fslib
+    * Macro that takes a file system type and returns 1 if the type
+    * is for a XFS file system. */
+#define TSK_FS_TYPE_ISXFS(ftype) \
+    (((ftype) & TSK_FS_TYPE_XFS_DETECT)?1:0)
+
     /**
     * \ingroup fslib
     * Macro that takes a file system type and returns 1 if the type
diff --git a/tsk/fs/tsk_fs_i.h b/tsk/fs/tsk_fs_i.h
index 3ad572579bf073767611128f148dbcaabc339c75..ab66c11368930d873731505fc52f362f9f7dab85 100644
--- a/tsk/fs/tsk_fs_i.h
+++ b/tsk/fs/tsk_fs_i.h
@@ -206,13 +206,15 @@ extern "C" {
         TSK_FS_TYPE_ENUM, uint8_t);
     extern TSK_FS_INFO *yaffs2_open(TSK_IMG_INFO *, TSK_OFF_T,
         TSK_FS_TYPE_ENUM, uint8_t);
-	extern TSK_FS_INFO *logical_fs_open(TSK_IMG_INFO *);
+    extern TSK_FS_INFO *logical_fs_open(TSK_IMG_INFO *);
 
     /* Specific pool file system routines */
     extern TSK_FS_INFO *apfs_open_auto_detect(TSK_IMG_INFO*, TSK_OFF_T,
         TSK_FS_TYPE_ENUM, uint8_t);
     extern TSK_FS_INFO *apfs_open(TSK_IMG_INFO*, TSK_OFF_T,
         TSK_FS_TYPE_ENUM, const char*);
+    extern TSK_FS_INFO *xfs_open(TSK_IMG_INFO *, TSK_OFF_T,
+        TSK_FS_TYPE_ENUM, uint8_t);
 
     /* Generic functions for swap and raw -- many say "not supported" */
     extern uint8_t tsk_fs_nofs_fsstat(TSK_FS_INFO * fs, FILE * hFile);
diff --git a/tsk/fs/tsk_xfs.h b/tsk/fs/tsk_xfs.h
new file mode 100644
index 0000000000000000000000000000000000000000..f9a0f0232b9f36b11b62b64caf4d7e599697abf4
--- /dev/null
+++ b/tsk/fs/tsk_xfs.h
@@ -0,0 +1,644 @@
+/*
+ * Contains the structures and function APIs for XFS file system support.
+ */
+
+#include <map>
+
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+// from: http://www.doc.ic.ac.uk/~svb/oslab/Minix/usr/include/sys/stat.h / https://unix.superglobalmegacorp.com/NetBSD-0.8/newsrc/sys/stat.h.html
+
+/* MODE */
+#define XFS_IN_FMT  0170000
+#define XFS_IN_SOCK 0140000
+#define XFS_IN_LNK  0120000
+#define XFS_IN_REG  0100000
+#define XFS_IN_BLK  0060000
+#define XFS_IN_DIR  0040000
+#define XFS_IN_CHR  0020000
+#define XFS_IN_FIFO  0010000
+
+#define XFS_IN_ISUID   0004000
+#define XFS_IN_ISGID   0002000
+#define XFS_IN_ISVTX   0001000
+#define XFS_IN_IRUSR   0000400
+#define XFS_IN_IWUSR   0000200
+#define XFS_IN_IXUSR   0000100
+#define XFS_IN_IRGRP   0000040
+#define XFS_IN_IWGRP   0000020
+#define XFS_IN_IXGRP   0000010
+#define XFS_IN_IROTH   0000004
+#define XFS_IN_IWOTH   0000002
+#define XFS_IN_IXOTH   0000001
+
+typedef uint64_t xfs_ino_t;
+typedef uint32_t xfs_agino_t;    // inode # within allocation grp (from https://github.com/torvalds/linux/blob/master/fs/xfs/libxfs/xfs_types.h#L23)
+typedef int64_t xfs_off_t;
+typedef int64_t xfs_daddr_t;
+typedef uint32_t xfs_agnumber_t;
+typedef uint32_t xfs_agblock_t;
+typedef uint32_t xfs_extlen_t;
+typedef int32_t xfs_extnum_t;
+typedef uint32_t xfs_dablk_t;
+typedef uint32_t xfs_dahash_t;
+typedef uint64_t xfs_dfsbno_t;
+typedef uint64_t xfs_drfsbno_t;
+typedef uint64_t xfs_drtbno_t;
+typedef uint64_t xfs_dfiloff_t;
+typedef uint64_t xfs_dfilblks_t;
+
+// from https://github.com/torvalds/linux/blob/master/fs/xfs/libxfs/xfs_types.h#L23
+typedef int64_t	xfs_lsn_t; /* log sequence number */
+
+/* from libuuid */
+#define UUID_SIZE 16
+typedef struct {
+    uint8_t b[UUID_SIZE];
+} uuid_t;
+
+// from https://github.com/torvalds/linux/blob/master/fs/xfs/libxfs/xfs_format.h
+/*
+ * There are two words to hold XFS "feature" bits: the original
+ * word, sb_versionnum, and sb_features2.  Whenever a bit is set in
+ * sb_features2, the feature bit XFS_SB_VERSION_MOREBITSBIT must be set.
+ *
+ * These defines represent bits in sb_features2.
+ */
+#define XFS_SB_VERSION2_RESERVED1BIT	0x00000001
+#define XFS_SB_VERSION2_LAZYSBCOUNTBIT	0x00000002	/* Superblk counters */
+#define XFS_SB_VERSION2_RESERVED4BIT	0x00000004
+#define XFS_SB_VERSION2_ATTR2BIT	0x00000008	/* Inline attr rework */
+#define XFS_SB_VERSION2_PARENTBIT	0x00000010	/* parent pointers */
+#define XFS_SB_VERSION2_PROJID32BIT	0x00000080	/* 32 bit project id */
+#define XFS_SB_VERSION2_CRCBIT		0x00000100	/* metadata CRCs */
+#define XFS_SB_VERSION2_FTYPE		0x00000200	/* inode type in dir */
+
+#define	XFS_SB_VERSION2_OKBITS		\
+    (XFS_SB_VERSION2_LAZYSBCOUNTBIT	| \
+     XFS_SB_VERSION2_ATTR2BIT	| \
+     XFS_SB_VERSION2_PROJID32BIT	| \
+     XFS_SB_VERSION2_FTYPE)
+
+
+// from https://github.com/torvalds/linux/blob/master/fs/xfs/libxfs/xfs_format.h#L243
+#define XFS_SB_FEAT_RO_COMPAT_FINOBT   (1 << 0)		/* free inode btree */
+#define XFS_SB_FEAT_RO_COMPAT_RMAPBT   (1 << 1)		/* reverse map btree */
+#define XFS_SB_FEAT_RO_COMPAT_REFLINK  (1 << 2)		/* reflinked files */
+
+#define XFS_SB_FEAT_INCOMPAT_FTYPE	(1 << 0)	/* filetype in dirent */
+#define XFS_SB_FEAT_INCOMPAT_SPINODES	(1 << 1)	/* sparse inode chunks */
+#define XFS_SB_FEAT_INCOMPAT_META_UUID	(1 << 2)	/* metadata UUID */
+
+
+// from xfs_format.h
+#define	XFS_SB_VERSION_NUMBITS		0x000f
+#define	XFS_SB_VERSION_NUM(sbp)	((sbp)->sb_versionnum & XFS_SB_VERSION_NUMBITS)
+
+
+/*
+** Super Block
+*/
+typedef struct xfs_sb
+{
+    __uint32_t        sb_magicnum;
+    __uint32_t        sb_blocksize;
+    xfs_drfsbno_t     sb_dblocks;
+    xfs_drfsbno_t     sb_rblocks;
+    xfs_drtbno_t      sb_rextents;
+    uuid_t            sb_uuid;
+    xfs_dfsbno_t      sb_logstart;
+    xfs_ino_t         sb_rootino;
+    xfs_ino_t         sb_rbmino;
+    xfs_ino_t         sb_rsumino;
+    xfs_agblock_t     sb_rextsize;
+    xfs_agblock_t     sb_agblocks;
+    xfs_agnumber_t    sb_agcount;
+    xfs_extlen_t      sb_rbmblocks;
+    xfs_extlen_t      sb_logblocks;
+    __uint16_t        sb_versionnum;
+    __uint16_t        sb_sectsize;
+    __uint16_t        sb_inodesize;
+    __uint16_t        sb_inopblock;
+    char              sb_fname[12];
+    __uint8_t         sb_blocklog;
+    __uint8_t         sb_sectlog;
+    __uint8_t         sb_inodelog;
+    __uint8_t         sb_inopblog;
+    __uint8_t         sb_agblklog;
+    __uint8_t         sb_rextslog;
+    __uint8_t         sb_inprogress;
+    __uint8_t         sb_imax_pct;
+    __uint64_t        sb_icount;
+    __uint64_t        sb_ifree;
+    __uint64_t        sb_fdblocks;
+    __uint64_t        sb_frextents;
+    xfs_ino_t         sb_uquotino;
+    xfs_ino_t         sb_gquotino;
+    __uint16_t        sb_qflags;
+    __uint8_t         sb_flags;
+    __uint8_t         sb_shared_vn;
+    xfs_extlen_t      sb_inoalignmt;
+    __uint32_t        sb_unit;
+    __uint32_t        sb_width;
+    __uint8_t         sb_dirblklog;
+    __uint8_t         sb_logsectlog;
+    __uint16_t        sb_logsectsize;
+    __uint32_t        sb_logsunit;
+    __uint32_t        sb_features2;
+    __uint32_t sb_bad_features2;
+
+    /* version 5 superblock fields start here */
+    __uint32_t sb_features_compat;
+    __uint32_t sb_features_ro_compat;
+    __uint32_t sb_features_incompat;
+    __uint32_t sb_features_log_incompat;
+    __uint32_t sb_crc;
+    xfs_extlen_t sb_spino_align;
+    xfs_ino_t sb_pquotino;
+    xfs_lsn_t sb_lsn;
+    uuid_t sb_meta_uuid;
+    xfs_ino_t sb_rrmapino;
+} xfs_sb_t;
+
+
+typedef __uint32_t __be32;
+typedef __uint16_t __be16;
+
+#define XFS_BTNUM_AGF 2
+
+typedef struct xfs_agf {
+     __be32              agf_magicnum;
+     __be32              agf_versionnum;
+     __be32              agf_seqno;
+     __be32              agf_length;
+     __be32              agf_roots[XFS_BTNUM_AGF];
+     __be32              agf_spare0;
+     __be32              agf_levels[XFS_BTNUM_AGF];
+     __be32              agf_spare1;
+     __be32              agf_flfirst;
+     __be32              agf_fllast;
+     __be32              agf_flcount;
+     __be32              agf_freeblks;
+     __be32              agf_longest;
+     __be32              agf_btreeblks;
+} xfs_agf_t;
+
+typedef struct xfs_agfl {
+    __be32		agfl_magicnum;
+    __be32		agfl_seqno;
+    uuid_t		agfl_uuid;
+    uint64_t	agfl_lsn;
+    __be32		agfl_crc;
+} __attribute__((__packed__)) xfs_agfl_t;
+
+// todo: assert sizeof(xfs_agfl) == 36
+
+typedef struct xfs_btree_sblock {
+     __be32                    bb_magic;
+     __be16                    bb_level;
+     __be16                    bb_numrecs;
+     __be32                    bb_leftsib;
+     __be32                    bb_rightsib;
+} xfs_btree_sblock_t;
+
+typedef struct xfs_alloc_rec {
+     __be32                    ar_startblock;
+     __be32                    ar_blockcount;
+} xfs_alloc_rec_t, xfs_alloc_key_t;
+
+typedef __be32 xfs_alloc_ptr_t;
+
+typedef struct xfs_agi {
+     __be32              agi_magicnum;
+     __be32              agi_versionnum;
+     __be32              agi_seqno;
+     __be32              agi_length;
+     __be32              agi_count;
+     __be32              agi_root;
+     __be32              agi_level;
+     __be32              agi_freecount;
+     __be32              agi_newino;
+     __be32              agi_dirino;
+     __be32              agi_unlinked[64];
+
+    /*
+    * v5 filesystem fields start here; this marks the end of logging region 1
+    * and start of logging region 2.
+    * /
+    uuid_t agi_uuid;
+    __be32 agi_crc;
+    __be32 agi_pad32;
+    __be64 agi_lsn;
+    __be32 agi_free_root;
+    __be32 agi_free_level;
+    */
+} xfs_agi_t;
+
+
+
+
+/*
+/* Inodes
+ */
+
+
+// from: https://github.com/torvalds/linux/blob/master/fs/xfs/libxfs/xfs_format.h
+/*
+ * Values for di_flags
+ */
+#define XFS_DIFLAG_REALTIME_BIT  0	/* file's blocks come from rt area */
+#define XFS_DIFLAG_PREALLOC_BIT  1	/* file space has been preallocated */
+#define XFS_DIFLAG_NEWRTBM_BIT   2	/* for rtbitmap inode, new format */
+#define XFS_DIFLAG_IMMUTABLE_BIT 3	/* inode is immutable */
+#define XFS_DIFLAG_APPEND_BIT    4	/* inode is append-only */
+#define XFS_DIFLAG_SYNC_BIT      5	/* inode is written synchronously */
+#define XFS_DIFLAG_NOATIME_BIT   6	/* do not update atime */
+#define XFS_DIFLAG_NODUMP_BIT    7	/* do not dump */
+#define XFS_DIFLAG_RTINHERIT_BIT 8	/* create with realtime bit set */
+#define XFS_DIFLAG_PROJINHERIT_BIT   9	/* create with parents projid */
+#define XFS_DIFLAG_NOSYMLINKS_BIT   10	/* disallow symlink creation */
+#define XFS_DIFLAG_EXTSIZE_BIT      11	/* inode extent size allocator hint */
+#define XFS_DIFLAG_EXTSZINHERIT_BIT 12	/* inherit inode extent size */
+#define XFS_DIFLAG_NODEFRAG_BIT     13	/* do not reorganize/defragment */
+#define XFS_DIFLAG_FILESTREAM_BIT   14  /* use filestream allocator */
+/* Do not use bit 15, di_flags is legacy and unchanging now */
+
+#define XFS_DIFLAG_REALTIME      (1 << XFS_DIFLAG_REALTIME_BIT)
+#define XFS_DIFLAG_PREALLOC      (1 << XFS_DIFLAG_PREALLOC_BIT)
+#define XFS_DIFLAG_NEWRTBM       (1 << XFS_DIFLAG_NEWRTBM_BIT)
+#define XFS_DIFLAG_IMMUTABLE     (1 << XFS_DIFLAG_IMMUTABLE_BIT)
+#define XFS_DIFLAG_APPEND        (1 << XFS_DIFLAG_APPEND_BIT)
+#define XFS_DIFLAG_SYNC          (1 << XFS_DIFLAG_SYNC_BIT)
+#define XFS_DIFLAG_NOATIME       (1 << XFS_DIFLAG_NOATIME_BIT)
+#define XFS_DIFLAG_NODUMP        (1 << XFS_DIFLAG_NODUMP_BIT)
+#define XFS_DIFLAG_RTINHERIT     (1 << XFS_DIFLAG_RTINHERIT_BIT)
+#define XFS_DIFLAG_PROJINHERIT   (1 << XFS_DIFLAG_PROJINHERIT_BIT)
+#define XFS_DIFLAG_NOSYMLINKS    (1 << XFS_DIFLAG_NOSYMLINKS_BIT)
+#define XFS_DIFLAG_EXTSIZE       (1 << XFS_DIFLAG_EXTSIZE_BIT)
+#define XFS_DIFLAG_EXTSZINHERIT  (1 << XFS_DIFLAG_EXTSZINHERIT_BIT)
+#define XFS_DIFLAG_NODEFRAG      (1 << XFS_DIFLAG_NODEFRAG_BIT)
+#define XFS_DIFLAG_FILESTREAM    (1 << XFS_DIFLAG_FILESTREAM_BIT)
+
+#define XFS_DIFLAG_ANY \
+    (XFS_DIFLAG_REALTIME | XFS_DIFLAG_PREALLOC | XFS_DIFLAG_NEWRTBM | \
+     XFS_DIFLAG_IMMUTABLE | XFS_DIFLAG_APPEND | XFS_DIFLAG_SYNC | \
+     XFS_DIFLAG_NOATIME | XFS_DIFLAG_NODUMP | XFS_DIFLAG_RTINHERIT | \
+     XFS_DIFLAG_PROJINHERIT | XFS_DIFLAG_NOSYMLINKS | XFS_DIFLAG_EXTSIZE | \
+     XFS_DIFLAG_EXTSZINHERIT | XFS_DIFLAG_NODEFRAG | XFS_DIFLAG_FILESTREAM)
+
+/*
+ * Bmap root header, on-disk form only.
+ */
+typedef struct xfs_bmdr_block {
+    __be16		bb_level;	/* 0 is a leaf */
+    __be16		bb_numrecs;	/* current # of data records */
+} xfs_bmdr_block_t;
+
+
+// From linux/v2.6.28/source/fs/xfs/xfs_bmap_btree.h
+typedef struct xfs_bmbt_rec_32
+{
+    __uint32_t		l0, l1, l2, l3;
+} xfs_bmbt_rec_32_t;
+typedef struct xfs_bmbt_rec_64
+{
+    uint64_t			l0, l1;
+} xfs_bmbt_rec_64_t;
+
+typedef __uint64_t	xfs_bmbt_rec_base_t;	/* use this for casts */
+typedef xfs_bmbt_rec_64_t xfs_bmbt_rec_t, xfs_bmdr_rec_t;
+
+// from http://www.dubeiko.com/development/FileSystems/XFS/xfs_filesystem_structure.pdf
+
+typedef struct { __uint8_t i[8]; } xfs_dir2_ino8_t;
+typedef struct { __uint8_t i[4]; } xfs_dir2_ino4_t;
+typedef union {
+     xfs_dir2_ino8_t i8;
+     xfs_dir2_ino4_t i4;
+} xfs_dir2_inou_t;
+
+typedef uint16_t xfs_dir2_sf_off_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;
+
+
+
+
+
+
+// from https://github.com/torvalds/linux/blob/master/fs/xfs/libxfs/xfs_format.h#L849
+
+#define	XFS_DADDR_TO_FSB(mp,d)	XFS_AGB_TO_FSB(mp, \
+        xfs_daddr_to_agno(mp,d), xfs_daddr_to_agbno(mp,d))
+#define	XFS_FSB_TO_DADDR(mp,fsbno)	XFS_AGB_TO_DADDR(mp, \
+            XFS_FSB_TO_AGNO(mp,fsbno), XFS_FSB_TO_AGBNO(mp,fsbno))
+
+
+// from https://github.com/torvalds/linux/blob/master/fs/xfs/libxfs/xfs_format.h#L849
+/*
+ * Bmap btree record and extent descriptor.
+ *  l0:63 is an extent flag (value 1 indicates non-normal).
+ *  l0:9-62 are startoff.
+ *  l0:0-8 and l1:21-63 are startblock.
+ *  l1:0-20 are blockcount.
+ */
+#define BMBT_EXNTFLAG_BITLEN	1
+#define BMBT_STARTOFF_BITLEN	54
+#define BMBT_STARTBLOCK_BITLEN	52
+#define BMBT_BLOCKCOUNT_BITLEN	21
+
+
+
+
+typedef struct xfs_dir2_sf_entry {
+     __uint8_t namelen;
+     xfs_dir2_sf_off_t offset;
+     __uint8_t name[1];
+     xfs_dir2_inou_t inumber;
+} xfs_dir2_sf_entry_t;
+
+typedef struct xfs_dir2_sf_hdr {
+     __uint8_t count;
+     __uint8_t i8count;
+     xfs_dir2_inou_t parent;
+} xfs_dir2_sf_hdr_t;
+typedef struct xfs_dir2_sf {
+     xfs_dir2_sf_hdr_t hdr;
+     xfs_dir2_sf_entry_t list[1];
+} xfs_dir2_sf_t;
+
+
+typedef	__int64_t	xfs_fsize_t;
+typedef int16_t		xfs_aextnum_t;	/* # extents in an attribute fork */
+
+typedef struct xfs_btree_sblock xfs_inobt_block_t;
+
+typedef struct xfs_inobt_rec {
+     uint32_t                   ir_startino;
+     uint32_t                   ir_freecount;
+     uint64_t                   ir_free;
+} xfs_inobt_rec_t;
+
+typedef struct xfs_inobt_key {
+     __be32                     ir_startino;
+} xfs_inobt_key_t;
+typedef __be32 xfs_inobt_ptr_t;
+
+
+typedef struct xfs_timestamp {
+     __int32_t                 t_sec;
+     __int32_t                 t_nsec;
+} xfs_timestamp_t;
+
+typedef enum xfs_dinode_fmt {
+     XFS_DINODE_FMT_DEV,
+     XFS_DINODE_FMT_LOCAL,
+     XFS_DINODE_FMT_EXTENTS,
+     XFS_DINODE_FMT_BTREE,
+     XFS_DINODE_FMT_UUID
+} xfs_dinode_fmt_t;
+
+typedef struct xfs_dinode_core {
+     __uint16_t                di_magic;
+     __uint16_t                di_mode;
+     __int8_t                  di_version;
+     __int8_t                  di_format;
+     __uint16_t                di_onlink;
+     __uint32_t                di_uid;
+     __uint32_t                di_gid;
+     __uint32_t                di_nlink;
+     __uint16_t                di_projid;
+     __uint16_t 			   di_projid_hi;
+     __uint8_t                 di_pad[6];
+     __uint16_t                di_flushiter;
+     xfs_timestamp_t           di_atime;
+     xfs_timestamp_t           di_mtime;
+     xfs_timestamp_t           di_ctime;
+     xfs_fsize_t               di_size;
+     xfs_drfsbno_t             di_nblocks;
+     xfs_extlen_t              di_extsize;
+     xfs_extnum_t              di_nextents;
+     xfs_aextnum_t             di_anextents;
+     __uint8_t                 di_forkoff;
+     __int8_t                  di_aformat;
+     __uint32_t                di_dmevmask;
+     __uint16_t                di_dmstate;
+     __uint16_t                di_flags;
+     __uint32_t                di_gen;
+} xfs_dinode_core_t;
+
+typedef struct xfs_attr_shortform {
+     struct xfs_attr_sf_hdr {
+         __be16 totsize;
+         uint8_t count;
+ } hdr;
+     struct xfs_attr_sf_entry {
+         __uint8_t namelen;
+         __uint8_t valuelen;
+         __uint8_t flags;
+         __uint8_t nameval[1];
+     } list[1];
+} xfs_attr_shortform_t;
+
+typedef struct xfs_dinode
+{
+    xfs_dinode_core_t	di_core;
+
+    __uint32_t				   di_next_unlinked;/* agi unlinked list ptr */
+
+    /* version 5 filesystem (inode version 3) fields start here */
+    uint32_t di_crc;
+    uint64_t di_changecount;
+    uint64_t di_lsn;
+    uint64_t di_flags2;
+    uint32_t di_cowextsize;
+    uint8_t di_pad2[12];
+    xfs_timestamp_t di_crtime;
+    uint64_t di_ino;
+    uuid_t di_uuid;
+
+    union {
+        xfs_bmdr_block_t di_bmbt;	/* btree root block */
+        xfs_bmbt_rec_t di_bmx[1];	/* extent list */
+        xfs_dir2_sf_t	di_dir2sf;	/* shortform directory v2 */
+        char		di_c[1];	/* local contents */
+        __be32		di_dev;		/* device for S_IFCHR/S_IFBLK */
+        uuid_t		di_muuid;	/* mount point value */
+        char		di_symlink[1];	/* local symbolic link */
+    }		di_u;
+    union {
+        xfs_bmdr_block_t di_abmbt;	/* btree root block */
+        xfs_bmbt_rec_32_t di_abmx[1];	/* extent list */
+        xfs_attr_shortform_t di_attrsf;	/* shortform attribute list */
+    }		di_a;
+} xfs_dinode_t;
+
+
+/*
+ * Size of the core inode on disk.  Version 1 and 2 inodes have
+ * the same size, but version 3 has grown a few additional fields.
+ */
+static inline uint xfs_dinode_size(int version)
+{
+    // The inode’s core is 96 bytes on a V4 filesystem and 176 bytes on a V5 filesystem. It contains information about the
+    // file itself including most stat data information about data and attribute forks after the core within the inode. It uses
+    // the following structure:
+
+    if (version == 3)
+    {
+        //return sizeof(struct xfs_dinode);
+        //return 176;
+        return 0; // apparently it's something more than null
+    }
+
+    //return sizeof(xfs_dinode_core_t);
+
+    return offsetof(struct xfs_dinode, di_next_unlinked) + sizeof(__uint32_t); // hacky
+     //96;
+}
+
+/*
+ * Inode fork identifiers.
+ */
+#define	XFS_DATA_FORK	0
+#define	XFS_ATTR_FORK	1
+#define	XFS_COW_FORK	2
+
+#define XFS_DFORK_BOFF(dip)		((int)((dip)->di_forkoff << 3))
+
+/*
+ * Return pointers to the data or attribute forks.
+ */
+#define XFS_DFORK_DPTR(dip) \
+    ((char *)dip + xfs_dinode_size(dip->di_version))
+#define XFS_DFORK_APTR(dip)	\
+    (XFS_DFORK_DPTR(dip) + XFS_DFORK_BOFF(dip))
+#define XFS_DFORK_PTR(dip,w)	\
+    ((w) == XFS_DATA_FORK ? XFS_DFORK_DPTR(dip) : XFS_DFORK_APTR(dip))
+
+
+
+/*
+ * Directories
+ */
+
+// from https://github.com/torvalds/linux/blob/master/fs/xfs/libxfs/xfs_types.h#L23
+/*
+ * XFS_MAXNAMELEN is the length (including the terminating null) of
+ * the longest permissible file (component) name.
+ */
+#define XFS_MAXNAMELEN	256
+
+
+// from https://github.com/torvalds/linux/blob/master/fs/xfs/libxfs/xfs_da_format.h
+
+/*
+ * Byte offset in data block and shortform entry.
+ */
+typedef uint16_t	xfs_dir2_data_off_t;
+
+#define	XFS_DIR2_DATA_FD_COUNT	3
+
+/*
+ * Describe a free area in the data block.
+ *
+ * The freespace will be formatted as a xfs_dir2_data_unused_t.
+ */
+typedef struct xfs_dir2_data_free {
+    uint16_t			offset;		/* start of freespace */
+    uint16_t			length;		/* length of freespace */
+} xfs_dir2_data_free_t;
+
+/*
+ * Header for the data blocks.
+ *
+ * The code knows that XFS_DIR2_DATA_FD_COUNT is 3.
+ */
+typedef struct xfs_dir2_data_hdr {
+    uint32_t				magic;		/* XFS_DIR2_DATA_MAGIC or */
+                              /* XFS_DIR2_BLOCK_MAGIC */
+    xfs_dir2_data_free_t	bestfree[XFS_DIR2_DATA_FD_COUNT];
+} xfs_dir2_data_hdr_t;
+
+
+/*
+ * Active entry in a data block.
+ *
+ * Aligned to 8 bytes.  After the variable length name field there is a
+ * 2 byte tag field, which can be accessed using xfs_dir3_data_entry_tag_p.
+ *
+ * For dir3 structures, there is file type field between the name and the tag.
+ * This can only be manipulated by helper functions. It is packed hard against
+ * the end of the name so any padding for rounding is between the file type and
+ * the tag.
+ */
+typedef struct xfs_dir2_data_entry {
+    uint64_t		inumber;	/* inode number */
+    uint8_t			namelen;	/* name length */
+     /* __u8		name[];		/* name bytes, no null */
+     /* __u8		filetype; */	/* type of inode we point to */
+     /*	__be16      tag; */		/* starting offset of us */
+} xfs_dir2_data_entry_t;
+
+typedef struct xfs_dir2_data_unused {
+    __uint16_t freetag; /* 0xffff */
+    xfs_dir2_data_off_t length;
+    xfs_dir2_data_off_t tag;
+} xfs_dir2_data_unused_t;
+
+#define XFS_DIR2_DATA_UNUSED_SIZE 6
+
+
+typedef struct xfs_dir2_block_tail {
+    uint32_t count;
+    uint32_t stale;
+} xfs_dir2_block_tail_t;
+
+
+typedef uint32_t xfs_dahash_t;
+typedef uint32_t xfs_dir2_dataptr_t;
+typedef struct xfs_dir2_leaf_entry {
+    xfs_dahash_t hashval;
+    xfs_dir2_dataptr_t address;
+} xfs_dir2_leaf_entry_t;
+
+/*
+ * Structure of an xfs file system handle.
+ */
+typedef struct {
+    TSK_FS_INFO fs_info;    /* super class */
+    xfs_sb_t *fs;
+    xfs_agi_t *agi;
+    // If the user specified that the image is XFS, print out additional verbose error messages
+    int autoDetect;
+    uint16_t inode_size;    /* size of each inode */
+
+} XFSFS_INFO;
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/tsk/fs/xfs.cpp b/tsk/fs/xfs.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..757541bb491edcb1671d8a34fda414a921b9c50e
--- /dev/null
+++ b/tsk/fs/xfs.cpp
@@ -0,0 +1,2193 @@
+/**
+*\file xfs.c
+* Contains the internal TSK XFS file system functions.
+*/
+
+/* TCT
+* LICENSE
+*	This software is distributed under the Eclipse Public License.
+* AUTHOR(S)
+*	Andrey Labunets,
+*	Andrey Lab Research
+*
+--*/
+
+#include "tsk_fs_i.h"
+#include "tsk_xfs.h"
+#include "tsk_fs.h"
+
+xfs_alloc_key_t recs[0x10000];
+xfs_alloc_ptr_t ptrs[0x10000];
+
+xfs_inobt_key_t ikeys[0x10000];
+xfs_inobt_ptr_t iptrs[0x10000];
+xfs_inobt_rec_t irecs[0x10000];
+
+/* xfs_inode_getallocflag - get an allocation state of the inode
+ * @param xfsfs A xfsfs file system information structure
+ * @param dino_inum Metadata address
+ * @param dino_buf (optional) The buffer with the inode contents (must be size of xfsfs->inode_size or larger). If null is passed, the inode magic is not checked
+ *
+ * return TSK_FS_META_FLAG_ALLOC or TSK_FS_META_FLAG_ALLOC on success and 0 on error
+ * */
+
+TSK_FS_META_FLAG_ENUM xfs_inode_getallocflag(XFSFS_INFO * xfsfs, TSK_INUM_T dino_inum, const xfs_dinode_t * dino_buf)
+{
+    char *myname = "xfs_inode_getallocflag";
+    xfs_agnumber_t ag_num = 0;
+    uint64_t rel_inum_neg = 0;
+    xfs_inobt_block_t *cur_inobt_block = NULL;
+    TSK_DADDR_T cur_block_num;
+    xfs_agino_t dino_aginum = 0;
+    ssize_t len = 0;
+    uint32_t cur_key = 0;
+    bool found_key = false;
+    ssize_t cnt;
+    uint16_t bb_depth = 0;
+
+  /*
+   * Sanity check.
+   */
+   if (dino_inum < xfsfs->fs_info.first_inum || dino_inum > xfsfs->fs_info.last_inum) {
+        tsk_error_reset();
+        tsk_error_set_errno(TSK_ERR_FS_WALK_RNG);
+        tsk_error_set_errstr("%s: start inode: %" PRIuINUM "", myname,
+            dino_inum);
+        return (TSK_FS_META_FLAG_ENUM) NULL;
+    }
+
+    if ((cur_inobt_block = (xfs_inobt_block_t *) tsk_malloc(sizeof(xfs_inobt_block_t))) == NULL)
+    {
+        // TODO: free other allocated structures
+        return (TSK_FS_META_FLAG_ENUM) NULL;
+    }
+
+    ag_num = dino_inum >> xfsfs->fs->sb_agblklog >> xfsfs->fs->sb_inopblog;
+    rel_inum_neg = 1 << (xfsfs->fs->sb_agblklog + xfsfs->fs->sb_inopblog);
+    rel_inum_neg -= 1;
+    dino_aginum = dino_inum & rel_inum_neg;
+
+    // take inode agi b+tree
+    cur_block_num = ag_num * xfsfs->fs->sb_agblocks + xfsfs->agi[ag_num].agi_root;
+    len = sizeof(xfs_inobt_block_t);
+    cnt = tsk_fs_read(&xfsfs->fs_info, (TSK_OFF_T) xfsfs->fs->sb_blocksize * cur_block_num, (char *) cur_inobt_block, sizeof(xfs_inobt_block_t));
+    if (cnt != len) {
+        if (cnt >= 0) {
+            tsk_error_reset();
+            tsk_error_set_errno(TSK_ERR_FS_READ);
+        }
+        tsk_error_set_errstr2("xfs_inode_getallocflag: Inode %" PRIuINUM
+            ", AGI from block %" PRIuOFF, dino_inum, cur_block_num);
+        return (TSK_FS_META_FLAG_ENUM) NULL;
+    }
+
+    cur_inobt_block->bb_level = bb_depth = tsk_getu16(TSK_BIG_ENDIAN, &cur_inobt_block->bb_level);
+    cur_inobt_block->bb_numrecs = tsk_getu16(TSK_BIG_ENDIAN, &cur_inobt_block->bb_numrecs);
+
+    // initialize the tree
+    memset(ikeys, 0, sizeof(ikeys));
+    memset(iptrs, 0, sizeof(iptrs));
+    memset(irecs, 0, sizeof(irecs));
+
+    // while not leaf node
+    while(cur_inobt_block->bb_level > 0)
+    {
+        // read all the keys
+        len = cur_inobt_block->bb_numrecs * sizeof(xfs_inobt_key_t);
+        cnt = tsk_fs_read(&xfsfs->fs_info, (TSK_OFF_T) xfsfs->fs->sb_blocksize * cur_block_num + sizeof(xfs_inobt_key_t), (char *) ikeys, len);
+        if (cnt != len) {
+            if (cnt >= 0) {
+                tsk_error_reset();
+                tsk_error_set_errno(TSK_ERR_FS_READ);
+            }
+            tsk_error_set_errstr2("%s: Inode %" PRIuINUM, myname,
+                dino_inum);
+            return (TSK_FS_META_FLAG_ENUM) NULL;
+        }
+
+        // read all the node pointers
+        len = cur_inobt_block->bb_numrecs * sizeof(xfs_inobt_ptr_t);
+        cnt = tsk_fs_read(&xfsfs->fs_info, (TSK_OFF_T) xfsfs->fs->sb_blocksize * cur_block_num + sizeof(xfs_inobt_block_t) + cur_inobt_block->bb_numrecs * sizeof(xfs_inobt_key_t), (char *) iptrs, len);
+        if (cnt != len) {
+            if (cnt >= 0) {
+                tsk_error_reset();
+                tsk_error_set_errno(TSK_ERR_FS_READ);
+            }
+            tsk_error_set_errstr2("%s: Inode %" PRIuINUM, myname,
+                dino_inum);
+            return (TSK_FS_META_FLAG_ENUM) NULL;
+        }
+
+        // iterate over the keys
+        found_key = false;
+        for(cur_key = 0; cur_key < cur_inobt_block->bb_numrecs; cur_key++)
+        {
+            ikeys[cur_key].ir_startino = tsk_getu32(TSK_BIG_ENDIAN, &ikeys[cur_key].ir_startino);
+
+            if(dino_aginum >= ikeys[cur_key].ir_startino && dino_aginum - ikeys[cur_key].ir_startino < 64)
+            {
+                // found in a range, go one level down in b+tree, read the next
+                found_key = true;
+
+                cur_block_num = ag_num * xfsfs->fs->sb_agblocks + (TSK_DADDR_T) tsk_getu32(TSK_BIG_ENDIAN, iptrs[cur_key]);
+
+                if (tsk_verbose) { tsk_fprintf(stderr, "go one level down in b+tree, cur_block_num = %u \n", cur_block_num); }
+
+                len = sizeof(xfs_inobt_block_t);
+                cnt = tsk_fs_read(&xfsfs->fs_info, (TSK_OFF_T) xfsfs->fs->sb_blocksize * cur_block_num, (char *) cur_inobt_block, sizeof(xfs_inobt_block_t));
+                if (cnt != len) {
+                    if (cnt >= 0) {
+                        tsk_error_reset();
+                        tsk_error_set_errno(TSK_ERR_FS_READ);
+                    }
+                    tsk_error_set_errstr2("%s: Inode %" PRIuINUM, myname,
+                        dino_inum);
+                    return (TSK_FS_META_FLAG_ENUM) NULL;
+                }
+
+                cur_inobt_block->bb_level = tsk_getu16(TSK_BIG_ENDIAN, &cur_inobt_block->bb_level);
+                cur_inobt_block->bb_numrecs = tsk_getu16(TSK_BIG_ENDIAN, &cur_inobt_block->bb_numrecs);
+            }
+        }
+
+        if(!found_key)
+        {
+            // The inode is not in a Inode B+tree, that means it's not tracked
+            if (tsk_verbose) { tsk_fprintf(stderr, "xfs_inode_getallocflag: Inode %" PRIuINUM " not found in AGI tree, it's not tracked \n", dino_inum); }
+
+            free(cur_inobt_block);
+            return (TSK_FS_META_FLAG_ENUM) NULL;
+        }
+    }
+
+    // Now we are at the leaf node
+
+    // read all the records
+    len = cur_inobt_block->bb_numrecs * sizeof(xfs_inobt_rec_t);
+    cnt = tsk_fs_read(&xfsfs->fs_info, (TSK_OFF_T) xfsfs->fs->sb_blocksize * cur_block_num + sizeof(xfs_btree_sblock_t), (char *) irecs, len);
+
+    // iterate over the records
+    for(cur_key = 0; cur_key < cur_inobt_block->bb_numrecs; cur_key++)
+    {
+        irecs[cur_key].ir_startino = tsk_getu32(TSK_BIG_ENDIAN, &irecs[cur_key].ir_startino);
+        irecs[cur_key].ir_freecount = tsk_getu32(TSK_BIG_ENDIAN, &irecs[cur_key].ir_freecount);
+        irecs[cur_key].ir_free = tsk_getu64(TSK_BIG_ENDIAN, &irecs[cur_key].ir_free);
+
+        if (tsk_verbose) { tsk_fprintf(stderr, "checking cur_key = %u, irecs[cur_key].ir_startino = %d, irecs[cur_key].ir_free = %" PRIx64 " \n",
+            cur_key, irecs[cur_key].ir_startino, irecs[cur_key].ir_free); }
+
+        if(dino_aginum >= irecs[cur_key].ir_startino && dino_aginum - irecs[cur_key].ir_startino < 64)
+        {
+            if (tsk_verbose) { tsk_fprintf(stderr, "found at cur_inobt_block->bb_level = %u, cur_key = %u, irecs[cur_key].ar_startblock = %u, irecs[cur_key].ir_free = %" PRIx64 " \n",
+                cur_inobt_block->bb_level, cur_key, irecs[cur_key].ir_startino, irecs[cur_key].ir_free); }
+
+            free(cur_inobt_block);
+
+            uint8_t rel_inum = dino_aginum - irecs[cur_key].ir_startino;
+            if (irecs[cur_key].ir_free & (1 << rel_inum))
+                return TSK_FS_META_FLAG_UNALLOC;
+            else
+                return TSK_FS_META_FLAG_ALLOC;
+        }
+    }
+
+    // tautology here: found_key must be false if bb_depth > 0
+    if (bb_depth > 0 || !found_key)
+    {
+        // The inode was listed in the node, but not found in the leaf: that should never happen. Report loudly, the world must know
+        tsk_error_set_errstr2("%s: Inode %" PRIuINUM " found in B+Tree node range, but not in the leaf", myname,
+            dino_inum);
+    }
+    else
+    {
+        // The inode is not in the B+tree of zero depth, that means it's not tracked
+        if (tsk_verbose) { tsk_fprintf(stderr, "Inode is not tracked? didn't find dino_aginum = %d at level cur_inobt_block->bb_level = %u \n", dino_aginum, cur_inobt_block->bb_level); }
+    }
+
+    free(cur_inobt_block);
+
+    return (TSK_FS_META_FLAG_ENUM) NULL;
+}
+
+/* xfs_dinode_load - look up disk inode & load into xfs_dinode_t structure
+ * @param xfsfs A xfsfs file system information structure
+ * @param dino_inum Metadata address
+ * @param dino_buf The buffer to store the block in (must be size of xfsfs->inode_size or larger)
+ *
+ * return 1 on error and 0 on success
+ * */
+
+static uint8_t
+xfs_dinode_load(XFSFS_INFO * xfsfs, TSK_INUM_T dino_inum,
+    xfs_dinode_t * dino_buf)
+{
+    char *myname = "xfs_dinode_load";
+    TSK_FS_INFO *fs = &(xfsfs->fs_info);
+    TSK_OFF_T addr = 0;
+    ssize_t cnt;
+
+    /*
+     * Sanity check.
+     */
+    if ((dino_inum < fs->first_inum) || (dino_inum > fs->last_inum)) {
+        tsk_error_reset();
+        tsk_error_set_errno(TSK_ERR_FS_INODE_NUM);
+        tsk_error_set_errstr("ext2fs_dinode_load: address: %" PRIuINUM,
+            dino_inum);
+        return 1;
+    }
+
+    if (dino_buf == NULL) {
+        tsk_error_reset();
+        tsk_error_set_errno(TSK_ERR_FS_ARG);
+        tsk_error_set_errstr("%s: dino_buf is NULL", myname);
+        return 1;
+    }
+
+    addr = dino_inum * xfsfs->inode_size;
+    cnt = tsk_fs_read(fs, addr, (char *) dino_buf, xfsfs->inode_size);
+    if (cnt != xfsfs->fs->sb_inodesize) {
+        if (cnt >= 0) {
+            tsk_error_reset();
+            tsk_error_set_errno(TSK_ERR_FS_READ);
+        }
+        tsk_error_set_errstr2("%s: Inode %" PRIuINUM
+            " from %" PRIuOFF, myname, dino_inum, addr);
+        return 1;
+    }
+
+    dino_buf->di_core.di_mode = tsk_getu16(TSK_BIG_ENDIAN, &dino_buf->di_core.di_mode);
+    dino_buf->di_core.di_onlink = tsk_getu16(TSK_BIG_ENDIAN, &dino_buf->di_core.di_onlink);
+    dino_buf->di_core.di_onlink = tsk_getu16(TSK_BIG_ENDIAN, &dino_buf->di_core.di_onlink);
+    dino_buf->di_core.di_uid = tsk_getu32(TSK_BIG_ENDIAN, &dino_buf->di_core.di_uid);
+    dino_buf->di_core.di_gid = tsk_getu32(TSK_BIG_ENDIAN, &dino_buf->di_core.di_gid);
+    dino_buf->di_core.di_nlink = tsk_getu32(TSK_BIG_ENDIAN, &dino_buf->di_core.di_nlink);
+    dino_buf->di_core.di_projid = tsk_getu16(TSK_BIG_ENDIAN, &dino_buf->di_core.di_projid);
+    dino_buf->di_core.di_flushiter = tsk_getu16(TSK_BIG_ENDIAN, &dino_buf->di_core.di_flushiter);
+    dino_buf->di_core.di_atime.t_sec = tsk_getu32(TSK_BIG_ENDIAN, &dino_buf->di_core.di_atime.t_sec);
+    dino_buf->di_core.di_atime.t_nsec = tsk_getu32(TSK_BIG_ENDIAN, &dino_buf->di_core.di_atime.t_nsec);
+    dino_buf->di_core.di_mtime.t_sec = tsk_getu32(TSK_BIG_ENDIAN, &dino_buf->di_core.di_mtime.t_sec);
+    dino_buf->di_core.di_mtime.t_nsec = tsk_getu32(TSK_BIG_ENDIAN, &dino_buf->di_core.di_mtime.t_nsec);
+    dino_buf->di_core.di_ctime.t_sec = tsk_getu32(TSK_BIG_ENDIAN, &dino_buf->di_core.di_ctime.t_sec);
+    dino_buf->di_core.di_ctime.t_nsec = tsk_getu32(TSK_BIG_ENDIAN, &dino_buf->di_core.di_ctime.t_nsec);
+
+    if (dino_buf->di_core.di_version == 3)
+    {
+        // only for inode v3 those fields mean what they say, otherwise don't try to initialize
+        dino_buf->di_crtime.t_sec = tsk_getu32(TSK_BIG_ENDIAN, &dino_buf->di_crtime.t_sec);
+        dino_buf->di_crtime.t_nsec = tsk_getu32(TSK_BIG_ENDIAN, &dino_buf->di_crtime.t_nsec);
+    }
+
+    dino_buf->di_core.di_size = tsk_getu64(TSK_BIG_ENDIAN, &dino_buf->di_core.di_size);
+    dino_buf->di_core.di_nblocks = tsk_getu64(TSK_BIG_ENDIAN, &dino_buf->di_core.di_nblocks);
+    dino_buf->di_core.di_extsize = tsk_getu32(TSK_BIG_ENDIAN, &dino_buf->di_core.di_extsize);
+    dino_buf->di_core.di_nextents = tsk_getu32(TSK_BIG_ENDIAN, &dino_buf->di_core.di_nextents);
+    dino_buf->di_core.di_anextents = tsk_getu16(TSK_BIG_ENDIAN, &dino_buf->di_core.di_anextents);
+    dino_buf->di_core.di_dmevmask = tsk_getu32(TSK_BIG_ENDIAN, &dino_buf->di_core.di_dmevmask);
+    dino_buf->di_core.di_flags = tsk_getu16(TSK_BIG_ENDIAN, &dino_buf->di_core.di_flags);
+    dino_buf->di_core.di_gen = tsk_getu32(TSK_BIG_ENDIAN, &dino_buf->di_core.di_gen);
+    dino_buf->di_next_unlinked = tsk_getu32(TSK_BIG_ENDIAN, &dino_buf->di_next_unlinked);
+
+    return 0;
+}
+
+// from https://github.com/torvalds/linux/blob/master/fs/xfs/libxfs/xfs_bit.h#L24
+static inline uint64_t xfs_mask64lo(int n)
+{
+    return ((uint64_t)1 << (n)) - 1;
+}
+
+// from https://github.com/torvalds/linux/blob/master/fs/xfs/libxfs/xfs_bmap_btree.c#L63
+void
+xfs_bmbt_disk_get_all(
+    xfs_bmbt_rec_t    *rec,
+    xfs_bmbt_irec_t    *irec)
+{
+    uint64_t l0 = tsk_getu64(TSK_BIG_ENDIAN, &rec->l0);
+    uint64_t l1 = tsk_getu64(TSK_BIG_ENDIAN, &rec->l1);
+
+    irec->br_startoff = (l0 & xfs_mask64lo(64 - BMBT_EXNTFLAG_BITLEN)) >> 9;
+    irec->br_startblock = ((l0 & xfs_mask64lo(9)) << 43) | (l1 >> 21);
+    irec->br_blockcount = l1 & xfs_mask64lo(21);
+
+    if (l0 >> (64 - BMBT_EXNTFLAG_BITLEN))
+        irec->br_state = XFS_EXT_UNWRITTEN;
+    else
+        irec->br_state = XFS_EXT_NORM;
+}
+
+/* xfs_dinode_copy - copy cached disk inode into generic inode
+ *
+ * returns 1 on error and 0 on success
+ * */
+static uint8_t
+xfs_dinode_copy(XFSFS_INFO * xfsfs, TSK_FS_META * fs_meta,
+    TSK_INUM_T inum, const xfs_dinode_t * dino_buf)
+{
+    char *myname = "xfs_dinode_copy";
+    xfs_bmbt_rec_t *extent_data_offset = 0;
+    xfs_dinode_core_t* di_core_ptr = NULL;
+    void *data_offset = NULL;
+    size_t content_len = 0;
+
+    if (dino_buf == NULL) {
+        tsk_error_reset();
+        tsk_error_set_errno(TSK_ERR_FS_ARG);
+        tsk_error_set_errstr("x2fs_dinode_copy: dino_buf is NULL");
+        return 1;
+    }
+
+    // if inode doesn't start with "IN", report loudly, the world should know
+    if (dino_buf != NULL && dino_buf->di_core.di_magic != 0x4e49) {
+        tsk_error_set_errno(TSK_ERR_FS_INODE_NUM);
+        tsk_error_set_errstr("%s: inode header magic incorrect", myname);
+        return 1;
+    }
+
+    fs_meta->attr_state = TSK_FS_META_ATTR_EMPTY;
+    if (fs_meta->attr) {
+        tsk_fs_attrlist_markunused(fs_meta->attr);
+    }
+
+    // set the type
+    switch (dino_buf->di_core.di_mode & XFS_IN_FMT) {
+    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;
+    }
+
+    // set the mode
+    fs_meta->mode = (TSK_FS_META_MODE_ENUM) 0;
+    if (dino_buf->di_core.di_mode & XFS_IN_ISUID)
+        fs_meta->mode = (TSK_FS_META_MODE_ENUM) (fs_meta->mode | TSK_FS_META_MODE_ISUID);
+    if (dino_buf->di_core.di_mode & XFS_IN_ISGID)
+        fs_meta->mode = (TSK_FS_META_MODE_ENUM) (fs_meta->mode | TSK_FS_META_MODE_ISGID);
+    if (dino_buf->di_core.di_mode & XFS_IN_ISVTX)
+        fs_meta->mode = (TSK_FS_META_MODE_ENUM) (fs_meta->mode | TSK_FS_META_MODE_ISVTX);
+
+    if (dino_buf->di_core.di_mode & XFS_IN_IRUSR)
+        fs_meta->mode = (TSK_FS_META_MODE_ENUM) (fs_meta->mode | TSK_FS_META_MODE_IRUSR);
+    if (dino_buf->di_core.di_mode & XFS_IN_IWUSR)
+        fs_meta->mode = (TSK_FS_META_MODE_ENUM) (fs_meta->mode | TSK_FS_META_MODE_IWUSR);
+    if (dino_buf->di_core.di_mode & XFS_IN_IXUSR)
+        fs_meta->mode = (TSK_FS_META_MODE_ENUM) (fs_meta->mode | TSK_FS_META_MODE_IXUSR);
+
+    if (dino_buf->di_core.di_mode & XFS_IN_IRGRP)
+        fs_meta->mode = (TSK_FS_META_MODE_ENUM) (fs_meta->mode | TSK_FS_META_MODE_IRGRP);
+    if (dino_buf->di_core.di_mode & XFS_IN_IWGRP)
+        fs_meta->mode = (TSK_FS_META_MODE_ENUM) (fs_meta->mode | TSK_FS_META_MODE_IWGRP);
+    if (dino_buf->di_core.di_mode & XFS_IN_IXGRP)
+        fs_meta->mode = (TSK_FS_META_MODE_ENUM) (fs_meta->mode | TSK_FS_META_MODE_IXGRP);
+
+    if (dino_buf->di_core.di_mode & XFS_IN_IROTH)
+        fs_meta->mode = (TSK_FS_META_MODE_ENUM) (fs_meta->mode | TSK_FS_META_MODE_IROTH);
+    if (dino_buf->di_core.di_mode & XFS_IN_IWOTH)
+        fs_meta->mode = (TSK_FS_META_MODE_ENUM) (fs_meta->mode | TSK_FS_META_MODE_IWOTH);
+    if (dino_buf->di_core.di_mode & XFS_IN_IXOTH)
+        fs_meta->mode = (TSK_FS_META_MODE_ENUM) (fs_meta->mode | TSK_FS_META_MODE_IXOTH);
+
+    /* di_onlink
+        In v1 inodes, this specifies the number of links to the inode from directories. When the number exceeds 65535,
+        the inode is converted to v2 and the link count is stored in di_nlink.
+      ...
+     di_nlink
+        Specifies the number of links to the inode from directories. This is maintained for both inode versions for
+        current versions of XFS. Prior to v2 inodes, this field was part of di_pad.
+    */
+
+    fs_meta->nlink = dino_buf->di_core.di_nlink;
+
+    fs_meta->size = dino_buf->di_core.di_size;
+
+    fs_meta->addr = inum;
+
+    fs_meta->uid = dino_buf->di_core.di_uid;
+    fs_meta->gid = dino_buf->di_core.di_gid;
+
+    fs_meta->mtime = dino_buf->di_core.di_mtime.t_sec;
+    fs_meta->mtime_nano = dino_buf->di_core.di_mtime.t_nsec;
+
+    fs_meta->atime = dino_buf->di_core.di_atime.t_sec;
+    fs_meta->atime_nano = dino_buf->di_core.di_atime.t_nsec;
+
+    fs_meta->ctime = dino_buf->di_core.di_ctime.t_sec;
+    fs_meta->ctime_nano = dino_buf->di_core.di_ctime.t_nsec;
+
+    if (dino_buf->di_core.di_version == 3)
+    {
+        fs_meta->crtime = dino_buf->di_crtime.t_sec;
+        fs_meta->crtime_nano = dino_buf->di_crtime.t_nsec;
+    }
+
+    fs_meta->seq = 0;
+
+    if (fs_meta->link) {
+        free(fs_meta->link);
+        fs_meta->link = NULL;
+    }
+
+    // The inode size itself is the minimum size for fs_meta->content
+    if (fs_meta->content_len != xfsfs->inode_size) {
+        if ((fs_meta =
+                tsk_fs_meta_realloc(fs_meta,
+                    xfsfs->inode_size)) == NULL) {
+            return 1;
+        }
+    }
+
+    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 == true \n"); }
+
+        fs_meta->content_type = TSK_FS_META_CONTENT_TYPE_XFS_LOCAL;
+
+        di_core_ptr = (xfs_dinode_core_t*) &dino_buf->di_core;
+        data_offset = XFS_DFORK_PTR(di_core_ptr, XFS_DATA_FORK);
+
+        if(fs_meta->type == TSK_FS_META_TYPE_LNK)
+        {
+            if ((fs_meta->link = (char *)
+                    tsk_malloc((size_t) (fs_meta->size + 1))) == NULL)
+                return 1;
+
+            memset(fs_meta->link, 0, fs_meta->size + 1);
+            memcpy(fs_meta->link, data_offset, fs_meta->size);
+        }
+        else if (fs_meta->type == TSK_FS_META_TYPE_DIR)
+        {
+            if (fs_meta->content_len < fs_meta->size) {
+                if ((fs_meta =
+                        tsk_fs_meta_realloc(fs_meta,
+                            fs_meta->size)) == NULL) {
+                    return 1;
+                }
+            }
+
+            memset(fs_meta->content_ptr, 0, fs_meta->size);
+            memcpy(fs_meta->content_ptr, data_offset, fs_meta->size);
+        }
+        else
+        {
+            if (tsk_verbose) { tsk_fprintf(stderr, "unknown type = %d \n", fs_meta->type); }
+        }
+    }
+    else if (dino_buf->di_core.di_format & XFS_DINODE_FMT_EXTENTS)
+    {
+        // if inode stores extents with pointers to the blocks with data, just copy all the extents to the meta->content_ptr
+
+        if (tsk_verbose) { tsk_fprintf(stderr, "dino_buf->di_format & XFS_DINODE_FMT_EXTENTS == true \n"); }
+
+        fs_meta->content_type = TSK_FS_META_CONTENT_TYPE_XFS_EXTENTS;
+
+        // has to be exactly of that size because extent count calculation will rely on this size
+        content_len = sizeof(xfs_bmbt_rec_t) * dino_buf->di_core.di_nextents;
+
+        if (fs_meta->content_len != content_len && content_len != 0) {
+            if ((fs_meta =
+                    tsk_fs_meta_realloc(fs_meta,
+                        content_len)) == NULL) {
+                return 1;
+            }
+        }
+
+        fs_meta->content_len = content_len;
+
+        if (tsk_verbose) { tsk_fprintf(stderr, "dino_buf->di_core.di_nextents = %d \n", dino_buf->di_core.di_nextents); }
+
+        di_core_ptr = (xfs_dinode_core_t*) &dino_buf->di_core;
+        extent_data_offset = (xfs_bmbt_rec_t*) XFS_DFORK_PTR(di_core_ptr, XFS_DATA_FORK);
+        memcpy(fs_meta->content_ptr, extent_data_offset, content_len);
+    }
+    else {
+        if (tsk_verbose) { tsk_fprintf(stderr, "dino_buf->di_format & XFS_DINODE_FMT_EXTENTS == false (XFS_DINODE_FMT_BTREE) \n"); }
+
+        fs_meta->content_type = TSK_FS_META_CONTENT_TYPE_XFS_FMT_BTREE;
+
+        /*
+        Walk the b+tree
+        */
+    }
+
+    if (tsk_verbose) { tsk_fprintf(stderr, "xfs_dinode_copy: fs_meta->content_len = %d, fs_meta->content_ptr = 0x %x, fs_meta->content_type = %d \n", fs_meta->content_len, fs_meta->content_ptr, fs_meta->content_type); }
+
+    fs_meta->flags = xfs_inode_getallocflag(xfsfs, inum, dino_buf);
+
+    if(fs_meta->flags == 0)
+    {
+        tsk_error_reset();
+        tsk_error_set_errno(TSK_ERR_FS_INODE_COR);
+        tsk_error_set_errstr2("%s: Inode %" PRIuINUM " is not found in the B+Tree", myname,
+            inum);
+        return 1;
+    }
+
+    /*
+     * Apply the used/unused restriction.
+     */
+    fs_meta->flags = (TSK_FS_META_FLAG_ENUM) (fs_meta->flags | (fs_meta->ctime ?
+        TSK_FS_META_FLAG_USED : TSK_FS_META_FLAG_UNUSED));
+
+    return 0;
+}
+
+/* xfs_inode_lookup - lookup inode, external interface
+ *
+ * Returns 1 on error and 0 on success
+ *
+ */
+
+static uint8_t
+xfs_inode_lookup(TSK_FS_INFO * fs, TSK_FS_FILE * a_fs_file,
+    TSK_INUM_T inum)
+{
+    char *myname = "xfs_inode_lookup";
+    XFSFS_INFO *xfsfs = (XFSFS_INFO *) fs;
+    xfs_dinode_t *dino_buf = NULL;
+    ssize_t size = 0;
+
+    if (a_fs_file == NULL) {
+        tsk_error_set_errno(TSK_ERR_FS_ARG);
+        tsk_error_set_errstr("%s: fs_file is NULL", myname);
+        return 1;
+    }
+
+    if (a_fs_file->meta == NULL) {
+        if ((a_fs_file->meta =
+                tsk_fs_meta_alloc(xfsfs->inode_size)) == NULL)
+            return 1;
+    }
+    else {
+        tsk_fs_meta_reset(a_fs_file->meta);
+    }
+
+    size =
+        xfsfs->inode_size >
+        sizeof(xfs_dinode) ? xfsfs->fs->sb_inodesize : sizeof(xfs_dinode);
+    if ((dino_buf = (xfs_dinode_t *) tsk_malloc(size)) == NULL) {
+        return 1;
+    }
+
+    if (xfs_dinode_load(xfsfs, inum, dino_buf)) {
+        free(dino_buf);
+        return 1;
+    }
+
+    if (xfs_dinode_copy(xfsfs, a_fs_file->meta, inum, dino_buf)) {
+        free(dino_buf);
+        return 1;
+    }
+
+    free(dino_buf);
+    return 0;
+}
+
+/* xfs_inode_walk - inode iterator
+ *
+ * flags used: TSK_FS_META_FLAG_USED, TSK_FS_META_FLAG_UNUSED,
+ *  TSK_FS_META_FLAG_ALLOC, TSK_FS_META_FLAG_UNALLOC, TSK_FS_META_FLAG_ORPHAN
+ *
+ *  Return 1 on error and 0 on success
+*/
+
+uint8_t
+xfs_inode_walk(TSK_FS_INFO * fs, TSK_INUM_T start_inum,
+    TSK_INUM_T end_inum, TSK_FS_META_FLAG_ENUM flags,
+    TSK_FS_META_WALK_CB a_action, void *a_ptr)
+{
+    char *myname = "xfsfs_inode_walk";
+    XFSFS_INFO *xfsfs = (XFSFS_INFO *) fs;
+    TSK_FS_FILE *fs_file;
+    unsigned int size = 0;
+    xfs_dinode_t *dino_buf = NULL;
+    TSK_INUM_T inum;
+    unsigned int myflags = 0;
+
+    // clean up any error messages that are lying around
+    tsk_error_reset();
+
+    /*
+     * Sanity checks.
+     */
+    if (start_inum < fs->first_inum || start_inum > fs->last_inum) {
+        tsk_error_reset();
+        tsk_error_set_errno(TSK_ERR_FS_WALK_RNG);
+        tsk_error_set_errstr("%s: start inode: %" PRIuINUM "", myname,
+            start_inum);
+        return 1;
+    }
+
+    if (end_inum < fs->first_inum || end_inum > fs->last_inum
+        || end_inum < start_inum) {
+        tsk_error_reset();
+        tsk_error_set_errno(TSK_ERR_FS_WALK_RNG);
+        tsk_error_set_errstr("%s: end inode: %" PRIuINUM "", myname,
+            end_inum);
+        return 1;
+    }
+
+    if (((flags & TSK_FS_META_FLAG_ALLOC) == 0) &&
+        ((flags & TSK_FS_META_FLAG_UNALLOC) == 0)) {
+        flags = (TSK_FS_META_FLAG_ENUM) (flags | (TSK_FS_META_FLAG_ALLOC | TSK_FS_META_FLAG_UNALLOC));
+    }
+
+    /* If neither of the USED or UNUSED flags are set, then set them
+     * both
+     */
+    if (((flags & TSK_FS_META_FLAG_USED) == 0) &&
+        ((flags & TSK_FS_META_FLAG_UNUSED) == 0)) {
+        flags = (TSK_FS_META_FLAG_ENUM) (flags | (TSK_FS_META_FLAG_USED | TSK_FS_META_FLAG_UNUSED));
+    }
+
+    if ((fs_file = tsk_fs_file_alloc(fs)) == NULL)
+        return 1;
+    if ((fs_file->meta =
+            tsk_fs_meta_alloc(xfsfs->inode_size)) == NULL)
+    {
+        // TODO: free fs_file?
+        return 1;
+    }
+
+    /*
+     * Iterate.
+     */
+    size =
+        xfsfs->fs->sb_inodesize >
+        sizeof(xfs_dinode) ? xfsfs->fs->sb_inodesize : sizeof(xfs_dinode);
+    if ((dino_buf = (xfs_dinode_t *) tsk_malloc(size)) == NULL) {
+        return 1;
+    }
+
+    // The proper way to iterate through all inodes is to traverse each AGI tree, but this will only discover what an OS can discover, besides the need for an optimization
+    // We "brute-force" inodes instead by sequentially walking all inodes until the end of the image, silently skipping those where magic doesn't match an expected "IN"
+    // A good idea would be to cross check
+    for (inum = start_inum; inum <= end_inum; inum++) {
+        int retval;
+
+        if (xfs_dinode_load(xfsfs, inum, dino_buf)) {
+            tsk_fs_file_close(fs_file);
+            free(dino_buf);
+            return 1;
+        }
+
+        if (dino_buf->di_core.di_magic != 0x4e49) {
+            continue;
+        }
+
+        myflags = xfs_inode_getallocflag(xfsfs, inum, dino_buf);
+
+        if (myflags == 0)
+        {
+            // skip a non-tracked inode
+            continue;
+        }
+
+        /*
+         * Apply the used/unused restriction.
+         */
+        myflags |= (dino_buf->di_core.di_ctime.t_sec || dino_buf->di_core.di_ctime.t_nsec) ? TSK_FS_META_FLAG_USED : TSK_FS_META_FLAG_UNUSED;
+
+        if (tsk_verbose) { tsk_fprintf(stderr, "flags = %d, myflags = %d \n", flags, myflags); }
+
+        if ((flags & myflags) != myflags)
+            continue;
+
+        /*
+         * Fill in a file system-independent inode structure and pass control
+         * to the application.
+         */
+        if (xfs_dinode_copy(xfsfs, fs_file->meta, inum, dino_buf)) {
+            tsk_fs_meta_close(fs_file->meta);
+            free(dino_buf);
+            return 1;
+        }
+
+        retval = a_action(fs_file, a_ptr);
+        if (retval == TSK_WALK_STOP) {
+            tsk_fs_file_close(fs_file);
+            free(dino_buf);
+            return 0;
+        }
+        else if (retval == TSK_WALK_ERROR) {
+            tsk_fs_file_close(fs_file);
+            free(dino_buf);
+            return 1;
+        }
+    }
+
+    /*
+     * Cleanup.
+     */
+    tsk_fs_file_close(fs_file);
+    free(dino_buf);
+
+    return 0;
+}
+
+TSK_FS_BLOCK_FLAG_ENUM
+xfs_block_getflags(TSK_FS_INFO * a_fs, TSK_DADDR_T a_addr)
+{
+    XFSFS_INFO *xfsfs = (XFSFS_INFO *) a_fs;
+    TSK_DADDR_T ag_start = 0;
+    TSK_OFF_T offset = 0;
+    unsigned int len = 0;
+    xfs_agf *agf = NULL;
+    xfs_agblock_t *agfl = NULL;
+    unsigned int agfl_cur_len = 0;
+    TSK_FS_META_FLAG_ENUM inode_flag = (TSK_FS_META_FLAG_ENUM) 0;
+    xfs_alloc_ptr_t cur_sblock_num = 0;
+    xfs_btree_sblock_t *cur_btree_sblock = NULL;
+    uint32_t cur_key = 0;
+    ssize_t cnt;
+    bool found;
+
+    // actually, determining the status of a block in a general case, without the reverse-mapping B+tree is difficult, or at least nonoptimal
+    // but let's try
+
+
+    // 0 - superblock, agf, agi, agfl
+    // 1 - inobt
+    // 2 - free space b+tree (key is block num)
+    // 3 - free space b+tree (key is block count)
+    // 4 -7 - free list ... "With a freshly made filesystem, 4 blocks are reserved immediately after the free space B+tree root blocks (blocks 4
+    //        to 7). As they are used up as the free space fragments, additional blocks will be reserved from the AG and added to
+    //        the free list array. This size may increase as features are added." (c) http://ftp.ntu.edu.tw/linux/utils/fs/xfs/docs/xfs_filesystem_structure.pdf
+    //
+
+    if (a_addr % xfsfs->fs->sb_agblocks <= 7)
+        return (TSK_FS_BLOCK_FLAG_ENUM) (TSK_FS_BLOCK_FLAG_META | TSK_FS_BLOCK_FLAG_ALLOC);
+
+
+    ag_start = rounddown(a_addr, xfsfs->fs->sb_agblocks);
+
+    // Check agfl
+
+    len = sizeof(xfs_agf);
+    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 = %" PRId64 ", sect_size = %u, len = %u \n", ag_start, xfsfs->fs->sb_sectsize, len); }
+    cnt = tsk_fs_read(&xfsfs->fs_info, (TSK_OFF_T) (ag_start * xfsfs->fs->sb_blocksize + 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);
+        free(agf);
+        tsk_fs_free((TSK_FS_INFO *)xfsfs);
+        return (TSK_FS_BLOCK_FLAG_ENUM) NULL;
+    }
+
+    agf->agf_versionnum = tsk_getu32(TSK_BIG_ENDIAN, &agf->agf_versionnum);
+    agf->agf_seqno = tsk_getu32(TSK_BIG_ENDIAN, &agf->agf_seqno);
+    agf->agf_length = tsk_getu32(TSK_BIG_ENDIAN, &agf->agf_length);
+    agf->agf_roots[0] = tsk_getu32(TSK_BIG_ENDIAN, &agf->agf_roots[0]);
+    agf->agf_roots[1] = tsk_getu32(TSK_BIG_ENDIAN, &agf->agf_roots[1]);
+    agf->agf_spare0 = tsk_getu32(TSK_BIG_ENDIAN, &agf->agf_spare0);
+    agf->agf_levels[0] = tsk_getu32(TSK_BIG_ENDIAN, &agf->agf_levels[0]);
+    agf->agf_levels[1] = tsk_getu32(TSK_BIG_ENDIAN, &agf->agf_levels[1]);
+    agf->agf_spare1 = tsk_getu32(TSK_BIG_ENDIAN, &agf->agf_spare1);
+    agf->agf_flfirst = tsk_getu32(TSK_BIG_ENDIAN, &agf->agf_flfirst);
+    agf->agf_fllast = tsk_getu32(TSK_BIG_ENDIAN, &agf->agf_fllast);
+    agf->agf_flcount = tsk_getu32(TSK_BIG_ENDIAN, &agf->agf_flcount);
+    agf->agf_freeblks = tsk_getu32(TSK_BIG_ENDIAN, &agf->agf_freeblks);
+    agf->agf_longest = tsk_getu32(TSK_BIG_ENDIAN, &agf->agf_longest);
+    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); }
+
+    // agfl is one sector and 4 blocks
+    len = (xfsfs->fs->sb_blocksize * 4 + xfsfs->fs->sb_sectsize) * sizeof(xfs_agblock_t);
+    if ((agfl = (xfs_agblock_t *) tsk_malloc(len)) == NULL)
+        return (TSK_FS_BLOCK_FLAG_ENUM) NULL;
+
+    offset = ag_start * xfsfs->fs->sb_blocksize + xfsfs->fs->sb_sectsize * 3;
+    len = xfsfs->fs->sb_sectsize;
+    if (XFS_SB_VERSION_NUM(xfsfs->fs) == 5)
+    {
+        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_fs_free((TSK_FS_INFO *)xfsfs);
+            return (TSK_FS_BLOCK_FLAG_ENUM) NULL;
+        }
+
+        offset += sizeof(xfs_agfl);
+        len -= sizeof(xfs_agfl);
+    }
+    agfl_cur_len = len;
+
+    cnt = tsk_fs_read(&xfsfs->fs_info, offset, (char *) agfl, 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);
+        free(agf);
+        tsk_fs_free((TSK_FS_INFO *)xfsfs);
+        return (TSK_FS_BLOCK_FLAG_ENUM) NULL;
+    }
+
+    // "As they are used up as the free space fragments, additional blocks will be reserved from the AG and added to
+    // the free list array. This size may increase as features are added." (c) http://ftp.ntu.edu.tw/linux/utils/fs/xfs/docs/xfs_filesystem_structure.pdf
+    // Q: will they be reserved right after the 7th block?
+
+    offset = ag_start * xfsfs->fs->sb_blocksize + xfsfs->fs->sb_blocksize * 4;
+    len = xfsfs->fs->sb_blocksize * 4;
+    cnt = tsk_fs_read(&xfsfs->fs_info, (TSK_OFF_T) offset, ((char *) agfl) + agfl_cur_len, 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);
+        free(agf);
+        tsk_fs_free((TSK_FS_INFO *)xfsfs);
+        return (TSK_FS_BLOCK_FLAG_ENUM) NULL;
+    }
+
+    for (cur_key = agf->agf_flfirst; cur_key <= agf->agf_fllast; cur_key++)
+    {
+        if (a_addr % xfsfs->fs->sb_agblocks == tsk_getu32(TSK_BIG_ENDIAN, &agfl[cur_key]))
+        {
+            // free
+            return (TSK_FS_BLOCK_FLAG_ENUM) (TSK_FS_BLOCK_FLAG_META | TSK_FS_BLOCK_FLAG_UNALLOC);
+        }
+    }
+
+    // Pet trick here: if the block possibly stores inodes, try to read the corresponding inode flags
+    if (tsk_verbose) { tsk_fprintf(stderr, "trying to treat block %u as inode %u \n", a_addr, a_addr * xfsfs->fs->sb_blocksize / xfsfs->fs->sb_inodesize); }
+
+    inode_flag =
+      //(TSK_FS_META_FLAG_ENUM) 0;
+        xfs_inode_getallocflag(xfsfs, a_addr * xfsfs->fs->sb_blocksize / xfsfs->fs->sb_inodesize, NULL);
+    if (inode_flag)
+    {
+        // free
+        if (inode_flag == TSK_FS_META_FLAG_ALLOC)
+            return (TSK_FS_BLOCK_FLAG_ENUM) (TSK_FS_BLOCK_FLAG_META | TSK_FS_BLOCK_FLAG_ALLOC);
+        else if (inode_flag == TSK_FS_META_FLAG_UNALLOC)
+            return (TSK_FS_BLOCK_FLAG_ENUM) (TSK_FS_BLOCK_FLAG_META | TSK_FS_META_FLAG_UNALLOC);
+    }
+
+    // Completed checking all meta blocks, now checking content blocks
+
+    // Checking the free space B+tree
+
+    memset(recs, 0, sizeof(recs));
+    memset(ptrs, 0, sizeof(ptrs));
+
+    if ((cur_btree_sblock = (xfs_btree_sblock_t *) tsk_malloc(sizeof(xfs_btree_sblock_t))) == NULL)
+    {
+        // TODO: free other allocated structures
+        free(agf);
+        return (TSK_FS_BLOCK_FLAG_ENUM) NULL;
+    }
+
+    // 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); }
+    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) {
+        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);
+        free(agf);
+        tsk_fs_free((TSK_FS_INFO *)xfsfs);
+        return (TSK_FS_BLOCK_FLAG_ENUM) NULL;
+    }
+
+    cur_btree_sblock->bb_level = tsk_getu16(TSK_BIG_ENDIAN, &cur_btree_sblock->bb_level);
+    cur_btree_sblock->bb_numrecs = tsk_getu16(TSK_BIG_ENDIAN, &cur_btree_sblock->bb_numrecs);
+    cur_btree_sblock->bb_leftsib = tsk_getu32(TSK_BIG_ENDIAN, &cur_btree_sblock->bb_leftsib);
+    cur_btree_sblock->bb_rightsib = tsk_getu32(TSK_BIG_ENDIAN, &cur_btree_sblock->bb_rightsib);
+
+    if (tsk_verbose) { tsk_fprintf(stderr, "cur_btree_sblock = %x, cur_btree_sblock->bb_magic = %.4s \n", cur_btree_sblock, &cur_btree_sblock->bb_magic); }
+
+    // while not leaf node
+    while(cur_btree_sblock->bb_level > 0)
+    {
+        // read all the keys
+        len = cur_btree_sblock->bb_numrecs * sizeof(xfs_alloc_key_t);
+        cnt = tsk_fs_read(&xfsfs->fs_info, (TSK_OFF_T) xfsfs->fs->sb_blocksize * cur_sblock_num + sizeof(xfs_btree_sblock_t), (char *) 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 = %u, len = %u", cnt, len);
+            free(agf);
+            tsk_fs_free((TSK_FS_INFO *)xfsfs);
+            return (TSK_FS_BLOCK_FLAG_ENUM) NULL;
+        }
+
+        // read all the node pointers
+        len = cur_btree_sblock->bb_numrecs * sizeof(xfs_alloc_ptr_t);
+        cnt = tsk_fs_read(&xfsfs->fs_info, (TSK_OFF_T) xfsfs->fs->sb_blocksize * cur_sblock_num + sizeof(xfs_btree_sblock_t) + cur_btree_sblock->bb_numrecs * sizeof(xfs_alloc_key_t), (char *) 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 = %u, len = %u", cnt, len);
+            free(agf);
+            tsk_fs_free((TSK_FS_INFO *)xfsfs);
+            return (TSK_FS_BLOCK_FLAG_ENUM) NULL;
+        }
+
+        // iterate over the keys
+        found = false;
+        for(cur_key = 0; cur_key < cur_btree_sblock->bb_numrecs; cur_key++)
+        {
+            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(a_addr >= recs[cur_key].ar_startblock && a_addr - recs[cur_key].ar_startblock < recs[cur_key].ar_blockcount)
+            {
+                // go one level down in b+tree
+                found = true;
+                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); }
+
+                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) {
+                    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);
+                    free(agf);
+                    tsk_fs_free((TSK_FS_INFO *)xfsfs);
+                    return (TSK_FS_BLOCK_FLAG_ENUM) NULL;
+                }
+
+                cur_btree_sblock->bb_level = tsk_getu16(TSK_BIG_ENDIAN, &cur_btree_sblock->bb_level);
+                cur_btree_sblock->bb_numrecs = tsk_getu16(TSK_BIG_ENDIAN, &cur_btree_sblock->bb_numrecs);
+                cur_btree_sblock->bb_leftsib = tsk_getu32(TSK_BIG_ENDIAN, &cur_btree_sblock->bb_leftsib);
+                cur_btree_sblock->bb_rightsib = tsk_getu32(TSK_BIG_ENDIAN, &cur_btree_sblock->bb_rightsib);
+            }
+        }
+
+        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); }
+            free(agf);
+            free(cur_btree_sblock);
+            return (TSK_FS_BLOCK_FLAG_ENUM) (TSK_FS_BLOCK_FLAG_CONT | TSK_FS_BLOCK_FLAG_ALLOC);
+        }
+    }
+
+    // read all the records
+    len = cur_btree_sblock->bb_numrecs * sizeof(xfs_alloc_rec_t);
+    cnt = tsk_fs_read(&xfsfs->fs_info, (TSK_OFF_T) xfsfs->fs->sb_blocksize * cur_sblock_num + sizeof(xfs_btree_sblock_t), (char *) recs, len);
+
+    // iterate over the keys
+    found = false;
+    for(cur_key = 0; cur_key < cur_btree_sblock->bb_numrecs; cur_key++)
+    {
+        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",
+            cur_key, recs[cur_key].ar_startblock, recs[cur_key].ar_blockcount); }
+
+        if(a_addr >= recs[cur_key].ar_startblock && a_addr - 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",
+                cur_btree_sblock->bb_level, cur_key, recs[cur_key].ar_startblock, recs[cur_key].ar_blockcount); }
+            free(agf);
+            free(cur_btree_sblock);
+            return (TSK_FS_BLOCK_FLAG_ENUM) (TSK_FS_BLOCK_FLAG_CONT | TSK_FS_BLOCK_FLAG_UNALLOC);
+        }
+    }
+
+    free(agf);
+    free(cur_btree_sblock);
+
+    // The block is neither metadata, nor in a free list, therefore it's allocated
+    return (TSK_FS_BLOCK_FLAG_ENUM) (TSK_FS_BLOCK_FLAG_CONT | TSK_FS_BLOCK_FLAG_ALLOC);
+}
+
+
+/* xfs_block_walk - block iterator
+ *
+ * flags: TSK_FS_BLOCK_FLAG_ALLOC, TSK_FS_BLOCK_FLAG_UNALLOC, TSK_FS_BLOCK_FLAG_CONT,
+ *  TSK_FS_BLOCK_FLAG_META
+ *
+ *  Return 1 on error and 0 on success
+*/
+
+uint8_t
+xfs_block_walk(TSK_FS_INFO * a_fs, TSK_DADDR_T a_start_blk,
+    TSK_DADDR_T a_end_blk, TSK_FS_BLOCK_WALK_FLAG_ENUM a_flags,
+    TSK_FS_BLOCK_WALK_CB a_action, void *a_ptr)
+
+{
+    char *myname = "xfs_block_walk";
+    TSK_FS_BLOCK *fs_block;
+    TSK_DADDR_T addr;
+
+    // clean up any error messages that are lying around
+    tsk_error_reset();
+
+    /*
+     * Sanity checks.
+     */
+    if (a_start_blk < a_fs->first_block || a_start_blk > a_fs->last_block) {
+        tsk_error_reset();
+        tsk_error_set_errno(TSK_ERR_FS_WALK_RNG);
+        tsk_error_set_errstr("%s: start block: %" PRIuDADDR, myname,
+            a_start_blk);
+        return 1;
+    }
+    if (a_end_blk < a_fs->first_block || a_end_blk > a_fs->last_block
+        || a_end_blk < a_start_blk) {
+        tsk_error_reset();
+        tsk_error_set_errno(TSK_ERR_FS_WALK_RNG);
+        tsk_error_set_errstr("%s: end block: %" PRIuDADDR, myname,
+            a_end_blk);
+        return 1;
+    }
+
+    /* Sanity check on a_flags -- make sure at least one ALLOC is set */
+    if (((a_flags & TSK_FS_BLOCK_WALK_FLAG_ALLOC) == 0) &&
+        ((a_flags & TSK_FS_BLOCK_WALK_FLAG_UNALLOC) == 0)) {
+        a_flags = (TSK_FS_BLOCK_WALK_FLAG_ENUM) (a_flags |
+            (TSK_FS_BLOCK_WALK_FLAG_ALLOC |
+            TSK_FS_BLOCK_WALK_FLAG_UNALLOC));
+    }
+    if (((a_flags & TSK_FS_BLOCK_WALK_FLAG_META) == 0) &&
+        ((a_flags & TSK_FS_BLOCK_WALK_FLAG_CONT) == 0)) {
+        a_flags = (TSK_FS_BLOCK_WALK_FLAG_ENUM) (a_flags |
+            (TSK_FS_BLOCK_WALK_FLAG_CONT |
+            TSK_FS_BLOCK_WALK_FLAG_META));
+    }
+
+    if ((fs_block = tsk_fs_block_alloc(a_fs)) == NULL) {
+        return 1;
+    }
+
+    /*
+     * Iterate
+     */
+
+    for (addr = a_start_blk; addr <= a_end_blk; addr++) {
+        int retval;
+        int myflags;
+
+        myflags = xfs_block_getflags(a_fs, addr);
+
+        // test if we should call the callback with this one
+        if ((myflags & TSK_FS_BLOCK_FLAG_ALLOC)
+            && (!(a_flags & TSK_FS_BLOCK_WALK_FLAG_ALLOC)))
+            continue;
+        else if ((myflags & TSK_FS_BLOCK_FLAG_UNALLOC)
+            && (!(a_flags & TSK_FS_BLOCK_WALK_FLAG_UNALLOC)))
+            continue;
+
+        if (a_flags & TSK_FS_BLOCK_WALK_FLAG_AONLY)
+            myflags |= TSK_FS_BLOCK_FLAG_AONLY;
+
+        if (tsk_fs_block_get_flag(a_fs, fs_block, addr, (TSK_FS_BLOCK_FLAG_ENUM) myflags) == NULL) {
+            tsk_error_set_errstr2("%s: block %" PRIuDADDR,
+                myname, addr);
+            tsk_fs_block_free(fs_block);
+            return 1;
+        }
+
+        retval = a_action(fs_block, a_ptr);
+        if (retval == TSK_WALK_STOP) {
+            break;
+        }
+        else if (retval == TSK_WALK_ERROR) {
+            tsk_fs_block_free(fs_block);
+            return 1;
+        }
+    }
+
+    /*
+     * Cleanup.
+     */
+    tsk_fs_block_free(fs_block);
+    return 0;
+}
+
+/** \internal
+ * Add the data runs and extents to the file attributes.
+ *
+ * @param fs_file File system to analyze
+ * @returns 0 on success, 1 otherwise
+ */
+static uint8_t
+xfs_load_attrs(TSK_FS_FILE * fs_file)
+{
+    TSK_FS_META *fs_meta = fs_file->meta;
+    TSK_FS_INFO *fs_info = fs_file->fs_info;
+    TSK_FS_ATTR *fs_attr;
+    TSK_OFF_T length = 0;
+    xfs_bmbt_irec_t irec;
+
+    if (fs_meta->attr != NULL) {
+        tsk_fs_attrlist_markunused(fs_meta->attr);
+    }
+    else {
+        fs_meta->attr = tsk_fs_attrlist_alloc();
+    }
+
+    if ((fs_attr =
+         tsk_fs_attrlist_getnew(fs_meta->attr,
+                                TSK_FS_ATTR_NONRES)) == NULL) {
+        return 1;
+    }
+
+    length = roundup(fs_meta->size, fs_info->block_size);
+
+    if (tsk_fs_attr_set_run(fs_file, fs_attr, NULL, NULL,
+                            TSK_FS_ATTR_TYPE_DEFAULT, TSK_FS_ATTR_ID_DEFAULT,
+                            fs_meta->size, fs_meta->size, length, (TSK_FS_ATTR_FLAG_ENUM) 0, 0)) {
+        return 1;
+    }
+
+    if (fs_meta->content_type == TSK_FS_META_CONTENT_TYPE_XFS_LOCAL)
+    {
+        // don't add data runs here
+    }
+    else if (fs_meta->content_type == TSK_FS_META_CONTENT_TYPE_XFS_EXTENTS)
+    {
+        xfs_bmbt_rec_t* addr_ptr = (xfs_bmbt_rec_t *) fs_meta->content_ptr;
+        uint16_t extent_count = fs_meta->content_len / sizeof(xfs_bmbt_rec_t);
+        for (uint16_t extent_num = 0; extent_num < extent_count; extent_num++)
+        {
+            if (tsk_verbose) { tsk_fprintf(stderr, "extent_num = %d, sizeof(xfs_bmbt_rec_t) = %d, fs_meta->content_len = %d \n", extent_num, sizeof(xfs_bmbt_rec_t), fs_meta->content_len); }
+
+            xfs_bmbt_disk_get_all(&addr_ptr[extent_num], &irec);
+
+            if (tsk_verbose) {
+                xfs_bmbt_disk_get_all(&addr_ptr[extent_num], &irec);
+                tsk_fprintf(stderr, "extent_num = %d, adding br_startblock = %d / br_blockcount = %d \n", extent_num, irec.br_startblock, irec.br_blockcount);
+            }
+
+            TSK_FS_ATTR_RUN *data_run = tsk_fs_attr_run_alloc();
+            if (data_run == NULL) {
+                return 1;
+            }
+            data_run->addr = irec.br_startblock;
+            data_run->len = irec.br_blockcount;
+
+            if (tsk_fs_attr_add_run(fs_info, fs_attr, data_run)) {
+                tsk_fs_attr_run_free(data_run);
+                return 1;
+            }
+        }
+    }
+
+    fs_meta->attr_state = TSK_FS_META_ATTR_STUDIED;
+
+    return 0;
+}
+
+/**
+* Print details about the file system to a file handle.
+*
+* @param fs File system to print details on
+* @param hFile File handle to print text to
+*
+* @returns 1 on error and 0 on success
+*/
+static uint8_t
+    xfsfs_fsstat(TSK_FS_INFO * fs, FILE * hFile)
+{
+    XFSFS_INFO *xfs = (XFSFS_INFO *) fs;
+    xfs_sb_t *sb = xfs->fs;
+
+    // clean up any error messages that are lying around
+    tsk_error_reset();
+
+    tsk_fprintf(hFile, "FILE SYSTEM INFORMATION\n");
+    tsk_fprintf(hFile, "--------------------------------------------\n");
+    tsk_fprintf(hFile, "File System Type: %s\n", "XFS");
+    tsk_fprintf(hFile, "Volume Name: %s\n", sb->sb_fname);
+    tsk_fprintf(hFile, "Volume ID: %" PRIx32 "-%" PRIx16 "-%" PRIx16 "-%" PRIx16 "-%" PRIx32 "%" PRIx16  "\n",
+        tsk_getu32(fs->endian, &sb->sb_uuid.b[0]),
+            tsk_getu16(fs->endian, &sb->sb_uuid.b[4]),
+            tsk_getu16(fs->endian, &sb->sb_uuid.b[6]),
+            tsk_getu16(fs->endian, &sb->sb_uuid.b[8]),
+            tsk_getu32(fs->endian, &sb->sb_uuid.b[10]),
+            tsk_getu16(fs->endian, &sb->sb_uuid.b[14]));
+    tsk_fprintf(hFile, "Features Compat: %" PRIu32 "\n", sb->sb_features_compat);
+    tsk_fprintf(hFile, "Features Read-Only Compat: %" PRIu32 "\n", sb->sb_features_ro_compat);
+    if (sb->sb_features_ro_compat) {
+        tsk_fprintf(hFile, "Read Only Compat Features: ");
+        if (sb->sb_features_ro_compat & XFS_SB_FEAT_RO_COMPAT_FINOBT)
+            tsk_fprintf(hFile, "Free inode B+tree, ");
+        if (sb->sb_features_ro_compat & XFS_SB_FEAT_RO_COMPAT_RMAPBT)
+            tsk_fprintf(hFile, "Reverse mapping B+tree, ");
+        if (sb->sb_features_ro_compat & XFS_SB_FEAT_RO_COMPAT_REFLINK)
+            tsk_fprintf(hFile, "Reference count B+tree, ");
+        tsk_fprintf(hFile, "\n");
+    }
+
+    // todo: sb_versionnum feature flags
+    // todo: sb_qflags
+
+    tsk_fprintf(hFile, "Features Incompat: %" PRIu32 "\n", sb->sb_features_incompat);
+    if (sb->sb_features_incompat) {
+        tsk_fprintf(hFile, "InCompat Features: ");
+
+        if (sb->sb_features_incompat & XFS_SB_FEAT_INCOMPAT_FTYPE)
+            tsk_fprintf(hFile, "Directory file type, ");
+        if (sb->sb_features_incompat & XFS_SB_FEAT_INCOMPAT_SPINODES)
+            tsk_fprintf(hFile, "Sparse inodes, ");
+        if (sb->sb_features_incompat & XFS_SB_FEAT_INCOMPAT_META_UUID)
+            tsk_fprintf(hFile, "Metadata UUID, ");
+
+        tsk_fprintf(hFile, "\n");
+    }
+
+    tsk_fprintf(hFile, "CRC: %" PRIu32 "\n", sb->sb_crc);
+
+    /* Print journal information */
+    // TODO
+
+    tsk_fprintf(hFile, "\nMETADATA INFORMATION\n");
+    tsk_fprintf(hFile, "--------------------------------------------\n");
+    tsk_fprintf(hFile, "Allocated inode count : %" PRIuINUM "\n", sb->sb_icount);
+    tsk_fprintf(hFile, "Root Directory: %" PRIuINUM "\n", fs->root_inum);
+    tsk_fprintf(hFile, "Free Inodes: %" PRIuINUM "\n", sb->sb_ifree);
+    tsk_fprintf(hFile, "Inode Size: %" PRIu16 "\n", sb->sb_inodesize);
+    tsk_fprintf(hFile, "Extent Size: %" PRIu32 "\n", sb->sb_rextsize);
+    tsk_fprintf(hFile, "Free Extent Count: %" PRIu64 "\n", sb->sb_frextents);
+
+    tsk_fprintf(hFile, "\nCONTENT INFORMATION\n");
+    tsk_fprintf(hFile, "--------------------------------------------\n");
+    tsk_fprintf(hFile, "Block Range: %" PRIuDADDR " - %" PRIuDADDR "\n",
+        fs->first_block, fs->last_block);
+    if (fs->last_block != fs->last_block_act)
+        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, "Free Blocks: %" PRIu64 "\n", sb->sb_fdblocks);
+    tsk_fprintf(hFile, "Sector Size: %" PRIu16 "\n", sb->sb_sectsize);
+
+    tsk_fprintf(hFile, "\nALLOCATION GROUP INFORMATION\n");
+    tsk_fprintf(hFile, "--------------------------------------------\n");
+    tsk_fprintf(hFile, "Number of Allocation Groups: %" PRIu32 "\n", sb->sb_agcount);
+    tsk_fprintf(hFile, "Blocks per allocation group: %" PRIu32 "\n", sb->sb_agblocks);
+
+    // TODO: print per-AG statss
+    // for (i = 0; i < sb->sb_agcount; i++) {
+        // TODO: print per-AG statss
+
+        // such as
+        // agf_length
+        // Specifies the size of the AG in filesystem blocks. For all AGs except the last, this must be equal to the superblock’s sb_agblocks value. For the last AG, this could be less than the sb_agblocks value. It is this
+        // value that should be used to determine the size of the AG.
+
+    // }
+
+    return 0;
+}
+
+
+typedef struct {
+    FILE *hFile;
+    int idx;
+} XFS_PRINT_ADDR;
+
+
+/* Callback for istat to print the block addresses */
+static TSK_WALK_RET_ENUM
+print_addr_act(TSK_FS_FILE * fs_file, TSK_OFF_T a_off, TSK_DADDR_T addr,
+    char *buf, size_t size, TSK_FS_BLOCK_FLAG_ENUM flags, void *a_ptr)
+{
+    TSK_FS_INFO *fs = fs_file->fs_info;
+    XFS_PRINT_ADDR *print = (XFS_PRINT_ADDR *) a_ptr;
+
+    if (flags & TSK_FS_BLOCK_FLAG_CONT) {
+        int i, s;
+        /* cycle through the blocks if they exist */
+        for (i = 0, s = (int) size; s > 0; s -= fs->block_size, i++) {
+
+            /* sparse file */
+            if (addr)
+                tsk_fprintf(print->hFile, "%" PRIuDADDR " ", addr + i);
+            else
+                tsk_fprintf(print->hFile, "0 ");
+
+            if (++(print->idx) == 8) {
+                tsk_fprintf(print->hFile, "\n");
+                print->idx = 0;
+            }
+        }
+    }
+
+    return TSK_WALK_CONT;
+}
+
+/**
+ * Print details on a specific file to a file handle.
+ *
+ * @param fs File system file is located in
+ * @param hFile File handle to print text to
+ * @param inum Address of file in file system
+ * @param numblock The number of blocks in file to force print (can go beyond file size)
+ * @param sec_skew Clock skew in seconds to also print times in
+ *
+ * @returns 1 on error and 0 on success
+ */
+static uint8_t
+xfs_istat(TSK_FS_INFO * fs, TSK_FS_ISTAT_FLAG_ENUM istat_flags, FILE * hFile, TSK_INUM_T inum,
+    TSK_DADDR_T numblock, int32_t sec_skew)
+{
+    XFSFS_INFO *xfsfs = (XFSFS_INFO *) fs;
+    xfs_dinode_t *dino_buf = NULL;
+    TSK_FS_FILE *fs_file;
+    TSK_FS_META *fs_meta;
+    void *attr_offset = 0;
+    XFS_PRINT_ADDR print;
+    char ls[12];
+    unsigned int size = 0;
+    char timeBuf[128];
+
+    if ((fs_file = tsk_fs_file_alloc(fs)) == NULL)
+        return 1;
+    if ((fs_file->meta =
+            tsk_fs_meta_alloc(xfsfs->inode_size)) == NULL)
+        {
+            return 1;
+        }
+
+    // clean up any error messages that are lying around
+    tsk_error_reset();
+
+    size =
+        xfsfs->fs->sb_inodesize >
+        sizeof(xfs_dinode) ? xfsfs->fs->sb_inodesize : sizeof(xfs_dinode);
+    if ((dino_buf = (xfs_dinode_t *) tsk_malloc(size)) == NULL) {
+        return 1;
+    }
+
+    if (xfs_dinode_load(xfsfs, inum, dino_buf)) {
+        free(dino_buf);
+        return 1;
+    }
+
+    if ((fs_file = tsk_fs_file_open_meta(fs, NULL, inum)) == NULL) {
+        free(dino_buf);
+        return 1;
+    }
+    fs_meta = fs_file->meta;
+
+    tsk_fprintf(hFile, "Inode: %" PRIuINUM "\n", inum);
+
+    tsk_fprintf(hFile, "%sAllocated\n",
+        (fs_meta->flags & TSK_FS_META_FLAG_ALLOC) ? "" : "Not ");
+
+    if (fs_meta->link)
+        tsk_fprintf(hFile, "symbolic link to: %s\n", fs_meta->link);
+
+    tsk_fprintf(hFile, "uid / gid: %" PRIuUID " / %" PRIuGID "\n",
+        fs_meta->uid, fs_meta->gid);
+
+    tsk_fs_meta_make_ls(fs_meta, ls, sizeof(ls));
+    tsk_fprintf(hFile, "mode: %s\n", ls);
+
+    tsk_fprintf(hFile, "Flags: ");
+    if (dino_buf->di_core.di_flags & XFS_DIFLAG_REALTIME)
+            tsk_fprintf(hFile, "Realtime, ");
+    if (dino_buf->di_core.di_flags & XFS_DIFLAG_PREALLOC)
+            tsk_fprintf(hFile, "Preallocated, ");
+    if (dino_buf->di_core.di_flags & XFS_DIFLAG_NEWRTBM)
+            tsk_fprintf(hFile, "NEWRTBM, ");
+    if (dino_buf->di_core.di_flags & XFS_DIFLAG_IMMUTABLE)
+            tsk_fprintf(hFile, "Immutable, ");
+    if (dino_buf->di_core.di_flags & XFS_DIFLAG_APPEND)
+            tsk_fprintf(hFile, "Append-only, ");
+    if (dino_buf->di_core.di_flags & XFS_DIFLAG_SYNC)
+            tsk_fprintf(hFile, "Sync, ");
+    if (dino_buf->di_core.di_flags & XFS_DIFLAG_NOATIME)
+            tsk_fprintf(hFile, "No A-Time, ");
+    if (dino_buf->di_core.di_flags & XFS_DIFLAG_NODUMP)
+            tsk_fprintf(hFile, "Do Not Dump, ");
+    if (dino_buf->di_core.di_flags & XFS_DIFLAG_RTINHERIT)
+            tsk_fprintf(hFile, "Inherit realtime, ");
+    if (dino_buf->di_core.di_flags & XFS_DIFLAG_PROJINHERIT)
+            tsk_fprintf(hFile, "Inheit di_projid, ");
+    if (dino_buf->di_core.di_flags & XFS_DIFLAG_NOSYMLINKS)
+            tsk_fprintf(hFile, "No symlinks, ");
+    if (dino_buf->di_core.di_flags & XFS_DIFLAG_EXTSIZE)
+            tsk_fprintf(hFile, "XFS_DIFLAG_EXTSIZE, ");
+    if (dino_buf->di_core.di_flags & XFS_DIFLAG_EXTSZINHERIT)
+            tsk_fprintf(hFile, "Inherit di_extsize, ");
+    if (dino_buf->di_core.di_flags & XFS_DIFLAG_NODEFRAG)
+            tsk_fprintf(hFile, "No defragmentation, ");
+    if (dino_buf->di_core.di_flags & XFS_DIFLAG_FILESTREAM)
+            tsk_fprintf(hFile, "Filestream allocator, ");
+
+    tsk_fprintf(hFile, "\n");
+
+    tsk_fprintf(hFile, "size: %" PRIuOFF "\n", fs_meta->size);
+    tsk_fprintf(hFile, "num of links: %d\n", fs_meta->nlink);
+
+    // Extended attributes
+    if (dino_buf->di_core.di_forkoff != 0)
+    {
+        xfs_dinode_core_t* _di_core = &dino_buf->di_core;
+        attr_offset = XFS_DFORK_PTR(_di_core, XFS_ATTR_FORK);
+        // tsk_fprintf(hFile, "attr_offset: 0x %x\n", attr_offset);
+
+        // TODO: parse extended attributes and test
+        // 14.4 Attribute Fork of http://ftp.ntu.edu.tw/linux/utils/fs/xfs/docs/xfs_filesystem_structure.pdf
+    }
+
+    if (sec_skew != 0) {
+        tsk_fprintf(hFile, "\nAdjusted Inode Times:\n");
+        if (fs_meta->mtime)
+            fs_meta->mtime -= sec_skew;
+        if (fs_meta->atime)
+            fs_meta->atime -= sec_skew;
+        if (fs_meta->ctime)
+            fs_meta->ctime -= sec_skew;
+        if (fs_meta->crtime)
+            fs_meta->crtime -= sec_skew;
+
+        tsk_fprintf(hFile, "Accessed:\t%s\n",
+            tsk_fs_time_to_str_subsecs(fs_meta->atime,
+                fs_meta->atime_nano, timeBuf));
+        tsk_fprintf(hFile, "File Modified:\t%s\n",
+            tsk_fs_time_to_str_subsecs(fs_meta->mtime,
+                fs_meta->mtime_nano, timeBuf));
+        tsk_fprintf(hFile, "Inode Modified:\t%s\n",
+            tsk_fs_time_to_str_subsecs(fs_meta->ctime,
+                fs_meta->ctime_nano, timeBuf));
+
+
+        if (fs_meta->mtime)
+            fs_meta->mtime += sec_skew;
+        if (fs_meta->atime)
+            fs_meta->atime += sec_skew;
+        if (fs_meta->ctime)
+            fs_meta->ctime += sec_skew;
+        if (fs_meta->crtime)
+            fs_meta->crtime += sec_skew;
+
+        tsk_fprintf(hFile, "\nOriginal Inode Times:\n");
+    }
+    else {
+        tsk_fprintf(hFile, "\nInode Times:\n");
+    }
+
+    tsk_fprintf(hFile, "Accessed:\t%s\n",
+            tsk_fs_time_to_str_subsecs(fs_meta->atime,
+                fs_meta->atime_nano, timeBuf));
+    tsk_fprintf(hFile, "File Modified:\t%s\n",
+        tsk_fs_time_to_str_subsecs(fs_meta->mtime,
+            fs_meta->mtime_nano, timeBuf));
+    tsk_fprintf(hFile, "Inode Modified:\t%s\n",
+        tsk_fs_time_to_str_subsecs(fs_meta->ctime,
+            fs_meta->ctime_nano, timeBuf));
+
+    if (dino_buf->di_core.di_version == 3)
+    {
+        // fs_meta->crtime only valid on v3 inodes (v5 filsystem)
+        tsk_fprintf(hFile, "File Created:\t%s\n",
+            tsk_fs_time_to_str_subsecs(fs_meta->crtime,
+                fs_meta->crtime_nano, timeBuf));
+    }
+
+    if (numblock > 0)
+        fs_meta->size = numblock * fs->block_size;
+
+    tsk_fprintf(hFile, "\nDirect Blocks:\n");
+
+    if (istat_flags & TSK_FS_ISTAT_RUNLIST) {
+        const TSK_FS_ATTR *fs_attr_default =
+            tsk_fs_file_attr_get_type(fs_file,
+                TSK_FS_ATTR_TYPE_DEFAULT, 0, 0);
+
+        if (tsk_verbose) { tsk_fprintf(hFile, "\n istat_flags & TSK_FS_ISTAT_RUNLIST = true, fs_attr_default = 0x %x\n", fs_attr_default);    }
+        if (fs_attr_default && (fs_attr_default->flags & TSK_FS_ATTR_NONRES)) {
+            if (tsk_fs_attr_print(fs_attr_default, hFile)) {
+                tsk_fprintf(hFile, "\nError creating run lists\n");
+                tsk_error_print(hFile);
+                tsk_error_reset();
+            }
+        }
+    }
+    else {
+        print.idx = 0;
+        print.hFile = hFile;
+
+        if (tsk_fs_file_walk(fs_file, TSK_FS_FILE_WALK_FLAG_AONLY,
+            print_addr_act, (void *)&print)) {
+            tsk_fprintf(hFile, "\nError reading file:  ");
+            tsk_error_print(hFile);
+            tsk_error_reset();
+        }
+        else if (print.idx != 0) {
+            tsk_fprintf(hFile, "\n");
+        }
+    }
+
+    tsk_fs_file_close(fs_file);
+    free(dino_buf);
+    return 0;
+}
+
+
+/* Directories */
+
+/** \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
+* FS_DIR structure is passed (i.e. NULL), then a new one will be created. If the return
+* value is error or corruption, then the FS_DIR structure could
+* have entries (depending on when the error occurred).
+*
+* @param a_fs File system to analyze
+* @param a_fs_dir Pointer to FS_DIR pointer. Can contain an already allocated
+* structure or a new structure.
+* @param a_addr Address of directory to process.
+* @returns error, corruption, ok etc.
+*/
+
+TSK_RETVAL_ENUM
+xfs_dir_open_meta(TSK_FS_INFO * a_fs, TSK_FS_DIR ** a_fs_dir,
+    TSK_INUM_T a_addr)
+{
+    XFSFS_INFO *xfs = (XFSFS_INFO *) a_fs;
+    TSK_FS_META *fs_meta;
+    uint8_t ftype_size = 0;
+    char *dirbuf = NULL;
+    TSK_OFF_T size;
+    TSK_FS_DIR *fs_dir;
+    TSK_RETVAL_ENUM retval = TSK_OK;
+    TSK_FS_NAME *fs_name;
+
+    if (tsk_verbose) { tsk_fprintf(stderr, "a_fs->first_inum = %d, a_fs->last_inum = %d \n", a_fs->first_inum, a_fs->last_inum); }
+
+    if (a_addr < a_fs->first_inum || a_addr > a_fs->last_inum) {
+        tsk_error_reset();
+        tsk_error_set_errno(TSK_ERR_FS_WALK_RNG);
+        tsk_error_set_errstr("xfs_dir_open_meta: inode value: %"
+            PRIuINUM "\n", a_addr);
+        return TSK_ERR;
+    }
+    else if (a_fs_dir == NULL) {
+        tsk_error_reset();
+        tsk_error_set_errno(TSK_ERR_FS_ARG);
+        tsk_error_set_errstr
+            ("xfs_dir_open_meta: NULL fs_attr argument given");
+        return TSK_ERR;
+    }
+
+    if (tsk_verbose) {
+        tsk_fprintf(stderr,
+            "xfs_dir_open_meta: Processing directory %" PRIuINUM
+            "\n", a_addr);
+    }
+
+    fs_dir = *a_fs_dir;
+    if (fs_dir) {
+        tsk_fs_dir_reset(fs_dir);
+        fs_dir->addr = a_addr;
+    }
+    else {
+        if ((*a_fs_dir = fs_dir =
+                tsk_fs_dir_alloc(a_fs, a_addr, 128)) == NULL) {
+            return TSK_ERR;
+        }
+    }
+
+    if ((fs_dir->fs_file =
+            tsk_fs_file_open_meta(a_fs, NULL, a_addr)) == NULL) {
+        tsk_error_reset();
+        tsk_error_errstr2_concat("- xfs_dir_open_meta");
+        return TSK_COR;
+    }
+
+    fs_meta = fs_dir->fs_file->meta;
+
+    if ((fs_name = tsk_fs_name_alloc(XFS_MAXNAMELEN, 0)) == NULL)
+        return TSK_ERR;
+
+    ftype_size = xfs->fs->sb_features2 & XFS_SB_VERSION2_FTYPE ? sizeof(uint8_t) : 0;
+
+    if (fs_meta->content_type == TSK_FS_META_CONTENT_TYPE_XFS_LOCAL)
+    {
+        xfs_dir2_sf_t *dir_sf = (xfs_dir2_sf_t*) fs_meta->content_ptr;
+
+        if (tsk_verbose) { tsk_fprintf(stderr, "dir_sf = 0x %" PRIx64  " \n", dir_sf); }
+
+        bool i8 = dir_sf->hdr.i8count != 0;
+        uint8_t count = i8 ? dir_sf->hdr.i8count : dir_sf->hdr.count;
+
+        /*
+        *    sf_entry goes after xfs_dir2_sf_hdr, which is defined as:
+        *
+        *    typedef struct xfs_dir2_sf_hdr {
+        *         __uint8_t count;
+        *         __uint8_t i8count;
+        *         xfs_dir2_inou_t parent; <-- uint32_t (uint64_t if i8count > 0)
+        *    } xfs_dir2_sf_hdr_t;
+        */
+
+        xfs_dir2_sf_entry *sf_entry = 0;
+        sf_entry = (xfs_dir2_sf_entry*) ((char *) dir_sf + sizeof(uint8_t) + sizeof(uint8_t) + (i8 ? sizeof(uint64_t) : sizeof(uint32_t)));
+
+        if (tsk_verbose) { tsk_fprintf(stderr, "sf_entry = 0x %" PRIx64  " \n", sf_entry); }
+
+        for(uint8_t dir_ent_num = 0; dir_ent_num < count; dir_ent_num++)
+        {
+            /*
+            *    typedef struct   {
+            *         __uint8_t namelen;
+            *         xfs_dir2_sf_off_t offset;
+            *         __uint8_t name[1];
+            *         __uint8_t ftype;
+            *         xfs_dir2_inou_t inumber;
+            *    } xfs_dir2_sf_entry_t;
+            */
+
+            uint8_t namelen = sf_entry->namelen;
+            char *name = (char *) sf_entry + sizeof(uint8_t) + sizeof(xfs_dir2_sf_off_t);
+            memcpy(fs_name->name, name, namelen);
+            fs_name->name[namelen] = '\0';
+
+            xfs_dir2_inou_t *inum_p = (xfs_dir2_inou_t*) (name + namelen + ftype_size);
+            fs_name->meta_addr = i8 ? tsk_getu64(TSK_BIG_ENDIAN, &inum_p->i8) : tsk_getu32(TSK_BIG_ENDIAN, &inum_p->i4);
+
+            uint8_t ftype = 0;
+            if (ftype_size > 0)
+            {
+                ftype = * (uint8_t *) (name + namelen);
+            }
+            else
+            {
+                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;
+                }
+
+                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;
+            }
+
+            fs_name->flags = (TSK_FS_NAME_FLAG_ENUM) 0;
+
+            /* Do we have a deleted entry? */
+            bool is_del = fs_dir->fs_file->meta->flags & TSK_FS_META_FLAG_UNALLOC;
+            if ((fs_name->meta_addr == 0) || (is_del)) {
+                fs_name->flags = TSK_FS_NAME_FLAG_UNALLOC;
+            }
+            /* We have a non-deleted entry */
+            else {
+                fs_name->flags = TSK_FS_NAME_FLAG_ALLOC;
+            }
+
+            if (tsk_verbose) { tsk_fprintf(stderr, "namelen = %d, fs_name->name = %s, fs_name->meta_addr = %" PRId64  " fs_name->flags = \n", namelen, fs_name->name, 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;
+            }
+
+            sf_entry = (xfs_dir2_sf_entry*) ((char *) sf_entry + sizeof(uint8_t) + sizeof(xfs_dir2_sf_off_t) + namelen + ftype_size + (i8 ? sizeof(uint64_t) : sizeof(uint32_t)));
+        }
+    }
+    else if (fs_meta->content_type == TSK_FS_META_CONTENT_TYPE_XFS_EXTENTS)
+    {
+        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);
+
+        if (nextents == 0)
+        {
+            tsk_fprintf(stderr, "nextents == 0 \n", nextents);
+        }
+
+        if (fs_meta->type == TSK_FS_META_TYPE_DIR && nextents > 1)
+        {
+            tsk_fprintf(stderr, "fs_meta->type == TSK_FS_META_TYPE_DIR and nextents = %d > 1. Directory blocks with multiple extents are unsupported now \n", nextents);
+            return TSK_ERR;
+        }
+
+        // unpack extent
+        xfs_bmbt_irec_t irec;
+        memset(&irec, 0, sizeof(irec));
+        xfs_bmbt_disk_get_all(extent_data_offset, &irec);
+
+        if (tsk_verbose) {
+            tsk_fprintf(stderr, "extent_num = %d, adding br_startblock = %d / br_blockcount = %d \n", /* extent_num */ 0, irec.br_startblock, irec.br_blockcount);
+        }
+
+        if ((dirbuf = (char*) tsk_malloc((size_t)a_fs->block_size)) == NULL) {
+            return TSK_ERR;
+        }
+
+        size = irec.br_blockcount * a_fs->block_size;
+
+        TSK_OFF_T offset = irec.br_startblock * a_fs->block_size;
+        TSK_OFF_T offset_in_block = 0;
+
+        // read xfs_dir2_data_hdr (on a v5 filesystem this is xfs_dir3_data_hdr_t)
+
+        ssize_t len = (size > a_fs->block_size) ? a_fs->block_size : 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;
+        }
+
+        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_block_tail block_tail;
+        memcpy(&block_tail, dirbuf + size - sizeof(xfs_dir2_block_tail), sizeof(xfs_dir2_block_tail));
+        block_tail.count = tsk_getu32(TSK_BIG_ENDIAN, &block_tail.count);
+        block_tail.stale = tsk_getu32(TSK_BIG_ENDIAN, &block_tail.stale);
+        uint32_t leaf_offset = size - sizeof(xfs_dir2_block_tail) - block_tail.count * sizeof(xfs_dir2_leaf_entry_t);
+
+        if (leaf_offset >= len) {
+            tsk_fprintf(stderr, "leaf_offset = %d past len = %d \n", leaf_offset, len);
+            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;
+        }
+
+        if (tsk_verbose) {
+            tsk_fprintf(stderr, "block_tail.count = %d, leaf_offset = %d (out of len = %d) \n", block_tail.count, leaf_offset, len);
+        }
+
+        size -= len;
+        offset += len;
+
+        xfs_dir2_data_entry_t data_entry;
+
+        while (offset_in_block < leaf_offset)
+        {
+            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) >= leaf_offset)
+                {
+                    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 >= leaf_offset)
+                {
+                    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(dirbuf);
+    }
+    else if (fs_meta->content_type == TSK_FS_META_CONTENT_TYPE_XFS_FMT_BTREE) {
+        if (tsk_verbose) { tsk_fprintf(stderr, "fs_meta->content_type == TSK_FS_META_CONTENT_TYPE_XFS_FMT_BTREE is not supported yet"); }
+    }
+
+    return retval;
+}
+
+
+
+/* xfsfs_close - close an xfsfs file system */
+static void
+    xfsfs_close(TSK_FS_INFO *fs)
+{
+    if(fs != NULL){
+        XFSFS_INFO *xfsfs = (XFSFS_INFO *)fs;
+        free(xfsfs->fs    );
+        free(xfsfs->agi);
+        tsk_fs_free(fs);
+    }
+}
+
+/**
+* \internal
+* Open part of a disk image as a XFS file system.
+*
+* @param img_info Disk image to analyze
+* @param offset Byte offset where file system starts
+* @param ftype Specific type of file system
+* @param test Going to use this - 1 if we're doing auto-detect, 0 if not (display more verbose messages if the user specified XFS)
+* @returns NULL on error or if data is not an XFS file system
+*/
+TSK_FS_INFO *
+    xfs_open(TSK_IMG_INFO * img_info, TSK_OFF_T offset,
+    TSK_FS_TYPE_ENUM ftype, uint8_t test)
+{
+    XFSFS_INFO *xfsfs = NULL;
+    TSK_FS_INFO *fs = NULL;
+    xfs_agi *agi = NULL;
+    unsigned int len = 0;
+    ssize_t cnt;
+
+    // clean up any error messages that are lying around
+    tsk_error_reset();
+
+    if (TSK_FS_TYPE_ISXFS(ftype) == 0) {
+        tsk_error_reset();
+        tsk_error_set_errno(TSK_ERR_FS_ARG);
+        tsk_error_set_errstr("Invalid FS Type in xfsfs_open");
+        return NULL;
+    }
+
+    if (img_info->sector_size == 0) {
+        tsk_error_reset();
+        tsk_error_set_errno(TSK_ERR_FS_ARG);
+        tsk_error_set_errstr("xfs_open: sector size is 0");
+        return NULL;
+    }
+
+    if ((xfsfs = (XFSFS_INFO *) tsk_fs_malloc(sizeof(XFSFS_INFO))) == NULL)
+        return NULL;
+
+
+    fs = &(xfsfs->fs_info);
+
+    fs->ftype = ftype;
+    fs->flags = TSK_FS_INFO_FLAG_NONE;
+    fs->img_info = img_info;
+    fs->offset = offset;
+    fs->tag = TSK_FS_INFO_TAG;
+
+    /*
+     * Read the superblock.
+    */
+
+    len = sizeof(xfs_sb_t);
+    if ((xfsfs->fs = (xfs_sb_t *) tsk_malloc(len)) == NULL) {
+        tsk_fs_free((TSK_FS_INFO *)xfsfs);
+        return NULL;
+    }
+    if (tsk_verbose) { tsk_fprintf(stderr, "reading xfs superblock, len = %u \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 (cnt != len) {
+        if (cnt >= 0) {
+            tsk_error_reset();
+            tsk_error_set_errno(TSK_ERR_FS_READ);
+        }
+        tsk_error_set_errstr2("xfs_open: superblock");
+        free(xfsfs->fs);
+        tsk_fs_free((TSK_FS_INFO *)xfsfs);
+        return NULL;
+    }
+
+    xfsfs->fs->sb_magicnum = tsk_getu32(TSK_BIG_ENDIAN, &xfsfs->fs->sb_magicnum);
+    xfsfs->fs->sb_blocksize = tsk_getu32(TSK_BIG_ENDIAN, &xfsfs->fs->sb_blocksize);
+    xfsfs->fs->sb_dblocks = tsk_getu64(TSK_BIG_ENDIAN, &xfsfs->fs->sb_dblocks);
+    xfsfs->fs->sb_rblocks = tsk_getu64(TSK_BIG_ENDIAN, &xfsfs->fs->sb_rblocks);
+    xfsfs->fs->sb_rextents = tsk_getu64(TSK_BIG_ENDIAN, &xfsfs->fs->sb_rextents);
+    xfsfs->fs->sb_logstart = tsk_getu64(TSK_BIG_ENDIAN, &xfsfs->fs->sb_logstart);
+    xfsfs->fs->sb_rootino = tsk_getu64(TSK_BIG_ENDIAN, &xfsfs->fs->sb_rootino);
+    xfsfs->fs->sb_rbmino = tsk_getu64(TSK_BIG_ENDIAN, &xfsfs->fs->sb_rbmino);
+    xfsfs->fs->sb_rsumino = tsk_getu64(TSK_BIG_ENDIAN, &xfsfs->fs->sb_rsumino);
+    xfsfs->fs->sb_rextsize = tsk_getu32(TSK_BIG_ENDIAN, &xfsfs->fs->sb_rextsize);
+    xfsfs->fs->sb_agblocks = tsk_getu32(TSK_BIG_ENDIAN, &xfsfs->fs->sb_agblocks);
+    xfsfs->fs->sb_agcount = tsk_getu32(TSK_BIG_ENDIAN, &xfsfs->fs->sb_agcount);
+    xfsfs->fs->sb_rbmblocks = tsk_getu32(TSK_BIG_ENDIAN, &xfsfs->fs->sb_rbmblocks);
+    xfsfs->fs->sb_logblocks = tsk_getu32(TSK_BIG_ENDIAN, &xfsfs->fs->sb_logblocks);
+    xfsfs->fs->sb_versionnum = tsk_getu16(TSK_BIG_ENDIAN, &xfsfs->fs->sb_versionnum);
+    xfsfs->fs->sb_sectsize = tsk_getu16(TSK_BIG_ENDIAN, &xfsfs->fs->sb_sectsize);
+    xfsfs->fs->sb_inodesize = tsk_getu16(TSK_BIG_ENDIAN, &xfsfs->fs->sb_inodesize);
+    xfsfs->fs->sb_inopblock = tsk_getu16(TSK_BIG_ENDIAN, &xfsfs->fs->sb_inopblock);
+    xfsfs->fs->sb_icount = tsk_getu64(TSK_BIG_ENDIAN, &xfsfs->fs->sb_icount);
+    xfsfs->fs->sb_ifree = tsk_getu64(TSK_BIG_ENDIAN, &xfsfs->fs->sb_ifree);
+    xfsfs->fs->sb_fdblocks = tsk_getu64(TSK_BIG_ENDIAN, &xfsfs->fs->sb_fdblocks);
+    xfsfs->fs->sb_frextents = tsk_getu64(TSK_BIG_ENDIAN, &xfsfs->fs->sb_frextents);
+    xfsfs->fs->sb_uquotino = tsk_getu64(TSK_BIG_ENDIAN, &xfsfs->fs->sb_uquotino);
+    xfsfs->fs->sb_qflags = tsk_getu16(TSK_BIG_ENDIAN, &xfsfs->fs->sb_qflags);
+    xfsfs->fs->sb_inoalignmt = tsk_getu32(TSK_BIG_ENDIAN, &xfsfs->fs->sb_inoalignmt);
+    xfsfs->fs->sb_unit = tsk_getu32(TSK_BIG_ENDIAN, &xfsfs->fs->sb_unit);
+    xfsfs->fs->sb_width = tsk_getu32(TSK_BIG_ENDIAN, &xfsfs->fs->sb_width);
+    xfsfs->fs->sb_logsectsize = tsk_getu16(TSK_BIG_ENDIAN, &xfsfs->fs->sb_logsectsize);
+    xfsfs->fs->sb_logsunit = tsk_getu32(TSK_BIG_ENDIAN, &xfsfs->fs->sb_logsunit);
+    xfsfs->fs->sb_features2 = tsk_getu32(TSK_BIG_ENDIAN, &xfsfs->fs->sb_features2);
+
+    /* version 5 superblock fields start here */
+    xfsfs->fs->sb_features_compat = tsk_getu32(TSK_BIG_ENDIAN, &xfsfs->fs->sb_features_compat);
+    xfsfs->fs->sb_features_ro_compat = tsk_getu32(TSK_BIG_ENDIAN, &xfsfs->fs->sb_features_ro_compat);
+    xfsfs->fs->sb_features_incompat = tsk_getu32(TSK_BIG_ENDIAN, &xfsfs->fs->sb_features_incompat);
+    xfsfs->fs->sb_features_log_incompat = tsk_getu32(TSK_BIG_ENDIAN, &xfsfs->fs->sb_features_log_incompat);
+    xfsfs->fs->sb_crc = tsk_getu32(TSK_BIG_ENDIAN, &xfsfs->fs->sb_crc);
+    xfsfs->fs->sb_spino_align = tsk_getu32(TSK_BIG_ENDIAN, &xfsfs->fs->sb_spino_align);
+    xfsfs->fs->sb_pquotino = tsk_getu64(TSK_BIG_ENDIAN, &xfsfs->fs->sb_pquotino);
+    xfsfs->fs->sb_lsn = tsk_getu64(TSK_BIG_ENDIAN, &xfsfs->fs->sb_lsn);
+    // uuid_t sb_meta_uuid;
+    xfsfs->fs->sb_rrmapino = tsk_getu64(TSK_BIG_ENDIAN, &xfsfs->fs->sb_rrmapino);
+
+    if (xfsfs->fs->sb_magicnum != 0x58465342) {
+        tsk_error_reset();
+        tsk_error_set_errno(TSK_ERR_FS_READ);
+        tsk_error_set_errstr2("xfs_open: magic number doesn't match XFSB");
+        free(xfsfs->fs);
+        tsk_fs_free((TSK_FS_INFO *)xfsfs);
+        return NULL;
+    }
+
+    len = sizeof(xfs_agi) * xfsfs->fs->sb_agcount;
+    if ((agi = (xfs_agi *) tsk_malloc(len)) == NULL)
+        return NULL;
+
+    for (xfs_agnumber_t current_ag = 0; current_ag < xfsfs->fs->sb_agcount; current_ag++)
+    {
+        TSK_OFF_T agi_offset = current_ag * xfsfs->fs->sb_agblocks * xfsfs->fs->sb_blocksize + xfsfs->fs->sb_sectsize * 2;
+        len = sizeof(xfs_agi);
+
+        if (tsk_verbose) { tsk_fprintf(stderr, "reading xfs AGI[%d/%d] from agi_offset = %" PRId64 " \n", current_ag, xfsfs->fs->sb_agcount, agi_offset); }
+        cnt = tsk_fs_read(&xfsfs->fs_info, agi_offset, (char *) (&agi[current_ag]), 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);
+            free(agi);
+            tsk_fs_free((TSK_FS_INFO *)xfsfs);
+            return NULL;
+        }
+
+        agi[current_ag].agi_magicnum = tsk_getu32(TSK_BIG_ENDIAN, &agi[current_ag].agi_magicnum);
+        agi[current_ag].agi_versionnum = tsk_getu32(TSK_BIG_ENDIAN, &agi[current_ag].agi_versionnum);
+        agi[current_ag].agi_seqno = tsk_getu32(TSK_BIG_ENDIAN, &agi[current_ag].agi_seqno);
+        agi[current_ag].agi_length = tsk_getu32(TSK_BIG_ENDIAN, &agi[current_ag].agi_length);
+        agi[current_ag].agi_count = tsk_getu32(TSK_BIG_ENDIAN, &agi[current_ag].agi_count);
+        agi[current_ag].agi_root = tsk_getu32(TSK_BIG_ENDIAN, &agi[current_ag].agi_root);
+        agi[current_ag].agi_level = tsk_getu32(TSK_BIG_ENDIAN, &agi[current_ag].agi_level);
+        agi[current_ag].agi_freecount = tsk_getu32(TSK_BIG_ENDIAN, &agi[current_ag].agi_freecount);
+        agi[current_ag].agi_newino = tsk_getu32(TSK_BIG_ENDIAN, &agi[current_ag].agi_newino);
+        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); }
+    }
+
+    xfsfs->agi = agi;
+
+    /* Set the size of the inode, but default to our data structure
+     * size if it is larger */
+    xfsfs->inode_size = xfsfs->fs->sb_inodesize;
+
+    if (xfsfs->inode_size < sizeof(xfs_dinode_core)) {
+        if (tsk_verbose)
+            tsk_fprintf(stderr, "SB inode size is small");
+    }
+
+    /*
+     * Calculate the block info
+     */
+    fs->dev_bsize = img_info->sector_size;
+    fs->block_count = xfsfs->fs->sb_dblocks;
+    fs->first_block = 0;
+    fs->last_block_act = fs->last_block = fs->block_count - 1;
+    fs->block_size = xfsfs->fs->sb_blocksize;
+
+    /*
+     * Calculate the meta data info
+     */
+    fs->root_inum = fs->first_inum = xfsfs->fs->sb_rootino; // usually 128
+    fs->inum_count = xfsfs->fs->sb_icount;
+    fs->last_inum = img_info->size / xfsfs->inode_size - 1; // pragmatic upper bound is defined by the image size
+    // right now, 0xffff prefix signifies the start of unused space in directory entry, so theoretical last inode num is 0xffff000000000000
+
+    fs->get_default_attr_type = tsk_fs_unix_get_default_attr_type;
+    fs->load_attrs = xfs_load_attrs;
+
+    fs->dir_open_meta = xfs_dir_open_meta;
+
+    fs->fsstat = xfsfs_fsstat;
+
+    fs->inode_walk = xfs_inode_walk;
+
+    fs->block_walk = xfs_block_walk;
+    fs->block_getflags = xfs_block_getflags;
+
+    fs->file_add_meta = xfs_inode_lookup;
+    fs->istat = xfs_istat;
+
+    fs->close = xfsfs_close;
+
+    return fs;
+}
diff --git a/tsk/img/img_open.cpp b/tsk/img/img_open.cpp
index 2b72d2da51967a0eda47d87e6d59403a80c82543..296bc1587b1e8619ca464d1482e05af652d3cc2f 100644
--- a/tsk/img/img_open.cpp
+++ b/tsk/img/img_open.cpp
@@ -393,8 +393,8 @@ tsk_img_open_utf8(int num_img,
 /**
 * \ingroup imglib
  * Opens an an image of type TSK_IMG_TYPE_EXTERNAL. The void pointer parameter
- * must be castable to a TSK_IMG_INFO pointer.  It is up to 
- * the caller to set the tag value in ext_img_info.  This 
+ * must be castable to a TSK_IMG_INFO pointer.  It is up to
+ * the caller to set the tag value in ext_img_info.  This
  * method will initialize the cache lock. 
  *
  * @param ext_img_info Pointer to the partially initialized disk image