UBIFS: format specification

UBIFS is a file system for raw flash memory devices. It works on top of UBI (Unsorted Block Images), which works as an abstraction layer so UBIFS does not need to care about low level details. This specification only covers UBIFS, not UBI.

A UBIFS image is divided into several Logical Erase Blocks (LEB). The first LEB (LEB0) contains the superblock node, which includes information about LEB size, amongst others. The next two LEBs (LEB1 and LEB2) comprise the so called "master area" and both contain a copy of the master node, which includes information about where to find the root index node, which is then used to access all the files on the file system.

Limitations of this Kaitai Struct implementation:

  1. It only supports UBIFS images created by mkfs.ubifs (such as those in firmware images built by OpenWrt), not file systems modified after creation, e.g. by writing files to them on a running Linux system. The Linux kernel appends a new copy of the master node to LEB1 and LEB2 whenever it mounts the file system read-write, unmounts it, or commits changes (updates the index on flash to include them). When mounting, Linux uses the last copy, whereas this implementation reads the first one (master_1), which still describes the file system as it was created. Changes made since then are therefore not visible via index_root.
  2. Changes that have not been committed yet are not in the index at all, so they are not visible via index_root either. Until they are committed, UBIFS keeps them only in the journal. This only matters for file systems that were not unmounted cleanly, because unmounting commits all changes.
  3. Parsing doesn't advance the stream position. This implementation reads the image only through instances, so that parsing doesn't copy the whole image into memory. To find where the image ends, e.g. when carving it out of a larger file, use len_image. For the same reason, parsing doesn't fail if the image is truncated. To detect that, check that the stream is at least len_image bytes long (_io.size >= len_image).

KS implementation details

License: GPL-2.0-only
Minimal Kaitai Struct required: 0.11

References

This page hosts a formal specification of UBIFS using Kaitai Struct. This specification can be automatically translated into a variety of programming languages to get a parsing library.

Block diagram

Format specification in Kaitai Struct YAML

meta:
  id: ubifs
  title: UBIFS
  xref:
    justsolve: UBIFS
    wikidata: Q1018125
  tags:
    - filesystem
    - linux
  license: GPL-2.0-only
  ks-version: '0.11'
  encoding: UTF-8
  endian: le
  bit-endian: le
