carlos / platform Public

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

Plain git — no account needed to clone.

Download

Download this file

1//go:build linux
2
3package park
4
5import (
6 "bytes"
7 "context"
8 "crypto/rand"
9 "encoding/hex"
10 "encoding/json"
11 "errors"
12 "io"
13 "os"
14 "reflect"
15 "strings"
16
17 "golang.org/x/sys/unix"
18)
19
20func localSupported() bool { return true }
21
22func localDirAt(parent int, name string, create bool) (int, error) {
23 if !localComponent(name) {
24 return -1, ErrLocalInventory
25 }
26 if create {
27 if err := unix.Mkdirat(parent, name, 0770); err != nil && !errors.Is(err, unix.EEXIST) {
28 return -1, err
29 }
30 }
31 fd, err := unix.Openat(parent, name, unix.O_RDONLY|unix.O_DIRECTORY|unix.O_NOFOLLOW|unix.O_CLOEXEC, 0)
32 if err != nil {
33 return -1, err
34 }
35 var st unix.Stat_t
36 if err := unix.Fstat(fd, &st); err != nil || st.Mode&unix.S_IFMT != unix.S_IFDIR {
37 unix.Close(fd)
38 if err != nil {
39 return -1, err
40 }
41 return -1, ErrLocalInventory
42 }
43 if create {
44 // A retry can encounter another worker's directory. Root also respects its owner.
45 if st.Uid == uint32(os.Geteuid()) {
46 if err := unix.Fchmod(fd, 02770); err != nil {
47 unix.Close(fd)
48 return -1, err
49 }
50 } else if st.Mode&02770 != 02770 {
51 unix.Close(fd)
52 return -1, ErrLocalInventory
53 }
54 if err := unix.Fsync(fd); err != nil {
55 unix.Close(fd)
56 return -1, err
57 }
58 if err := unix.Fsync(parent); err != nil {
59 unix.Close(fd)
60 return -1, err
61 }
62 }
63 return fd, nil
64}
65
66func localWalk(root int, parts []string, create bool) (int, error) {
67 fd, err := unix.Dup(root)
68 if err != nil {
69 return -1, err
70 }
71 unix.CloseOnExec(fd)
72 for _, p := range parts {
73 next, err := localDirAt(fd, p, create)
74 unix.Close(fd)
75 if err != nil {
76 return -1, err
77 }
78 fd = next
79 }
80 return fd, nil
81}
82
83func localParent(root int, p string, create bool) (int, string, error) {
84 if !localPath(p) {
85 return -1, "", ErrLocalInventory
86 }
87 parts := strings.Split(p, "/")
88 fd, err := localWalk(root, parts[:len(parts)-1], create)
89 return fd, parts[len(parts)-1], err
90}
91
92func localStat(fd int, name string) (unix.Stat_t, error) {
93 var st unix.Stat_t
94 err := unix.Fstatat(fd, name, &st, unix.AT_SYMLINK_NOFOLLOW)
95 if err == nil && st.Mode&unix.S_IFMT != unix.S_IFDIR && st.Mode&unix.S_IFMT != unix.S_IFREG {
96 return st, ErrLocalInventory
97 }
98 return st, err
99}
100
101func localSame(st unix.Stat_t, item localItem) bool {
102 return uint64(st.Dev) == item.Dev && st.Ino == item.Ino && st.Mode&unix.S_IFMT == item.Type
103}
104
105func localReadJournal(dir int) (*localJournal, error) {
106 fd, err := unix.Openat(dir, ".journal.json", unix.O_RDONLY|unix.O_NOFOLLOW|unix.O_CLOEXEC|unix.O_NONBLOCK, 0)
107 if err != nil {
108 return nil, err
109 }
110 f := os.NewFile(uintptr(fd), "journal")
111 defer f.Close()
112 st, err := f.Stat()
113 if err != nil {
114 return nil, err
115 }
116 if !st.Mode().IsRegular() {
117 return nil, ErrLocalJournal
118 }
119 b, err := io.ReadAll(io.LimitReader(f, (4<<20)+1))
120 if err != nil {
121 return nil, err
122 }
123 if len(b) > 4<<20 {
124 return nil, ErrLocalJournal
125 }
126 d := json.NewDecoder(bytes.NewReader(b))
127 d.DisallowUnknownFields()
128 var j localJournal
129 if err := d.Decode(&j); err != nil {
130 return nil, err
131 }
132 if _, err := d.Token(); err != io.EOF {
133 return nil, ErrLocalJournal
134 }
135 return &j, nil
136}
137
138func localWriteJournal(dir int, j *localJournal) error {
139 b, err := json.Marshal(j)
140 if err != nil {
141 return err
142 }
143 var nonce [16]byte
144 if _, err := rand.Read(nonce[:]); err != nil {
145 return err
146 }
147 name := ".journal-" + hex.EncodeToString(nonce[:])
148 fd, err := unix.Openat(dir, name, unix.O_WRONLY|unix.O_CREAT|unix.O_EXCL|unix.O_NOFOLLOW|unix.O_CLOEXEC, 0660)
149 if err != nil {
150 return err
151 }
152 f := os.NewFile(uintptr(fd), "journal-temp")
153 defer f.Close()
154 defer unix.Unlinkat(dir, name, 0)
155 if err := unix.Fchmod(fd, 0660); err != nil {
156 return err
157 }
158 if _, err := f.Write(b); err != nil {
159 return err
160 }
161 if err := f.Sync(); err != nil {
162 return err
163 }
164 if err := unix.Renameat(dir, name, dir, ".journal.json"); err != nil {
165 return err
166 }
167 return unix.Fsync(dir)
168}
169
170func localJournalMatches(a, b localJournal) bool {
171 if len(a.Items) != len(b.Items) {
172 return false
173 }
174 for i := range a.Items {
175 if a.Items[i].Path != b.Items[i].Path || a.Items[i].Stamp != b.Items[i].Stamp {
176 return false
177 }
178 if a.Items[i].Missing {
179 if a.Items[i].Dev != 0 || a.Items[i].Ino != 0 || a.Items[i].Type != 0 {
180 return false
181 }
182 } else if a.Items[i].Ino == 0 || (a.Items[i].Type != unix.S_IFDIR && a.Items[i].Type != unix.S_IFREG) {
183 return false
184 }
185 }
186 a.Items, b.Items = nil, nil
187 return reflect.DeepEqual(a, b)
188}
189
190func (l Local) parkLocal(ctx context.Context, want *localJournal) error {
191 root, err := unix.Open(l.DataDir, unix.O_RDONLY|unix.O_DIRECTORY|unix.O_NOFOLLOW|unix.O_CLOEXEC, 0)
192 if err != nil {
193 return err
194 }
195 defer unix.Close(root)
196 parked, err := localDirAt(root, ".parked", false)
197 if err != nil {
198 return err
199 }
200 defer unix.Close(parked)
201 event, err := localWalk(parked, []string{want.Incarnation, want.Host, want.Retirement}, true)
202 if err != nil {
203 return err
204 }
205 defer unix.Close(event)
206 dest, err := localDirAt(event, "artifacts", true)
207 if err != nil {
208 return err
209 }
210 defer unix.Close(dest)
211 prior, err := localReadJournal(event)
212 if errors.Is(err, unix.ENOENT) {
213 for i := range want.Items {
214 it := &want.Items[i]
215 src, name, e := localParent(root, it.Path, false)
216 if errors.Is(e, unix.ENOENT) {
217 it.Missing = true
218 continue
219 }
220 if e != nil {
221 return e
222 }
223 st, e := localStat(src, name)
224 unix.Close(src)
225 if errors.Is(e, unix.ENOENT) {
226 it.Missing = true
227 continue
228 }
229 if e != nil {
230 return e
231 }
232 it.Dev, it.Ino, it.Type = uint64(st.Dev), st.Ino, st.Mode&unix.S_IFMT
233 dp, dn, e := localParent(dest, it.Path, true)
234 if e != nil {
235 return e
236 }
237 _, e = localStat(dp, dn)
238 unix.Close(dp)
239 if !errors.Is(e, unix.ENOENT) {
240 if e != nil {
241 return e
242 }
243 return ErrLocalJournal
244 }
245 }
246 if err := localWriteJournal(event, want); err != nil {
247 return err
248 }
249 } else if err != nil {
250 return err
251 } else {
252 if !localJournalMatches(*prior, *want) {
253 return ErrLocalJournal
254 }
255 *want = *prior
256 }
257 for i := range want.Items {
258 if err := ctx.Err(); err != nil {
259 return err
260 }
261 it := &want.Items[i]
262 if it.Missing {
263 // A still-active installation can recreate an absent path between attempts.
264 sp, sn, err := localParent(root, it.Path, false)
265 if err == nil {
266 _, err = localStat(sp, sn)
267 unix.Close(sp)
268 }
269 if !errors.Is(err, unix.ENOENT) {
270 if err != nil {
271 return err
272 }
273 return ErrLocalJournal
274 }
275 it.Done = true
276 continue
277 }
278 dp, dn, err := localParent(dest, it.Path, true)
279 if err != nil {
280 return err
281 }
282 sp, sn, err := localParent(root, it.Path, false)
283 if err != nil {
284 unix.Close(dp)
285 return err
286 }
287 err = l.moveLocalItem(sp, sn, dp, dn, it)
288 if err == nil {
289 err = unix.Fsync(sp)
290 }
291 if err == nil {
292 err = unix.Fsync(dp)
293 }
294 unix.Close(sp)
295 unix.Close(dp)
296 if err != nil {
297 return err
298 }
299 it.Done = true
300 if err := localWriteJournal(event, want); err != nil {
301 return err
302 }
303 }
304 if err := localWriteJournal(event, want); err != nil {
305 return err
306 }
307 if err := unix.Fsync(dest); err != nil {
308 return err
309 }
310 return unix.Fsync(event)
311}
312
313func (l Local) moveLocalItem(sp int, sn string, dp int, dn string, it *localItem) error {
314 dst, de := localStat(dp, dn)
315 if de == nil {
316 if !localSame(dst, *it) {
317 return ErrLocalJournal
318 }
319 _, se := localStat(sp, sn)
320 if se == nil {
321 return ErrLocalJournal
322 }
323 if se != nil && !errors.Is(se, unix.ENOENT) {
324 return se
325 }
326 return nil
327 }
328 if !errors.Is(de, unix.ENOENT) {
329 return de
330 }
331 if it.Done {
332 return ErrLocalJournal
333 }
334 src, err := localStat(sp, sn)
335 if err != nil {
336 return err
337 }
338 if !localSame(src, *it) {
339 return ErrLocalJournal
340 }
341 if err := unix.Renameat2(sp, sn, dp, dn, unix.RENAME_NOREPLACE); err != nil {
342 return err
343 }
344 if l.afterRename != nil {
345 return l.afterRename(it.Path)
346 }
347 return nil
348}
349