[PATCH 20/22] docs: add XFS extended attributes structures to the DS&A book

Darrick J. Wong
2018-10-04T04:20:45Z

.../filesystems/xfs-data-structures/dynamic.rst | 1
.../xfs-data-structures/extended_attributes.rst | 933 ++++++++++++++++++++
2 files changed, 934 insertions(+)
create mode 100644 Documentation/filesystems/xfs-data-structures/extended_attributes.rst

diff --git a/Documentation/filesystems/xfs-data-structures/dynamic.rst b/Documentation/filesystems/xfs-data-structures/dynamic.rst
index 2c12fca905fd..16755381d0f8 100644
--- a/Documentation/filesystems/xfs-data-structures/dynamic.rst
+++ b/Documentation/filesystems/xfs-data-structures/dynamic.rst
@@ -6,3 +6,4 @@ Dynamic Allocated Structures
.. include:: ondisk_inode.rst
.. include:: data_extents.rst
.. include:: directories.rst
+.. include:: extended_attributes.rst
diff --git a/Documentation/filesystems/xfs-data-structures/extended_attributes.rst b/Documentation/filesystems/xfs-data-structures/extended_attributes.rst
new file mode 100644
index 000000000000..db6de15227cd
--- /dev/null
+++ b/Documentation/filesystems/xfs-data-structures/extended_attributes.rst
@@ -0,0 +1,933 @@
+.. SPDX-License-Identifier: CC-BY-SA-4.0
+
+Extended Attributes
+-------------------
+
+Extended attributes enable users and administrators to attach (name: value)
+pairs to inodes within the XFS filesystem. They could be used to store
+meta-information about the file.
+
+Attribute names can be up to 256 bytes in length, terminated by the first 0
+byte. The intent is that they be printable ASCII (or other character set)
+names for the attribute. The values can contain up to 64KB of arbitrary binary
+data. Some XFS internal attributes (eg. parent pointers) use non-printable
+names for the attribute.
+
+Access Control Lists (ACLs) and Data Migration Facility (DMF) use extended
+attributes to store their associated metadata with an inode.
+
+XFS uses two disjoint attribute name spaces associated with every inode. These
+are the root and user address spaces. The root address space is accessible
+only to the superuser, and then only by specifying a flag argument to the
+function call. Other users will not see or be able to modify attributes in the
+root address space. The user address space is protected by the normal file
+permissions mechanism, so the owner of the file can decide who is able to see
+and/or modify the value of attributes on any particular file.
+
+To view extended attributes from the command line, use the getfattr command.
+To set or delete extended attributes, use the setfattr command. ACLs control
+should use the getfacl and setfacl commands.
+
+XFS attributes supports three namespaces: "user", "trusted" (or "root" using
+IRIX terminology), and "secure".
+
+See the section about `extended attributes <#extended-attribute-versions>`__
+in the inode for instructions on how to calculate the location of the
+attributes.
+
+The following four sections describe each of the on-disk formats.
+
+Short Form Attributes
+~~~~~~~~~~~~~~~~~~~~~
+
+When the all extended attributes can fit within the inode’s attribute fork,
+the inode’s di\_aformat is set to "local" and the attributes are stored in
+the inode’s literal area starting at offset di\_forkoff × 8.
+
+Shortform attributes use the following structures:
+
+.. code:: c
+
+ typedef struct xfs_attr_shortform {
+ struct xfs_attr_sf_hdr {
+ __be16 totsize;
+ __u8 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_attr_sf_hdr xfs_attr_sf_hdr_t;
+ typedef struct xfs_attr_sf_entry xfs_attr_sf_entry_t;
+
+**totsize**
+ Total size of the attribute structure in bytes.
+
+**count**
+ The number of entries that can be found in this structure.
+
+**namelen** and **valuelen**
+ These values specify the size of the two byte arrays containing the name
+ and value pairs. valuelen is zero for extended attributes with no value.
+
+**nameval[]**
+ A single array whose size is the sum of namelen and valuelen. The names
+ and values are not null terminated on-disk. The value immediately follows
+ the name in the array.
+
+.. _attribute-flags:
+
+**flags**
+ A combination of the following:
+
+.. list-table::
+ :widths: 28 52
+ :header-rows: 1
+
+ * - Flag
+ - Description
+
+ * - 0
+ - The attribute's namespace is "user".
+
+ * - XFS_ATTR_ROOT
+ - The attribute's namespace is "trusted".
+
+ * - XFS_ATTR_SECURE
+ - The attribute's namespace is "secure".
+
+ * - XFS_ATTR_INCOMPLETE
+ - This attribute is being modified.
+
+ * - XFS_ATTR_LOCAL
+ - The attribute value is contained within this block.
+
+Table: Attribute Namespaces
+
+.. figure:: images/64.png
+ :alt: Short form attribute layout
+
+ Short form attribute layout
+
+xfs\_db Short Form Attribute Example
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+A file is created and two attributes are set:
+
+::
+
+ # setfattr -n user.empty few_attr
+ # setfattr -n trusted.trust -v val1 few_attr
+
+Using xfs\_db, we dump the inode:
+
+::
+
+ xfs_db> inode <inode#>
+ xfs_db> p
+ core.magic = 0x494e
+ core.mode = 0100644
+ ...
+ core.naextents = 0
+ core.forkoff = 15
+ core.aformat = 1 (local)
+ ...
+ a.sfattr.hdr.totsize = 24
+ a.sfattr.hdr.count = 2
+ a.sfattr.list[0].namelen = 5
+ a.sfattr.list[0].valuelen = 0
+ a.sfattr.list[0].root = 0
+ a.sfattr.list[0].secure = 0
+ a.sfattr.list[0].name = "empty"
+ a.sfattr.list[1].namelen = 5
+ a.sfattr.list[1].valuelen = 4
+ a.sfattr.list[1].root = 1
+ a.sfattr.list[1].secure = 0
+ a.sfattr.list[1].name = "trust"
+ a.sfattr.list[1].value = "val1"
+
+We can determine the actual inode offset to be 220 (15 x 8 + 100) or 0xdc.
+Examining the raw dump, the second attribute is highlighted:
+
+::
+
+ xfs_db> type text
+ xfs_db> p
+ 09: 49 4e 81 a4 01 02 00 01 00 00 00 00 00 00 00 00 IN..............
+ 10: 00 00 00 01 00 00 00 00 00 00 00 00 00 00 00 02 ................
+ 20: 44 be 19 be 38 d1 26 98 44 be 1a be 38 d1 26 98 D...8...D...8...
+ 30: 44 be 1a e1 3a 9a ea 18 00 00 00 00 00 00 00 04 D...............
+ 40: 00 00 00 00 00 00 00 01 00 00 00 00 00 00 00 01 ................
+ 50: 00 00 0f 01 00 00 00 00 00 00 00 00 00 00 00 00 ................
+ 60: ff ff ff ff 00 00 00 00 00 00 00 00 00 00 00 12 ................
+ 70: 53 a0 00 01 00 00 00 00 00 00 00 00 00 00 00 00 ................
+ 80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
+ 90: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
+ a0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
+ b0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
+ c0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
+ d0: 00 00 00 00 00 00 00 00 00 00 00 00 00 18 02 00 ................
+ ^^ hdr.totsize = 0x18
+ e0: 05 00 00 65 6d 70 74 79 05 04 02 74 72 75 73 74 ...empty...trust
+ f0: 76 61 6c 31 00 00 00 00 00 00 00 00 00 00 00 00 val1............
+
+Adding another attribute with attr1, the format is converted to extents and
+di\_forkoff remains unchanged (and all those zeros in the dump above remain
+unused):
+
+::
+
+ xfs_db> inode <inode#>
+ xfs_db> p
+ ...
+ core.naextents = 1
+ core.forkoff = 15
+ core.aformat = 2 (extents)
+ ...
+ a.bmx[0] = [startoff,startblock,blockcount,extentflag] 0:[0,37534,1,0]
+
+Performing the same steps with attr2, adding one attribute at a time, you can
+see di\_forkoff change as attributes are added:
+
+::
+
+ xfs_db> inode <inode#>
+ xfs_db> p
+ ...
+ core.naextents = 0
+ core.forkoff = 15
+ core.aformat = 1 (local)
+ ...
+ a.sfattr.hdr.totsize = 17
+ a.sfattr.hdr.count = 1
+ a.sfattr.list[0].namelen = 10
+ a.sfattr.list[0].valuelen = 0
+ a.sfattr.list[0].root = 0
+ a.sfattr.list[0].secure = 0
+ a.sfattr.list[0].name = "empty_attr"
+
+Attribute added:
+
+::
+
+ xfs_db> p
+ ...
+ core.naextents = 0
+ core.forkoff = 15
+ core.aformat = 1 (local)
+ ...
+ a.sfattr.hdr.totsize = 31
+ a.sfattr.hdr.count = 2
+ a.sfattr.list[0].namelen = 10
+ a.sfattr.list[0].valuelen = 0
+ a.sfattr.list[0].root = 0
+ a.sfattr.list[0].secure = 0
+ a.sfattr.list[0].name = "empty_attr"
+ a.sfattr.list[1].namelen = 7
+ a.sfattr.list[1].valuelen = 4
+ a.sfattr.list[1].root = 1
+ a.sfattr.list[1].secure = 0
+ a.sfattr.list[1].name = "trust_a"
+ a.sfattr.list[1].value = "val1"
+
+Another attribute is added:
+
+::
+
+ xfs_db> p
+ ...
+ core.naextents = 0
+ core.forkoff = 13
+ core.aformat = 1 (local)
+ ...
+ a.sfattr.hdr.totsize = 52
+ a.sfattr.hdr.count = 3
+ a.sfattr.list[0].namelen = 10
+ a.sfattr.list[0].valuelen = 0
+ a.sfattr.list[0].root = 0
+ a.sfattr.list[0].secure = 0
+ a.sfattr.list[0].name = "empty_attr"
+ a.sfattr.list[1].namelen = 7
+ a.sfattr.list[1].valuelen = 4
+ a.sfattr.list[1].root = 1
+ a.sfattr.list[1].secure = 0
+ a.sfattr.list[1].name = "trust_a"
+ a.sfattr.list[1].value = "val1"
+ a.sfattr.list[2].namelen = 6
+ a.sfattr.list[2].valuelen = 12
+ a.sfattr.list[2].root = 0
+ a.sfattr.list[2].secure = 0
+ a.sfattr.list[2].name = "second"
+ a.sfattr.list[2].value = "second_value"
+
+One more is added:
+
+::
+
+ xfs_db> p
+ core.naextents = 0
+ core.forkoff = 10
+ core.aformat = 1 (local)
+ ...
+ a.sfattr.hdr.totsize = 69
+ a.sfattr.hdr.count = 4
+ a.sfattr.list[0].namelen = 10
+ a.sfattr.list[0].valuelen = 0
+ a.sfattr.list[0].root = 0
+ a.sfattr.list[0].secure = 0
+ a.sfattr.list[0].name = "empty_attr"
+ a.sfattr.list[1].namelen = 7
+ a.sfattr.list[1].valuelen = 4
+ a.sfattr.list[1].root = 1
+ a.sfattr.list[1].secure = 0
+ a.sfattr.list[1].name = "trust_a"
+ a.sfattr.list[1].value = "val1"
+ a.sfattr.list[2].namelen = 6
+ a.sfattr.list[2].valuelen = 12
+ a.sfattr.list[2].root = 0
+ a.sfattr.list[2].secure = 0
+ a.sfattr.list[2].name = "second"
+ a.sfattr.list[2].value = "second_value"
+ a.sfattr.list[3].namelen = 6
+ a.sfattr.list[3].valuelen = 8
+ a.sfattr.list[3].root = 0
+ a.sfattr.list[3].secure = 1
+ a.sfattr.list[3].name = "policy"
+ a.sfattr.list[3].value = "contents"
+
+A raw dump is shown to compare with the attr1 dump on a prior page, the header
+is highlighted:
+
+::
+
+ xfs_db> type text
+ xfs_db> p
+ 00: 49 4e 81 a4 01 02 00 01 00 00 00 00 00 00 00 00 IN..............
+ 10: 00 00 00 01 00 00 00 00 00 00 00 00 00 00 00 05 ................
+ 20: 44 be 24 cd 0f b0 96 18 44 be 24 cd 0f b0 96 18 D.......D.......
+ 30: 44 be 2d f5 01 62 7a 18 00 00 00 00 00 00 00 04 D....bz.........
+ 40: 00 00 00 00 00 00 00 01 00 00 00 00 00 00 00 01 ................
+ 50: 00 00 0a 01 00 00 00 00 00 00 00 00 00 00 00 00 ................
+ 60: ff ff ff ff 00 00 00 00 00 00 00 00 00 00 00 01 ................
+ 70: 41 c0 00 01 00 00 00 00 00 00 00 00 00 00 00 00 A...............
+ 80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
+ 90: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
+ a0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
+ b0: 00 00 00 00 00 45 04 00 0a 00 00 65 6d 70 74 79 .....E.....empty
+ c0: 5f 61 74 74 72 07 04 02 74 72 75 73 74 5f 61 76 .attr...trust.av
+ d0: 61 6c 31 06 0c 00 73 65 63 6f 6e 64 73 65 63 6f all...secondseco
+ e0: 6e 64 5f 76 61 6c 75 65 06 08 04 70 6f 6c 69 63 nd.value...polic
+ f0: 79 63 6f 6e 74 65 6e 74 73 64 5f 76 61 6c 75 65 ycontentsd.value
+
+It can be clearly seen that attr2 allows many more attributes to be stored in
+an inode before they are moved to another filesystem block.
+
+Leaf Attributes
+~~~~~~~~~~~~~~~
+
+When an inode’s attribute fork space is used up with shortform attributes and
+more are added, the attribute format is migrated to "extents".
+
+Extent based attributes use hash/index pairs to speed up an attribute lookup.
+The first part of the "leaf" contains an array of fixed size hash/index
+pairs with the flags stored as well. The remaining part of the leaf block
+contains the array name/value pairs, where each element varies in length.
+
+Each leaf is based on the xfs\_da\_blkinfo\_t block header declared in the
+section about `directories <#directory-attribute-block-header>`__. On a v5
+filesystem, the block header is xfs\_da3\_blkinfo\_t. The structure
+encapsulating all other structures in the attribute block is
+xfs\_attr\_leafblock\_t.
+
+The structures involved are:
+
+.. code:: c
+
+ typedef struct xfs_attr_leaf_map {
+ __be16 base;
+ __be16 size;
+ } xfs_attr_leaf_map_t;
+
+**base**
+ Block offset of the free area, in bytes.
+
+**size**
+ Size of the free area, in bytes.
+
+.. code:: c
+
+ typedef struct xfs_attr_leaf_hdr {
+ xfs_da_blkinfo_t info;
+ __be16 count;
+ __be16 usedbytes;
+ __be16 firstused;
+ __u8 holes;
+ __u8 pad1;
+ xfs_attr_leaf_map_t freemap[3];
+ } xfs_attr_leaf_hdr_t;
+
+**info**
+ Directory/attribute block header.
+
+**count**
+ Number of entries.
+
+**usedbytes**
+ Number of bytes used in the leaf block.
+
+**firstused**
+ Block offset of the first entry in use, in bytes.
+
+**holes**
+ Set to 1 if block compaction is necessary.
+
+**pad1**
+ Padding to maintain alignment to 64-bit boundaries.
+
+.. code:: c
+
+ typedef struct xfs_attr_leaf_entry {
+ __be32 hashval;
+ __be16 nameidx;
+ __u8 flags;
+ __u8 pad2;
+ } xfs_attr_leaf_entry_t;
+ ----
+
+**hashval**
+ Hash value of the attribute name.
+
+**nameidx**
+ Block offset of the name entry, in bytes.
+
+**flags**
+ Attribute flags, as specified `above <#attribute-flags>`__.
+
+**pad2**
+ Pads the structure to 64-bit boundaries.
+
+.. code:: c
+
+ typedef struct xfs_attr_leaf_name_local {
+ __be16 valuelen;
+ __u8 namelen;
+ __u8 nameval[1];
+ } xfs_attr_leaf_name_local_t;
+
+**valuelen**
+ Length of the value, in bytes.
+
+**namelen**
+ Length of the name, in bytes.
+
+**nameval**
+ The name and the value. String values are not zero-terminated.
+
+.. code:: c
+
+ typedef struct xfs_attr_leaf_name_remote {
+ __be32 valueblk;
+ __be32 valuelen;
+ __u8 namelen;
+ __u8 name[1];
+ } xfs_attr_leaf_name_remote_t;
+
+**valueblk**
+ The logical block in the attribute map where the value is located.
+
+**valuelen**
+ Length of the value, in bytes.
+
+**namelen**
+ Length of the name, in bytes.
+
+**nameval**
+ The name. String values are not zero-terminated.
+
+.. code:: c
+
+ typedef struct xfs_attr_leafblock {
+ xfs_attr_leaf_hdr_t hdr;
+ xfs_attr_leaf_entry_t entries[1];
+ xfs_attr_leaf_name_local_t namelist;
+ xfs_attr_leaf_name_remote_t valuelist;
+ } xfs_attr_leafblock_t;
+
+**hdr**
+ Attribute block header.
+
+**entries**
+ A variable-length array of attribute entries.
+
+**namelist**
+ A variable-length array of descriptors of local attributes. The location
+ and size of these entries is determined dynamically.
+
+**valuelist**
+ A variable-length array of descriptors of remote attributes. The location
+ and size of these entries is determined dynamically.
+
+On a v5 filesystem, the header becomes xfs\_da3\_blkinfo\_t to accomodate the
+extra metadata integrity fields:
+
+.. code:: c
+
+ typedef struct xfs_attr3_leaf_hdr {
+ xfs_da3_blkinfo_t info;
+ __be16 count;
+ __be16 usedbytes;
+ __be16 firstused;
+ __u8 holes;
+ __u8 pad1;
+ xfs_attr_leaf_map_t freemap[3];
+ __be32 pad2;
+ } xfs_attr3_leaf_hdr_t;
+
+
+ typedef struct xfs_attr3_leafblock {
+ xfs_attr3_leaf_hdr_t hdr;
+ xfs_attr_leaf_entry_t entries[1];
+ xfs_attr_leaf_name_local_t namelist;
+ xfs_attr_leaf_name_remote_t valuelist;
+ } xfs_attr3_leafblock_t;
+
+Each leaf header uses the magic number XFS\_ATTR\_LEAF\_MAGIC (0xfbee). On a
+v5 filesystem, the magic number is XFS\_ATTR3\_LEAF\_MAGIC (0x3bee).
+
+The hash/index elements in the entries[] array are packed from the top of the
+block. Name/values grow from the bottom but are not packed. The freemap
+contains run-length-encoded entries for the free bytes after the entries[]
+array, but only the three largest runs are stored (smaller runs are dropped).
+When the freemap doesn’t show enough space for an allocation, the name/value
+area is compacted and allocation is tried again. If there still isn’t enough
+space, then the block is split. The name/value structures (both local and
+remote versions) must be 32-bit aligned.
+
+For attributes with small values (ie. the value can be stored within the
+leaf), the XFS\_ATTR\_LOCAL flag is set for the attribute. The entry details
+are stored using the xfs\_attr\_leaf\_name\_local\_t structure. For large
+attribute values that cannot be stored within the leaf, separate filesystem
+blocks are allocated to store the value. They use the
+xfs\_attr\_leaf\_name\_remote\_t structure. See `Remote
+Values <#remote-attribute-values>`__ for more information.
+
+.. ifconfig:: builder != 'latex'
+
+ .. figure:: images/69.png
+ :alt: Leaf attribute layout
+
+ Leaf attribute layout
+
+.. ifconfig:: builder == 'latex'
+
+ .. figure:: images/69.png
+ :scale: 45%
+ :alt: Leaf attribute layout
+
+ Leaf attribute layout
+
+Both local and remote entries can be interleaved as they are only addressed by
+the hash/index entries. The flag is stored with the hash/index pairs so the
+appropriate structure can be used.
+
+Since duplicate hash keys are possible, for each hash that matches during a
+lookup, the actual name string must be compared.
+
+An "incomplete" bit is also used for attribute flags. It shows that an
+attribute is in the middle of being created and should not be shown to the
+user if we crash during the time that the bit is set. The bit is cleared when
+attribute has finished being set up. This is done because some large
+attributes cannot be created inside a single transaction.
+
+xfs\_db Leaf Attribute Example
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+A single 30KB extended attribute is added to an inode:
+
+::
+
+ xfs_db> inode <inode#>
+ xfs_db> p
+ ...
+ core.nblocks = 9
+ core.nextents = 0
+ core.naextents = 1
+ core.forkoff = 15
+ core.aformat = 2 (extents)
+ ...
+ a.bmx[0] = [startoff,startblock,blockcount,extentflag]
+ 0:[0,37535,9,0]
+ xfs_db> ablock 0
+ xfs_db> p
+ hdr.info.forw = 0
+ hdr.info.back = 0
+ hdr.info.magic = 0xfbee
+ hdr.count = 1
+ hdr.usedbytes = 20
+ hdr.firstused = 4076
+ hdr.holes = 0
+ hdr.freemap[0-2] = [base,size] 0:[40,4036] 1:[0,0] 2:[0,0]
+ entries[0] = [hashval,nameidx,incomplete,root,secure,local]
+ 0:[0xfcf89d4f,4076,0,0,0,0]
+ nvlist[0].valueblk = 0x1
+ nvlist[0].valuelen = 30692
+ nvlist[0].namelen = 8
+ nvlist[0].name = "big_attr"
+
+Attribute blocks 1 to 8 (filesystem blocks 37536 to 37543) contain the raw
+binary value data for the attribute.
+
+Index 4076 (0xfec) is the offset into the block where the name/value
+information is. As can be seen by the value, it’s at the end of the block:
+
+::
+
+ xfs_db> type text
+ xfs_db> p
+
+ 000: 00 00 00 00 00 00 00 00 fb ee 00 00 00 01 00 14 ................
+ 010: 0f ec 00 00 00 28 0f c4 00 00 00 00 00 00 00 00 ................
+ 020: fc f8 9d 4f 0f ec 00 00 00 00 00 00 00 00 00 00 ...O............
+ 030: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
+ ...
+ fe0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 01 ................
+ ff0: 00 00 77 e4 08 62 69 67 5f 61 74 74 72 00 00 00 ..w..big.attr...
+
+A 30KB attribute and a couple of small attributes are added to a file:
+
+::
+
+ xfs_db> inode <inode#>
+ xfs_db> p
+ ...
+ core.nblocks = 10
+ core.extsize = 0
+ core.nextents = 1
+ core.naextents = 2
+ core.forkoff = 15
+ core.aformat = 2 (extents)
+ ...
+ u.bmx[0] = [startoff,startblock,blockcount,extentflag]
+ 0:[0,81857,1,0]
+ a.bmx[0-1] = [startoff,startblock,blockcount,extentflag]
+ 0:[0,81858,1,0]
+ 1:[1,182398,8,0]
+ xfs_db> ablock 0
+ xfs_db> p
+ hdr.info.forw = 0
+ hdr.info.back = 0
+ hdr.info.magic = 0xfbee
+ hdr.count = 3
+ hdr.usedbytes = 52
+ hdr.firstused = 4044
+ hdr.holes = 0
+ hdr.freemap[0-2] = [base,size] 0:[56,3988] 1:[0,0] 2:[0,0]
+ entries[0-2] = [hashval,nameidx,incomplete,root,secure,local]
+ 0:[0x1e9d3934,4044,0,0,0,1]
+ 1:[0x1e9d3937,4060,0,0,0,1]
+ 2:[0xfcf89d4f,4076,0,0,0,0]
+ nvlist[0].valuelen = 6
+ nvlist[0].namelen = 5
+ nvlist[0].name = "attr2"
+ nvlist[0].value = "value2"
+ nvlist[1].valuelen = 6
+ nvlist[1].namelen = 5
+ nvlist[1].name = "attr1"
+ nvlist[1].value = "value1"
+ nvlist[2].valueblk = 0x1
+ nvlist[2].valuelen = 30692
+ nvlist[2].namelen = 8
+ nvlist[2].name = "big_attr"
+
+As can be seen in the entries array, the two small attributes have the local
+flag set and the values are printed.
+
+A raw disk dump shows the attributes. The last attribute added is highlighted
+(offset 4044 or 0xfcc):
+
+::
+
+ 000: 00 00 00 00 00 00 00 00 fb ee 00 00 00 03 00 34 ...............4
+ 010: 0f cc 00 00 00 38 0f 94 00 00 00 00 00 00 00 00 .....8..........
+ 020: 1e 9d 39 34 0f cc 01 00 1e 9d 39 37 0f dc 01 00 ..94......97....
+ 030: fc f8 9d 4f 0f ec 00 00 00 00 00 00 00 00 00 00 ...0............
+ 040: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00.................
+ ...
+ fc0: 00 00 00 00 00 00 00 00 00 00 00 00 00 06 05 61 ...............a
+ fd0: 74 74 72 32 76 61 6c 75 65 32 00 00 00 06 05 61 ttr2value2.....a
+ fe0: 74 74 72 31 76 61 6c 75 65 31 00 00 00 00 00 01 ttr1value1......
+ ff0: 00 00 77 e4 08 62 69 67 5f 61 74 74 72 00 00 00 ..w..big.attr...
+
+Node Attributes
+~~~~~~~~~~~~~~~
+
+When the number of attributes exceeds the space that can fit in one filesystem
+block (ie. hash, flag, name and local values), the first attribute block
+becomes the root of a B+tree where the leaves contain the hash/name/value
+information that was stored in a single leaf block. The inode’s attribute
+format itself remains extent based. The nodes use the xfs\_da\_intnode\_t or
+xfs\_da3\_intnode\_t structures introduced in the section about
+`directories <#directory-attribute-internal-node>`__.
+
+The location of the attribute leaf blocks can be in any order. The only way to
+find an attribute is by walking the node block hash/before values. Given a
+hash to look up, search the node’s btree array for the first hashval in the
+array that exceeds the given hash. The entry is in the block pointed to by the
+before value.
+
+Each attribute node block has a magic number of XFS\_DA\_NODE\_MAGIC (0xfebe).
+On a v5 filesystem this is XFS\_DA3\_NODE\_MAGIC (0x3ebe).
+
+.. figure:: images/72.png
+ :alt: Node attribute layout
+
+ Node attribute layout
+
+xfs\_db Node Attribute Example
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+An inode with 1000 small attributes with the naming "attribute\_n" where
+'n' is a number:
+
+::
+
+ xfs_db> inode <inode#>
+ xfs_db> p
+ ...
+ core.nblocks = 15
+ core.nextents = 0
+ core.naextents = 1
+ core.forkoff = 15
+ core.aformat = 2 (extents)
+ ...
+ a.bmx[0] = [startoff,startblock,blockcount,extentflag] 0:[0,525144,15,0]
+ xfs_db> ablock 0
+ xfs_db> p
+ hdr.info.forw = 0
+ hdr.info.back = 0
+ hdr.info.magic = 0xfebe
+ hdr.count = 14
+ hdr.level = 1
+ btree[0-13] = [hashval,before]
+ 0:[0x3435122d,1]
+ 1:[0x343550a9,14]
+ 2:[0x343553a6,13]
+ 3:[0x3436122d,12]
+ 4:[0x343650a9,8]
+ 5:[0x343653a6,7]
+ 6:[0x343691af,6]
+ 7:[0x3436d0ab,11]
+ 8:[0x3436d3a7,10]
+ 9:[0x3437122d,9]
+ 10:[0x3437922e,3]
+ 11:[0x3437d22a,5]
+ 12:[0x3e686c25,4]
+ 13:[0x3e686fad,2]
+
+The hashes are in ascending order in the btree array, and if the hash for the
+attribute we are looking up is before the entry, we go to the addressed
+attribute block.
+
+For example, to lookup attribute "attribute\_267":
+
+::
+
+ xfs_db> hash attribute_267
+ 0x3437d1a8
+
+In the root btree node, this falls between 0x3437922e and 0x3437d22a,
+therefore leaf 11 or attribute block 5 will contain the entry.
+
+::
+
+ xfs_db> ablock 5
+ xfs_db> p
+ hdr.info.forw = 4
+ hdr.info.back = 3
+ hdr.info.magic = 0xfbee
+ hdr.count = 96
+ hdr.usedbytes = 2688
+ hdr.firstused = 1408
+ hdr.holes = 0
+ hdr.freemap[0-2] = [base,size] 0:[800,608] 1:[0,0] 2:[0,0]
+ entries[0.95] = [hashval,nameidx,incomplete,root,secure,local]
+ 0:[0x3437922f,4068,0,0,0,1]
+ 1:[0x343792a6,4040,0,0,0,1]
+ 2:[0x343792a7,4012,0,0,0,1]
+ 3:[0x343792a8,3984,0,0,0,1]
+ ...
+ 82:[0x3437d1a7,2892,0,0,0,1]
+ 83:[0x3437d1a8,2864,0,0,0,1]
+ 84:[0x3437d1a9,2836,0,0,0,1]
+ ...
+ 95:[0x3437d22a,2528,0,0,0,1]
+ nvlist[0].valuelen = 10
+ nvlist[0].namelen = 13
+ nvlist[0].name = "attribute_310"
+ nvlist[0].value = "value_316\d"
+ nvlist[1].valuelen = 16
+ nvlist[1].namelen = 13
+ nvlist[1].name = "attribute_309"
+ nvlist[1].value = "value_309\d"
+ nvlist[2].valuelen = 10
+ nvlist[2].namelen = 13
+ nvlist[2].name = "attribute_308"
+ nvlist[2].value = "value_308\d"
+ nvlist[3].valuelen = 10
+ nvlist[3].namelen = 13
+ nvlist[3].name = "attribute_307"
+ nvlist[3].value = "value_307\d"
+ ...
+ nvlist[82].valuelen = 10
+ nvlist[82].namelen = 13
+ nvlist[82].name = "attribute_268"
+ nvlist[82].value = "value_268\d"
+ nvlist[83].valuelen = 10
+ nvlist[83].namelen = 13
+ nvlist[83].name = "attribute_267"
+ nvlist[83].value = "value_267\d"
+ nvlist[84].valuelen = 10
+ nvlist[84].namelen = 13
+ nvlist[84].name = "attribute_266"
+ nvlist[84].value = "value_266\d"
+ ...
+
+Each of the hash entries has XFS\_ATTR\_LOCAL flag set (1), which means the
+attribute’s value follows immediately after the name. Raw disk of the
+name/value pair at offset 2864 (0xb30), highlighted with "value\_267"
+following immediately after the name:
+
+::
+
+ b00: 62 75 74 65 5f 32 36 35 76 61 6c 75 65 5f 32 36 bute.265value.26
+ b10: 35 0a 00 00 00 0a 0d 61 74 74 72 69 62 75 74 65 5......attribute
+ b20: 51 32 36 36 76 61 6c 75 65 5f 32 36 36 0a 00 00 .266value.266...
+ b30: 00 0a 0d 61 74 74 72 69 62 75 74 65 5f 32 36 37 ...attribute.267
+ b40: 76 61 6c 75 65 5f 32 36 37 0a 00 00 00 0a 0d 61 value.267......a
+ b50: 74 74 72 69 62 75 74 65 5f 32 36 38 76 61 6c 75 ttribute.268va1u
+ b60: 65 5f 32 36 38 0a 00 00 00 0a 0d 61 74 74 72 69 e.268......attri
+ b70: 62 75 74 65 5f 32 36 39 76 61 6c 75 65 5f 32 36 bute.269value.26
+
+Each entry starts on a 32-bit (4 byte) boundary, therefore the highlighted
+entry has 2 unused bytes after it.
+
+B+tree Attributes
+~~~~~~~~~~~~~~~~~
+
+When the attribute’s extent map in an inode grows beyond the available space,
+the inode’s attribute format is changed to a "btree". The inode contains
+root node of the extent B+tree which then address the leaves that contains the
+extent arrays for the attribute data. The attribute data itself in the
+allocated filesystem blocks use the same layout and structures as described in
+`Node Attributes <#node-attributes>`__.
+
+Refer to the previous section on `B+tree Data Extents <#b-tree-extent-list>`__
+for more information on XFS B+tree extents.
+
+xfs\_db B+tree Attribute Example
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+Added 2000 attributes with 729 byte values to a file:
+
+::
+
+ xfs_db> inode <inode#>
+ xfs_db> p
+ ...
+ core.nblocks = 640
+ core.extsize = 0
+ core.nextents = 1
+ core.naextents = 274
+ core.forkoff = 15
+ core.aformat = 3 (btree)
+ ...
+ a.bmbt.level = 1
+ a.bmbt.numrecs = 2
+ a.bmbt.keys[1-2] = [startoff] 1:[0] 2:[219]
+ a.bmbt.ptrs[1-2] = 1:83162 2:109968
+ xfs_db> fsblock 83162
+ xfs_db> type bmapbtd
+ xfs_db> p
+ magic = 0x424d4150
+ level = 0
+ numrecs = 127
+ leftsib = null
+ rightsib = 109968
+ recs[1-127] = [startoff,startblock,blockcount,extentflag]
+ 1:[0,81870,1,0]
+ ...
+ xfs_db> fsblock 109968
+ xfs_db> type bmapbtd
+ xfs_db> p
+ magic = 0x424d4150
+ level = 0
+ numrecs = 147
+ leftsib = 83162
+ rightsib = null
+ recs[1-147] = [startoff,startblock,blockcount,extentflag]
+ ...
+ (which is fsblock 81870)
+ xfs_db> ablock 0
+ xfs_db> p
+ hdr.info.forw = 0
+ hdr.info.back = 0
+ hdr.info.magic = 0xfebe
+ hdr.count = 2
+ hdr.level = 2
+ btree[0-1] = [hashval,before] 0:[0x343612a6,513] 1:[0x3e686fad,512]
+
+The extent B+tree has two leaves that specify the 274 extents used for the
+attributes. Looking at the first block, it can be seen that the attribute
+B+tree is two levels deep. The two blocks at offset 513 and 512 (ie. access
+using the ablock command) are intermediate xfs\_da\_intnode\_t nodes that
+index all the attribute leaves.
+
+Remote Attribute Values
+~~~~~~~~~~~~~~~~~~~~~~~
+
+On a v5 filesystem, all remote value blocks start with this header:
+
+.. code:: c
+
+ struct xfs_attr3_rmt_hdr {
+ __be32 rm_magic;
+ __be32 rm_offset;
+ __be32 rm_bytes;
+ __be32 rm_crc;
+ uuid_t rm_uuid;
+ __be64 rm_owner;
+ __be64 rm_blkno;
+ __be64 rm_lsn;
+ };
+
+**rm\_magic**
+ Specifies the magic number for the remote value block: "XARM"
+ (0x5841524d).
+
+**rm\_offset**
+ Offset of the remote value data, in bytes.
+
+**rm\_bytes**
+ Number of bytes used to contain the remote value data.
+
+**rm\_crc**
+ Checksum of the remote value block.
+
+**rm\_uuid**
+ The UUID of this block, which must match either sb\_uuid or sb\_meta\_uuid
+ depending on which features are set.
+
+**rm\_owner**
+ The inode number that this remote value block belongs to.
+
+**rm\_blkno**
+ Disk block number of this remote value block.
+
+**rm\_lsn**
+ Log sequence number of the last write to this block.
+
+Filesystems formatted prior to v5 do not have this header in the remote block.
+Value data begins immediately at offset zero.