-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathatomic.go
More file actions
64 lines (54 loc) · 1.58 KB
/
Copy pathatomic.go
File metadata and controls
64 lines (54 loc) · 1.58 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
package main
import (
"os"
"path/filepath"
)
// atomicWriteFile writes data to path atomically using a temp-file + rename strategy.
//
// HOW IT WORKS:
// 1. Write data to a temporary file in the same directory.
// 2. Sync to disk (fsync) so the data is physically written.
// 3. Rename the temp file over the target path.
//
// WHY THIS IS SAFE:
// - If the process crashes during step 1 or 2, only the temp file is lost;
// the original file at [path] is untouched (implicit rollback).
// - os.Rename on the same filesystem is atomic on all POSIX systems and
// on Windows NTFS — the target path either has the old content or the new
// content, never a partial/corrupted state.
// - The temp file is always cleaned up on failure.
func atomicWriteFile(path string, data []byte, perm os.FileMode) (err error) {
dir := filepath.Dir(path)
if err := os.MkdirAll(dir, 0o755); err != nil {
return err
}
tmp, err := os.CreateTemp(dir, ".tmp-*.write")
if err != nil {
return err
}
tmpPath := tmp.Name()
// Clean up temp file on any failure; on success the rename moves it away.
defer func() {
if err != nil {
os.Remove(tmpPath) // best effort
}
}()
if _, err = tmp.Write(data); err != nil {
tmp.Close()
return err
}
// Force flush to disk so a power loss after rename doesn't lose data.
if err = tmp.Sync(); err != nil {
tmp.Close()
return err
}
if err = tmp.Chmod(perm); err != nil {
tmp.Close()
return err
}
if err = tmp.Close(); err != nil {
return err
}
// Atomic swap — same filesystem, so this is truly atomic.
return os.Rename(tmpPath, path)
}