XFS Dirty Log: head/tail error analysis

Context

When _check_xfs_filesystem runs after a test that triggers ENOSPC during mmap copy-on-write (e.g. generic/173), it can report two errors:

_check_xfs_filesystem: filesystem on /dev/loop1 has dirty log
_check_xfs_filesystem: filesystem on /dev/loop1 is inconsistent (r)

What the errors mean

Dirty log

_check_xfs_filesystem unmounts the filesystem and then runs xfs_logprint -t. A clean unmount should flush all dirty buffers, drain the AIL, and write a clean log marker. If the log state is still <DIRTY> after unmount, it means XFS was force-shut down before the unmount could checkpoint the log — preventing it from writing the clean marker.

Inconsistent (r)

xfs_repair -n (read-only mode) reads the raw on-disk data structures. Because the AIL never flushed the logged changes to those structures, the on-disk state is the pre-transaction state — inconsistent with what the log says should be there. xfs_repair -n cannot replay the log to reconcile them, so it flags the filesystem as inconsistent.

A plain xfs_repair (without -n) would replay the log first and would very likely find no actual corruption.

Example log output

xfs_logprint:
    data device: 0x701
    log device: 0x701 daddr: 327728 length: 131072

    log tail: 40 head: 46 state: <DIRTY>
  • Internal log — log and data are on the same device (0x701).
  • tail: 40, head: 46 — 6 blocks of live log records sit between tail and head. These records were written to the on-disk log but the corresponding buffer writes (AGF, btree blocks, inodes) never reached the data structures. On the next mount XFS would replay them.

Transactions in the log

Transaction 1 — LSN (cycle 1, block 40), tid 0x31cd442c, 11 items

ItemDetail
AGF buffer at blkno 0x1AG 0 free space manager — space was allocated or freed
3 data buffers at 0x8, 0x10, 0x28Free space or inode btree blocks modified by the allocation
Inode 0x84 (132), flags 0x5, dsize 48Core + data fork extents — a file whose extent map was being updated

Consistent with a CoW block allocation in progress: AGF modified, btree blocks updated, and the target inode’s extent map changed.

Transaction 2 — LSN (cycle 1, block 44), tid 0xa2d07cca, 2 items

ItemDetail
Inode 0x83 (131), flags 0x1, dsize 0Core only, no extent data — an inode whose data fork was cleared or reset

Inode log item anatomy

Using inode 0x84 from transaction 1 as an example:

INO: cnt:3 total:3 a:0xaaaaef94fe40 len:56 a:0xaaaaef94fef0 len:176 a:0xaaaaef94ffb0 len:48
    INODE: #regs:3   ino:0x84  flags:0x5   dsize:48
    CORE inode:
        DATA FORK EXTENTS inode data:

The inode log item is composed of 3 memory regions copied into the log:

AddressLengthContent
0xaaaaef94fe4056 bytesxfs_inode_log_format — header describing which parts of the inode were dirtied
0xaaaaef94fef0176 bytesInode core (xfs_dinode) — timestamps, size, link count, etc.
0xaaaaef94ffb048 bytesData fork extent records

flags:0x5 = XFS_ILOG_CORE (0x1) | XFS_ILOG_DEXT (0x4) — both the inode core and the data fork extent list were dirtied by this transaction.

dsize:48 — 48 bytes of extent data = 3 extents (each xfs_bmbt_rec_t is 16 bytes). These are the packed extent records describing the new block mappings for the CoW operation.

Summary of what happened

  1. The test fills the filesystem and then attempts an mmap CoW write with no free space.
  2. XFS starts a transaction: allocates CoW blocks (modifying the AGF and btree blocks) and updates the target inode’s extent map.
  3. ENOSPC is hit mid-flight. XFS force-shuts down the filesystem to avoid leaving metadata in a half-updated state.
  4. The transaction records are committed to the on-disk log (that is why xfs_logprint can see them), but the AIL never flushes the modified buffers to their actual on-disk locations — the log tail is never pushed.
  5. Force-shutdown prevents any further log writes, so the clean log marker cannot be written on unmount.
  6. xfs_logprint sees <DIRTY> and xfs_repair -n sees stale on-disk structures that do not match the logged intent.