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
+18
View File
@@ -974,3 +974,21 @@ TEST(Audit_SaveAs_FileOpenInOtherBuffer_Refused)
ASSERT_EQ(ed.BufferCount(), n);
ASSERT_EQ(ed.FindOpenBuffer((d.path / "y.txt").string()) != ed.CurrentBufferIndex(), true);
}
// Saving keeps setuid/setgid bits (chown after chmod used to clear them).
TEST(Audit_Save_KeepsSpecialModeBits)
{
TempDir d("save_setuid");
const auto f = d.path / "s.sh";
std::ofstream(f) << "echo\n";
ASSERT_EQ(::chmod(f.c_str(), 06755), 0);
Buffer b;
std::string err;
ASSERT_TRUE(b.OpenFromFile(f.string(), err));
b.insert_text(0, 0, "#");
ASSERT_TRUE(b.Save(err));
struct stat st{};
ASSERT_EQ(::stat(f.c_str(), &st), 0);
ASSERT_EQ(st.st_mode & 07777, (mode_t) 06755);
}