#+TITLE: M3: Bug Fixes & Codebase Cleanup — Step Plan
#+DATE: 2026-03-15

*Branch: =m3-bugfixes= (off =m1-testing=)*

This is the first milestone that modifies =src/=. Every change runs
on production servers — work carefully, self-review, human review
on every commit. One bug at a time, verified by the test suite.

* Approach

Order: self-contained fixes first (build confidence, prove the test
suite catches things), then investigation-heavy bugs, then cosmetic
cleanup last. Each fix is one commit, tested, reviewed.

The test suite (M1) exists to verify these fixes. For each bug:
1. Confirm the test currently shows the bug (or write a test first)
2. Fix the code
3. Run tests — confirm the fix, confirm no regressions
4. Self-review the diff
5. Commit, push, human review

* Tasks

** DONE 3.1 Fix: +0M clears quota instead of no-op

*File*: =src/parse.c= line 475
*Bug*: =parse_size()= returns 0 when =count==0=, ignoring =orig=
and =op=. So =+0M= applied to 100M → 0 instead of staying at 100M.
*Test*: =test/tests/host/t-parse-units.sh= — currently shows
=KNOWN BUG - +0M clears quota=. After fix, should flip to =ok=.
*Fix*: Move the =count==0= early return after the =switch(op)=,
or handle it inside the switch (return =orig= for add/sub with 0).

/Verification/: =run-tests --host-only= — known bug line disappears.
Full matrix not needed (parse_size is kernel-independent).

** DONE 3.2 Fix: "5m" timespan ambiguity passes -1 to quotactl

*Files*: =src/parse.c= =parse_timespan()= + =src/main.c= lines 139,146
*Bug*: =parse_timespan()= returns -1 on error (ambiguous "m", invalid
format), but =main.c= stores the return value directly into
=quota->block_grace= without checking for -1. The bad value reaches
=quotactl()=.
*Fix*: Check =parse_timespan()= return value in =main.c=. If -1,
print error and exit. Optionally: make bare "m" resolve to minutes
(more intuitive than error) — but that's a behavior change, discuss.
*Test*: Add a test case to =t-parse-timespan.sh= or =t-error-args.sh=
for the "5m" case once the behavior is decided.

/Verification/: =run-tests --host-only=.

** DONE 3.3 Fix: -d grace field operator precedence

*File*: =src/main.c= lines 109, 123
*Bug*: =(unsigned long) quota->block_time ? ...= — cast binds only
to =block_time=, not to the ternary result. When grace has expired,
=block_time - now= is negative, but =%lu= prints it as a huge
unsigned number. BSD code path (lines 101-107) is correct.
*Fix*: Add parentheses: =(unsigned long)(quota->block_time ? ... : 0)=
Match the BSD code path's style.
*Test*: =t-dump-format.sh= checks field types. Could add an
expired-grace test (set grace very short, wait, verify -d output).

/Verification/: =run-tests --host-only=.

** DONE 3.4 Remove: unused sys/cdefs.h include

*File*: =src/linux/linux_quota.h= line 17
*Bug*: =#include <sys/cdefs.h>= — glibc-only header. Nothing from
it is used. Breaks musl-gcc build (we work around with empty stub
in =build-alpine-rootfs.sh=).
*Fix*: Delete the include line. Remove the stub from
=build-alpine-rootfs.sh=.
*Test*: Normal =make= succeeds. =build-alpine-rootfs.sh= still works
without the stub.

/Verification/: =./configure && make= clean. =run-tests --quick=.

** DONE 3.5 Investigate: grace -r restart (≤4.12) — NOT A BUG

