carlos / platform Public

Clone
git clone https://amadan.net/carlos/platform

Plain git — no account needed to clone.

Download

Download this file

1package safefile
2
3import (
4 "crypto/rand"
5 "encoding/hex"
6 "fmt"
7 "io"
8 "os"
9 "path/filepath"
10 "strings"
11
12 "golang.org/x/sys/unix"
13)
14
15func openDir(path string, create bool, mode os.FileMode) (int, error) {
16 abs, err := filepath.Abs(path)
17 if err != nil {
18 return -1, err
19 }
20 fd, err := unix.Open("/", unix.O_RDONLY|unix.O_DIRECTORY|unix.O_CLOEXEC, 0)
21 if err != nil {
22 return -1, err
23 }
24 for _, name := range strings.Split(strings.TrimPrefix(abs, "/"), "/") {
25 if name == "" {
26 continue
27 }
28 next, e := unix.Openat(fd, name, unix.O_RDONLY|unix.O_DIRECTORY|unix.O_NOFOLLOW|unix.O_CLOEXEC, 0)
29 if e == unix.ENOENT && create {
30 e = unix.Mkdirat(fd, name, uint32(mode.Perm()))
31 if e == nil || e == unix.EEXIST {
32 next, e = unix.Openat(fd, name, unix.O_RDONLY|unix.O_DIRECTORY|unix.O_NOFOLLOW|unix.O_CLOEXEC, 0)
33 }
34 }
35 unix.Close(fd)
36 if e != nil {
37 return -1, &os.PathError{Op: "open directory", Path: path, Err: e}
38 }
39 fd = next
40 }
41 return fd, nil
42}
43
44func MkdirAll(path string, mode os.FileMode) error {
45 fd, err := openDir(path, true, mode)
46 if err == nil {
47 err = unix.Close(fd)
48 }
49 return err
50}
51
52func OpenFile(path string, flags int, mode os.FileMode) (*os.File, error) {
53 dir, err := openDir(filepath.Dir(path), false, 0)
54 if err != nil {
55 return nil, err
56 }
57 defer unix.Close(dir)
58 return OpenFileAt(dir, filepath.Base(path), flags, mode)
59}
60
61func OpenFileAt(dir int, path string, flags int, mode os.FileMode) (*os.File, error) {
62 if path != filepath.Base(path) || path == "." || path == ".." {
63 return nil, fmt.Errorf("invalid file name")
64 }
65 var err error
66 fd := -1
67 if flags&os.O_CREATE != 0 {
68 fd, err = unix.Openat(dir, filepath.Base(path), (flags&^os.O_TRUNC)|unix.O_EXCL|unix.O_NOFOLLOW|unix.O_NONBLOCK|unix.O_CLOEXEC, uint32(mode.Perm()))
69 if err != nil && (err != unix.EEXIST || flags&os.O_EXCL != 0) {
70 return nil, &os.PathError{Op: "open", Path: path, Err: err}
71 }
72 }
73 if fd < 0 {
74 pinned, e := unix.Openat(dir, filepath.Base(path), unix.O_PATH|unix.O_NOFOLLOW|unix.O_CLOEXEC, 0)
75 if e != nil {
76 return nil, &os.PathError{Op: "open", Path: path, Err: e}
77 }
78 defer unix.Close(pinned)
79 var st unix.Stat_t
80 if e = unix.Fstat(pinned, &st); e != nil {
81 return nil, e
82 }
83 if st.Mode&unix.S_IFMT != unix.S_IFREG || st.Nlink != 1 {
84 return nil, fmt.Errorf("%s: not a regular file with one link", path)
85 }
86 fd, err = unix.Open(fmt.Sprintf("/proc/self/fd/%d", pinned), (flags&^(os.O_CREATE|os.O_EXCL|os.O_TRUNC))|unix.O_NONBLOCK|unix.O_CLOEXEC, 0)
87 if err != nil {
88 return nil, &os.PathError{Op: "open", Path: path, Err: err}
89 }
90 }
91
92 f := os.NewFile(uintptr(fd), path)
93 var st unix.Stat_t
94 if err = unix.Fstat(fd, &st); err == nil && (st.Mode&unix.S_IFMT != unix.S_IFREG || st.Nlink != 1) {
95 err = fmt.Errorf("not a regular file with one link")
96 }
97 if err == nil && flags&os.O_TRUNC != 0 {
98 err = f.Truncate(0)
99 }
100 if err != nil {
101 f.Close()
102 return nil, &os.PathError{Op: "open", Path: path, Err: err}
103 }
104 return f, nil
105}
106
107func Remove(path string) error {
108 dir, err := openDir(filepath.Dir(path), false, 0)
109 if err != nil {
110 return err
111 }
112 defer unix.Close(dir)
113 err = unix.Unlinkat(dir, filepath.Base(path), 0)
114 if err != nil {
115 return &os.PathError{Op: "remove", Path: path, Err: err}
116 }
117 return nil
118}
119
120func Write(path string, mode os.FileMode, write func(io.Writer) error) error {
121 dir, err := openDir(filepath.Dir(path), false, 0)
122 if err != nil {
123 return err
124 }
125 defer unix.Close(dir)
126 return writeAt(dir, filepath.Base(path), mode, write)
127}
128
129func WriteAt(dir int, name string, body []byte, mode os.FileMode) error {
130 return writeAt(dir, name, mode, func(w io.Writer) error { _, err := w.Write(body); return err })
131}
132
133func OpenDir(path string) (*os.File, error) {
134 fd, err := openDir(path, false, 0)
135 if err != nil {
136 return nil, err
137 }
138 return os.NewFile(uintptr(fd), path), nil
139}
140
141func writeAt(dir int, path string, mode os.FileMode, write func(io.Writer) error) error {
142 return writeAtSteps(dir, path, mode, write, func(_ string, op func() error) error { return op() })
143}
144
145func writeAtSteps(dir int, path string, mode os.FileMode, write func(io.Writer) error, step func(string, func() error) error) error {
146 if path != filepath.Base(path) || path == "." || path == ".." {
147 return fmt.Errorf("invalid file name")
148 }
149 var nonce [16]byte
150 if _, err := rand.Read(nonce[:]); err != nil {
151 return err
152 }
153 name := ".carlos-write-" + hex.EncodeToString(nonce[:])
154 if err := unix.Mkdirat(dir, name, 0o700); err != nil {
155 return err
156 }
157 defer unix.Unlinkat(dir, name, unix.AT_REMOVEDIR)
158 stage, err := unix.Openat(dir, name, unix.O_RDONLY|unix.O_DIRECTORY|unix.O_NOFOLLOW|unix.O_CLOEXEC, 0)
159 if err != nil {
160 return err
161 }
162 defer unix.Close(stage)
163 var st unix.Stat_t
164 if err := unix.Fstat(stage, &st); err != nil {
165 return err
166 }
167 if st.Uid != uint32(os.Geteuid()) || st.Mode&0o077 != 0 {
168 return fmt.Errorf("unsafe staging directory")
169 }
170 fd, err := unix.Openat(stage, "data", unix.O_WRONLY|unix.O_CREAT|unix.O_EXCL|unix.O_NOFOLLOW|unix.O_CLOEXEC, 0o600)
171 if err != nil {
172 return err
173 }
174 defer unix.Unlinkat(stage, "data", 0)
175 f := os.NewFile(uintptr(fd), path)
176 defer f.Close()
177 if err := unix.Fstat(fd, &st); err != nil {
178 return err
179 }
180 if st.Mode&unix.S_IFMT != unix.S_IFREG || st.Nlink != 1 {
181 return fmt.Errorf("not a regular file with one link")
182 }
183 if err := step("write", func() error { return write(f) }); err != nil {
184 return err
185 }
186 if err := step("chmod", func() error { return f.Chmod(mode) }); err != nil {
187 return err
188 }
189 if err := step("file-sync", f.Sync); err != nil {
190 return err
191 }
192 if err := step("close", f.Close); err != nil {
193 return err
194 }
195 if err := step("rename", func() error { return unix.Renameat(stage, "data", dir, filepath.Base(path)) }); err != nil {
196 return err
197 }
198 return step("parent-sync", func() error { return unix.Fsync(dir) })
199}
200