doc: |
  UBIFS is a file system for raw flash memory devices. It works on top of
  UBI (Unsorted Block Images), which works as an abstraction layer so UBIFS
  does not need to care about low level details. This specification only covers
  UBIFS, not UBI.

  A UBIFS image is divided into several Logical Erase Blocks (LEB). The first
  LEB (LEB0) contains the superblock node, which includes information about LEB
  size, amongst others. The next two LEBs (LEB1 and LEB2) comprise the so called
  "master area" and both contain a copy of the master node, which includes
  information about where to find the root index node, which is then used to
  access all the files on the file system.

  **Limitations of this Kaitai Struct implementation:**

  1. It only supports UBIFS images created by `mkfs.ubifs` (such as those in
     firmware images built by OpenWrt), not file systems modified after
     creation, e.g. by writing files to them on a running Linux system. The
     Linux kernel appends a new copy of the master node to LEB1 and LEB2
     whenever it mounts the file system read-write, unmounts it, or commits
     changes (updates the index on flash to include them). When mounting, Linux
     uses the
     [last copy](https://github.com/torvalds/linux/blob/8d3ae59288f1e7d58d76558a6ee96d533bc5019f/fs/ubifs/master.c#L84-L165),
     whereas this implementation reads the first one (`master_1`), which still
     describes the file system as it was created. Changes made since then are
     therefore not visible via `index_root`.
  2. Changes that have not been committed yet are not in the index at all, so
     they are not visible via `index_root` either. Until they are committed,
     UBIFS keeps them only in the journal. This only matters for file systems
     that were not unmounted cleanly, because unmounting
     [commits](https://github.com/torvalds/linux/blob/8d3ae59288f1e7d58d76558a6ee96d533bc5019f/fs/ubifs/super.c#L486-L493)
     all changes.
  3. Parsing doesn't advance the stream position. This implementation reads
     the image only through instances, so that parsing doesn't copy the
     whole image into memory. To find where the image ends, e.g. when
     carving it out of a larger file, use `len_image`. For the same reason,
     parsing doesn't fail if the image is truncated. To detect that, check
     that the stream is at least `len_image` bytes long
     (`_io.size >= len_image`).
doc-ref:
  - https://github.com/torvalds/linux/blob/8d3ae59288f1e7d58d76558a6ee96d533bc5019f/fs/ubifs/ubifs-media.h Git tag "v7.2"
  - https://github.com/torvalds/linux/blob/8d3ae59288f1e7d58d76558a6ee96d533bc5019f/fs/ubifs/key.h Git tag "v7.2"
  - http://www.linux-mtd.infradead.org/doc/ubifs.pdf
# some instances to access important data structures directly
instances:
  superblock:
    pos: 0
    type: superblock_node
    doc: |
      The superblock node will always be at the start of
      the first LEB.
  master_1:
    pos: superblock.node_header.leb_size
    size: superblock.node_header.leb_size
    type: masterblock_node
    doc: |
      The first copy of the master node in LEB1. This Kaitai Struct
      implementation uses it to locate `index_root`. In an image created by
      `mkfs.ubifs`, it is the only copy in LEB1.
    doc-ref: https://github.com/sigma-star/mtd-utils/blob/053ee1038e5dedae61a88cadfb7bdfe9894d8bb6/ubifs-utils/mkfs.ubifs/mkfs.ubifs.c#L2627-L2633 Git tag "v2.3.1"
  master_2:
    pos: superblock.node_header.leb_size * 2
    size: superblock.node_header.leb_size
    type: masterblock_node
    doc: |
      The first copy of the master node in LEB2. In an image created by
      `mkfs.ubifs`, it is the same as `master_1` except for the sequence
      number and CRC in the common header. Linux keeps the master node in both
      LEB1 and LEB2 for redundancy, and when mounting, it checks that the
      copies in both match.
    doc-ref:
      - https://github.com/sigma-star/mtd-utils/blob/053ee1038e5dedae61a88cadfb7bdfe9894d8bb6/ubifs-utils/mkfs.ubifs/mkfs.ubifs.c#L2627-L2633 Git tag "v2.3.1"
      - https://github.com/torvalds/linux/blob/8d3ae59288f1e7d58d76558a6ee96d533bc5019f/fs/ubifs/master.c#L115-L130 Git tag "v7.2"
  index:
    pos: superblock.node_header.leb_size * master_1.node_header.leb_root
    size: superblock.node_header.leb_size
    type: dummy
    doc: |
      The LEB containing the root index node, as given by `leb_root` in
      `master_1`.

      This instance only provides the substream for `index_root`, which can then
      use `ofs_root` (an offset within the LEB) as its `pos` directly.
  index_root:
    io: index._io
    pos: master_1.node_header.ofs_root
    type: index_block
    valid:
      # Linux (v7.2) [loads](https://github.com/torvalds/linux/blob/8d3ae59288f1e7d58d76558a6ee96d533bc5019f/fs/ubifs/tnc.c#L1216-L1218)
      # the root index node like any other index node, with
      # [`read_znode()`](https://github.com/torvalds/linux/blob/8d3ae59288f1e7d58d76558a6ee96d533bc5019f/fs/ubifs/tnc_misc.c#L290-L300).
      # The expected length is
      # [taken from](https://github.com/torvalds/linux/blob/8d3ae59288f1e7d58d76558a6ee96d533bc5019f/fs/ubifs/master.c#L360)
      # `<master_header>.len_root`, so Linux rejects any other length.
      expr: _.header.len_full_node == master_1.node_header.len_root
    doc: |
      The root index node, in the `index` LEB at the offset given by
      `ofs_root` in `master_1`. All other index nodes, and through them all
      the files, are reached from it by recursively following the `target` of
      each of the `branches`.
  len_image:
    value: superblock.node_header.leb_size * superblock.node_header.num_lebs
    doc: |
      Length of the file system image in bytes:
      `<superblock_header>.num_lebs` LEBs of `<superblock_header>.leb_size`
      bytes each. Use it to find where the image ends and to detect a
      truncated image (see the top-level `doc`).
  lebs:
    pos: 0
    size: superblock.node_header.leb_size
    repeat: expr
    repeat-expr: superblock.node_header.num_lebs
    doc: |
      All LEBs of the file system: `lebs[n]` is LEB number `n`, which starts
      at offset `n * <superblock_header>.leb_size`.

      Accessing this instance reads the whole image into memory. To get a
      single LEB, it's cheaper to read `<superblock_header>.leb_size` bytes
      at the offset of that LEB.

types:
  dummy: {}
  masterblock_node:
    seq:
      - id: header
        type: common_header
        valid:
          expr: _.node_type == node_types::master
      - id: node_header
        size: header.len_full_node - header._sizeof
        type: master_header
  index_block:
    seq:
      - id: header
        type: common_header
        valid:
          expr: _.node_type == node_types::index
      - id: node_header
        size: header.len_full_node - header._sizeof
        type: index_header
  superblock_node:
    seq:
      - id: header
        type: common_header
        valid:
          expr: _.node_type == node_types::superblock
      - id: node_header
        size: header.len_full_node - header._sizeof
        type: superblock_header

  block:
    seq:
      - id: header
        type: common_header
      - id: node_header
        size: header.len_full_node - header._sizeof
        type:
          switch-on: header.node_type
          cases:
            node_types::inode: inode_header
            node_types::data: data_header
            node_types::directory: directory_header
            # Extended attribute entry nodes are identical to directory entry
            # nodes
            node_types::extended_attribute: directory_header
            node_types::truncation: truncation_header
            node_types::padding: padding_header
            node_types::superblock: superblock_header
            node_types::master: master_header
            node_types::reference: reference_header
            node_types::index: index_header
            node_types::commit_start: commit_start_header
            node_types::orphan: orphan_header
            node_types::authentication: authentication_header
            node_types::signature: signature_header

  # Common types
  common_header:
    -orig-id: ubifs_ch
    doc-ref: https://github.com/torvalds/linux/blob/8d3ae59288f1e7d58d76558a6ee96d533bc5019f/fs/ubifs/ubifs-media.h#L443-L464 Git tag "v7.2"
    seq:
      - id: magic
        type: u4
        valid: 0x06101831
        doc: UBIFS node magic number (`UBIFS_NODE_MAGIC`)
      - id: crc
        type: u4
        doc: |
          CRC-32 checksum of the whole node except `magic` and `crc` (i.e.
          starting at offset 8). It is computed with an initial register value
          of 0xFFFFFFFF and no final XOR, so it is the bitwise NOT of a standard
          CRC-32 (e.g.
          [`zlib.crc32()`](https://docs.python.org/3/library/zlib.html#zlib.crc32)
          in Python).
      - id: sequence_number
        -orig-id: sqnum
        type: u8
        doc: sequence number
      - id: len_full_node
        -orig-id: len
        type: u4
        doc: full node length
      - id: node_type
        type: u1
        enum: node_types
        doc: node type
      - id: group_type
        type: u1
        enum: node_group_types
        doc: node group type
      - id: padding
        contents: [0x00, 0x00]
        doc: reserved for future, zeroes

  # Node types
  commit_start_header:
    -orig-id: ubifs_cs_node
    doc-ref: https://github.com/torvalds/linux/blob/8d3ae59288f1e7d58d76558a6ee96d533bc5019f/fs/ubifs/ubifs-media.h#L833-L841 Git tag "v7.2"
    seq:
      - id: commit_number
        -orig-id: cmt_no
        type: u8
        doc: commit number
  data_header:
    -orig-id: ubifs_data_node
    doc-ref: https://github.com/torvalds/linux/blob/8d3ae59288f1e7d58d76558a6ee96d533bc5019f/fs/ubifs/ubifs-media.h#L567-L584 Git tag "v7.2"
    seq:
      - id: key
        size: 16
        type: key
        doc: node key
      - id: len_uncompressed
        -orig-id: size
        type: u4
        valid:
          # https://github.com/torvalds/linux/blob/8d3ae59288f1e7d58d76558a6ee96d533bc5019f/fs/ubifs/file.c#L64-L66 (Git tag "v7.2")
          min: 1
          max: 4096
        doc: uncompressed data size in bytes
      - id: compression
        -orig-id: compr_type
        type: u2
        enum: compression
        valid:
          # https://github.com/torvalds/linux/blob/8d3ae59288f1e7d58d76558a6ee96d533bc5019f/fs/ubifs/compress.c#L201-L204 (Git tag "v7.2")
          in-enum: true
        doc: |
          compression type (`compression::no_compression`, `compression::lzo`,
          etc.)
      - id: len_compressed_data
        -orig-id: compr_size
        type: u2
        doc: compressed data size in bytes, only valid when data is encrypted
      - id: data
        size-eos: true
        doc: |
          One block (up to 4096 bytes) of the file, block number `key.value`,
          compressed as given by `compression`. Blocks missing from the index
          are holes (zeros). In an encrypted inode, the compressed data is
          padded
          and [encrypted](https://github.com/torvalds/linux/blob/8d3ae59288f1e7d58d76558a6ee96d533bc5019f/fs/ubifs/crypto.c#L38-L59),
          and `len_compressed_data` is the length of the compressed data
          before padding.
  directory_header:
    -orig-id: ubifs_dent_node
    doc-ref: https://github.com/torvalds/linux/blob/8d3ae59288f1e7d58d76558a6ee96d533bc5019f/fs/ubifs/ubifs-media.h#L541-L565 Git tag "v7.2"
    seq:
      - id: key
        size: 16
        type: key
        valid:
          # https://github.com/torvalds/linux/blob/8d3ae59288f1e7d58d76558a6ee96d533bc5019f/fs/ubifs/replay.c#L497-L500 (Git tag "v7.2")
          expr: |
            _.type == key_types::directory or
            _.type == key_types::extended_attribute
        doc: node key
      - id: inode_number
        -orig-id: inum
        type: u8
        valid:
          # Linux (v7.2) [rejects](https://github.com/torvalds/linux/blob/8d3ae59288f1e7d58d76558a6ee96d533bc5019f/fs/ubifs/replay.c#L491)
          # values above [`MAX_INUM`](https://github.com/torvalds/linux/blob/8d3ae59288f1e7d58d76558a6ee96d533bc5019f/fs/ubifs/ubifs.h#L84).
          max: 0xffff_ffff
        doc: target inode number
      - id: padding1
        contents: [0x00]
        doc: reserved for future, zeroes
      - id: inode_type
        -orig-id: type
        type: u1
        enum: inode_types
        valid:
          # https://github.com/torvalds/linux/blob/8d3ae59288f1e7d58d76558a6ee96d533bc5019f/fs/ubifs/replay.c#L487-L488 (Git tag "v7.2")
          in-enum: true
        doc: |
          type of the target inode (`inode_types::regular`,
          `inode_types::directory`, etc.)
      - id: len_name
        -orig-id: nlen
        type: u2
        doc: name length
      - id: cookie
        type: u4
        doc: A 32bits random number, used to construct a 64bits identifier.
      - id: name
        size: len_name
        doc: |
          Name of the entry.

          It is kept as bytes rather than decoded as a string: names in a
          directory whose inode has `<inode_header>.encrypted` set are
          ciphertext, which may contain any bytes, including zeros.

          Unencrypted names, including all names of extended attributes, contain
          no zero bytes, because Linux can't create such names. They aren't
          necessarily valid UTF-8, though, as UBIFS doesn't enforce any encoding
          of file names. A zero byte in such a name means that the image was
          crafted, since an accidental corruption would most likely fail the
          node's
          [CRC check](https://github.com/torvalds/linux/blob/8d3ae59288f1e7d58d76558a6ee96d533bc5019f/fs/ubifs/io.c#L287-L293).
      - id: name_terminator
        contents: [0x00]
        doc: |
          Zero byte after `name`, [not counted in
          `len_name`](https://github.com/torvalds/linux/blob/8d3ae59288f1e7d58d76558a6ee96d533bc5019f/fs/ubifs/replay.c#L487-L489).
  index_header:
    -orig-id: ubifs_idx_node
    doc-ref: https://github.com/torvalds/linux/blob/8d3ae59288f1e7d58d76558a6ee96d533bc5019f/fs/ubifs/ubifs-media.h#L819-L831 Git tag "v7.2"
    seq:
      - id: num_branches
        -orig-id: child_cnt
        type: u2
        doc: number of child index nodes
      - id: level
        type: u2
        doc: tree level
      - id: branches
        type: branch
        repeat: expr
        repeat-expr: num_branches
        doc: |
          LEB number / offset / length / key branches

          All branches have the same size, which is 20 bytes (with the simple
          key format) plus the hash length in an authenticated file system.
        doc-ref: https://github.com/torvalds/linux/blob/8d3ae59288f1e7d58d76558a6ee96d533bc5019f/fs/ubifs/misc.h#L186-L205 Git tag "v7.2"
  inode_header:
    -orig-id: ubifs_ino_node
    doc-ref: https://github.com/torvalds/linux/blob/8d3ae59288f1e7d58d76558a6ee96d533bc5019f/fs/ubifs/ubifs-media.h#L480-L539 Git tag "v7.2"
    seq:
      - id: key
        size: 16
        type: key
        doc: node key
      - id: creation_sequence_number
        -orig-id: creat_sqnum
        type: u8
        doc: sequence number at time of creation
      - id: len_uncompressed
        -orig-id: size
        type: u8
        doc: inode size in bytes (amount of uncompressed data)
      - id: atime_sec
        type: u8
        doc: access time seconds
      - id: ctime_sec
        type: u8
        doc: status change time seconds
      - id: mtime_sec
        type: u8
        doc: modification time seconds
      - id: atime_nsec
        type: u4
        doc: access time nanoseconds
      - id: ctime_nsec
        type: u4
        doc: status change time nanoseconds
      - id: mtime_nsec
        type: u4
        doc: modification time nanoseconds
      - id: num_links
        -orig-id: nlink
        type: u4
        doc: number of hard links
      - id: uid
        type: u4
        doc: owner ID
      - id: gid
        type: u4
        doc: group ID
      - id: mode
        type: u4
        doc: |
          file type and permissions, as in `st_mode` (see the `is_*`
          instances for the file type)
      - id: flags
        type: u4
        doc: |
          per-inode flags (see the instances `compressed`, `synchronous`,
          etc.)
      - id: len_data
        -orig-id: data_len
        type: u4
        doc: inode data length
      - id: num_xattrs
        -orig-id: xattr_cnt
        type: u4
        doc: count of extended attributes this inode has
      - id: xattr_size
        type: u4
        doc: summarized size of all extended attributes in bytes
      - id: padding1
        contents: [0x00, 0x00, 0x00, 0x00]
      - id: xattr_names
        type: u4
        doc: |
          sum of lengths of all extended attribute names belonging
          to this inode
      - id: compression
        -orig-id: compr_type
        type: u2
        enum: compression
        valid:
          # Values outside the enum are never written: Linux (v7.2)
          # [writes](https://github.com/torvalds/linux/blob/8d3ae59288f1e7d58d76558a6ee96d533bc5019f/fs/ubifs/journal.c#L594)
          # its in-memory copy, which is a
          # [2-bit field](https://github.com/torvalds/linux/blob/8d3ae59288f1e7d58d76558a6ee96d533bc5019f/fs/ubifs/ubifs.h#L409),
          # and `mkfs.ubifs` (mtd-utils v2.3.1)
          # [writes](https://github.com/sigma-star/mtd-utils/blob/053ee1038e5dedae61a88cadfb7bdfe9894d8bb6/ubifs-utils/mkfs.ubifs/mkfs.ubifs.c#L1496)
          # the compressor chosen with
          # [`-x`](https://github.com/sigma-star/mtd-utils/blob/053ee1038e5dedae61a88cadfb7bdfe9894d8bb6/ubifs-utils/mkfs.ubifs/mkfs.ubifs.c#L665-L688)
          # (or a default).
          #
          # The kernel itself accepts any value when reading: it
          # [copies](https://github.com/torvalds/linux/blob/8d3ae59288f1e7d58d76558a6ee96d533bc5019f/fs/ubifs/super.c#L152)
          # the value into that field, which keeps only its low 2 bits, so its
          # [range check in
          # `validate_inode()`](https://github.com/torvalds/linux/blob/8d3ae59288f1e7d58d76558a6ee96d533bc5019f/fs/ubifs/super.c#L80-L83)
          # never fails.
          #
          # This `valid` is therefore deliberately stricter than the kernel. A
          # value outside the enum means a corrupted file system, which isn't
          # worth supporting by taking the value modulo 4 as the kernel does.
          in-enum: true
        doc: compression type used for this inode
      - id: padding2
        size: 26
        doc: reserved for future, zeroes
      - id: data
        size: len_data
        doc: |
          Target of a [symbolic link](https://github.com/torvalds/linux/blob/8d3ae59288f1e7d58d76558a6ee96d533bc5019f/fs/ubifs/dir.c#L1220-L1241),
          (encrypted if the inode is encrypted), device number of a device node
          ([`union ubifs_dev_desc`](https://github.com/torvalds/linux/blob/8d3ae59288f1e7d58d76558a6ee96d533bc5019f/fs/ubifs/ubifs-media.h#L466-L478):
          4 bytes from Linux, 8 from `mkfs.ubifs`), or value of an
          [extended attribute](https://github.com/torvalds/linux/blob/8d3ae59288f1e7d58d76558a6ee96d533bc5019f/fs/ubifs/xattr.c#L115-L121)
          (in its own inode, see `xattr`). Empty otherwise.
    instances:
      # https://github.com/torvalds/linux/blob/8d3ae59288f1e7d58d76558a6ee96d533bc5019f/fs/ubifs/ubifs-media.h#L309-L332 (Git tag "v7.2")
      compressed:
        -orig-id: UBIFS_COMPR_FL
        value: flags & 0x01 != 0
        doc: use compression for this inode
      synchronous:
        -orig-id: UBIFS_SYNC_FL
        value: flags & 0x02 != 0
        doc: I/O on this inode has to be synchronous
      immutable:
        -orig-id: UBIFS_IMMUTABLE_FL
        value: flags & 0x04 != 0
        doc: inode is immutable
      append:
        -orig-id: UBIFS_APPEND_FL
        value: flags & 0x08 != 0
        doc: writes to the inode may only append data
      dirsync:
        -orig-id: UBIFS_DIRSYNC_FL
        value: flags & 0x10 != 0
        doc: I/O on this directory inode has to be synchronous
      xattr:
        -orig-id: UBIFS_XATTR_FL
        value: flags & 0x20 != 0
        doc: this inode is the inode for an extended attribute value
      encrypted:
        -orig-id: UBIFS_CRYPT_FL
        value: flags & 0x40 != 0
        doc: use encryption for this inode
      # https://github.com/torvalds/linux/blob/8d3ae59288f1e7d58d76558a6ee96d533bc5019f/include/uapi/linux/stat.h#L9-L27 (Git tag "v7.2")
      is_socket:
        -orig-id: S_ISSOCK
        value: mode & 0o0170000 == 0o140000
      is_link:
        -orig-id: S_ISLNK
        value: mode & 0o0170000 == 0o120000
      is_regular:
        -orig-id: S_ISREG
        value: mode & 0o0170000 == 0o100000
      is_block_device:
        -orig-id: S_ISBLK
        value: mode & 0o0170000 == 0o60000
      is_dir:
        -orig-id: S_ISDIR
        value: mode & 0o0170000 == 0o40000
      is_character_device:
        -orig-id: S_ISCHR
        value: mode & 0o0170000 == 0o20000
      is_fifo:
        -orig-id: S_ISFIFO
        value: mode & 0o0170000 == 0o10000
  master_header:
    -orig-id: ubifs_mst_node
    doc-ref: https://github.com/torvalds/linux/blob/8d3ae59288f1e7d58d76558a6ee96d533bc5019f/fs/ubifs/ubifs-media.h#L685-L756 Git tag "v7.2"
    seq:
      - id: highest_inum
        type: u8
        doc: highest inode number in the committed index
      - id: commit_number
        -orig-id: cmt_no
        type: u8
        doc: commit number
      - id: flags
        type: u4
        doc: |
          various flags (see the instances `dirty`, `no_orphans` and
          `recovery`)
      - id: leb_log
        -orig-id: log_lnum
        type: u4
        doc: start of the log
      - id: leb_root
        -orig-id: root_lnum
        type: u4
        doc: LEB number of the root indexing node
      - id: ofs_root
        -orig-id: root_offs
        type: u4
        doc: offset within the LEB `leb_root`
      - id: len_root
        -orig-id: root_len
        type: u4
        doc: root indexing node length
      - id: leb_garbage_collect
        -orig-id: gc_lnum
        type: u4
        doc: |
          LEB reserved for garbage collection, which must be in the main area.

          The [comment above `struct ubifs_mst_node`](https://github.com/torvalds/linux/blob/8d3ae59288f1e7d58d76558a6ee96d533bc5019f/fs/ubifs/ubifs-media.h#L695-L696)
          says that -1 (`0xffff_ffff`) means the LEB was not reserved and should
          be reserved on mount. However, Linux refuses to mount a file system
          with -1. Linux
          [stores](https://github.com/torvalds/linux/blob/8d3ae59288f1e7d58d76558a6ee96d533bc5019f/fs/ubifs/master.c#L362)
          the field in an
          [`int`](https://github.com/torvalds/linux/blob/8d3ae59288f1e7d58d76558a6ee96d533bc5019f/fs/ubifs/ubifs.h#L1424),
          so `0xffff_ffff` becomes -1. Then -1 is rejected by the condition
          `c->gc_lnum < c->main_first`, because the main area
          [starts](https://github.com/torvalds/linux/blob/8d3ae59288f1e7d58d76558a6ee96d533bc5019f/fs/ubifs/sb.c#L775-L777)
          after the superblock, master, log, LPT and orphan areas.
        doc-ref: https://github.com/torvalds/linux/blob/8d3ae59288f1e7d58d76558a6ee96d533bc5019f/fs/ubifs/master.c#L214-L217 Git tag "v7.2"
      - id: leb_index_head
        -orig-id: ihead_lnum
        type: u4
        doc: LEB number of index head
      - id: ofs_index_head
        -orig-id: ihead_offs
        type: u4
        doc: offset of index head
      - id: len_index
        -orig-id: index_size
        type: u8
        doc: size of index on flash
      - id: total_free
        type: u8
        doc: total free space in bytes
      - id: total_dirty
        type: u8
        doc: total dirty space in bytes
      - id: total_used
        type: u8
        doc: total used space in bytes (includes only data LEBs)
      - id: total_dead
        type: u8
        doc: total dead space in bytes (includes only data LEBs)
      - id: total_dark
        type: u8
        doc: total dark space in bytes (includes only data LEBs)
      - id: leb_lpt
        -orig-id: lpt_lnum
        type: u4
        doc: LEB number of LPT root nnode
      - id: ofs_lpt
        -orig-id: lpt_offs
        type: u4
        doc: offset of LPT root nnode
      - id: leb_nhead
        -orig-id: nhead_lnum
        type: u4
        doc: LEB number of LPT head
      - id: ofs_nhead
        -orig-id: nhead_offs
        type: u4
        doc: offset of LPT head
      - id: leb_lprops_table
        -orig-id: ltab_lnum
        type: u4
        doc: LEB number of LPT's own lprops table
      - id: ofs_lprops_table
        -orig-id: ltab_offs
        type: u4
        doc: offset of LPT's own lprops table
      - id: leb_save_table
        -orig-id: lsave_lnum
        type: u4
        doc: |
          LEB number of LPT's save table. Present in every master node, but
          only meaningful if `<superblock_header>.biglpt` is set: only then
          does Linux [check the location of the save table](https://github.com/torvalds/linux/blob/8d3ae59288f1e7d58d76558a6ee96d533bc5019f/fs/ubifs/master.c#L252-L256).
      - id: ofs_save_table
        -orig-id: lsave_offs
        type: u4
        doc: |
          offset of LPT's save table (only meaningful if
          `<superblock_header>.biglpt` is set, see `leb_save_table`)
      - id: leb_last_lpt_scan
        -orig-id: lscan_lnum
        type: u4
        doc: LEB number of last LPT scan
      - id: num_empty_lebs
        -orig-id: empty_lebs
        type: u4
        doc: number of empty logical eraseblocks
      - id: num_index_lebs
        -orig-id: idx_lebs
        type: u4
        doc: number of indexing logical eraseblocks
      - id: num_lebs
        -orig-id: leb_cnt
        type: u4
        doc: count of LEBs used by file-system
      - id: hash_root_index
        -orig-id: hash_root_idx
        size: 64
        doc: the hash of the root index node
      - id: hash_lpt
        size: 64
        doc: the hash of the LPT
      - id: hmac
        size: 64
        doc: HMAC to authenticate the master node
      - id: padding
        size: 152
        doc: reserved for future, zeroes
    instances:
      # https://github.com/torvalds/linux/blob/8d3ae59288f1e7d58d76558a6ee96d533bc5019f/fs/ubifs/ubifs-media.h#L395-L406 (Git tag "v7.2")
      dirty:
        -orig-id: UBIFS_MST_DIRTY
        value: flags & 1 != 0
        doc: master node is dirty
      no_orphans:
        -orig-id: UBIFS_MST_NO_ORPHS
        value: flags & 2 != 0
        doc: no orphan inodes present
      recovery:
        -orig-id: UBIFS_MST_RCVRY
        value: flags & 4 != 0
        doc: written by recovery
  orphan_header:
    -orig-id: ubifs_orph_node
    doc-ref: https://github.com/torvalds/linux/blob/8d3ae59288f1e7d58d76558a6ee96d533bc5019f/fs/ubifs/ubifs-media.h#L843-L853 Git tag "v7.2"
    seq:
      - id: commit_number
        -orig-id: cmt_no
        type: u8
        doc: commit number (also top bit is set on the last node of the commit)
      - id: inode_numbers
        -orig-id: inos
        type: u8
        repeat: eos
        doc: inode numbers of orphans
  authentication_header:
    -orig-id: ubifs_auth_node
    doc-ref: https://github.com/torvalds/linux/blob/8d3ae59288f1e7d58d76558a6ee96d533bc5019f/fs/ubifs/ubifs-media.h#L774-L782 Git tag "v7.2"
    seq:
      - id: hmac
        # When Linux (v7.2)
        # [checks](https://github.com/torvalds/linux/blob/8d3ae59288f1e7d58d76558a6ee96d533bc5019f/fs/ubifs/replay.c#L616-L625)
        # the `hmac`, it
        # [compares](https://github.com/torvalds/linux/blob/8d3ae59288f1e7d58d76558a6ee96d533bc5019f/fs/ubifs/ubifs.h#L1609-L1613)
        # as many bytes as the digest is long (`c->hmac_desc_len`), regardless
        # of the node length.
        size: _root.superblock.node_header.len_hash
        doc: |
          HMAC authenticating the journal before this node, keyed with the
          file system's authentication key (which is not stored on flash).
          The kernel [uses](https://github.com/torvalds/linux/blob/8d3ae59288f1e7d58d76558a6ee96d533bc5019f/fs/ubifs/auth.c#L275-L276)
          HMAC with the hash algorithm in `<superblock_header>.hash_algo`
          ([checked when mounting](https://github.com/torvalds/linux/blob/8d3ae59288f1e7d58d76558a6ee96d533bc5019f/fs/ubifs/sb.c#L575-L580)),
          so `hmac` is as long as its digest, e.g. 32 bytes for SHA-256.
  padding_header:
    -orig-id: ubifs_pad_node
    doc-ref: https://github.com/torvalds/linux/blob/8d3ae59288f1e7d58d76558a6ee96d533bc5019f/fs/ubifs/ubifs-media.h#L606-L615 Git tag "v7.2"
    seq:
      - id: len_padding
        -orig-id: pad_len
        type: u4
        doc: how many bytes after this node are unused (because padded)
  reference_header:
    -orig-id: ubifs_ref_node
    doc: logical eraseblock reference node.
    doc-ref: https://github.com/torvalds/linux/blob/8d3ae59288f1e7d58d76558a6ee96d533bc5019f/fs/ubifs/ubifs-media.h#L758-L772 Git tag "v7.2"
    seq:
      - id: leb_number
        -orig-id: lnum
        type: u4
        doc: the referred logical eraseblock number
      - id: ofs_leb
        -orig-id: offs
        type: u4
        doc: start offset in the referred LEB
      - id: journal_head_number
        -orig-id: jhead
        type: u4
        doc: journal head number
      - id: padding
        size: 28
        doc: reserved for future, zeroes
  signature_header:
    -orig-id: ubifs_sig_node
    doc: |
      Signature of the superblock node of a file system signed when it was
      created (`mkfs.ubifs` with a key). Linux
      [expects it](https://github.com/torvalds/linux/blob/8d3ae59288f1e7d58d76558a6ee96d533bc5019f/fs/ubifs/auth.c#L198-L216)
      as the first node after the superblock node in LEB0.
    doc-ref: https://github.com/torvalds/linux/blob/8d3ae59288f1e7d58d76558a6ee96d533bc5019f/fs/ubifs/ubifs-media.h#L784-L799 Git tag "v7.2"
    seq:
      - id: type
        type: u4
        # `UBIFS_SIGNATURE_TYPE_PKCS7`, the only type that Linux supports:
        # https://github.com/torvalds/linux/blob/8d3ae59288f1e7d58d76558a6ee96d533bc5019f/fs/ubifs/auth.c#L226-L231 (Git tag "v7.2")
        valid: 1
        doc: type of the signature
      - id: len_signature
        -orig-id: len
        type: u4
        doc: length of `signature` in bytes
      - id: padding
        size: 32
        doc: reserved for future, zeroes
      - id: signature
        -orig-id: sig
        size: len_signature
        doc: |
          [PKCS #7 signature](https://github.com/torvalds/linux/blob/8d3ae59288f1e7d58d76558a6ee96d533bc5019f/fs/ubifs/auth.c#L233-L236)
          of the whole superblock node (4096 bytes, including the common
          header)
  superblock_header:
    -orig-id: ubifs_sb_node
    doc-ref: https://github.com/torvalds/linux/blob/8d3ae59288f1e7d58d76558a6ee96d533bc5019f/fs/ubifs/ubifs-media.h#L617-L683 Git tag "v7.2"
    seq:
      - id: padding0
        -orig-id: padding
        contents: [0x00, 0x00]
        doc: reserved for future, zeroes
      - id: key_hash
        type: u1
        enum: hashes
        valid:
          # Linux (v7.2) [sets
          # `c->key_hash`](https://github.com/torvalds/linux/blob/8d3ae59288f1e7d58d76558a6ee96d533bc5019f/fs/ubifs/sb.c#L691-L701)
          # only for the values in the enum, and [`validate_sb()`
          # rejects](https://github.com/torvalds/linux/blob/8d3ae59288f1e7d58d76558a6ee96d533bc5019f/fs/ubifs/sb.c#L384-L387)
          # the superblock if it's left unset.
          in-enum: true
        doc: type of hash function used in keys
      - id: key_fmt
        type: u1
        enum: key_formats
        # https://github.com/torvalds/linux/blob/8d3ae59288f1e7d58d76558a6ee96d533bc5019f/fs/ubifs/sb.c#L705-L713 (Git tag "v7.2")
        valid: key_formats::simple
        doc: format of the key
      - id: flags
        type: u4
        doc: file-system flags (see the instances `biglpt`, `space_fixup`, etc.)
      - id: min_io_size
        type: u4
        doc: minimal input/output unit size
      - id: leb_size
        type: u4
        doc: logical eraseblock size in bytes
      - id: num_lebs
        -orig-id: leb_cnt
        type: u4
        doc: count of LEBs used by file-system
      - id: max_num_lebs
        -orig-id: max_leb_cnt
        type: u4
        valid:
          # Linux (v7.2)
          # [rejects](https://github.com/torvalds/linux/blob/8d3ae59288f1e7d58d76558a6ee96d533bc5019f/fs/ubifs/sb.c#L428-L432)
          # a `max_num_lebs` smaller than the LEB count. Linux may first
          # [increase the LEB
          # count](https://github.com/torvalds/linux/blob/8d3ae59288f1e7d58d76558a6ee96d533bc5019f/fs/ubifs/sb.c#L756-L767),
          # but never above `max_num_lebs`, so Linux rejects the superblock
          # exactly when `num_lebs` is greater than `max_num_lebs`.
          min: num_lebs
        doc: maximum count of LEBs used by file-system
      - id: max_bud_bytes
        type: u8
        doc: maximum amount of data stored in buds
      - id: num_log_lebs
        -orig-id: log_lebs
        type: u4
        valid:
          # https://github.com/torvalds/linux/blob/8d3ae59288f1e7d58d76558a6ee96d533bc5019f/fs/ubifs/sb.c#L406-L412 (Git tag "v7.2")
          min: 2
        doc: log size in logical eraseblocks
      - id: num_lpt_lebs
        -orig-id: lpt_lebs
        type: u4
        valid:
          # https://github.com/torvalds/linux/blob/8d3ae59288f1e7d58d76558a6ee96d533bc5019f/fs/ubifs/sb.c#L406-L412 (Git tag "v7.2")
          min: 2
        doc: number of LEBs used for lprops table
      - id: num_orphan_lebs
        -orig-id: orph_lebs
        type: u4
        valid:
          # https://github.com/torvalds/linux/blob/8d3ae59288f1e7d58d76558a6ee96d533bc5019f/fs/ubifs/sb.c#L406-L412 (Git tag "v7.2")
          min: 1
        doc: number of LEBs used for recording orphans
      - id: num_journal_heads
        -orig-id: jhead_cnt
        type: u4
        doc: count of journal heads
      - id: fanout
        type: u4
        doc: tree fanout (max. number of links per indexing node)
      - id: num_save_table_entries
        -orig-id: lsave_cnt
        type: u4
        doc: number of LEB numbers in LPT's save table
      - id: fmt_version
        type: u4
        valid:
          # Linux (v7.2) also
          # [accepts](https://github.com/torvalds/linux/blob/8d3ae59288f1e7d58d76558a6ee96d533bc5019f/fs/ubifs/sb.c#L684-L689)
          # version 3, but that was a development format: UBIFS
          # [went into mainline](https://github.com/torvalds/linux/blob/8d3ae59288f1e7d58d76558a6ee96d533bc5019f/fs/ubifs/ubifs-media.h#L34-L35)
          # kernel with version 4, and neither Linux nor `mkfs.ubifs` has
          # written version 3 since.
          min: 4
        doc: UBIFS on-flash format version
      - id: default_compression
        -orig-id: default_compr
        type: u2
        enum: compression
        valid:
          # Values outside the enum are never written: Linux (v7.2), when
          # formatting an empty volume,
          # [writes](https://github.com/torvalds/linux/blob/8d3ae59288f1e7d58d76558a6ee96d533bc5019f/fs/ubifs/sb.c#L209-L212)
          # either a [2-bit field](https://github.com/torvalds/linux/blob/8d3ae59288f1e7d58d76558a6ee96d533bc5019f/fs/ubifs/ubifs.h#L950)
          # (set by the `compr` mount option) or its
          # [default compressor](https://github.com/torvalds/linux/blob/8d3ae59288f1e7d58d76558a6ee96d533bc5019f/fs/ubifs/sb.c#L54-L66),
          # and `mkfs.ubifs` (mtd-utils v2.3.1)
          # [writes](https://github.com/sigma-star/mtd-utils/blob/053ee1038e5dedae61a88cadfb7bdfe9894d8bb6/ubifs-utils/mkfs.ubifs/mkfs.ubifs.c#L2535)
          # the compressor chosen with
          # [`-x`](https://github.com/sigma-star/mtd-utils/blob/053ee1038e5dedae61a88cadfb7bdfe9894d8bb6/ubifs-utils/mkfs.ubifs/mkfs.ubifs.c#L665-L688)
          # (or a default).
          #
          # The kernel itself accepts any value when reading: it
          # [copies](https://github.com/torvalds/linux/blob/8d3ae59288f1e7d58d76558a6ee96d533bc5019f/fs/ubifs/sb.c#L728-L729)
          # the value into a [2-bit field](https://github.com/torvalds/linux/blob/8d3ae59288f1e7d58d76558a6ee96d533bc5019f/fs/ubifs/ubifs.h#L1312),
          # which keeps only its low 2 bits, so its
          # [range check in `validate_sb()`](https://github.com/torvalds/linux/blob/8d3ae59288f1e7d58d76558a6ee96d533bc5019f/fs/ubifs/sb.c#L479-L482)
          # never fails.
          #
          # This `valid` is therefore deliberately stricter than the kernel. A
          # value outside the enum means a corrupted file system, which isn't
          # worth supporting by taking the value modulo 4 as the kernel does.
          in-enum: true
        doc: default compression algorithm (`compression::lzo`, etc.)
      - id: padding1
        contents: [0x00, 0x00]
        doc: reserved for future, zeroes
      - id: reserve_pool_uid
        -orig-id: rp_uid
        type: u4
        doc: reserve pool UID
      - id: reserve_pool_gid
        -orig-id: rp_gid
        type: u4
        doc: reserve pool GID
      - id: reserve_pool_size
        -orig-id: rp_size
        type: u8
        doc: size of the reserved pool in bytes
      - id: time_granularity
        -orig-id: time_gran
        type: u4
        valid:
          # https://github.com/torvalds/linux/blob/8d3ae59288f1e7d58d76558a6ee96d533bc5019f/fs/ubifs/sb.c#L489-L493 (Git tag "v7.2")
          min: 1
          max: 1_000_000_000
        doc: time granularity in nanoseconds
      - id: uuid
        size: 16
        doc: UUID generated when the file system image was created
      - id: ro_compat_version
        type: u4
        doc: UBIFS R/O compatibility version
      - id: hmac
        size: 64
        doc: HMAC to authenticate the superblock node
      - id: hmac_wkm
        size: 64
        doc: |
          HMAC of a well known message (the string "UBIFS") as a convenience
          to the user to check if the correct key is passed
      - id: hash_algo
        type: u2
        enum: hash_algos
        valid:
          # https://github.com/torvalds/linux/blob/8d3ae59288f1e7d58d76558a6ee96d533bc5019f/fs/ubifs/sb.c#L568-L573 (Git tag "v7.2")
          in-enum: true
        if: authenticated
        doc: The hash algo used for this filesystem
        doc-ref: https://github.com/torvalds/linux/blob/8d3ae59288f1e7d58d76558a6ee96d533bc5019f/fs/ubifs/sb.c#L549-L573 Git tag "v7.2"
      - id: hash_algo_unused
        -orig-id: hash_algo
        type: u2
        if: not authenticated
        doc: |
          Unused field (would only be meaningful in an authenticated file
          system, see `hash_algo`). The kernel writes it as 0xffff, `mkfs.ubifs`
          writes it as 0.
        doc-ref: https://github.com/torvalds/linux/blob/8d3ae59288f1e7d58d76558a6ee96d533bc5019f/fs/ubifs/sb.c#L183-L191 Git tag "v7.2"
      - id: hash_mst
        size: 64
        doc: |
          hash of the master node, only valid for signed images in which the
          master node does not contain a hmac
      - id: padding2
        size: 3774
        doc: reserved for future, zeroes
    instances:
      # https://github.com/torvalds/linux/blob/8d3ae59288f1e7d58d76558a6ee96d533bc5019f/fs/ubifs/ubifs-media.h#L421-L437 (Git tag "v7.2")
      biglpt:
        -orig-id: UBIFS_FLG_BIGLPT
        value: flags & 0x02 != 0
        doc: if "big" LPT model is used if set
      space_fixup:
        -orig-id: UBIFS_FLG_SPACE_FIXUP
        value: flags & 0x04 != 0
        doc: first-mount "fixup" of free space within LEBs needed
      double_hash:
        -orig-id: UBIFS_FLG_DOUBLE_HASH
        value: flags & 0x08 != 0
        doc: |
          store a 32bit cookie in directory entry nodes to
          support 64bit cookies for lookups by hash
      encrypted:
        -orig-id: UBIFS_FLG_ENCRYPTION
        value: flags & 0x10 != 0
        doc: filesystem contains encrypted files
      authenticated:
        -orig-id: UBIFS_FLG_AUTHENTICATION
        value: flags & 0x20 != 0
        doc: this filesystem contains hashes for authentication
      len_hash:
        # Linux (v7.2) sets `c->hash_len` to the
        # [digest size](https://github.com/torvalds/linux/blob/8d3ae59288f1e7d58d76558a6ee96d533bc5019f/fs/ubifs/auth.c#L309)
        # of the hash algorithm it is told to use at mount time. Mounting
        # [fails](https://github.com/torvalds/linux/blob/8d3ae59288f1e7d58d76558a6ee96d533bc5019f/fs/ubifs/sb.c#L568-L580)
        # unless that algorithm is the one in `hash_algo`, so the superblock
        # determines it. Without authentication, `c->hash_len` stays 0.
        #
        # Digest sizes are as in the kernel's
        # [`hash_digest_size`](https://github.com/torvalds/linux/blob/8d3ae59288f1e7d58d76558a6ee96d533bc5019f/lib/crypto/hash_info.c#L38-L62)
        # table. Every value of `hash_algos` is listed, so the final `-1` is
        # unreachable. It is only there because the ternary operator needs an
        # "else" value, and if a value added to the enum later is not added
        # here, `-1` makes parsing fail (on a negative size) instead of
        # silently using a wrong length.
        value: |
          not authenticated
            ? 0 :
          hash_algo == hash_algos::md4 or
          hash_algo == hash_algos::md5 or
          hash_algo == hash_algos::ripe_md_128 or
          hash_algo == hash_algos::tgr_128
            ? 16 :
          hash_algo == hash_algos::sha1 or
          hash_algo == hash_algos::ripe_md_160 or
          hash_algo == hash_algos::tgr_160
            ? 20 :
          hash_algo == hash_algos::tgr_192
            ? 24 :
          hash_algo == hash_algos::sha224
            ? 28 :
          hash_algo == hash_algos::sha256 or
          hash_algo == hash_algos::ripe_md_256 or
          hash_algo == hash_algos::wp_256 or
          hash_algo == hash_algos::sm3_256 or
          hash_algo == hash_algos::streebog_256 or
          hash_algo == hash_algos::sha3_256
            ? 32 :
          hash_algo == hash_algos::ripe_md_320
            ? 40 :
          hash_algo == hash_algos::sha384 or
          hash_algo == hash_algos::wp_384 or
          hash_algo == hash_algos::sha3_384
            ? 48 :
          hash_algo == hash_algos::sha512 or
          hash_algo == hash_algos::wp_512 or
          hash_algo == hash_algos::streebog_512 or
          hash_algo == hash_algos::sha3_512
            ? 64
            : -1
        doc: |
          Length in bytes of the hash of a node (`c->hash_len` in Linux), e.g.
          in each branch of an index node. In an authenticated file system, it
          is determined by `hash_algo`. Otherwise, it is 0.
  truncation_header:
    -orig-id: ubifs_trun_node
    doc: This node exists only in the journal and never goes to the main area.
    doc-ref: https://github.com/torvalds/linux/blob/8d3ae59288f1e7d58d76558a6ee96d533bc5019f/fs/ubifs/ubifs-media.h#L586-L604 Git tag "v7.2"
    seq:
      - id: inode_number
        -orig-id: inum
        type: u4
        doc: truncated inode number
      - id: padding
        size: 12
        doc: reserved for future, zeroes
      - id: old_size
        type: u8
        valid:
          # Linux (v7.2) [requires `new_size` to be
          # smaller](https://github.com/torvalds/linux/blob/8d3ae59288f1e7d58d76558a6ee96d533bc5019f/fs/ubifs/replay.c#L786-L791),
          # so `old_size` can't be 0.
          min: 1
        doc: size before truncation
      - id: new_size
        type: u8
        valid:
          # https://github.com/torvalds/linux/blob/8d3ae59288f1e7d58d76558a6ee96d533bc5019f/fs/ubifs/replay.c#L786-L791 (Git tag "v7.2")
          max: old_size - 1
        doc: size after truncation

  branch:
    -orig-id: ubifs_branch
    doc-ref: https://github.com/torvalds/linux/blob/8d3ae59288f1e7d58d76558a6ee96d533bc5019f/fs/ubifs/ubifs-media.h#L801-L817 Git tag "v7.2"
    seq:
      - id: target_leb
        -orig-id: lnum
        type: u4
        doc: LEB number of the target node
      - id: ofs_target
        -orig-id: offs
        type: u4
        doc: offset within the LEB `target_leb`
      - id: len_target
        -orig-id: len
        type: u4
        doc: target node length
      - id: key
        # In Linux (v7.2), keys in branches take up only
        # [`c->key_len`](https://github.com/torvalds/linux/blob/8d3ae59288f1e7d58d76558a6ee96d533bc5019f/fs/ubifs/sb.c#L707)
        # bytes, which is 8 (`UBIFS_SK_LEN`) for the simple key format, the
        # only one there is (see `<superblock_header>.key_fmt`).
        size: 8
        type: key
      - id: hash
        size: _root.superblock.node_header.len_hash
        doc: |
          Hash of the target node. It is empty in a file system without
          authentication (see `<superblock_header>.len_hash`).
        doc-ref: https://github.com/torvalds/linux/blob/8d3ae59288f1e7d58d76558a6ee96d533bc5019f/fs/ubifs/ubifs.h#L1642-L1654 Git tag "v7.2"
    instances:
      target:
        io: _root._io
        pos: _root.superblock.node_header.leb_size * target_leb + ofs_target
        size: len_target
        type: block
        valid:
          # Linux (v7.2) reads the target with `ubifs_read_node()`, which
          # [rejects](https://github.com/torvalds/linux/blob/8d3ae59288f1e7d58d76558a6ee96d533bc5019f/fs/ubifs/io.c#L1107-L1123)
          # a node of a different type or length than expected. The
          # expected length is `len_target`. The expected type is an index
          # node if the index node holding this branch is above level 0
          # ([`read_znode()`](https://github.com/torvalds/linux/blob/8d3ae59288f1e7d58d76558a6ee96d533bc5019f/fs/ubifs/tnc_misc.c#L290-L300)),
          # and otherwise the type in `key`
          # ([`ubifs_tnc_read_node()`](https://github.com/torvalds/linux/blob/8d3ae59288f1e7d58d76558a6ee96d533bc5019f/fs/ubifs/tnc_misc.c#L478-L492)).
          # Key types 0-3 have the same values as the corresponding node
          # types.
          expr: |
            _.header.len_full_node == len_target and
            (_parent.level != 0
              ? _.header.node_type == node_types::index
              : _.header.node_type.to_i == key.type.to_i)

  # Key types
  key:
    doc: |
      Inode nodes (`inode_header`), directory and extended attribute entry
      nodes (`directory_header`) and data nodes (`data_header`) store keys in a
      16-byte field (`UBIFS_MAX_KEY_LEN`). The simple key format, the only one
      there is (see `<superblock_header>.key_fmt`), uses only the first 8
      bytes, and Linux (v7.2)
      [zeroes](https://github.com/torvalds/linux/blob/8d3ae59288f1e7d58d76558a6ee96d533bc5019f/fs/ubifs/key.h#L432-L440)
      the rest. Branches of index nodes (`branch`)
      [store only these 8 bytes](https://github.com/torvalds/linux/blob/8d3ae59288f1e7d58d76558a6ee96d533bc5019f/fs/ubifs/key.h#L448-L455).
    doc-ref: https://github.com/torvalds/linux/blob/8d3ae59288f1e7d58d76558a6ee96d533bc5019f/fs/ubifs/key.h#L16-L22 Git tag "v7.2"
    seq:
      - id: inode_number
        type: u4
        doc: |
          Number of the inode that the node belongs to: the parent directory
          for a directory entry, the inode that the attribute belongs to for an
          extended attribute entry.
      - id: value
        # With `meta/bit-endian: le`, the first field gets the least
        # significant bits, so `value` comes before `type`.
        type: b29
        doc: |
          Block number for a data node, hash of the name (see
          `<superblock_header>.key_hash`) for a directory or extended
          attribute entry, 0 for an inode node.
      - id: type
        type: b3
        enum: key_types
        valid:
          # Linux (v7.2) [rejects other key types in
          # branches](https://github.com/torvalds/linux/blob/8d3ae59288f1e7d58d76558a6ee96d533bc5019f/fs/ubifs/tnc_misc.c#L347-L357).
          # The key in an inode, data, directory entry or extended attribute
          # entry node has the same type, because
          # [`ubifs_tnc_read_node()`](https://github.com/torvalds/linux/blob/8d3ae59288f1e7d58d76558a6ee96d533bc5019f/fs/ubifs/tnc_misc.c#L500-L508)
          # checks that the node's key equals the key in the branch that
          # points to the node.
          in-enum: true
        doc: Type of the key, which matches the type of the node.
      - id: unused
        size-eos: true
        doc: |
          Rest of the field holding the key: 8 zero bytes in `inode_header`,
          `directory_header` and `data_header`, empty in `branch`.

enums:
  # https://github.com/torvalds/linux/blob/8d3ae59288f1e7d58d76558a6ee96d533bc5019f/fs/ubifs/ubifs-media.h#L337-L352 (Git tag "v7.2")
  compression:
    0:
      id: no_compression
      -orig-id: UBIFS_COMPR_NONE
      doc: no compression
    1:
      id: lzo
      -orig-id: UBIFS_COMPR_LZO
      doc: LZO compression
    2:
      id: zlib
      -orig-id: UBIFS_COMPR_ZLIB
      doc: |
        zlib compression, stored as
        [raw deflate](https://github.com/torvalds/linux/blob/8d3ae59288f1e7d58d76558a6ee96d533bc5019f/crypto/deflate.c#L125-L127)
        data, i.e. without the zlib header and checksum
    3:
      id: zstd
      -orig-id: UBIFS_COMPR_ZSTD
      doc: zstd compression
  # https://github.com/torvalds/linux/blob/8d3ae59288f1e7d58d76558a6ee96d533bc5019f/fs/ubifs/ubifs-media.h#L152-L173 (Git tag "v7.2")
  inode_types:
    0:
      id: regular
      -orig-id: UBIFS_ITYPE_REG
      doc: regular file
    1:
      id: directory
      -orig-id: UBIFS_ITYPE_DIR
      doc: directory
    2:
      id: link
      -orig-id: UBIFS_ITYPE_LNK
      doc: soft link
    3:
      id: block_device
      -orig-id: UBIFS_ITYPE_BLK
      doc: block device node
    4:
      id: character_device
      -orig-id: UBIFS_ITYPE_CHR
      doc: character device node
    5:
      id: fifo
      -orig-id: UBIFS_ITYPE_FIFO
      doc: fifo
    6:
      id: socket
      -orig-id: UBIFS_ITYPE_SOCK
      doc: socket
  # https://github.com/torvalds/linux/blob/8d3ae59288f1e7d58d76558a6ee96d533bc5019f/fs/ubifs/ubifs-media.h#L354-L393 (Git tag "v7.2")
  node_types:
    0:
      id: inode
      -orig-id: UBIFS_INO_NODE
      doc: inode node
    1:
      id: data
      -orig-id: UBIFS_DATA_NODE
      doc: data node
    2:
      id: directory
      -orig-id: UBIFS_DENT_NODE
      doc: directory entry node
    3:
      id: extended_attribute
      -orig-id: UBIFS_XENT_NODE
      doc: extended attribute node
    4:
      id: truncation
      -orig-id: UBIFS_TRUN_NODE
      doc: truncation node
    5:
      id: padding
      -orig-id: UBIFS_PAD_NODE
      doc: padding node
    6:
      id: superblock
      -orig-id: UBIFS_SB_NODE
      doc: superblock node
    7:
      id: master
      -orig-id: UBIFS_MST_NODE
      doc: master node
    8:
      id: reference
      -orig-id: UBIFS_REF_NODE
      doc: LEB reference node
    9:
      id: index
      -orig-id: UBIFS_IDX_NODE
      doc: index node
    10:
      id: commit_start
      -orig-id: UBIFS_CS_NODE
      doc: commit start node
    11:
      id: orphan
      -orig-id: UBIFS_ORPH_NODE
      doc: orphan node
    12:
      id: authentication
      -orig-id: UBIFS_AUTH_NODE
      doc: authentication node
    13:
      id: signature
      -orig-id: UBIFS_SIG_NODE
      doc: signature node
  # https://github.com/torvalds/linux/blob/8d3ae59288f1e7d58d76558a6ee96d533bc5019f/fs/ubifs/ubifs-media.h#L408-L419 (Git tag "v7.2")
  node_group_types:
    0:
      id: no_node_group
      -orig-id: UBIFS_NO_NODE_GROUP
      doc: this node is not part of a group
    1:
      id: in_node_group
      -orig-id: UBIFS_IN_NODE_GROUP
      doc: this node is a part of a group
    2:
      id: last_of_node_group
      -orig-id: UBIFS_LAST_OF_NODE_GROUP
      doc: this node is the last in a group
  # https://github.com/torvalds/linux/blob/8d3ae59288f1e7d58d76558a6ee96d533bc5019f/fs/ubifs/ubifs-media.h#L175-L184 (Git tag "v7.2")
  hashes:
    0:
      id: r5
      -orig-id: UBIFS_KEY_HASH_R5
      doc: R5 hash
    1:
      id: test_hash
      -orig-id: UBIFS_KEY_HASH_TEST
      doc: test hash which just returns first 4 bytes of the name
  # https://github.com/torvalds/linux/blob/8d3ae59288f1e7d58d76558a6ee96d533bc5019f/fs/ubifs/ubifs-media.h#L186-L193 (Git tag "v7.2")
  key_formats:
    0:
      id: simple
      -orig-id: UBIFS_SIMPLE_KEY_FMT
      doc: |
        The simple key format uses 29 bits for storing UBIFS
        block number and hash value.
  # https://github.com/torvalds/linux/blob/8d3ae59288f1e7d58d76558a6ee96d533bc5019f/fs/ubifs/ubifs-media.h#L204-L219 (Git tag "v7.2")
  key_types:
    0:
      id: inode
      -orig-id: UBIFS_INO_KEY
      doc: inode node key
    1:
      id: data
      -orig-id: UBIFS_DATA_KEY
      doc: data node key
    2:
      id: directory
      -orig-id: UBIFS_DENT_KEY
      doc: directory entry node key
    3:
      id: extended_attribute
      -orig-id: UBIFS_XENT_KEY
      doc: extended attribute node key
  # When adding a value here, also add its digest size to
  # `<superblock_header>.len_hash`, otherwise `len_hash` becomes `-1`, and
  # parsing an authenticated file system fails.
  # https://github.com/torvalds/linux/blob/8d3ae59288f1e7d58d76558a6ee96d533bc5019f/include/uapi/linux/hash_info.h#L17-L42 (Git tag "v7.2")
  hash_algos:
    0:
      id: md4
      -orig-id: HASH_ALGO_MD4
    1:
      id: md5
      -orig-id: HASH_ALGO_MD5
    2:
      id: sha1
      -orig-id: HASH_ALGO_SHA1
    3:
      id: ripe_md_160
      -orig-id: HASH_ALGO_RIPE_MD_160
    4:
      id: sha256
      -orig-id: HASH_ALGO_SHA256
    5:
      id: sha384
      -orig-id: HASH_ALGO_SHA384
    6:
      id: sha512
      -orig-id: HASH_ALGO_SHA512
    7:
      id: sha224
      -orig-id: HASH_ALGO_SHA224
    8:
      id: ripe_md_128
      -orig-id: HASH_ALGO_RIPE_MD_128
    9:
      id: ripe_md_256
      -orig-id: HASH_ALGO_RIPE_MD_256
    10:
      id: ripe_md_320
      -orig-id: HASH_ALGO_RIPE_MD_320
    11:
      id: wp_256
      -orig-id: HASH_ALGO_WP_256
    12:
      id: wp_384
      -orig-id: HASH_ALGO_WP_384
    13:
      id: wp_512
      -orig-id: HASH_ALGO_WP_512
    14:
      id: tgr_128
      -orig-id: HASH_ALGO_TGR_128
    15:
      id: tgr_160
      -orig-id: HASH_ALGO_TGR_160
    16:
      id: tgr_192
      -orig-id: HASH_ALGO_TGR_192
    17:
      id: sm3_256
      -orig-id: HASH_ALGO_SM3_256
    18:
      id: streebog_256
      -orig-id: HASH_ALGO_STREEBOG_256
    19:
      id: streebog_512
      -orig-id: HASH_ALGO_STREEBOG_512
    20:
      id: sha3_256
      -orig-id: HASH_ALGO_SHA3_256
    21:
      id: sha3_384
      -orig-id: HASH_ALGO_SHA3_384
    22:
      id: sha3_512
      -orig-id: HASH_ALGO_SHA3_512