Skip to content

File Utilities

Eshan Roy edited this page Jun 16, 2026 · 2 revisions

File Utilities

M31A uses atomic file operations throughout to prevent data corruption on crash or power loss.

Source: internal/fileutil/atomic.go

AtomicWrite

func AtomicWrite(path string, data []byte) error
func AtomicWriteWithPerm(path string, data []byte, perm os.FileMode) error

Algorithm

1. Stat target file → preserve existing permissions (if exists)
2. Create temp file in same directory (.m31a_tmp_*)
3. chmod temp file to desired permissions
4. Write data to temp file
5. fsync() temp file
6. Close temp file
7. Rename temp → target (atomic on POSIX filesystems)

Permission Preservation

When overwriting an existing file, AtomicWriteWithPerm reads the current file's permissions and preserves them:

if info, err := os.Stat(path); err == nil {
    perm = info.Mode().Perm()  // H-19: preserve original permissions
}

Crash Safety

The rename operation is atomic on POSIX filesystems. If the process crashes:

  • Before rename: original file is intact, temp file is orphaned (cleaned on next run)
  • After rename: new file is in place, no partial state possible

Usage in M31A

Atomic writes are used for all persistent state:

File Written By
session.json session.Manager.SaveSession()
messages.json session.Manager.SaveMessages()
checkpoint.json session.Manager.SaveCheckpoint()
LEDGER.md ledger.Ledger.rewriteFile()
recent_models.json session.Manager.SaveRecentModels()
config.toml Settings editor save
Exported sessions session.Manager.ExportSession*()

Temp File Cleanup

The defer os.Remove(tmpPath) ensures the temp file is cleaned up if any step fails before the rename. After rename succeeds, the temp path no longer exists so the deferred remove is a no-op.

Clone this wiki locally