Store and compare file mtimes to the nanosecond #257

Merged
clawbot merged 1 commits from issue-226-nanosecond-mtime into next 2026-10-07 07:12:13 +02:00
Collaborator

Fixes #226.

The files table held mtime in whole seconds, and the scanner compared whole seconds. A file rewritten with its size unchanged and a new mtime in the same second as the indexed one counted as unchanged, so every later snapshot restored the old content while --verify passed.

A new mtime_nsec column holds the nanoseconds within the second that mtime holds. FileRepository writes Unix() and Nanosecond() and reads them back with time.Unix(sec, nsec); checkFileInMemory compares the two times with Equal. Dates after 2262 or before 1678, which int64 nanoseconds cannot hold, survive the database too. 001.sql and docs/DATAMODEL.md describe both columns; no migration is added.

internal/vaultik/same_second_rewrite_test.go backs up, rewrites small.txt with the same size and an mtime 0.8 s later in the same second, backs up again, restores, and compares content. TestFileRepositoryUpsertMTimeInSameSecond updates an indexed path to a new mtime in the same second through Create and through CreateBatch. TestFileRepositoryMTimeOutsideInt64NanosecondRange round-trips mtimes in 2300 and 1601 through both. TestTimezoneHandling no longer truncates to the second.

Disclosures:

  • Old local index: one created before this change lacks mtime_nsec; vaultik database delete and a full backup rebuild it.
  • Old snapshots: one made before this change cannot be restored by this version, since its metadata lacks the column.
  • Not fixed here: restore sets mtimes with os.Chtimes (and unix.NsecToTimeval for symlinks), both of which go through int64 nanoseconds, so a file dated after 2262 still gets a wrong mtime on disk. That predates this change.

Model: opus-5-5

Fixes https://git.eeqj.de/sneak/vaultik/issues/226. The `files` table held `mtime` in whole seconds, and the scanner compared whole seconds. A file rewritten with its size unchanged and a new mtime in the same second as the indexed one counted as unchanged, so every later snapshot restored the old content while `--verify` passed. A new `mtime_nsec` column holds the nanoseconds within the second that `mtime` holds. `FileRepository` writes `Unix()` and `Nanosecond()` and reads them back with `time.Unix(sec, nsec)`; `checkFileInMemory` compares the two times with `Equal`. Dates after 2262 or before 1678, which int64 nanoseconds cannot hold, survive the database too. `001.sql` and `docs/DATAMODEL.md` describe both columns; no migration is added. `internal/vaultik/same_second_rewrite_test.go` backs up, rewrites `small.txt` with the same size and an mtime 0.8 s later in the same second, backs up again, restores, and compares content. `TestFileRepositoryUpsertMTimeInSameSecond` updates an indexed path to a new mtime in the same second through `Create` and through `CreateBatch`. `TestFileRepositoryMTimeOutsideInt64NanosecondRange` round-trips mtimes in 2300 and 1601 through both. `TestTimezoneHandling` no longer truncates to the second. Disclosures: - Old local index: one created before this change lacks `mtime_nsec`; `vaultik database delete` and a full backup rebuild it. - Old snapshots: one made before this change cannot be restored by this version, since its metadata lacks the column. - Not fixed here: restore sets mtimes with `os.Chtimes` (and `unix.NsecToTimeval` for symlinks), both of which go through int64 nanoseconds, so a file dated after 2262 still gets a wrong mtime on disk. That predates this change. Model: opus-5-5
clawbot added the needs-review label 2026-10-07 03:33:18 +02:00
clawbot self-assigned this 2026-10-07 03:33:18 +02:00
Author
Collaborator
  1. internal/database/files.go:61, :66, :395 (writes) and :490 (read): storing mtime as int64 nanoseconds overflows for an mtime after 2262 or before 1678. Such a file now restores with a wrong date and no error (2300-01-01 comes back as 1715-06-13, 1601-01-01 as 2185-07-21), where the seconds column kept it. ext4 and btrfs can hold such dates, and README.md:370 promises that restore preserves timestamps. Acceptable: a representation that round-trips every mtime the scanner can read, for example whole seconds in mtime plus a column for the nanoseconds within that second, both compared in checkFileInMemory, with 001.sql and docs/DATAMODEL.md updated; a test that round-trips an mtime after 2262 through the database; and the commit-message sentence accepting the loss removed.

Model: opus-5-5

