-
Notifications
You must be signed in to change notification settings - Fork 0
/
atomic.go
84 lines (77 loc) · 1.96 KB
/
atomic.go
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
package fscache
import (
"fmt"
"io"
"os"
"path/filepath"
"golang.org/x/sys/unix"
)
// atomicWriteFile atomically writes data to a file named by filename.
func atomicWriteFile(filename, tmpfile string, src []byte, perm os.FileMode) error {
dst, err := newAtomicFileWriter(filename, tmpfile, perm)
if err != nil {
return err
}
if written, err := dst.Write(src); err != nil || written != len(src) {
dst.(*atomicFileWriter).writeErr = fmt.Errorf("atomic write file %s with tmpfile %s, "+
"err %s, srcBytes %d, writtenBytes %d", filename, tmpfile, err, len(src), written)
}
return dst.Close()
}
type atomicFileWriter struct {
f *os.File
fn string
writeErr error
perm os.FileMode
}
// newAtomicFileWriter returns WriteCloser so that writing to it writes to a
// temporary file and closing it atomically changes the temporary file to
// destination path. Writing and closing concurrently is not allowed.
// tmpdir and filename must be within the same filesystem.
func newAtomicFileWriter(filename, tmpfile string, perm os.FileMode) (io.WriteCloser, error) {
f, err := os.OpenFile(tmpfile, os.O_WRONLY|os.O_CREATE|os.O_EXCL, 0664)
if err != nil {
return nil, err
}
if err = unix.Flock(int(f.Fd()), unix.LOCK_EX); err != nil {
f.Close()
return nil, err
}
abspath, err := filepath.Abs(filename)
if err != nil {
return nil, err
}
return &atomicFileWriter{
f: f,
fn: abspath,
perm: perm,
}, nil
}
func (w *atomicFileWriter) Write(dt []byte) (int, error) {
n, err := w.f.Write(dt)
if err != nil {
w.writeErr = err
}
return n, err
}
func (w *atomicFileWriter) Close() (retErr error) {
defer func() {
if retErr != nil || w.writeErr != nil {
os.Remove(w.f.Name())
}
}()
if err := w.f.Sync(); err != nil {
w.f.Close()
return err
}
if err := w.f.Close(); err != nil {
return err
}
if err := os.Chmod(w.f.Name(), w.perm); err != nil {
return err
}
if w.writeErr == nil {
return os.Rename(w.f.Name(), w.fn)
}
return nil
}