Skip to content

fix: report actual st_blocks for directories (XFS inline dirs) - #694

Open
shivampwm2020 wants to merge 1 commit into
dundee:masterfrom
shivampwm2020:fix-393-xfs-dir-blocks
Open

shivampwm2020 wants to merge 1 commit into
dundee:masterfrom
shivampwm2020:fix-393-xfs-dir-blocks

Conversation

@shivampwm2020

Copy link
Copy Markdown

Problem

gdu charges a directory a fixed one-block (4096 B) of disk usage instead of reading what the filesystem actually allocated to it.

On XFS a small directory is stored inline in the inode: stat reports Blocks: 0, xfs_bmap reports no extents, and du reports 0. gdu reported 4.0 KiB for such a directory (issue #393).

Root cause

gdu never read a directory's own st_blocks.

  • Before fix: correct item count and size for empty dirs #574 the analyzers hardcoded totalSize/totalUsage = 4096 for every directory, which is exactly the reported 4.0 KiB on an inline XFS directory.
  • fix: correct item count and size for empty dirs #574 replaced that with EmptyDirSize (a 512 B apparent-size sentinel) and set directory usage to 0. That removes the wrong number but not the mistake: directories are now charged nothing at all, so on a filesystem that does allocate a block, the directory's own usage is dropped from the total. On ext4 a directory holding two files reports 0 B where du reports 4096 B.

Both directions come from the same place: a directory's usage was assumed, never measured.

Fix

Measure it, once, in the shared platform hook, and fold it into the directory's usage where every analyzer already converges:

  • dirOwnUsage(path) reads st_blocks * 512 for the directory itself, per platform (0 where the platform exposes no block information, as before).
  • setDirPlatformSpecificAttrs stores it as Dir.OwnUsage.
  • dirTotals gains ownUsage, and resolveDirStats adds it to the reported usage in both branches. resolveDirStats remains the single definition of the rule, so the parallel, sequential, top-dir, sqlite and stored analyzers all agree.
  • The top-dir fast path (processSubDir), which aggregates separately, does the same.

A directory is now charged exactly its st_blocks: 0 for an XFS inline directory, the real block count elsewhere — the same rule du uses.

Testing

Verified against du on ext4 (-n, --summarize, and --db/sqlite all agree):

case du before after
dir with two files 8192 B 0 B 8192 B
dir + 1-byte files 16384 B 0 B 16384 B
--apparent-size (unchanged) 0 B 0 B 0 B

A multi-block XFS directory reduces to the inline case: st_blocks is 0, so the reported usage is 0, matching du.

New tests:

Existing tests that encoded the old "directories cost nothing" rule (TestEmptyDirStatsAcrossAnalyzers, TestSqliteAnalyzerIgnoreFileType, TestSqliteAnalyzerTimeFilter, TestTopDirAnalyzerCancellationStopsActiveSubdirectory) now take their expected usage from stat, so they pass on any block size and on any filesystem.

  • go test ./... — pass
  • golangci-lint run -c .golangci.yml — 0 issues
  • gofmt — clean

Fixes #393

— Shivam Maurya

gdu charged every directory a fixed one-block (4096) usage instead of
reading what the filesystem actually allocated. On XFS a small directory
is stored inline in the inode, so stat reports Blocks: 0 and du reports
0, but gdu still printed 4.0 KiB.

The fixed 4096 was later replaced by charging directories nothing at all,
which swings the error the other way: on filesystems that do allocate a
block, the directory's own usage disappeared from the total.

Both come from the same place: gdu never read a directory's st_blocks.
Read it, once, in the shared platform hook, and fold it into the
directory's usage in resolveDirStats so every analyzer agrees.

Verified against du on ext4: a directory holding two files now reports
the same total as du, an inline directory reports 0, and --summarize,
--db (sqlite) and the top-dir fast path all match.
@codecov

codecov Bot commented Oct 15, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 91.66667% with 2 lines in your changes missing coverage. Please review.
✅ Project coverage is 90.74%. Comparing base (387cf50) to head (356aa73).

Files with missing lines Patch % Lines
pkg/analyze/file.go 75.00% 2 Missing ⚠️
Additional details and impacted files
@@           Coverage Diff           @@
##           master     #694   +/-   ##
=======================================
  Coverage   90.73%   90.74%           
=======================================
  Files          76       76           
  Lines        6843     6859   +16     
=======================================
+ Hits         6209     6224   +15     
- Misses        633      634    +1     
  Partials        1        1           

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

gdu assumes directory takes up 1 block when it doesn't (on XFS)

1 participant