*Result*: Not a quotatool bug. The =-r= flag correctly clears the
old grace timer via raise/restore. On kernels ≤4.14, the kernel
only re-triggers the grace timer on the next user write, not from
=quotactl= limit changes. On ≥4.15, the kernel re-triggers
immediately. In real-world usage the user writes after =-r= is
run (that's the whole point), so grace restarts naturally.

*Investigation done*:
- Confirmed raise/restore clears grace on all kernels (3.16+)
- Confirmed kernel does not re-trigger grace via quotactl on ≤4.14
- Confirmed direct =QIF_BTIME= setting works on ≥5.10 but is
  silently ignored on ≤4.9 — kernel limitation, not quotatool
- Confirmed a user write after =-r= triggers fresh grace on 3.16
- Checked upstream quota-tools: uses direct =QIF_BTIME= (same
  limitation on old kernels)

*Fix*: Test updated — =t-grace-period.sh= now does a tiny write
after =-r= before checking grace, matching real-world usage.
No code change to =quota_reset_grace()=. GitHub #35 closed.

** DONE 3.6 Fix: XFS quota issues

Detailed investigation in =steps/03-xfs-grace.org=.
Verified 25/25 on multiple hosts with different mkfs/kernel versions.

*** XFS grace display — sync issue (test fix)
XFS lazy quota metadata writeback: grace timer set in memory but
not visible via =quotactl= until =sync -f=. Added =sync -f= after
user writes in all XFS-affected tests. Not a quotatool code bug.

*** XFS grace reset -r — hybrid approach (code fix)
Raise/restore hack fails on newer kernels (6.19+) with XFS V4:
kernel blocks writes when over soft with no timer. Fix: hybrid
approach in =quota_reset_grace()= — raise/restore (works on old
kernels ≤5.4) + direct timer set via =Q_XSETQLIM= + =FS_DQ_BTIMER=
(works on new kernels ≥5.10). Both applied, safe on all kernels.

*** XFS block accounting (50→52) — working as intended
XFS rounds quota limits to allocation unit (4KB). Not a bug.

** DONE 3.7 Compiler warnings: -Wall -Wextra clean build

Do AFTER bug fixes (fixes change code that may trigger warnings).

- Build with =CFLAGS="-Wall -Wextra"= on gcc
- Build with clang (=CC=clang=) if available
- Fix all warnings — no new warnings allowed after this
- Consider adding =-Werror= to CI/test builds (not default)

/Verification/: =./configure && make CFLAGS="-Wall -Wextra"= clean.

** DONE 3.8 Codebase cleanup

- Dead code and debug blocks removed (committed)
- Custom kernel headers: kept — they ARE the portability layer
- =TODO= file: already clean (points to GitHub issues)
- Indentation (tabs vs spaces): deferred to M2/v1.8.0 when platform
  drops shrink the codebase — do style sweep once on surviving code

** DONE 3.9 CLI UX: run-tests flag review + timespan clarity

Revisit before v1.7.0 release:
- No-args behavior (help vs full run?)
- Flag naming (=--quick= → something clearer?)
- Should full matrix require explicit flag?
- General idiom check for developer CLI tools
- Timespan parsing: unclear and undocumented. "5d" parses as 5 days
  (same as "5day" or "5days"), but this is opaque to users. Help/man
  page should list accepted suffixes with examples. Consider whether
  the "mi"/"mo" disambiguation is user-friendly enough.
- Test filesystem image sizes: ext4=200M and XFS=512M are oversized
  for what the tests need. Reduce to save VM memory (currently 1024M
  total, 512M tmpfs — the 512M XFS image barely fits). Would allow
  lower BOOT_MEMORY or more headroom.
- Consider adding an older LTS distribution as a test host (e.g.
  Ubuntu 20.04 or Debian 11). Intent: catch differences between
  host environments that affect test behavior, like mkfs.xfs version
  differences causing XFS V4 fallback. Current test hosts are
  relatively new distributions.

*** Future docs
- Document btrfs in README/man: btrfs uses a different quota model
  (tree-based =btrfs qgroup=, not user/group). Not applicable to
  =quotactl()=. quotatool does not and will not support btrfs quotas.
  Mention this to avoid confusion.

*** Already done (moved here from TODO for reference)
- Pre-flight check for stale =/tmp/test-{xfs,ext4}.img= (implemented)
- Inode limit test coverage across all tests (implemented)
- debian-7 XFS t-enforce-block-soft failure (fixed: missing sync -f)

** DONE 3.10 Release prep: v1.7.0

- Version bump in =configure.ac= (1.6.5 → 1.7.0)
- Update =ChangeLog=
- Update =README.md= (tested kernel matrix, test suite docs)
- Add ROADMAP section announcing v1.8.0 platform changes
- Update man page if any user-visible behavior changed
- Merge =m1-testing= → =master= (includes all M1 + M3 work)
- Tag =v1.7.0=
- Create =release-1.7= branch
- Notify downstream packagers

* Dependency Order

#+begin_example
3.1 (parse_size +0)     ──→ independent, do first
3.2 (timespan -1)       ──→ independent
3.3 (operator prec)     ──→ independent, may inform 3.6
3.4 (sys/cdefs.h)       ──→ independent
3.5 (grace -r ≤4.12)    ──→ needs investigation, full matrix
3.6 (XFS grace)         ──→ needs investigation, may depend on 3.3
3.7 (-Wall -Wextra)     ──→ after 3.1-3.6
3.8 (cleanup)           ──→ after 3.7
3.9 (CLI UX)            ──→ before 3.10
3.10 (release)          ──→ after everything
#+end_example
