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
| Item | Detail |
|---|---|
AGF buffer at blkno 0x1 | AG 0 free space manager — space was allocated or freed |
3 data buffers at 0x8, 0x10, 0x28 | Free space or inode btree blocks modified by the allocation |
Inode 0x84 (132), flags 0x5, dsize 48 | Core + 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
| Item | Detail |
|---|---|
Inode 0x83 (131), flags 0x1, dsize 0 | Core 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:
| Address | Length | Content |
|---|---|---|
0xaaaaef94fe40 | 56 bytes | xfs_inode_log_format — header describing which parts of the inode were dirtied |
0xaaaaef94fef0 | 176 bytes | Inode core (xfs_dinode) — timestamps, size, link count, etc. |
0xaaaaef94ffb0 | 48 bytes | Data 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
- The test fills the filesystem and then attempts an mmap CoW write with no free space.
- XFS starts a transaction: allocates CoW blocks (modifying the AGF and btree blocks) and updates the target inode’s extent map.
- ENOSPC is hit mid-flight. XFS force-shuts down the filesystem to avoid leaving metadata in a half-updated state.
- The transaction records are committed to the on-disk log (that is why
xfs_logprintcan see them), but the AIL never flushes the modified buffers to their actual on-disk locations — the log tail is never pushed. - Force-shutdown prevents any further log writes, so the clean log marker cannot be written on unmount.
xfs_logprintsees<DIRTY>andxfs_repair -nsees stale on-disk structures that do not match the logged intent.