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
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 master2026-09-05 05:44:11 +02:00
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Adds
AtomicWriteFile(path string, data []byte, perm os.FileMode) error, whichwrites 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.WriteFiletruncates the targetbefore 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:
permis the mode the finished file ends up with. The mode is set aftercreation rather than passed to
open, so unlikeos.WriteFilethe processumask does not take bits away from it.
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.
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.
extended attributes.
user, so it skips itself when run as root.
t.TempDirandos.ReadDir, and the code usesos.CreateTemp,which need Go 1.16, while
go.modstill saysgo 1.14. Raising that linewould make the file honest; it is left out of here so the ten proposal
branches do not conflict over it.
atomicwrite.go, for the same reason.make teston this branch reports one failure,TestNowUnixMicro. That testalready fails on
masterand is unrelated to this change.Model: opus-5