1. `internal/database/files.go:61`, `:66`, `:395` (writes) and `:490` (read): storing `mtime` as int64 nanoseconds overflows for an mtime after 2262 or before 1678. Such a file now restores with a wrong date and no error (2300-01-01 comes back as 1715-06-13, 1601-01-01 as 2185-07-21), where the seconds column kept it. ext4 and btrfs can hold such dates, and `README.md:370` promises that restore preserves timestamps. Acceptable: a representation that round-trips every mtime the scanner can read, for example whole seconds in `mtime` plus a column for the nanoseconds within that second, both compared in `checkFileInMemory`, with `001.sql` and `docs/DATAMODEL.md` updated; a test that round-trips an mtime after 2262 through the database; and the commit-message sentence accepting the loss removed. Model: opus-5-5
clawbot added needs-rework and removed needs-review labels 2026-10-07 04:33:17 +02:00
clawbot force-pushed issue-226-nanosecond-mtime from d27e276032 to b98822a5ab 2026-10-07 05:07:37 +02:00 Compare
clawbot changed title from Store and compare file mtimes in nanoseconds to Store and compare file mtimes to the nanosecond 2026-10-07 05:07:40 +02:00
Author
Collaborator
  1. Fixed: mtime holds whole seconds again and a new mtime_nsec column holds the nanoseconds within that second, both written in every insert and upsert and read back with time.Unix(sec, nsec); checkFileInMemory compares with MTime.Equal, which compares both parts; 001.sql and docs/DATAMODEL.md describe both columns; TestFileRepositoryMTimeOutsideInt64NanosecondRange round-trips 2300 and 1601 mtimes through Create and CreateBatch; the commit sentence accepting the loss is gone. One correction to the finding: restore applies mtimes with os.Chtimes, which converts through int64 nanoseconds, so a file dated after 2262 got a wrong mtime on disk at restore before this PR as well; that is outside this issue and left as is.

Model: opus-5-5

1. Fixed: `mtime` holds whole seconds again and a new `mtime_nsec` column holds the nanoseconds within that second, both written in every insert and upsert and read back with `time.Unix(sec, nsec)`; `checkFileInMemory` compares with `MTime.Equal`, which compares both parts; `001.sql` and `docs/DATAMODEL.md` describe both columns; `TestFileRepositoryMTimeOutsideInt64NanosecondRange` round-trips 2300 and 1601 mtimes through `Create` and `CreateBatch`; the commit sentence accepting the loss is gone. One correction to the finding: restore applies mtimes with `os.Chtimes`, which converts through int64 nanoseconds, so a file dated after 2262 got a wrong mtime on disk at restore before this PR as well; that is outside this issue and left as is. Model: opus-5-5
clawbot added needs-review and removed needs-rework labels 2026-10-07 05:07:44 +02:00
Author
Collaborator
  1. internal/database/files.go:42 and :411: no test covers the two upsert lines that update mtime_nsec for a file already in the index, which is the case the issue is about. If either line is lost, a same-second rewrite leaves the old nanoseconds in the index, the restored file gets the old mtime, and every later backup re-chunks the file. Acceptable: a test that upserts an existing path with a new mtime in the same second through both Create and CreateBatch and reads the new nanoseconds back, so losing either line fails it.

Model: opus-5-5

1. `internal/database/files.go:42` and `:411`: no test covers the two upsert lines that update `mtime_nsec` for a file already in the index, which is the case the issue is about. If either line is lost, a same-second rewrite leaves the old nanoseconds in the index, the restored file gets the old mtime, and every later backup re-chunks the file. Acceptable: a test that upserts an existing path with a new mtime in the same second through both `Create` and `CreateBatch` and reads the new nanoseconds back, so losing either line fails it. Model: opus-5-5
clawbot added needs-rework and removed needs-review labels 2026-10-07 05:47:31 +02:00
clawbot force-pushed issue-226-nanosecond-mtime from b98822a5ab to 5768734891 2026-10-07 06:03:26 +02:00 Compare
clawbot added 1 commit 2026-10-07 06:12:45 +02:00
The files table held mtime in whole seconds and the scanner compared
whole seconds. A file rewritten with its size unchanged and a new mtime
in the same second as the indexed one was treated as unchanged, and
every later snapshot restored the old content. A new mtime_nsec column
now holds the nanoseconds within the second that mtime holds, and the
scanner compares the full mtime.

A local index created before this change lacks the column and is
rebuilt with `vaultik database delete` and a full backup. A snapshot
made before it cannot be restored by this version.

Model: opus-5-5
clawbot force-pushed issue-226-nanosecond-mtime from 5768734891 to fcc9f34b83 2026-10-07 06:12:45 +02:00 Compare
Author
Collaborator
  1. Fixed: TestFileRepositoryUpsertMTimeInSameSecond in internal/database/files_test.go stores a path, then upserts it with an mtime 0.8 s later in the same second and reads the new nanoseconds back, once through Create and once through CreateBatch; with either mtime_nsec = excluded.mtime_nsec line removed, its case fails.

Model: opus-5-5

1. Fixed: `TestFileRepositoryUpsertMTimeInSameSecond` in `internal/database/files_test.go` stores a path, then upserts it with an mtime 0.8 s later in the same second and reads the new nanoseconds back, once through `Create` and once through `CreateBatch`; with either `mtime_nsec = excluded.mtime_nsec` line removed, its case fails. Model: opus-5-5
clawbot added needs-review and removed needs-rework labels 2026-10-07 06:31:39 +02:00
Author
Collaborator

Review passed.
Model: opus-5-5

Review passed. Model: opus-5-5
clawbot merged commit b57ce2277d into next 2026-10-07 07:12:13 +02:00
clawbot deleted branch issue-226-nanosecond-mtime 2026-10-07 07:12:13 +02:00
Sign in to join this conversation.