Add AtomicWriteFile to replace a file's contents in one step #18

Merged
sneak merged 1 commits from clawbot/util:proposal-atomic-write-file into master 2026-09-05 05:44:11 +02:00
Contributor

Adds AtomicWriteFile(path string, data []byte, perm os.FileMode) error, which
writes to a temporary file in the same directory, flushes it, sets its mode and
renames it over the target. A reader sees either the old contents or the
complete new ones, and a failure part way through leaves the old file untouched.

This belongs here because any program that keeps a configuration file, a cache,
a state file or a lock file needs it, and os.WriteFile truncates the target
before writing, so a crash or a concurrent reader in the middle of the write
finds an empty or half-written file. The correct pattern is short but has four
places to get it wrong.

Things to know:

  • perm is the mode the finished file ends up with. The mode is set after
    creation rather than passed to open, so unlike os.WriteFile the process
    umask does not take bits away from it.
  • The directory holding the file is not flushed, only the file itself. A
    machine that loses power immediately after this returns can come back with
    the rename undone. Guarding against that means opening the directory and
    syncing it too, which does not work the same way everywhere, so it is left
    out and said plainly in the doc comment.
  • The temporary file is created in the target's directory, not in the system
    temporary directory, because a rename cannot cross filesystems. Its name
    starts with a dot so that a program listing the directory mid-write is less
    likely to trip over it.
  • This replaces the file, so it does not preserve the previous owner or any
    extended attributes.
  • The failure test makes the directory unwritable, which does not stop the root
    user, so it skips itself when run as root.
  • The test uses t.TempDir and os.ReadDir, and the code uses os.CreateTemp,
    which need Go 1.16, while go.mod still says go 1.14. Raising that line
    would make the file honest; it is left out of here so the ten proposal
    branches do not conflict over it.
  • The code is in a new file, atomicwrite.go, for the same reason.
  • make test on this branch reports one failure, TestNowUnixMicro. That test
    already fails on master and is unrelated to this change.

Model: opus-5

Adds `AtomicWriteFile(path string, data []byte, perm os.FileMode) error`, which writes to a temporary file in the same directory, flushes it, sets its mode and renames it over the target. A reader sees either the old contents or the complete new ones, and a failure part way through leaves the old file untouched. This belongs here because any program that keeps a configuration file, a cache, a state file or a lock file needs it, and `os.WriteFile` truncates the target before writing, so a crash or a concurrent reader in the middle of the write finds an empty or half-written file. The correct pattern is short but has four places to get it wrong. Things to know: - `perm` is the mode the finished file ends up with. The mode is set after creation rather than passed to `open`, so unlike `os.WriteFile` the process umask does not take bits away from it. - The directory holding the file is not flushed, only the file itself. A machine that loses power immediately after this returns can come back with the rename undone. Guarding against that means opening the directory and syncing it too, which does not work the same way everywhere, so it is left out and said plainly in the doc comment. - The temporary file is created in the target's directory, not in the system temporary directory, because a rename cannot cross filesystems. Its name starts with a dot so that a program listing the directory mid-write is less likely to trip over it. - This replaces the file, so it does not preserve the previous owner or any extended attributes. - The failure test makes the directory unwritable, which does not stop the root user, so it skips itself when run as root. - The test uses `t.TempDir` and `os.ReadDir`, and the code uses `os.CreateTemp`, which need Go 1.16, while `go.mod` still says `go 1.14`. Raising that line would make the file honest; it is left out of here so the ten proposal branches do not conflict over it. - The code is in a new file, `atomicwrite.go`, for the same reason. - `make test` on this branch reports one failure, `TestNowUnixMicro`. That test already fails on `master` and is unrelated to this change. Model: opus-5
clawbot self-assigned this 2026-09-05 05:30:53 +02:00
clawbot added 1 commit 2026-09-05 05:30:54 +02:00
AtomicWriteFile writes to a temporary file in the same directory, flushes it,
sets the mode and renames it over the target, so a reader never sees a
half-written file and a failure leaves the old one untouched. Comes with a doc
comment and table-driven tests. (closes #17)

Model: opus-5
sneak merged commit c407af0d21 into master 2026-09-05 05:44:11 +02:00
Sign in to join this conversation.
No Reviewers
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: sneak/util#18