Save metadata and journal staleness: second-pass fixes

- Ownership is applied before permissions: chown clears setuid/setgid,
  so saving a 06755 file left it 0755.
- The umask is read once during static initialisation. Reading it at
  save time needed umask(0)/umask(old), briefly changing it for the swap
  writer thread creating files.
- Before rewriting a hard-linked file in place, the new content is
  written and synced to a copy beside it; if the in-place write fails
  (ENOSPC, EIO) the copy is kept and named in the error.
- Journal headers also record a CRC-32 of the base file (flags bit 1,
  bytes 40..43). A changed mtime with the same size and content
  (touch, checkout back and forth) no longer marks a journal stale.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015rcVQiiWyTzoNrsvLSaAfB
This commit is contained in:
Claude
2026-09-24 09:16:04 -07:00
committed by Kyle Isom
parent 6c2727789e
commit 1005f5dbe8
6 changed files with 162 additions and 14 deletions
+9
View File
@@ -222,6 +222,8 @@ private:
bool has_base{false};
std::uint64_t base_size{0};
std::int64_t base_mtime_ns{0};
bool has_base_crc{false}; // CRC-32 of the base file's content
std::uint32_t base_crc{0};
};
struct Pending {
@@ -254,6 +256,13 @@ private:
static std::uint32_t crc32(const std::uint8_t *data, std::size_t len, std::uint32_t seed = 0);
// CRC-32 of a file's content; false if it cannot be read.
static bool file_crc32(const std::string &path, std::uint32_t &out);
// Record the identity (size, mtime, content CRC) of the file a journal's
// records will apply to.
static void capture_base(const std::string &file, JournalCtx &ctx);
static void put_le32(std::vector<std::uint8_t> &out, std::uint32_t v);
static void put_le64(std::uint8_t dst[8], std::uint64_t v);