-
-
Notifications
You must be signed in to change notification settings - Fork 2.1k
Expand file tree
/
Copy pathatomic.go
More file actions
94 lines (84 loc) · 2.1 KB
/
atomic.go
File metadata and controls
94 lines (84 loc) · 2.1 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
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
package securefile
import (
"fmt"
"os"
"path/filepath"
"time"
)
// EnsurePrivateDir creates dirPath (and parents) with 0700 permissions.
func EnsurePrivateDir(dirPath string) error {
if dirPath == "" {
return fmt.Errorf("securefile: dir path is empty")
}
if err := os.MkdirAll(dirPath, 0o700); err != nil {
return err
}
// Best-effort permission hardening. Ignore errors (e.g., non-POSIX FS).
_ = os.Chmod(dirPath, 0o700)
return nil
}
// AtomicWriteFile writes data to path using a temp file + rename, and attempts to fsync.
// mode controls the final file permissions.
func AtomicWriteFile(path string, data []byte, mode os.FileMode) error {
if path == "" {
return fmt.Errorf("securefile: path is empty")
}
dir := filepath.Dir(path)
if err := EnsurePrivateDir(dir); err != nil {
return err
}
tmp, err := os.CreateTemp(dir, ".tmp.*")
if err != nil {
return err
}
tmpName := tmp.Name()
defer func() {
_ = tmp.Close()
_ = os.Remove(tmpName)
}()
if mode == 0 {
mode = 0o600
}
if err := tmp.Chmod(mode); err != nil {
// Best-effort: ignore chmod failure on some filesystems.
}
if _, err := tmp.Write(data); err != nil {
return err
}
if err := tmp.Sync(); err != nil {
return err
}
if err := tmp.Close(); err != nil {
return err
}
if err := os.Rename(tmpName, path); err != nil {
return err
}
// Best-effort: ensure final mode.
_ = os.Chmod(path, mode)
return nil
}
// ReadFileRawLocked reads the file at path while holding an advisory lock on path+".lock".
func ReadFileRawLocked(path string) ([]byte, error) {
lockPath := path + ".lock"
var out []byte
err := WithLock(lockPath, 10*time.Second, func() error {
data, err := os.ReadFile(path)
if err != nil {
return err
}
out = data
return nil
})
if err != nil {
return nil, err
}
return out, nil
}
// WriteFileRawLocked writes data to path using an advisory lock on path+".lock" and atomic replace.
func WriteFileRawLocked(path string, data []byte, mode os.FileMode) error {
lockPath := path + ".lock"
return WithLock(lockPath, 10*time.Second, func() error {
return AtomicWriteFile(path, data, mode)
})
}