fix(alerts): avoid unchanged checkpoint directory chmod

The JSON mirror fast path avoided file replacement but still dirtied directory metadata on every save through chmod. Check the current permissions before repairing them, retaining correction of unsafe modes. A Linux regression reproduces the ctime change and verifies unchanged metadata and permission repair. This is a narrow contributor to #1966, not a claim that aggregate write amplification is resolved.

Change-source: pulse-maintainer
This commit is contained in:
pulse-triage[bot]
2026-09-08 11:17:51 +01:00
parent 1f2e696381
commit bcc869b0ca
4 changed files with 113 additions and 2 deletions
@@ -1294,6 +1294,16 @@ synchronous durability for this authority, and the recovery mirror fsyncs its
temporary file plus a platform-native durable rename barrier (parent-directory
sync on Unix and write-through replacement on Windows), so the contract covers
host power loss rather than only orderly process restart.
An unchanged JSON recovery checkpoint preserves already-correct directory
permissions without issuing chmod, avoiding redundant Linux directory metadata
mutation. It still repairs unsafe access permissions and special mode bits;
permission repair must not replace identical JSON. Directory sync and atomic
file replacement durability remain unchanged. This is not a guarantee of zero
aggregate process writes. `TestActiveMirrorUnchangedRepairsDirectoryPermissions`
in `internal/alerts/alerts_test.go` pins permission repair and inode retention;
`TestActiveMirrorUnchangedPreservesDirectoryMetadata` in
`internal/alerts/active_mirror_metadata_linux_test.go` pins stable Linux ctime.
`active-alerts.json` remains an atomic recovery mirror, not a competing healthy
read authority. A new or recreated database imports the readable mirror. A
failed SQLite checkpoint writes a durable degraded marker, and the next startup
@@ -0,0 +1,51 @@
//go:build linux
package alerts
import (
"os"
"syscall"
"testing"
"time"
)
// Skipping the JSON rename must not dirty the containing directory's metadata
// through an unconditional chmod on every checkpoint.
func TestActiveMirrorUnchangedPreservesDirectoryMetadata(t *testing.T) {
dir := t.TempDir()
m := &Manager{alertsDir: dir}
alerts := []*Alert{{ID: "a"}}
save := func() {
t.Helper()
if err := m.writeActiveAlertsRecoveryMirror(alerts); err != nil {
t.Fatal(err)
}
}
ctime := func() syscall.Timespec {
t.Helper()
info, err := os.Stat(dir)
if err != nil {
t.Fatal(err)
}
return info.Sys().(*syscall.Stat_t).Ctim
}
save()
before := ctime()
time.Sleep(20 * time.Millisecond)
save()
if after := ctime(); after != before {
t.Fatalf("unchanged checkpoint changed directory ctime: %v -> %v", before, after)
}
// Still repair a directory made accessible to other users.
if err := os.Chmod(dir, 0755); err != nil {
t.Fatal(err)
}
save()
info, err := os.Stat(dir)
if err != nil {
t.Fatal(err)
}
if info.Mode().Perm() != alertsDirPerm {
t.Fatalf("directory mode = %v", info.Mode())
}
}
+10 -2
View File
@@ -160,8 +160,16 @@ func (m *Manager) writeActiveAlertsRecoveryMirrorLocked(alerts []*Alert) error {
if err := os.MkdirAll(alertsDir, alertsDirPerm); err != nil {
return fmt.Errorf("failed to create alerts directory: %w", err)
}
if err := os.Chmod(alertsDir, alertsDirPerm); err != nil {
return fmt.Errorf("failed to set alerts directory permissions: %w", err)
// Even chmod to the existing mode dirties directory metadata on Linux.
// Preserve the no-change checkpoint path while still repairing permissions.
info, err := os.Stat(alertsDir)
if err != nil {
return fmt.Errorf("failed to stat alerts directory: %w", err)
}
if info.Mode().Perm() != alertsDirPerm || info.Mode()&(os.ModeSetuid|os.ModeSetgid|os.ModeSticky) != 0 {
if err := os.Chmod(alertsDir, alertsDirPerm); err != nil {
return fmt.Errorf("failed to set alerts directory permissions: %w", err)
}
}
// Snapshots originate from maps. Canonicalise the complete records rather
+42
View File
@@ -21280,3 +21280,45 @@ func TestBackupDivergentDefaultsOnLoad(t *testing.T) {
})
}
}
// The unchanged-content fast path must not accept unsafe directory permissions.
func TestActiveMirrorUnchangedRepairsDirectoryPermissions(t *testing.T) {
if runtime.GOOS == "windows" {
t.Skip("POSIX directory permissions")
}
for _, mode := range []os.FileMode{0755, 0700 | os.ModeSticky, 0700 | os.ModeSetgid} {
t.Run(mode.String(), func(t *testing.T) {
dir := t.TempDir()
m := &Manager{alertsDir: dir}
alerts := []*Alert{{ID: "a"}}
if err := m.writeActiveAlertsRecoveryMirror(alerts); err != nil {
t.Fatal(err)
}
path := filepath.Join(dir, "active-alerts.json")
before, err := os.Stat(path)
if err != nil {
t.Fatal(err)
}
if err := os.Chmod(dir, mode); err != nil {
t.Fatal(err)
}
if err := m.writeActiveAlertsRecoveryMirror(alerts); err != nil {
t.Fatal(err)
}
info, err := os.Stat(dir)
if err != nil {
t.Fatal(err)
}
if info.Mode().Perm() != alertsDirPerm || info.Mode()&(os.ModeSetuid|os.ModeSetgid|os.ModeSticky) != 0 {
t.Fatalf("unsafe directory mode survived: %v", info.Mode())
}
after, err := os.Stat(path)
if err != nil {
t.Fatal(err)
}
if !os.SameFile(before, after) {
t.Fatal("directory permission repair replaced unchanged JSON")
}
})
}
}